diff --git a/package.json b/package.json index eaaa0b812..a61c8f21a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.42.0", + "version": "24.44.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", diff --git a/src/boot/axios.js b/src/boot/axios.js index 99a163cca..3bd80f487 100644 --- a/src/boot/axios.js +++ b/src/boot/axios.js @@ -2,9 +2,11 @@ import axios from 'axios'; import { useSession } from 'src/composables/useSession'; import { Router } from 'src/router'; import useNotify from 'src/composables/useNotify.js'; +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; const session = useSession(); const { notify } = useNotify(); +const stateQuery = useStateQueryStore(); const baseUrl = '/api/'; axios.defaults.baseURL = baseUrl; @@ -15,7 +17,7 @@ const onRequest = (config) => { if (token.length && !config.headers.Authorization) { config.headers.Authorization = token; } - + stateQuery.add(config); return config; }; @@ -24,10 +26,10 @@ const onRequestError = (error) => { }; const onResponse = (response) => { - const { method } = response.config; + const config = response.config; + stateQuery.remove(config); - const isSaveRequest = method === 'patch'; - if (isSaveRequest) { + if (config.method === 'patch') { notify('globals.dataSaved', 'positive'); } @@ -35,6 +37,8 @@ const onResponse = (response) => { }; const onResponseError = (error) => { + stateQuery.remove(error.config); + let message = ''; const response = error.response; diff --git a/src/components/CreateBankEntityForm.vue b/src/components/CreateBankEntityForm.vue index 0a3c10f57..a42be6ef8 100644 --- a/src/components/CreateBankEntityForm.vue +++ b/src/components/CreateBankEntityForm.vue @@ -31,8 +31,8 @@ const countriesFilter = { const countriesOptions = ref([]); -const onDataSaved = (formData, requestResponse) => { - emit('onDataSaved', formData, requestResponse); +const onDataSaved = (...args) => { + emit('onDataSaved', ...args); }; onMounted(async () => { diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index 99cba5360..030ca1388 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -79,14 +79,20 @@ async function onProvinceCreated(data) { watch( () => [postcodeFormData.countryFk], async (newCountryFk, oldValueFk) => { - if (!!oldValueFk[0] && newCountryFk[0] !== oldValueFk[0]) { + if (Array.isArray(newCountryFk)) { + newCountryFk = newCountryFk[0]; + } + if (Array.isArray(oldValueFk)) { + oldValueFk = oldValueFk[0]; + } + if (!!oldValueFk && newCountryFk !== oldValueFk) { postcodeFormData.provinceFk = null; postcodeFormData.townFk = null; } - if ((newCountryFk, newCountryFk !== postcodeFormData.countryFk)) { + if (oldValueFk !== newCountryFk) { await provincesFetchDataRef.value.fetch({ where: { - countryFk: newCountryFk[0], + countryFk: newCountryFk, }, }); await townsFetchDataRef.value.fetch({ @@ -103,9 +109,12 @@ watch( watch( () => postcodeFormData.provinceFk, async (newProvinceFk) => { - if (newProvinceFk[0] && newProvinceFk[0] !== postcodeFormData.provinceFk) { + if (Array.isArray(newProvinceFk)) { + newProvinceFk = newProvinceFk[0]; + } + if (newProvinceFk !== postcodeFormData.provinceFk) { await townsFetchDataRef.value.fetch({ - where: { provinceFk: newProvinceFk[0] }, + where: { provinceFk: newProvinceFk }, }); } } @@ -125,16 +134,26 @@ async function handleCountries(data) { - + { }, }" url="Autonomies/location" + :sort-by="['name ASC']" + :limit="30" /> + diff --git a/src/components/VnTable/VnTable.vue b/src/components/VnTable/VnTable.vue index 5a30f4d53..5239fe859 100644 --- a/src/components/VnTable/VnTable.vue +++ b/src/components/VnTable/VnTable.vue @@ -134,6 +134,7 @@ const splittedColumns = ref({ columns: [] }); const columnsVisibilitySkipped = ref(); const createForm = ref(); const tableFilterRef = ref([]); +const tableRef = ref(); const tableModes = [ { @@ -321,6 +322,13 @@ function handleOnDataSaved(_) { if (_.onDataSaved) _.onDataSaved({ CrudModelRef: CrudModelRef.value }); else $props.create.onDataSaved(_); } + +function handleScroll() { + const tMiddle = tableRef.value.$el.querySelector('.q-table__middle'); + const { scrollHeight, scrollTop, clientHeight } = tMiddle; + const isAtBottom = Math.abs(scrollHeight - scrollTop - clientHeight) <= 40; + if (isAtBottom) CrudModelRef.value.vnPaginateRef.paginate(); +} - event.index > rows.length - 2 && - ($props.crudModel?.paginate ?? true) && - CrudModelRef.vnPaginateRef.paginate() - " + @virtual-scroll="handleScroll" @row-click="(_, row) => rowClickFunction && rowClickFunction(row)" @update:selected="emit('update:selected', $event)" > diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 952b7dd8c..b0aa648c1 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -141,6 +141,7 @@ function findKeyInOptions() { function setOptions(data) { myOptions.value = JSON.parse(JSON.stringify(data)); myOptionsOriginal.value = JSON.parse(JSON.stringify(data)); + emit('update:options', data); } function filter(val, options) { diff --git a/src/components/ui/VnToSummary.vue b/src/components/ui/VnToSummary.vue new file mode 100644 index 000000000..1c7d6cf01 --- /dev/null +++ b/src/components/ui/VnToSummary.vue @@ -0,0 +1,33 @@ + + + + + + + diff --git a/src/components/ui/VnUsesMana.vue b/src/components/ui/VnUsesMana.vue new file mode 100644 index 000000000..891de5f63 --- /dev/null +++ b/src/components/ui/VnUsesMana.vue @@ -0,0 +1,55 @@ + + + + + + + + diff --git a/src/css/app.scss b/src/css/app.scss index 905934d4c..d4c76ad6b 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -288,3 +288,7 @@ input::-webkit-inner-spin-button { color: $info; } } + +.no-visible { + visibility: hidden; +} diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index b73395df2..fa0a14f45 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -277,6 +277,7 @@ globals: medical: Mutual RouteExtendedList: Router wasteRecalc: Waste recaclulate + operator: Operator supplier: Supplier created: Created worker: Worker @@ -743,6 +744,7 @@ worker: locker: Locker balance: Balance medical: Medical + operator: Operator list: name: Name email: Email @@ -840,6 +842,18 @@ worker: debit: Debt credit: Have concept: Concept + operator: + numberOfWagons: Number of wagons + train: Train + itemPackingType: Item packing type + warehouse: Warehouse + sector: Sector + labeler: Printer + linesLimit: Lines limit + volumeLimit: Volume limit + sizeLimit: Size limit + isOnReservationMode: Reservation mode + machine: Machine wagon: pageTitles: wagons: Wagons diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 2552c9549..5dfe90e98 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -281,6 +281,7 @@ globals: serial: Facturas por serie medical: Mutua wasteRecalc: Recalcular mermas + operator: Operario supplier: Proveedor created: Fecha creación worker: Trabajador @@ -750,6 +751,7 @@ worker: balance: Balance formation: Formación medical: Mutua + operator: Operario list: name: Nombre email: Email @@ -838,6 +840,19 @@ worker: debit: Debe credit: Haber concept: Concepto + operator: + numberOfWagons: Número de vagones + train: tren + itemPackingType: Tipo de embalaje + warehouse: Almacén + sector: Sector + labeler: Impresora + linesLimit: Líneas límite + volumeLimit: Volumen límite + sizeLimit: Tamaño límite + isOnReservationMode: Modo de reserva + machine: Máquina + wagon: pageTitles: wagons: Vagones diff --git a/src/pages/Customer/components/CustomerSummaryTable.vue b/src/pages/Customer/components/CustomerSummaryTable.vue index 946296e67..f6013dea9 100644 --- a/src/pages/Customer/components/CustomerSummaryTable.vue +++ b/src/pages/Customer/components/CustomerSummaryTable.vue @@ -6,7 +6,7 @@ import { useRoute, useRouter } from 'vue-router'; import { date } from 'quasar'; import { toDateFormat } from 'src/filters/date.js'; -import { toCurrency } from 'src/filters'; +import { dashIfEmpty, toCurrency } from 'src/filters'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import TicketSummary from 'src/pages/Ticket/Card/TicketSummary.vue'; @@ -32,6 +32,16 @@ const filter = { }, { relation: 'invoiceOut', scope: { fields: ['id'] } }, { relation: 'agencyMode', scope: { fields: ['name'] } }, + { + relation: 'ticketSales', + scope: { + fields: ['id', 'concept', 'itemFk'], + include: { relation: 'item' }, + scope: { + fields: ['id', 'name', 'itemPackingTypeFk'], + }, + }, + }, ], where: { clientFk: route.params.id }, order: ['shipped DESC', 'id'], @@ -87,7 +97,12 @@ const columns = computed(() => [ label: t('Total'), name: 'total', }, - + { + align: 'left', + name: 'itemPackingTypeFk', + label: t('ticketSale.packaging'), + format: (row) => getItemPackagingType(row), + }, { align: 'right', label: '', @@ -135,6 +150,15 @@ const setShippedColor = (date) => { if (difference == 0) return 'warning'; if (difference < 0) return 'success'; }; + +const getItemPackagingType = (row) => { + const packagingType = row?.ticketSales + .map((sale) => sale.item?.itemPackingTypeFk || '-') + .filter((value) => value !== '-') + .join(', '); + + return dashIfEmpty(packagingType); +}; diff --git a/src/pages/Entry/Card/EntrySummary.vue b/src/pages/Entry/Card/EntrySummary.vue index 58a5c2e1b..c2f1e6b57 100644 --- a/src/pages/Entry/Card/EntrySummary.vue +++ b/src/pages/Entry/Card/EntrySummary.vue @@ -11,6 +11,7 @@ import { toDate, toCurrency } from 'src/filters'; import { getUrl } from 'src/composables/getUrl'; import axios from 'axios'; import FetchedTags from 'src/components/ui/FetchedTags.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; const route = useRoute(); const { t } = useI18n(); @@ -163,14 +164,12 @@ const fetchEntryBuys = async () => { data-key="EntrySummary" > - - - + :route-name="'EntrySummary'" + :entity-id="entityId" + :url="entryUrl" + /> {{ entry.id }} - {{ entry.supplier.nickname }} diff --git a/src/pages/Item/ItemRequest.vue b/src/pages/Item/ItemRequest.vue index 8a41bbe04..82c3b48e0 100644 --- a/src/pages/Item/ItemRequest.vue +++ b/src/pages/Item/ItemRequest.vue @@ -229,7 +229,7 @@ onBeforeMount(() => { > - {{ row.ticketFk }} + {{ row.ticketFk }} @@ -251,7 +251,7 @@ onBeforeMount(() => { - {{ row.requesterName }} + {{ row.requesterName }} @@ -292,7 +292,7 @@ onBeforeMount(() => { - {{ row.itemDescription }} + {{ row.itemDescription }} diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index 0e81ff5a5..64bc0e575 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -174,6 +174,16 @@ const decrement = (paramsObj, key) => { + + + + + @@ -274,11 +284,11 @@ en: to: To mine: For me state: State + myTeam: My team dateFiltersTooltip: Cannot choose a range of dates and days onward at the same time denied: Denied accepted: Accepted pending: Pending - es: params: search: Búsqueda general @@ -291,6 +301,7 @@ es: to: Hasta mine: Para mi state: Estado + myTeam: Mi equipo dateFiltersTooltip: No se puede seleccionar un rango de fechas y días en adelante a la vez denied: Denegada accepted: Aceptada diff --git a/src/pages/ItemType/Card/ItemTypeSummary.vue b/src/pages/ItemType/Card/ItemTypeSummary.vue index 62d1c74ab..c51d59e13 100644 --- a/src/pages/ItemType/Card/ItemTypeSummary.vue +++ b/src/pages/ItemType/Card/ItemTypeSummary.vue @@ -6,6 +6,7 @@ import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.v import CardSummary from 'components/ui/CardSummary.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnToSummary from 'src/components/ui/VnToSummary.vue'; onUpdated(() => summaryRef.value.fetch()); @@ -55,6 +56,11 @@ async function setItemTypeData(data) { > + diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue index fdc75abda..f5ce8a0f3 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue @@ -12,6 +12,7 @@ import VnInputTime from 'components/common/VnInputTime.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import { useAcl } from 'src/composables/useAcl'; import { useValidator } from 'src/composables/useValidator'; import { toTimeFormat } from 'filters/date.js'; @@ -28,14 +29,17 @@ const { validate } = useValidator(); const { notify } = useNotify(); const router = useRouter(); const { t } = useI18n(); -const agencyFetchRef = ref(null); -const zonesFetchRef = ref(null); +const canEditZone = useAcl().hasAny([ + { model: 'Ticket', props: 'editZone', accessType: 'WRITE' }, +]); +const agencyFetchRef = ref(); const warehousesOptions = ref([]); const companiesOptions = ref([]); const agenciesOptions = ref([]); const zonesOptions = ref([]); const addresses = ref([]); +const zoneSelectRef = ref(); const formData = ref($props.formData); watch( @@ -44,6 +48,8 @@ watch( { deep: true } ); +onMounted(() => onFormModelInit()); + const agencyByWarehouseFilter = computed(() => ({ fields: ['id', 'name'], order: 'name ASC', @@ -52,18 +58,16 @@ const agencyByWarehouseFilter = computed(() => ({ }, })); -function zoneWhere() { - if (formData?.value?.agencyModeFk) { - return formData.value?.agencyModeFk - ? { - shipped: formData.value?.shipped, - addressFk: formData.value?.addressFk, - agencyModeFk: formData.value?.agencyModeFk, - warehouseFk: formData.value?.warehouseFk, - } - : {}; - } -} +const zoneWhere = computed(() => { + return formData.value?.agencyModeFk + ? { + shipped: formData.value?.shipped, + addressFk: formData.value?.addressFk, + agencyModeFk: formData.value?.agencyModeFk, + warehouseFk: formData.value?.warehouseFk, + } + : {}; +}); const getLanded = async (params) => { try { @@ -270,7 +274,17 @@ const redirectToCustomerAddress = () => { }); }; -onMounted(() => onFormModelInit()); +async function getZone(options) { + if (!zoneId.value) return; + + const zone = options.find((z) => z.id == zoneId.value); + if (zone) return; + + const { data } = await axios.get('Zones/' + zoneId.value, { + params: { filter: JSON.stringify({ fields: ['id', 'name'] }) }, + }); + zoneSelectRef.value.opts.push(data); +} onFormModelInit()); :rules="validate('basicData.agency')" /> onFormModelInit()); :fields="['id', 'name']" sort-by="id" :where="zoneWhere" - hide-selected - map-options - :required="true" - @focus="zonesFetchRef.fetch()" :rules="validate('basicData.zone')" + :required="true" + :disable="!canEditZone" + @update:options="getZone" > diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 73104fe27..834fced87 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -3,7 +3,7 @@ import axios from 'axios'; import { ref, toRefs } from 'vue'; import { useQuasar } from 'quasar'; import { useI18n } from 'vue-i18n'; -import { useRouter } from 'vue-router'; +import { useRoute, useRouter } from 'vue-router'; import { usePrintService } from 'composables/usePrintService'; import SendEmailDialog from 'components/common/SendEmailDialog.vue'; import VnConfirm from 'components/ui/VnConfirm.vue'; @@ -23,6 +23,7 @@ const props = defineProps({ required: true, }, }); +const route = useRoute(); const { push, currentRoute } = useRouter(); const { dialog, notify } = useQuasar(); @@ -40,6 +41,8 @@ const isEditable = ref(); const hasInvoicing = useAcl('invoicing'); const hasPdf = ref(); const weight = ref(); +const hasDocuwareFile = ref(); +const quasar = useQuasar(); const actions = { clone: async () => { const opts = { message: t('Ticket cloned'), type: 'positive' }; @@ -331,10 +334,49 @@ async function handleInvoiceOutData() { }); hasPdf.value = data[0]?.hasPdf; } + +async function docuwareDownload() { + await axios.get(`Tickets/${ticketId}/docuwareDownload`); +} + +async function hasDocuware() { + const { data } = await axios.post(`Docuwares/${ticketId}/checkFile`, { + fileCabinet: 'deliveryNote', + signed: true, + }); + hasDocuwareFile.value = data; +} + +async function uploadDocuware(force) { + console.log('force: ', force); + if (!force) + return quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Send PDF to tablet'), + message: t('Are you sure you want to replace this delivery note?'), + }, + }) + .onOk(async () => { + uploadDocuware(true); + }); + + const { data } = await axios.post(`Docuwares/upload`, { + fileCabinet: 'deliveryNote', + ticketIds: [parseInt(ticketId)], + }); + + if (data) notify({ message: t('PDF sent!'), type: 'positive' }); +} @@ -452,7 +494,13 @@ async function handleInvoiceOutData() { - + {{ t('as PDF') }} @@ -460,6 +508,14 @@ async function handleInvoiceOutData() { {{ t('as PDF without prices') }} + + {{ t('as PDF signed') }} + - + {{ t('Send PDF') }} - + {{ t('Send PDF to tablet') }} diff --git a/src/pages/Ticket/Card/TicketEditMana.vue b/src/pages/Ticket/Card/TicketEditMana.vue index 428e5e8c2..3d5b04a41 100644 --- a/src/pages/Ticket/Card/TicketEditMana.vue +++ b/src/pages/Ticket/Card/TicketEditMana.vue @@ -1,8 +1,8 @@ @@ -99,6 +105,14 @@ function toTicketUrl(section) { :url="`Tickets/${entityId}/summary`" data-key="TicketSummary" > + + + Ticket #{{ entity.id }} - {{ entity.client?.name }} ({{ @@ -108,23 +122,37 @@ function toTicketUrl(section) { - - - + + + + + + + + + + diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index 6d72de764..7f72f9862 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -20,11 +20,31 @@ const provinces = ref([]); const states = ref([]); const agencies = ref([]); const warehouses = ref([]); +const groupedStates = ref([]); + +const getGroupedStates = (data) => { + for (const state of data) { + groupedStates.value.push({ + id: state.id, + name: t(`${state.code}`), + code: state.code, + }); + } +}; (provinces = data)" auto-load /> (states = data)" auto-load /> + { + getGroupedStates(data); + } + " + auto-load + /> (agencies = data)" auto-load /> (warehouses = data)" auto-load /> @@ -90,12 +110,35 @@ const warehouses = ref([]); option-label="name" emit-value map-options + use-input dense outlined rounded /> + + + + + + + + + + + + + + + + + + @@ -245,6 +309,11 @@ en: provinceFk: Province agencyModeFk: Agency warehouseFk: Warehouse + FREE: Free + ON_PREPARATION: On preparation + PACKED: Packed + DELIVERED: Delivered + ON_PREVIOUS: ON_PREVIOUS es: params: search: Contiene @@ -278,4 +347,12 @@ es: Yes: Si No: No Days onward: Días adelante + Grouped state: Estado agrupado + FREE: Libre + ON_PREPARATION: En preparación + PACKED: Encajado + DELIVERED: Servido + ON_PREVIOUS: ON_PREVIOUS + Collection: Colección + Nickname: Nombre mostrado diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index ad97e75c1..c8687ed5f 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -95,6 +95,7 @@ const columns = computed(() => [ columnField: { component: null, }, + columnClass: 'expand', format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson), }, { @@ -153,11 +154,6 @@ const columns = computed(() => [ }, columnClass: 'expand', }, - { - align: 'left', - name: 'refFk', - label: t('ticketList.ref'), - }, { align: 'left', name: 'zoneFk', @@ -191,6 +187,12 @@ const columns = computed(() => [ }, format: (row) => toCurrency(row.totalWithVat), }, + { + align: 'left', + name: 'packing', + label: t('ticketSale.packaging'), + format: (row, dashIfEmpty) => dashIfEmpty(row.packing), + }, { align: 'right', name: 'tableActions', @@ -548,7 +550,7 @@ function setReference(data) { - {{ row.salesPerson }} + {{ dashIfEmpty(row.userName) }} @@ -577,16 +579,16 @@ function setReference(data) { {{ row.state }} + + + {{ row.refFk }} + + + {{ row.state }} - - - {{ dashIfEmpty(row.refFk) }} - - - {{ dashIfEmpty(row.zoneName) }} diff --git a/src/pages/Worker/Card/WorkerCalendarFilter.vue b/src/pages/Worker/Card/WorkerCalendarFilter.vue index 2b81b6eaf..67b7df907 100644 --- a/src/pages/Worker/Card/WorkerCalendarFilter.vue +++ b/src/pages/Worker/Card/WorkerCalendarFilter.vue @@ -3,9 +3,11 @@ import WorkerEventLabel from 'pages/Worker/Card/WorkerEventLabel.vue'; import FetchData from 'components/FetchData.vue'; import { useI18n } from 'vue-i18n'; import VnSelect from 'components/common/VnSelect.vue'; +import useNotify from 'src/composables/useNotify'; import { useRoute } from 'vue-router'; -import { computed, ref } from 'vue'; +import { computed, ref, watch } from 'vue'; import { toDateFormat } from '../../../filters/date'; +const { notify } = useNotify(); const { t } = useI18n(); const route = useRoute(); @@ -33,6 +35,13 @@ const props = defineProps({ }, }); +watch( + () => props.contractHolidays, + (newValue) => { + checkHolidays(newValue); + }, + { deep: true, immediate: true } +); const emit = defineEmits(['update:businessFk', 'update:year', 'update:absenceType']); const selectedBusinessFk = computed({ @@ -53,12 +62,22 @@ const selectedAbsenceType = computed({ }, }); -const generateYears = () => { +function generateYears() { const now = Date.vnNew(); const maxYear = now.getFullYear() + 1; return Array.from({ length: 5 }, (_, i) => String(maxYear - i)) || []; -}; +} + +function checkHolidays(contractHolidays) { + if (!contractHolidays) return; + if ( + contractHolidays.holidaysEnjoyed > contractHolidays.totalHolidays || + contractHolidays.hoursEnjoyed > contractHolidays.totalHours + ) { + notify(t('Vacation days have been exceeded'), 'negative'); + } +} const absenceTypeList = ref([]); const contractList = ref([]); diff --git a/src/pages/Worker/Card/WorkerOperator.vue b/src/pages/Worker/Card/WorkerOperator.vue new file mode 100644 index 000000000..cdacc72c0 --- /dev/null +++ b/src/pages/Worker/Card/WorkerOperator.vue @@ -0,0 +1,204 @@ + + + + + (trainsData = data)" auto-load /> + (itemPackingTypesData = data)" + auto-load + /> + (warehousesData = data)" + auto-load + /> + (PrintersData = data)" auto-load /> + (sectorsData = data)" auto-load /> + (machinesData = data)" auto-load /> + + + + + + + + + + + + + + + + + + + ID: {{ scope.opt?.id }} + + {{ scope.opt?.id }}, + {{ scope.opt?.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +es: + Model: Modelo + Serial number: Número de serie + Current SIM: SIM actual + Add new device: Añadir nuevo dispositivo + PDA deallocated: PDA desasignada + Remove PDA: Eliminar PDA + Do you want to remove this PDA?: ¿Desea eliminar este PDA? + You can only have one PDA: Solo puedes tener un PDA si no eres autonomo + This PDA is already assigned to another user: Este PDA ya está asignado a otro usuario + diff --git a/src/pages/Worker/WorkerList.vue b/src/pages/Worker/WorkerList.vue index 9795cbed0..7a3f760bc 100644 --- a/src/pages/Worker/WorkerList.vue +++ b/src/pages/Worker/WorkerList.vue @@ -29,6 +29,7 @@ const postcodesOptions = ref([]); const user = useState().getUser(); const defaultPayMethod = ref(); +const bankEntitiesRef = ref(); const columns = computed(() => [ { align: 'left', @@ -118,6 +119,12 @@ onBeforeMount(async () => { ).data?.payMethodFk; }); +async function handleNewBankEntity(data, resp) { + await bankEntitiesRef.value.fetch(); + data.bankEntityFk = resp.id; + bankEntitiesOptions.value.push(resp); +} + function handleLocation(data, location) { const { town, code, provinceFk, countryFk } = location ?? {}; data.postcode = code; @@ -177,6 +184,7 @@ async function autofillBic(worker) { auto-load /> (bankEntitiesOptions = data)" auto-load @@ -344,7 +352,9 @@ async function autofillBic(worker) { > bankEntitiesOptions.push(data)" + @on-data-saved=" + (_, resp) => handleNewBankEntity(data, resp) + " /> diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index 7258881be..c2a9e668f 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -27,6 +27,7 @@ export default { 'WorkerBalance', 'WorkerFormation', 'WorkerMedical', + 'WorkerOperator', ], }, children: [ @@ -208,6 +209,15 @@ export default { }, component: () => import('src/pages/Worker/Card/WorkerMedical.vue'), }, + { + name: 'WorkerOperator', + path: 'operator', + meta: { + title: 'operator', + icon: 'person', + }, + component: () => import('src/pages/Worker/Card/WorkerOperator.vue'), + }, ], }, ], diff --git a/src/stores/useStateQueryStore.js b/src/stores/useStateQueryStore.js new file mode 100644 index 000000000..d25dbb921 --- /dev/null +++ b/src/stores/useStateQueryStore.js @@ -0,0 +1,31 @@ +import { ref, computed } from 'vue'; +import { defineStore } from 'pinia'; + +export const useStateQueryStore = defineStore('stateQueryStore', () => { + const queries = ref(new Set()); + + function add(query) { + queries.value.add(query); + return query; + } + + function isLoading() { + return computed(() => queries.value.size); + } + + function remove(query) { + queries.value.delete(query); + } + + function reset() { + queries.value = new Set(); + } + + return { + add, + isLoading, + remove, + queries, + reset, + }; +}); diff --git a/test/cypress/integration/claim/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js index 81fc33ecd..eb39f340a 100755 --- a/test/cypress/integration/claim/claimDevelopment.spec.js +++ b/test/cypress/integration/claim/claimDevelopment.spec.js @@ -33,7 +33,8 @@ describe('ClaimDevelopment', () => { cy.saveCard(); }); - it('should add and remove new line', () => { + // TODO: #8112 + xit('should add and remove new line', () => { cy.wait(['@workers', '@workers']); cy.addCard(); diff --git a/test/cypress/integration/client/clientAddress.spec.js b/test/cypress/integration/client/clientAddress.spec.js new file mode 100644 index 000000000..db876b64b --- /dev/null +++ b/test/cypress/integration/client/clientAddress.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client consignee', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/address', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientBalance.spec.js b/test/cypress/integration/client/clientBalance.spec.js new file mode 100644 index 000000000..4a666bdb1 --- /dev/null +++ b/test/cypress/integration/client/clientBalance.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client balance', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/balance', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientBasicData.spec.js b/test/cypress/integration/client/clientBasicData.spec.js new file mode 100644 index 000000000..7b0a19828 --- /dev/null +++ b/test/cypress/integration/client/clientBasicData.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client basic data', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/basic-data', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientBillingData.spec.js b/test/cypress/integration/client/clientBillingData.spec.js new file mode 100644 index 000000000..00af82e39 --- /dev/null +++ b/test/cypress/integration/client/clientBillingData.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client billing data', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/billing-data', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientCredits.spec.js b/test/cypress/integration/client/clientCredits.spec.js new file mode 100644 index 000000000..f81bf987d --- /dev/null +++ b/test/cypress/integration/client/clientCredits.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client credits', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/credits', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientFiscalData.spec.js b/test/cypress/integration/client/clientFiscalData.spec.js new file mode 100644 index 000000000..e337c26f8 --- /dev/null +++ b/test/cypress/integration/client/clientFiscalData.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client fiscal data', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/fiscal-data', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientGreuges.spec.js b/test/cypress/integration/client/clientGreuges.spec.js new file mode 100644 index 000000000..23f8b3182 --- /dev/null +++ b/test/cypress/integration/client/clientGreuges.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client greuges', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/greuges', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js new file mode 100644 index 000000000..93e53b9f6 --- /dev/null +++ b/test/cypress/integration/client/clientList.spec.js @@ -0,0 +1,63 @@ +/// +describe('Client list', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/customer/list', { + timeout: 5000, + onBeforeLoad(win) { + cy.stub(win, 'open'); + }, + }); + }); + + it('Client list create new client', () => { + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + const data = { + Name: { val: 'Name 1' }, + 'Social name': { val: 'TEST 1' }, + 'Tax number': { val: '20852113Z' }, + 'Web user': { val: 'user_test_1' }, + Street: { val: 'C/ STREET 1' }, + Email: { val: 'user.test@1.com' }, + 'Business type': { val: 'Otros', type: 'select' }, + 'Sales person': { val: 'salesboss', type: 'select' }, + Location: { val: '46000, Valencia(Province one), España', type: 'select' }, + }; + cy.fillInForm(data); + + cy.get('.q-mt-lg > .q-btn--standard').click(); + + cy.checkNotification('created'); + cy.url().should('include', '/summary'); + }); + it('Client list search client', () => { + const search = 'Jessica Jones'; + cy.searchByLabel('Name', search); + + cy.get('.title > span').should('have.text', search); + let id = null; + cy.get('.q-item > .q-item__label').then((text) => { + id = text.text().trim().split('#')[1]; + cy.get('.q-item > .q-item__label').should('have.text', ` #${id}`); + cy.url().should('include', `/customer/${id}/summary`); + }); + }); + + it('Client founded create ticket', () => { + const search = 'Jessica Jones'; + cy.searchByLabel('Name', search); + cy.clickButtonsDescriptor(2); + cy.waitForElement('#formModel'); + cy.waitForElement('.q-form'); + cy.checkValueForm(1, search); + }); + it('Client founded create order', () => { + const search = 'Jessica Jones'; + cy.searchByLabel('Name', search); + cy.clickButtonsDescriptor(4); + cy.waitForElement('#formModel'); + cy.waitForElement('.q-form'); + cy.checkValueForm(2, search); + }); +}); diff --git a/test/cypress/integration/client/clientNotes.spec.js b/test/cypress/integration/client/clientNotes.spec.js new file mode 100644 index 000000000..99a7c66c5 --- /dev/null +++ b/test/cypress/integration/client/clientNotes.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client notes', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/notes', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientRecoveries.spec.js b/test/cypress/integration/client/clientRecoveries.spec.js new file mode 100644 index 000000000..a4e220008 --- /dev/null +++ b/test/cypress/integration/client/clientRecoveries.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client recoveries', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/recoveries', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientWebAccess.spec.js b/test/cypress/integration/client/clientWebAccess.spec.js new file mode 100644 index 000000000..47f9efa4c --- /dev/null +++ b/test/cypress/integration/client/clientWebAccess.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client web-access', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/web-access', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js b/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js new file mode 100644 index 000000000..3c35d5ed0 --- /dev/null +++ b/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client credit opinion', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/credit-management/credit-contracts', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/credit-management/clientCreditOpinion.spec.js b/test/cypress/integration/client/credit-management/clientCreditOpinion.spec.js new file mode 100644 index 000000000..7d9c0fa77 --- /dev/null +++ b/test/cypress/integration/client/credit-management/clientCreditOpinion.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client credit opinion', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/credit-management/credit-opinion', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientConsumption.spec.js b/test/cypress/integration/client/others/clientConsumption.spec.js new file mode 100644 index 000000000..179a37707 --- /dev/null +++ b/test/cypress/integration/client/others/clientConsumption.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client consumption', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/others/consumption', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientContacts.spec.js b/test/cypress/integration/client/others/clientContacts.spec.js new file mode 100644 index 000000000..66a86801a --- /dev/null +++ b/test/cypress/integration/client/others/clientContacts.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client contacts', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/others/contacts', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientMandates.spec.js b/test/cypress/integration/client/others/clientMandates.spec.js new file mode 100644 index 000000000..aaeb7f930 --- /dev/null +++ b/test/cypress/integration/client/others/clientMandates.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client mandates', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/others/mandates', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientSamples.spec.js b/test/cypress/integration/client/others/clientSamples.spec.js new file mode 100644 index 000000000..03b7238f4 --- /dev/null +++ b/test/cypress/integration/client/others/clientSamples.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client samples', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/others/samples', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientUnpaid.spec.js b/test/cypress/integration/client/others/clientUnpaid.spec.js new file mode 100644 index 000000000..9972ba0e9 --- /dev/null +++ b/test/cypress/integration/client/others/clientUnpaid.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client unpaid', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1110/others/unpaid', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/others/clientWebPayments.spec.js b/test/cypress/integration/client/others/clientWebPayments.spec.js new file mode 100644 index 000000000..5f7087d21 --- /dev/null +++ b/test/cypress/integration/client/others/clientWebPayments.spec.js @@ -0,0 +1,13 @@ +/// +describe('Client web payments', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/others/web-payments', { + timeout: 5000, + }); + }); + it('Should load layout', () => { + cy.get('.q-card').should('be.visible'); + }); +}); diff --git a/test/vitest/__tests__/boot/axios.spec.js b/test/vitest/__tests__/boot/axios.spec.js index feb0d93ea..7a802b4d2 100644 --- a/test/vitest/__tests__/boot/axios.spec.js +++ b/test/vitest/__tests__/boot/axios.spec.js @@ -7,41 +7,46 @@ vi.mock('src/composables/useSession', () => ({ getToken: () => 'DEFAULT_TOKEN', isLoggedIn: () => vi.fn(), destroy: () => vi.fn(), - }) + }), +})); + +vi.mock('src/stores/useStateQueryStore', () => ({ + useStateQueryStore: () => ({ + add: () => vi.fn(), + remove: () => vi.fn(), + }), })); describe('Axios boot', () => { - describe('onRequest()', async () => { it('should set the "Authorization" property on the headers', async () => { const config = { headers: {} }; const resultConfig = onRequest(config); - expect(resultConfig).toEqual(expect.objectContaining({ - headers: { - Authorization: 'DEFAULT_TOKEN' - } - })); + expect(resultConfig).toEqual( + expect.objectContaining({ + headers: { + Authorization: 'DEFAULT_TOKEN', + }, + }) + ); }); - }) + }); describe('onResponseError()', async () => { it('should call to the Notify plugin with a message error for an status code "500"', async () => { - Notify.create = vi.fn() + Notify.create = vi.fn(); const error = { response: { - status: 500 - } + status: 500, + }, }; const result = onResponseError(error); - - expect(result).rejects.toEqual( - expect.objectContaining(error) - ); + expect(result).rejects.toEqual(expect.objectContaining(error)); expect(Notify.create).toHaveBeenCalledWith( expect.objectContaining({ message: 'An internal server error has ocurred', @@ -51,25 +56,22 @@ describe('Axios boot', () => { }); it('should call to the Notify plugin with a message from the response property', async () => { - Notify.create = vi.fn() + Notify.create = vi.fn(); const error = { response: { status: 401, data: { error: { - message: 'Invalid user or password' - } - } - } + message: 'Invalid user or password', + }, + }, + }, }; const result = onResponseError(error); - - expect(result).rejects.toEqual( - expect.objectContaining(error) - ); + expect(result).rejects.toEqual(expect.objectContaining(error)); expect(Notify.create).toHaveBeenCalledWith( expect.objectContaining({ message: 'Invalid user or password', @@ -77,5 +79,5 @@ describe('Axios boot', () => { }) ); }); - }) + }); }); diff --git a/test/vitest/__tests__/stores/useStateQueryStore.spec.js b/test/vitest/__tests__/stores/useStateQueryStore.spec.js new file mode 100644 index 000000000..ab3afb007 --- /dev/null +++ b/test/vitest/__tests__/stores/useStateQueryStore.spec.js @@ -0,0 +1,58 @@ +import { describe, expect, it, beforeEach, beforeAll } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; + +import { useStateQueryStore } from 'src/stores/useStateQueryStore'; + +describe('useStateQueryStore', () => { + beforeAll(() => { + createWrapper({}, {}); + }); + + const stateQueryStore = useStateQueryStore(); + const { add, isLoading, remove, reset } = useStateQueryStore(); + const firstQuery = { url: 'myQuery' }; + + function getQueries() { + return stateQueryStore.queries; + } + + beforeEach(() => { + reset(); + expect(getQueries().size).toBeFalsy(); + }); + + it('should add two queries', async () => { + expect(getQueries().size).toBeFalsy(); + add(firstQuery); + + expect(getQueries().size).toBeTruthy(); + expect(getQueries().has(firstQuery)).toBeTruthy(); + + add(); + expect(getQueries().size).toBe(2); + }); + + it('should add and remove loading state', async () => { + expect(isLoading().value).toBeFalsy(); + add(firstQuery); + expect(isLoading().value).toBeTruthy(); + remove(firstQuery); + expect(isLoading().value).toBeFalsy(); + }); + + it('should add and remove query', async () => { + const secondQuery = { ...firstQuery }; + const thirdQuery = { ...firstQuery }; + + add(firstQuery); + add(secondQuery); + + const beforeCount = getQueries().size; + add(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeTruthy(); + + remove(thirdQuery); + expect(getQueries().has(thirdQuery)).toBeFalsy(); + expect(getQueries().size).toBe(beforeCount); + }); +});