#7354 end Zone migration #539

Merged
jon merged 58 commits from 7354_ZoneMigration_End into dev 2024-09-03 04:48:18 +00:00
28 changed files with 475 additions and 340 deletions

View File

@ -22,7 +22,7 @@ const { t } = useI18n();
const { validate } = useValidator();
const { notify } = useNotify();
const route = useRoute();
const myForm = ref(null);
const $props = defineProps({
url: {
type: String,
@ -109,11 +109,14 @@ const defaultButtons = computed(() => ({
color: 'primary',
icon: 'save',
label: 'globals.save',
click: () => myForm.value.submit(),
type: 'submit',
},
reset: {
color: 'primary',
icon: 'restart_alt',
label: 'globals.reset',
click: () => reset(),
},
...$props.defaultButtons,
}));
@ -276,7 +279,14 @@ defineExpose({
</script>
<template>
<div class="column items-center full-width">
<QForm @submit="save" @reset="reset" class="q-pa-md" id="formModel">
<QForm
ref="myForm"
v-if="formData"
@submit="save"
@reset="reset"
class="q-pa-md"
id="formModel"
>
<QCard>
<slot
v-if="formData"
@ -304,7 +314,7 @@ defineExpose({
:color="defaultButtons.reset.color"
:icon="defaultButtons.reset.icon"
flat
@click="reset"
@click="defaultButtons.reset.click"
:disable="!hasChanges"
:title="t(defaultButtons.reset.label)"
/>
@ -344,7 +354,7 @@ defineExpose({
:label="tMobile('globals.save')"
color="primary"
icon="save"
@click="save"
@click="defaultButtons.save.click"
:disable="!hasChanges"
:title="t(defaultButtons.save.label)"
/>

View File

@ -178,10 +178,20 @@ function setUserParams(watchedParams, watchedOrder) {
watchedParams = { ...watchedParams, ...where };
delete watchedParams.filter;
delete params.value?.filter;
params.value = { ...params.value, ...watchedParams };
params.value = { ...params.value, ...sanitizer(watchedParams) };
orders.value = parseOrder(order);
}
function sanitizer(params) {
for (const [key, value] of Object.entries(params)) {
if (typeof value == 'object') {
const param = Object.values(value)[0];
if (typeof param == 'string') params[key] = param.replaceAll('%', '');
}
}
return params;
}
function splitColumns(columns) {
splittedColumns.value = {
columns: [],

View File

@ -1,6 +1,7 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useValidator } from 'src/composables/useValidator';
const emit = defineEmits([
'update:modelValue',
@ -27,9 +28,11 @@ const $props = defineProps({
default: true,
},
});
const { validations } = useValidator();
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');
const requiredFieldRule = (val) => validations().required($attrs.required, val);
const vnInputRef = ref(null);
const value = computed({
get() {
@ -57,21 +60,22 @@ const focus = () => {
defineExpose({
focus,
});
import { useAttrs } from 'vue';
const $attrs = useAttrs();
const inputRules = [
const mixinRules = [
requiredFieldRule,
...($attrs.rules ?? []),
(val) => {
const { min } = vnInputRef.value.$attrs;
if (!min) return null;
if (min >= 0) if (Math.floor(val) < min) return t('inputMin', { value: min });
},
];
</script>
<template>
<div
@mouseover="hover = true"
@mouseleave="hover = false"
:rules="$attrs.required ? [requiredFieldRule] : null"
>
<div @mouseover="hover = true" @mouseleave="hover = false">
<QInput
ref="vnInputRef"
v-model="value"
@ -80,7 +84,7 @@ const inputRules = [
:class="{ required: $attrs.required }"
@keyup.enter="emit('keyup.enter')"
:clearable="false"
:rules="inputRules"
:rules="mixinRules"
:lazy-rules="true"
hide-bottom-space
>

View File

@ -14,7 +14,7 @@ const props = defineProps({
default: false,
},
});
const initialDate = ref(model.value);
const initialDate = ref(model.value ?? Date.vnNew());
const { t } = useI18n();
const requiredFieldRule = (val) => !!val || t('globals.fieldRequired');

View File

@ -92,16 +92,18 @@ function setUserParams(watchedParams) {
const order = watchedParams.filter?.order;
delete watchedParams.filter;
userParams.value = { ...userParams.value, ...sanitizer(watchedParams) };
userParams.value = sanitizer(watchedParams);
emit('setUserParams', userParams.value, order);
}
watch(
() => [route.query[$props.searchUrl], arrayData.store.userParams],
([newSearchUrl, newUserParams], [oldSearchUrl, oldUserParams]) => {
if (newSearchUrl || oldSearchUrl) setUserParams(newSearchUrl);
if (newUserParams || oldUserParams) setUserParams(newUserParams);
}
() => route.query[$props.searchUrl],
(val, oldValue) => (val || oldValue) && setUserParams(val)
);
watch(
() => arrayData.store.userParams,
(val, oldValue) => (val || oldValue) && setUserParams(val)
);
watch(

View File

@ -63,17 +63,13 @@ const props = defineProps({
type: String,
default: '',
},
makeFetch: {
type: Boolean,
default: true,
},
searchUrl: {
type: String,
default: 'params',
whereFilter: {
type: Function,
default: undefined,
},
});
const searchText = ref('');
const searchText = ref();
let arrayDataProps = { ...props };
if (props.redirect)
arrayDataProps = {
@ -107,13 +103,20 @@ async function search() {
const staticParams = Object.entries(store.userParams);
arrayData.reset(['skip', 'page']);
if (props.makeFetch)
await arrayData.applyFilter({
const filter = {
params: {
...Object.fromEntries(staticParams),
search: searchText.value,
},
});
};
if (props.whereFilter) {
filter.filter = {
where: props.whereFilter(searchText.value),
};
delete filter.params.search;
}
await arrayData.applyFilter(filter);
}
</script>
<template>

View File

@ -28,7 +28,7 @@ export function useValidator() {
}
const { t } = useI18n();
const validations = function (validation) {
const validations = function (validation = {}) {
return {
format: (value) => {
const { allowNull, with: format, allowBlank } = validation;
@ -40,12 +40,15 @@ export function useValidator() {
if (!isValid) return message;
},
presence: (value) => {
let message = `Value can't be empty`;
let message = t(`globals.valueCantBeEmpty`);
if (validation.message)
message = t(validation.message) || validation.message;
return !validator.isEmpty(value ? String(value) : '') || message;
},
required: (required, value) => {
return required ? !!value || t('globals.fieldRequired') : null;
},
length: (value) => {
const options = {
min: validation.min || validation.is,
@ -71,12 +74,17 @@ export function useValidator() {
return validator.isInt(value) || 'Value should be integer';
return validator.isNumeric(value) || 'Value should be a number';
},
min: (value, min) => {
if (min >= 0)
if (Math.floor(value) < min) return t('inputMin', { value: min });
},
custom: (value) => validation.bindedFunction(value) || 'Invalid value',
};
};
return {
validate,
validations,
models,
};
}

View File

@ -67,6 +67,7 @@ globals:
allRows: 'All { numberRows } row(s)'
markAll: Mark all
requiredField: Required field
valueCantBeEmpty: Value cannot be empty
class: clase
type: Type
reason: reason

View File

@ -76,6 +76,9 @@ globals:
warehouse: Almacén
company: Empresa
fieldRequired: Campo requerido
valueCantBeEmpty: El valor no puede estar vacío
Value can't be blank: El valor no puede estar en blanco
Value can't be null: El valor no puede ser nulo
allowedFilesText: 'Tipos de archivo permitidos: { allowedContentTypes }'
smsSent: SMS enviado
confirmDeletion: Confirmar eliminación
@ -237,7 +240,7 @@ globals:
purchaseRequest: Petición de compra
weeklyTickets: Tickets programados
formation: Formación
locations: Ubicaciones
locations: Localizaciones
warehouses: Almacenes
roles: Roles
connections: Conexiones

View File

@ -83,6 +83,7 @@ const agencyOptions = ref([]);
:label="t('Price')"
type="number"
min="0"
required="true"
clearable
/>
<VnInput
@ -95,7 +96,12 @@ const agencyOptions = ref([]);
</VnRow>
<VnRow>
<VnInput v-model="data.inflation" :label="t('Inflation')" clearable />
<VnInput
v-model="data.inflation"
:label="t('Inflation')"
type="number"
clearable
/>
<QCheckbox
v-model="data.isVolumetric"
:label="t('Volumetric')"

View File

@ -2,36 +2,47 @@
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import { computed } from 'vue';
import VnCard from 'components/common/VnCard.vue';
import ZoneDescriptor from './ZoneDescriptor.vue';
import ZoneSearchbar from './ZoneSearchbar.vue';
import ZoneFilterPanel from '../ZoneFilterPanel.vue';
const { t } = useI18n();
const route = useRoute();
const routeName = computed(() => route.name);
const customRouteRedirectName = computed(() => {
if (routeName.value === 'ZoneLocations') return null;
return routeName.value;
});
const searchbarMakeFetch = computed(() => routeName.value !== 'ZoneEvents');
const searchBarDataKeys = {
ZoneWarehouses: 'ZoneWarehouses',
ZoneSummary: 'ZoneSummary',
ZoneLocations: 'ZoneLocations',
ZoneEvents: 'ZoneEvents',
};
function notIsLocations(ifIsFalse, ifIsTrue) {
if (routeName.value != 'ZoneLocations') return ifIsFalse;
return ifIsTrue;
}
</script>
<template>
<VnCard
data-key="Zone"
data-key="zone"
base-url="Zones"
:descriptor="ZoneDescriptor"
:search-data-key="searchBarDataKeys[routeName]"
:filter-panel="ZoneFilterPanel"
:search-data-key="notIsLocations('ZoneList', 'ZoneLocations')"
:searchbar-props="{
url: 'Zones',
label: t('list.searchZone'),
label: notIsLocations(t('list.searchZone'), t('list.searchLocation')),
info: t('list.searchInfo'),
whereFilter: notIsLocations((value) => {
return /^\d+$/.test(value)
? { id: value }
: { name: { like: `%${value}%` } };
}),
}"
>
<template #searchbar>
<ZoneSearchbar />
</template>
</VnCard>
/>
</template>

View File

@ -8,13 +8,6 @@ import VnConfirm from 'components/ui/VnConfirm.vue';
import axios from 'axios';
const $props = defineProps({
zone: {
type: Object,
default: () => {},
},
});
const { t } = useI18n();
const { push, currentRoute } = useRouter();
const zoneId = currentRoute.value.params.id;
@ -22,32 +15,21 @@ const zoneId = currentRoute.value.params.id;
const actions = {
clone: async () => {
const opts = { message: t('Zone cloned'), type: 'positive' };
let clonedZoneId;
try {
const { data } = await axios.post(`Zones/${zoneId}/clone`, {
shipped: $props.zone.value.shipped,
});
clonedZoneId = data;
const { data } = await axios.post(`Zones/${zoneId}/clone`, {});
notify(opts);
push(`/zone/${data.id}/basic-data`);
} catch (e) {
opts.message = t('It was not able to clone the zone');
opts.type = 'negative';
} finally {
notify(opts);
if (clonedZoneId) push({ name: 'ZoneSummary', params: { id: clonedZoneId } });
}
},
remove: async () => {
try {
await axios.post(`Zones/${zoneId}/setDeleted`);
await axios.post(`Zones/${zoneId}/deleteZone`);
jon marked this conversation as resolved
Review

Revisar la traducción para esta acción

Revisar la traducción para esta acción
notify({ message: t('Zone deleted'), type: 'positive' });
notify({
message: t('You can undo this action within the first hour'),
icon: 'info',
});
push({ name: 'ZoneList' });
} catch (e) {
notify({ message: e.message, type: 'negative' });
@ -64,30 +46,31 @@ function openConfirmDialog(callback) {
}
</script>
<template>
<QItem @click="openConfirmDialog('clone')" v-ripple clickable>
<QItemSection avatar>
<QIcon name="content_copy" />
</QItemSection>
<QItemSection>{{ t('To clone zone') }}</QItemSection>
</QItem>
<QItem @click="openConfirmDialog('remove')" v-ripple clickable>
<QItemSection avatar>
<QIcon name="delete" />
</QItemSection>
<QItemSection>{{ t('deleteZone') }}</QItemSection>
</QItem>
<QItem @click="openConfirmDialog('clone')" v-ripple clickable>
<QItemSection avatar>
<QIcon name="content_copy" />
</QItemSection>
<QItemSection>{{ t('cloneZone') }}</QItemSection>
</QItem>
</template>
<i18n>
en:
deleteZone: Delete zone
deleteZone: Delete
cloneZone: Clone
confirmDeletion: Confirm deletion
confirmDeletionMessage: Are you sure you want to delete this zone?
es:
To clone zone: Clonar zone
deleteZone: Eliminar zona
cloneZone: Clonar
deleteZone: Eliminar
confirmDeletion: Confirmar eliminación
confirmDeletionMessage: Seguro que quieres eliminar este zona?
Zone deleted: Zona eliminada
</i18n>

View File

@ -58,20 +58,12 @@ const arrayData = useArrayData('ZoneEvents');
const exclusionGeoCreate = async () => {
try {
if (isNew.value) {
const params = {
zoneFk: parseInt(route.params.id),
date: dated.value,
geoIds: tickedNodes.value,
};
await axios.post('Zones/exclusionGeo', params);
} else {
const params = {
zoneExclusionFk: props.event?.zoneExclusionFk,
geoIds: tickedNodes.value,
};
await axios.post('Zones/updateExclusionGeo', params);
}
await refetchEvents();
} catch (err) {
console.error('Error creating exclusion geo: ', err);
@ -85,7 +77,7 @@ const exclusionCreate = async () => {
{ dated: dated.value },
]);
else
await axios.put(`Zones/${route.params.id}/exclusions/${props.event?.id}`, {
await axios.post(`Zones/${route.params.id}/exclusions`, {
dated: dated.value,
});
@ -103,8 +95,7 @@ const onSubmit = async () => {
const deleteEvent = async () => {
try {
if (!props.event) return;
const exclusionId = props.event?.zoneExclusionFk || props.event?.id;
await axios.delete(`Zones/${route.params.id}/exclusions/${exclusionId}`);
await axios.delete(`Zones/${route.params.id}/exclusions`);
await refetchEvents();
} catch (err) {
console.error('Error deleting event: ', err);
@ -141,7 +132,11 @@ onMounted(() => {
>
<template #form-inputs>
<VnRow class="row q-gutter-md q-mb-lg">
<VnInputDate :label="t('eventsInclusionForm.day')" v-model="dated" />
<VnInputDate
:label="t('eventsInclusionForm.day')"
v-model="dated"
:model-value="props.date"
/>
</VnRow>
<div class="column q-gutter-y-sm q-mb-md">
<QRadio

View File

@ -13,8 +13,8 @@ import { reactive } from 'vue';
const { t } = useI18n();
const stateStore = useStateStore();
const firstDay = ref(null);
const lastDay = ref(null);
const firstDay = ref();
const lastDay = ref();
const events = ref([]);
const formModeName = ref('include');
@ -44,25 +44,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</script>
<template>
<template v-if="stateStore.isHeaderMounted()">
<Teleport to="#actions-append">
<div class="row q-gutter-x-sm">
<QBtn
flat
@click="stateStore.toggleRightDrawer()"
round
dense
icon="menu"
>
<QTooltip bottom anchor="bottom right">
{{ t('globals.collapseMenu') }}
</QTooltip>
</QBtn>
</div>
</Teleport>
</template>
<QDrawer v-model="stateStore.rightDrawer" side="right" :width="256" show-if-above>
<QScrollArea class="fit text-grey-8">
<Teleport to="#right-panel" v-if="useStateStore().isHeaderMounted()">
jon marked this conversation as resolved
Review

El año aparece como 2001 y en salix como 01, confirmar como debe ser

El año aparece como 2001 y en salix como 01, confirmar como debe ser
Review

Lo he comentado con jgallego y se deja con el año completo

Lo he comentado con jgallego y se deja con el año completo
<ZoneEventsPanel
:first-day="firstDay"
:last-day="lastDay"
@ -70,8 +52,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
v-model:formModeName="formModeName"
@open-zone-form="openForm"
/>
</QScrollArea>
</QDrawer>
</Teleport>
<QPage class="q-pa-md flex justify-center">
<ZoneCalendarGrid
v-model:events="events"

View File

@ -1,10 +1,7 @@
<script setup>
import { onMounted, ref, computed, watch, onUnmounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import VnInput from 'src/components/common/VnInput.vue';
import { useState } from 'src/composables/useState';
import axios from 'axios';
import { useArrayData } from 'composables/useArrayData';
@ -30,7 +27,6 @@ const props = defineProps({
const emit = defineEmits(['update:tickedNodes']);
const { t } = useI18n();
const route = useRoute();
const state = useState();
@ -186,16 +182,6 @@ onUnmounted(() => {
</script>
<template>
<VnInput
v-if="showSearchBar"
v-model="store.userParams.search"
:placeholder="t('globals.search')"
@keydown.enter.prevent="reFetch()"
>
<template #prepend>
<QIcon class="cursor-pointer" name="search" />
</template>
</VnInput>
<QTree
ref="treeRef"
:nodes="nodes"

View File

@ -19,24 +19,14 @@ const exprBuilder = (param, value) => {
agencyModeFk: value,
};
case 'search':
if (value) {
if (!isNaN(value)) {
return { id: value };
} else {
return {
name: {
like: `%${value}%`,
},
};
}
}
return /^\d+$/.test(value) ? { id: value } : { name: { like: `%${value}%` } };
}
};
</script>
<template>
<VnSearchbar
data-key="ZoneList"
data-key="Zones"
url="Zones"
:filter="{
include: { relation: 'agencyMode', scope: { fields: ['name'] } },

View File

@ -14,7 +14,7 @@ const { t } = useI18n();
const route = useRoute();
const { openConfirmationModal } = useVnConfirm();
const paginateRef = ref(null);
const paginateRef = ref();
const createWarehouseDialogRef = ref(null);
const arrayData = useArrayData('ZoneWarehouses');

View File

@ -1,47 +1,25 @@
<script setup>
import { onMounted, ref, reactive } from 'vue';
import { onMounted, ref, reactive, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import VnSelect from 'src/components/common/VnSelect.vue';
import { useArrayData } from 'src/composables/useArrayData';
import axios from 'axios';
import useNotify from 'src/composables/useNotify.js';
import { watch } from 'vue';
import FetchData from 'src/components/FetchData.vue';
const { t } = useI18n();
const { notify } = useNotify();
const deliveryMethodFk = ref(null);
const deliveryMethods = ref([]);
const deliveryMethodFk = ref('delivery');
const deliveryMethods = ref({});
const inq = ref([]);
const formData = reactive({});
const arrayData = useArrayData('ZoneDeliveryDays', {
url: 'Zones/getEvents',
});
const fetchDeliveryMethods = async (filter) => {
try {
const params = { filter: JSON.stringify(filter) };
const { data } = await axios.get('DeliveryMethods', { params });
return data.map((deliveryMethod) => deliveryMethod.id);
} catch (err) {
console.error('Error fetching delivery methods: ', err);
}
};
watch(
() => deliveryMethodFk.value,
async (val) => {
let filter;
if (val === 'pickUp') filter = { where: { code: 'PICKUP' } };
else filter = { where: { code: { inq: ['DELIVERY', 'AGENCY'] } } };
deliveryMethods.value = await fetchDeliveryMethods(filter);
},
{ immediate: true }
);
const deliveryMethodsConfig = { pickUp: ['PICKUP'], delivery: ['AGENCY', 'DELIVERY'] };
const fetchData = async (params) => {
try {
const { data } = params
@ -62,14 +40,38 @@ const onSubmit = async () => {
};
onMounted(async () => {
deliveryMethodFk.value = 'delivery';
formData.geoFk = arrayData.store?.userParams?.geoFk;
formData.agencyModeFk = arrayData.store?.userParams?.agencyModeFk;
if (formData.geoFk || formData.agencyModeFk) await fetchData();
});
watch(
() => deliveryMethodFk.value,
() => {
inq.value = {
deliveryMethodFk: { inq: deliveryMethods.value[deliveryMethodFk.value] },
};
}
);
</script>
<template>
<FetchData
url="DeliveryMethods"
:fields="['id', 'name', 'deliveryMethodFk']"
@on-fetch="
(data) => {
Object.entries(deliveryMethodsConfig).forEach(([key, value]) => {
deliveryMethods[key] = data
.filter((code) => value.includes(code.code))
.map((method) => method.id);
});
inq = {
deliveryMethodFk: { inq: deliveryMethods[deliveryMethodFk] },
};
}
"
auto-load
/>
<QForm @submit="onSubmit()" class="q-pa-md">
<div class="column q-gutter-y-sm">
<QRadio
@ -90,7 +92,7 @@ onMounted(async () => {
:label="t('deliveryPanel.postcode')"
v-model="formData.geoFk"
url="Postcodes/location"
:fields="['geoFk', 'code', 'townFk']"
:fields="['geoFk', 'code', 'townFk', 'countryFk']"
sort-by="code, townFk"
option-value="geoFk"
option-label="code"
@ -106,26 +108,35 @@ onMounted(async () => {
<QItemLabel>{{ opt.code }}</QItemLabel>
<QItemLabel caption
>{{ opt.town?.province?.name }},
{{ opt.town?.province?.country?.country }}</QItemLabel
{{ opt.town?.province?.country?.name }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelect>
<VnSelect
:label="
t(
deliveryMethodFk === 'delivery'
? 'deliveryPanel.agency'
: 'deliveryPanel.warehouse'
)
"
data-key="delivery"
v-if="deliveryMethodFk == 'delivery'"
:label="t('deliveryPanel.agency')"
v-model="formData.agencyModeFk"
url="AgencyModes/isActive"
:fields="['id', 'name']"
:where="{
deliveryMethodFk: { inq: deliveryMethods },
}"
:where="inq"
sort-by="name ASC"
option-value="id"
option-label="name"
hide-selected
dense
outlined
rounded
/>
<VnSelect
v-else
:label="t('deliveryPanel.warehouse')"
v-model="formData.agencyModeFk"
url="AgencyModes/isActive"
:fields="['id', 'name']"
:where="inq"
sort-by="name ASC"
option-value="id"
option-label="name"

View File

@ -27,6 +27,7 @@ const agencies = ref([]);
:data-key="props.dataKey"
:search-button="true"
:hidden-tags="['search']"
search-url="table"
>
<template #tags="{ tag }">
<div class="q-gutter-x-xs">

View File

@ -1,74 +1,120 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { onMounted, computed } from 'vue';
import { computed, ref, onMounted } from 'vue';
import axios from 'axios';
import { toCurrency } from 'src/filters';
import VnPaginate from 'src/components/ui/VnPaginate.vue';
import ZoneSummary from 'src/pages/Zone/Card/ZoneSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { toTimeFormat } from 'src/filters/date';
import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useStateStore } from 'stores/useStateStore';
import axios from 'axios';
import ZoneSummary from 'src/pages/Zone/Card/ZoneSummary.vue';
import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputTime from 'src/components/common/VnInputTime.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import ZoneFilterPanel from './ZoneFilterPanel.vue';
import ZoneSearchbar from './Card/ZoneSearchbar.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const router = useRouter();
const { notify } = useNotify();
const { viewSummary } = useSummaryDialog();
const { openConfirmationModal } = useVnConfirm();
const stateStore = useStateStore();
const tableRef = ref();
const warehouseOptions = ref([]);
const redirectToZoneSummary = (event, { id }) => {
router.push({ name: 'ZoneSummary', params: { id } });
const tableFilter = {
include: [
{
relation: 'agencyMode',
scope: {
fields: ['id', 'name'],
},
},
],
};
const columns = computed(() => [
{
name: 'ID',
label: t('list.id'),
field: (row) => row.id,
sortable: true,
align: 'left',
name: 'id',
label: t('list.id'),
chip: {
condition: () => true,
},
isId: true,
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
name: 'name',
label: t('list.name'),
field: (row) => row.name,
sortable: true,
align: 'left',
isTitle: true,
create: true,
columnFilter: {
optionLabel: 'name',
optionValue: 'id',
},
},
{
name: 'agency',
align: 'left',
name: 'agencyModeFk',
label: t('list.agency'),
field: (row) => row?.agencyMode?.name,
sortable: true,
align: 'left',
cardVisible: true,
columnFilter: {
component: 'select',
inWhere: true,
attrs: {
url: 'AgencyModes',
},
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row?.agencyMode?.name),
},
{
name: 'close',
label: t('list.close'),
field: (row) => (row?.hour ? toTimeFormat(row?.hour) : '-'),
sortable: true,
align: 'left',
},
{
name: 'price',
label: t('list.price'),
field: (row) => (row?.price ? toCurrency(row.price) : '-'),
sortable: true,
align: 'left',
cardVisible: true,
format: (row) => toCurrency(row.price),
columnFilter: {
inWhere: true,
},
},
{
align: 'left',
name: 'hour',
label: t('list.close'),
cardVisible: true,
format: (row) => toTimeFormat(row.hour),
hidden: true,
},
{
name: 'actions',
label: '',
sortable: false,
align: 'right',
name: 'tableActions',
actions: [
{
title: t('list.zoneSummary'),
icon: 'preview',
action: (row) => viewSummary(row.id, ZoneSummary),
isPrimary: true,
},
{
title: t('globals.clone'),
icon: 'vn:clone',
action: (row) => handleClone(row.id),
isPrimary: true,
},
],
},
]);
@ -84,6 +130,7 @@ const handleClone = (id) => {
() => clone(id)
);
};
onMounted(() => (stateStore.rightDrawer = true));
</script>
@ -91,82 +138,72 @@ onMounted(() => (stateStore.rightDrawer = true));
<ZoneSearchbar />
<RightMenu>
<template #right-panel>
<ZoneFilterPanel data-key="ZoneList" :expr-builder="exprBuilder" />
<ZoneFilterPanel data-key="Zones" />
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<div class="vn-card-list">
<VnPaginate
data-key="ZoneList"
<VnTable
ref="tableRef"
data-key="Zones"
url="Zones"
:filter="{
include: { relation: 'agencyMode', scope: { fields: ['name'] } },
:create="{
urlCreate: 'Zones',
title: t('list.createZone'),
onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`),
formInitialData: {},
}"
:limit="20"
:user-filter="tableFilter"
:columns="columns"
redirect="zone"
:right-search="false"
auto-load
>
<template #body="{ rows }">
<div class="q-pa-md">
<QTable
:rows="rows"
:columns="columns"
row-key="clientId"
class="full-width"
@row-click="redirectToZoneSummary"
>
<template #header="props">
<QTr :props="props" class="bg">
<QTh
v-for="col in props.cols"
:key="col.name"
:props="props"
>
{{ t(col.label) }}
<QTooltip v-if="col.tooltip">{{
col.tooltip
}}</QTooltip>
</QTh>
</QTr>
<template #more-create-dialog="{ data }">
<VnSelect
url="AgencyModes"
v-model="data.agencyModeFk"
option-value="id"
option-label="name"
:label="t('list.agency')"
/>
<VnInput
v-model="data.price"
:label="t('list.price')"
min="0"
type="number"
required="true"
/>
<VnInput
v-model="data.bonus"
:label="t('list.bonus')"
min="0"
type="number"
/>
<VnInput
v-model="data.travelingDays"
:label="t('list.travelingDays')"
type="number"
min="0"
/>
<VnInputTime v-model="data.hour" :label="t('list.close')" />
<VnSelect
url="Warehouses"
v-model="data.warehouseFK"
option-value="id"
option-label="name"
:label="t('list.warehouse')"
:options="warehouseOptions"
/>
<QCheckbox
v-model="data.isVolumetric"
:label="t('list.isVolumetric')"
:toggle-indeterminate="false"
/>
</template>
</VnTable>
</template>
<template #body-cell="props">
<QTd :props="props">
<QTr :props="props" class="cursor-pointer">
{{ props.value }}
</QTr>
</QTd>
</template>
<template #body-cell-actions="props">
<QTd :props="props" class="q-gutter-x-sm">
<QIcon
name="vn:clone"
size="sm"
color="primary"
@click.stop="handleClone(props.row.id)"
>
<QTooltip>{{ t('globals.clone') }}</QTooltip>
</QIcon>
<QIcon
name="preview"
size="sm"
color="primary"
@click.stop="
viewSummary(props.row.id, ZoneSummary)
"
>
<QTooltip>{{ t('Preview') }}</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</div>
</template>
</VnPaginate>
</div>
<QPageSticky position="bottom-right" :offset="[18, 18]">
<QBtn :to="{ path: `/zone/create` }" fab icon="add" color="primary">
<QTooltip>{{ t('list.create') }}</QTooltip>
</QBtn>
</QPageSticky>
</QPage>
</template>
<i18n>
es:
Search zone: Buscar zona
You can search zones by id or name: Puedes buscar zonas por id o nombre
</i18n>

View File

@ -18,9 +18,16 @@ list:
create: Create zone
openSummary: Details
searchZone: Search zones
searchLocation: Search locations
searchInfo: Search zone by id or name
confirmCloneTitle: All it's properties will be copied
confirmCloneSubtitle: Do you want to clone this zone?
travelingDays: Traveling days
warehouse: Warehouse
bonus: Bonus
isVolumetric: Volumetric
createZone: Create zone
zoneSummary: Summary
create:
name: Name
warehouse: Warehouse
@ -30,6 +37,8 @@ create:
price: Price
bonus: Bonus
volumetric: Volumetric
itemMaxSize: Max m³
inflation: Inflation
summary:
agency: Agency
price: Price

View File

@ -18,9 +18,16 @@ list:
create: Crear zona
openSummary: Detalles
searchZone: Buscar zonas
searchLocation: Buscar localizaciones
jon marked this conversation as resolved
Review

El texto de la sección aparece como ubicaciones en vez de localizaciones

El texto de la sección aparece como ubicaciones en vez de localizaciones
searchInfo: Buscar zonas por identificador o nombre
confirmCloneTitle: Todas sus propiedades serán copiadas
confirmCloneSubtitle: ¿Seguro que quieres clonar esta zona?
travelingDays: Días de viaje
warehouse: Almacén
bonus: Bonus
jgallego marked this conversation as resolved
Review

hour es hora..si quieres que sea hora de cierre, cambia la clave sino puede ser muy confuso o colisionar en el futuro con otras claves

hour es hora..si quieres que sea hora de cierre, cambia la clave sino puede ser muy confuso o colisionar en el futuro con otras claves
isVolumetric: Volumétrico
createZone: Crear zona
zoneSummary: Resumen
create:
name: Nombre
warehouse: Almacén
@ -30,6 +37,8 @@ create:
price: Precio
bonus: Bonificación
volumetric: Volumétrico
itemMaxSize: Medida máxima
inflation: Inflación
summary:
agency: Agencia
price: Precio

View File

@ -50,33 +50,6 @@ export default {
},
component: () => import('src/pages/Zone/ZoneDeliveryDays.vue'),
},
{
path: 'create',
name: 'ZoneCreate',
meta: {
title: 'zoneCreate',
icon: 'create',
},
component: () => import('src/pages/Zone/ZoneCreate.vue'),
},
{
path: ':id/edit',
name: 'ZoneEdit',
meta: {
title: 'zoneEdit',
icon: 'edit',
},
component: () => import('src/pages/Zone/ZoneCreate.vue'),
},
// {
// path: 'counter',
// name: 'ZoneCounter',
// meta: {
// title: 'zoneCounter',
// icon: 'add_circle',
// },
// component: () => import('src/pages/Zone/ZoneCounter.vue'),
// },
{
name: 'ZoneUpcomingDeliveries',
path: 'upcoming-deliveries',

View File

@ -0,0 +1,21 @@
describe('ZoneBasicData', () => {
const notification = '.q-notification__message';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('/#/zone/4/basic-data');
});
it('should throw an error if the name is empty', () => {
cy.get('.q-card > :nth-child(1)').clear();
cy.get('.q-btn-group > .q-btn--standard').click();
cy.get(notification).should('contains.text', "can't be blank");
});
it("should edit the basicData's zone", () => {
cy.get('.q-card > :nth-child(1)').type(' modified');
cy.get('.q-btn-group > .q-btn--standard').click();
cy.get(notification).should('contains.text', 'Data saved');
});
});

View File

@ -0,0 +1,38 @@
describe('ZoneCreate', () => {
const notification = '.q-notification__message';
const data = {
Name: { val: 'Zone pickup D' },
Price: { val: '3' },
Bonus: { val: '0' },
'Traveling days': { val: '0' },
Warehouse: { val: 'Algemesi', type: 'select' },
Volumetric: { val: 'true', type: 'checkbox' },
};
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit('/#/zone/list');
cy.get('.q-page-sticky > div > .q-btn').click();
});
it('should throw an error if an agency has not been selected', () => {
cy.fillInForm({
...data,
});
cy.get('input[aria-label="Close"]').type('10:00');
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.get(notification).should('contains.text', 'Agency cannot be blank');
});
it('should create a zone', () => {
cy.fillInForm({
...data,
Agency: { val: 'inhouse pickup', type: 'select' },
});
cy.get('input[aria-label="Close"]').type('10:00');
cy.get('.q-mt-lg > .q-btn--standard').click();
cy.get(notification).should('contains.text', 'Data created');
});
});

View File

@ -1,15 +1,18 @@
describe('ZoneList', () => {
beforeEach(() => {
cy.viewport(1920, 1080);
cy.viewport(1280, 720);
cy.login('developer');
cy.visit(`/#/zone/list`);
cy.visit('/#/zone/list');
});
it('should open the details', () => {
cy.get(':nth-child(1) > .text-right > .material-symbols-outlined').click();
it('should filter by agency', () => {
cy.get(
':nth-child(1) > .column > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'
).type('{downArrow}{enter}');
});
it('should redirect to summary', () => {
cy.waitForElement('.q-page');
cy.get('tbody > :nth-child(1)').click();
it('should open the zone summary', () => {
cy.get('input[aria-label="Name"]').type('zone refund');
cy.get('.q-scrollarea__content > .q-btn--standard > .q-btn__content').click();
});
});

View File

@ -0,0 +1,34 @@
describe('ZoneWarehouse', () => {
const data = {
Warehouse: { val: 'Algemesi', type: 'select' },
};
const deviceProductionField =
'.vn-row > :nth-child(1) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container';
const dataError = "ER_DUP_ENTRY: Duplicate entry '2-2' for key 'zoneFk'";
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
cy.visit(`/#/zone/2/warehouses`);
});
it('should throw an error if the warehouse chosen is already put in the zone', () => {
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get(deviceProductionField).click();
cy.get(deviceProductionField).type('{upArrow}{enter}');
cy.get('.q-notification__message').should('have.text', dataError);
});
it('should create a warehouse', () => {
cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click();
cy.get(deviceProductionField).click();
cy.fillInForm(data);
cy.get('.q-mt-lg > .q-btn--standard').click();
});
it('should delete a warehouse', () => {
cy.get('tbody > :nth-child(2) > :nth-child(2) > .q-icon').click();
cy.get('.q-card__actions > .q-btn--flat > .q-btn__content').click();
cy.reload();
});
});

View File

@ -105,6 +105,12 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => {
case 'date':
cy.wrap(el).type(val.split('-').join(''));
break;
case 'time':
cy.wrap(el).click();
cy.get('.q-time .q-time__clock').contains(val.h).click();
cy.get('.q-time .q-time__clock').contains(val.m).click();
cy.get('.q-time .q-time__link').contains(val.x).click();
break;
default:
cy.wrap(el).type(val);
break;