diff --git a/package.json b/package.json index ead0193c9..a61c8f21a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.40.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/boot/defaults/qInput.js b/src/boot/defaults/qInput.js new file mode 100644 index 000000000..299b98718 --- /dev/null +++ b/src/boot/defaults/qInput.js @@ -0,0 +1,4 @@ +import { QInput } from 'quasar'; +import setDefault from './setDefault'; + +setDefault(QInput, 'dense', true); diff --git a/src/boot/defaults/qSelect.js b/src/boot/defaults/qSelect.js new file mode 100644 index 000000000..be0ba048a --- /dev/null +++ b/src/boot/defaults/qSelect.js @@ -0,0 +1,4 @@ +import { QSelect } from 'quasar'; +import setDefault from './setDefault'; + +setDefault(QSelect, 'dense', true); diff --git a/src/boot/quasar.defaults.js b/src/boot/quasar.defaults.js index c792100d7..9638e2057 100644 --- a/src/boot/quasar.defaults.js +++ b/src/boot/quasar.defaults.js @@ -1 +1,3 @@ export * from './defaults/qTable'; +export * from './defaults/qInput'; +export * from './defaults/qSelect'; 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/CreateNewCityForm.vue b/src/components/CreateNewCityForm.vue index 9a7d8666c..85d13beb1 100644 --- a/src/components/CreateNewCityForm.vue +++ b/src/components/CreateNewCityForm.vue @@ -1,35 +1,42 @@ - (provincesOptions = data)" - auto-load - url="Provinces" - /> { - + diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index 4c44d29e2..030ca1388 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -1,5 +1,5 @@ (provincesOptions = data)" + @on-fetch="handleProvinces" + :sort-by="['name ASC']" + :limit="30" auto-load url="Provinces/location" /> (countriesOptions = data)" + ref="townsFetchDataRef" + :sort-by="['name ASC']" + :limit="30" + @on-fetch="handleTowns" + auto-load + url="Towns/location" + /> + @@ -96,18 +169,20 @@ async function setProvince(id, data) { :label="t('Postcode')" v-model="data.code" :rules="validate('postcode.code')" + clearable /> setTown(value, data)" + :tooltip="t('Create city')" v-model="data.townFk" + :options="townsOptions" option-label="name" option-value="id" :rules="validate('postcode.city')" :acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]" :emit-value="false" - clearable + :clearable="true" > @@ -122,6 +197,9 @@ async function setProvince(id, data) { onCityCreated(requestResponse, data) @@ -132,8 +210,13 @@ async function setProvince(id, data) { setProvince(value, data)" v-model="data.provinceFk" + :clearable="true" + :provinces="provincesOptions" + @on-province-created="onProvinceCreated" /> es: New postcode: Nuevo código postal + Create city: Crear población Please, ensure you put the correct data!: ¡Por favor, asegúrese de poner los datos correctos! City: Población Province: Provincia diff --git a/src/components/CreateNewProvinceForm.vue b/src/components/CreateNewProvinceForm.vue index e32684a98..d7df38f9f 100644 --- a/src/components/CreateNewProvinceForm.vue +++ b/src/components/CreateNewProvinceForm.vue @@ -16,7 +16,16 @@ const provinceFormData = reactive({ name: null, autonomyFk: null, }); - +const $props = defineProps({ + countryFk: { + type: Number, + default: null, + }, + provinces: { + type: Array, + default: () => [], + }, +}); const autonomiesOptions = ref([]); const onDataSaved = (dataSaved, requestResponse) => { @@ -31,7 +40,14 @@ const onDataSaved = (dataSaved, requestResponse) => { (autonomiesOptions = data)" auto-load + :filter="{ + where: { + countryFk: $props.countryFk, + }, + }" url="Autonomies/location" + :sort-by="['name ASC']" + :limit="30" /> { model="thermograph" :title="t('New thermograph')" :form-initial-data="thermographFormData" - @on-data-saved="onDataSaved($event)" + @on-data-saved="(_, response) => onDataSaved(response)" > diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 0386e037b..a4cb55a2c 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -234,6 +234,8 @@ async function remove(data) { newData = newData.filter((form) => !ids.some((id) => id == form[pk])); fetch(newData); }); + } else { + reset(); } emit('update:selected', []); } diff --git a/src/components/LeftMenuItem.vue b/src/components/LeftMenuItem.vue index ab74c1de5..a3112b17f 100644 --- a/src/components/LeftMenuItem.vue +++ b/src/components/LeftMenuItem.vue @@ -44,7 +44,6 @@ const itemComputed = computed(() => { - diff --git a/src/components/common/VnChangePassword.vue b/src/components/common/VnChangePassword.vue new file mode 100644 index 000000000..79784f3c5 --- /dev/null +++ b/src/components/common/VnChangePassword.vue @@ -0,0 +1,136 @@ + + + (requirements = data)" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +es: + New password: Nueva contraseña + Repeat password: Repetir contraseña + You must enter a new password: Debes introducir la nueva contraseña + Passwords don't match: Las contraseñas no coinciden + diff --git a/src/components/common/VnDate.vue b/src/components/common/VnDate.vue new file mode 100644 index 000000000..761ac995e --- /dev/null +++ b/src/components/common/VnDate.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/src/components/common/VnInput.vue b/src/components/common/VnInput.vue index 26160929c..1246eedcd 100644 --- a/src/components/common/VnInput.vue +++ b/src/components/common/VnInput.vue @@ -103,6 +103,7 @@ const mixinRules = [ @click=" () => { value = null; + vnInputRef.focus(); emit('remove'); } " diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue index fd8993d6f..1aa797ab7 100644 --- a/src/components/common/VnInputDate.vue +++ b/src/components/common/VnInputDate.vue @@ -3,6 +3,7 @@ import { onMounted, watch, computed, ref } from 'vue'; import { date } from 'quasar'; import { useI18n } from 'vue-i18n'; import { useAttrs } from 'vue'; +import VnDate from './VnDate.vue'; const model = defineModel({ type: [String, Date] }); const $props = defineProps({ @@ -20,6 +21,7 @@ const { validations } = useValidator(); const { t } = useI18n(); const requiredFieldRule = (val) => validations().required($attrs.required, val); +const vnInputDateRef = ref(null); const dateFormat = 'DD/MM/YYYY'; const isPopupOpen = ref(); @@ -86,11 +88,17 @@ const styleAttrs = computed(() => { } : {}; }); + +const manageDate = (date) => { + formattedDate.value = date; + isPopupOpen.value = false; +}; { !$attrs.disable " @click=" + vnInputDateRef.focus(); model = null; isPopupOpen = false; " @@ -126,6 +135,7 @@ const styleAttrs = computed(() => { /> { :no-focus="true" :no-parent-event="true" > - { - formattedDate = date; - isPopupOpen = false; - } - " - /> + + + + diff --git a/src/components/common/VnInputTime.vue b/src/components/common/VnInputTime.vue index 42ec79479..6d69bc4a5 100644 --- a/src/components/common/VnInputTime.vue +++ b/src/components/common/VnInputTime.vue @@ -3,6 +3,8 @@ import { computed, ref, useAttrs } from 'vue'; import { useI18n } from 'vue-i18n'; import { date } from 'quasar'; import { useValidator } from 'src/composables/useValidator'; +import VnTime from './VnTime.vue'; + const { validations } = useValidator(); const $attrs = useAttrs(); const model = defineModel({ type: String }); @@ -16,6 +18,7 @@ const props = defineProps({ default: false, }, }); +const vnInputTimeRef = ref(null); const initialDate = ref(model.value ?? Date.vnNew()); const { t } = useI18n(); const requiredFieldRule = (val) => validations().required($attrs.required, val); @@ -69,6 +72,7 @@ function dateToTime(newDate) { - + + + + diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index c1b921915..5f94c466a 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -12,6 +12,16 @@ const props = defineProps({ default: null, }, }); + +const locationProperties = [ + 'postcode', + (obj) => + obj.city + ? `${obj.city}${obj.province?.name ? `(${obj.province.name})` : ''}` + : null, + (obj) => obj.country?.name, +]; + const formatLocation = (obj, properties) => { const parts = properties.map((prop) => { if (typeof prop === 'string') { @@ -29,23 +39,10 @@ const formatLocation = (obj, properties) => { return filteredParts.join(', '); }; -const locationProperties = [ - 'postcode', - (obj) => - obj.city - ? `${obj.city}${obj.province?.name ? `(${obj.province.name})` : ''}` - : null, - (obj) => obj.country?.name, -]; - const modelValue = ref( props.location ? formatLocation(props.location, locationProperties) : null ); -const handleModelValue = (data) => { - emit('update:model-value', data); -}; - function showLabel(data) { const dataProperties = [ 'code', @@ -54,6 +51,10 @@ function showLabel(data) { ]; return formatLocation(data, dataProperties); } + +const handleModelValue = (data) => { + emit('update:model-value', data); +}; en: search_by_postalcode: Search by postalcode, town, province or country + Create new location: Create new location es: Location: Ubicación + Create new location: Crear nueva ubicación search_by_postalcode: Buscar por código postal, ciudad o país 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/common/VnTime.vue b/src/components/common/VnTime.vue new file mode 100644 index 000000000..369f80432 --- /dev/null +++ b/src/components/common/VnTime.vue @@ -0,0 +1,16 @@ + + + + + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 90ac856e5..7f35f0a28 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -47,6 +47,7 @@ let store; let entity; const isLoading = ref(false); const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName); +const menuRef = ref(); defineExpose({ getData }); onBeforeMount(async () => { @@ -170,7 +171,7 @@ const toModule = computed(() => {{ t('components.cardDescriptor.moreOptions') }} - + diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index ef07b7bef..43d634ad9 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -3,7 +3,6 @@ import { onMounted, ref, computed, watch } from 'vue'; import { useI18n } from 'vue-i18n'; import { useArrayData } from 'composables/useArrayData'; import { useRoute } from 'vue-router'; -import { date } from 'quasar'; import toDate from 'filters/toDate'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; @@ -59,7 +58,6 @@ const $props = defineProps({ }); defineExpose({ search, sanitizer }); - const emit = defineEmits([ 'update:modelValue', 'refresh', @@ -114,9 +112,9 @@ watch( ); const isLoading = ref(false); -async function search() { +async function search(evt) { try { - if ($props.disableSubmitEvent) return; + if (evt && $props.disableSubmitEvent) return; store.filter.where = {}; isLoading.value = true; @@ -167,7 +165,7 @@ const tagsList = computed(() => { for (const key of Object.keys(userParams.value)) { const value = userParams.value[key]; if (value == null || ($props.hiddenTags || []).includes(key)) continue; - tagList.push({ label: aliasField(key), value }); + tagList.push({ label: key, value }); } return tagList; }); @@ -187,7 +185,6 @@ async function remove(key) { } function formatValue(value) { - if (value instanceof Date) return date.formatDate(value, 'DD/MM/YYYY'); if (typeof value === 'boolean') return value ? t('Yes') : t('No'); if (isNaN(value) && !isNaN(Date.parse(value))) return toDate(value); @@ -203,11 +200,6 @@ function sanitizer(params) { } return params; } - -function aliasField(field) { - const split = field.split('.'); - return split[1] ?? split[0]; -} @@ -219,7 +211,7 @@ function aliasField(field) { icon="search" @click="search()" > - + diff --git a/src/components/ui/VnImg.vue b/src/components/ui/VnImg.vue index ceb4e8468..1b57c20d0 100644 --- a/src/components/ui/VnImg.vue +++ b/src/components/ui/VnImg.vue @@ -58,7 +58,7 @@ defineExpose({ :class="{ zoomIn: zoom }" :src="getUrl()" v-bind="$attrs" - @click.stop="show = $props.zoom ? true : false" + @click.stop="show = $props.zoom" spinner-color="primary" /> diff --git a/src/components/ui/VnNotes.vue b/src/components/ui/VnNotes.vue index bf5e86a16..b395b3934 100644 --- a/src/components/ui/VnNotes.vue +++ b/src/components/ui/VnNotes.vue @@ -1,6 +1,6 @@ + (observationTypes = data)" + /> @@ -62,29 +73,42 @@ onBeforeRouteLeave((to, from, next) => { {{ t('globals.now') }} - - - - - - + + + + + + + + + { class="show" v-bind="$attrs" search-url="notes" + @on-fetch=" + newNote.text = ''; + newNote.observationTypeFk = null; + " > @@ -111,13 +139,28 @@ onBeforeRouteLeave((to, from, next) => { :descriptor="false" :worker-id="note.workerFk" size="md" + :title="note.worker?.user.nickname" /> - - {{ toDateHourMin(note.created) }} + + + + {{ + observationTypes.find( + (ot) => ot.id === note.observationTypeFk + )?.description + }} + + + @@ -131,12 +174,6 @@ onBeforeRouteLeave((to, from, next) => { es: Add note here...: Añadir nota aquí... New note: Nueva nota Save (Enter): Guardar (Intro) - + Observation type: Tipo de observación diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index a78403b5c..dc6d4751c 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -108,6 +108,7 @@ async function search() { ...Object.fromEntries(staticParams), search: searchText.value, }, + ...{ filter: props.filter }, }; if (props.whereFilter) { 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/composables/useArrayData.js b/src/composables/useArrayData.js index b1c150410..30bcac66b 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -114,7 +114,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { for (const row of response.data) store.data.push(row); } else { store.data = response.data; - if (!document.querySelectorAll('[role="dialog"]').length) + if (!document.querySelectorAll('[role="dialog"][aria-modal="true"]').length) updateRouter && updateStateParams(); } 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/filters/toHour.js b/src/filters/toHour.js index 40821e237..52df9c8cd 100644 --- a/src/filters/toHour.js +++ b/src/filters/toHour.js @@ -4,7 +4,7 @@ export default function toHour(date) { if (!isValidDate(date)) { return '--:--'; } - return (new Date(date || '')).toLocaleTimeString([], { + return new Date(date || '').toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', }); diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 6ba3f9b58..aa8df17e2 100644 --- a/src/i18n/locale/en.yml +++ b/src/i18n/locale/en.yml @@ -50,6 +50,7 @@ globals: summary: basicData: Basic data daysOnward: Days onward + daysAgo: Days ago today: Today yesterday: Yesterday dateFormat: en-GB @@ -104,6 +105,7 @@ globals: campaign: Campaign weight: Weight error: Ups! Something went wrong + recalc: Recalculate pageTitles: logIn: Login addressEdit: Update address @@ -111,7 +113,7 @@ globals: basicData: Basic data log: Logs parkingList: Parkings list - agencyList: Agencies list + agencyList: Agencies agency: Agency workCenters: Work centers modes: Modes @@ -135,7 +137,7 @@ globals: fiscalData: Fiscal data billingData: Billing data consignees: Consignees - 'address-create': New address + address-create: New address notes: Notes credits: Credits greuges: Greuges @@ -207,7 +209,7 @@ globals: roadmap: Roadmap stops: Stops routes: Routes - cmrsList: CMRs list + cmrsList: CMRs RouteList: List routeCreate: New route RouteRoadmap: Roadmaps @@ -273,6 +275,9 @@ globals: clientsActionsMonitor: Clients and actions serial: Serial medical: Mutual + RouteExtendedList: Router + wasteRecalc: Waste recaclulate + operator: Operator supplier: Supplier created: Created worker: Worker @@ -288,7 +293,10 @@ globals: createInvoiceIn: Create invoice in myAccount: My account noOne: No one + maxTemperature: Max + minTemperature: Min params: + id: ID clientFk: Client id salesPersonFk: Sales person warehouseFk: Warehouse @@ -296,8 +304,13 @@ globals: from: From To: To stateFk: State + email: Email + SSN: SSN + fi: FI myTeam: My team departmentFk: Department + changePass: Change password + deleteConfirmTitle: Delete selected elements changeState: Change state errors: statusUnauthorized: Access denied @@ -305,6 +318,7 @@ errors: statusBadGateway: It seems that the server has fall down statusGatewayTimeout: Could not contact the server userConfig: Error fetching user config + updateUserConfig: Error updating user config tokenConfig: Error fetching token config writeRequest: The requested operation could not be completed login: @@ -498,6 +512,8 @@ ticket: warehouse: Warehouse customerCard: Customer card alias: Alias + ticketList: Ticket List + newOrder: New Order boxing: expedition: Expedition item: Item @@ -519,6 +535,7 @@ ticket: landed: Landed consigneePhone: Consignee phone consigneeMobile: Consignee mobile + consigneeAddress: Consignee address clientPhone: Client phone clientMobile: Client mobile consignee: Consignee @@ -547,6 +564,11 @@ ticket: weight: Weight goTo: Go to summaryAmount: Summary + purchaseRequest: Purchase request + service: Service + description: Description + attender: Attender + ok: Ok create: client: Client address: Address @@ -570,7 +592,6 @@ invoiceOut: client: Client company: Company customerCard: Customer card - ticketList: Ticket List summary: issued: Issued created: Created @@ -724,6 +745,7 @@ worker: locker: Locker balance: Balance medical: Medical + operator: Operator list: name: Name email: Email @@ -796,14 +818,14 @@ worker: bankEntity: Swift / BIC formation: tableVisibleColumns: - course: Curso - startDate: Fecha Inicio - endDate: Fecha Fin - center: Centro Formación - invoice: Factura - amount: Importe - remark: Bonficado - hasDiploma: Diploma + course: Course + startDate: Start date + endDate: End date + center: Training center + invoice: Invoice + amount: Amount + remark: Remark + hasDiploma: Has diploma medical: tableVisibleColumns: date: Date @@ -821,6 +843,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 @@ -860,34 +894,7 @@ wagon: minHeightBetweenTrays: 'The minimum height between trays is ' maxWagonHeight: 'The maximum height of the wagon is ' uncompleteTrays: There are incomplete trays -route: - pageTitles: - agency: Agency List - routes: Routes - cmrsList: CMRs list - RouteList: List - routeCreate: New route - basicData: Basic Data - summary: Summary - RouteRoadmap: Roadmaps - RouteRoadmapCreate: Create roadmap - tickets: Tickets - log: Log - autonomous: Autonomous - cmr: - list: - results: results - cmrFk: CMR id - hasCmrDms: Attached in gestdoc - 'true': 'Yes' - 'false': 'No' - ticketFk: Ticketd id - routeFk: Route id - country: Country - clientFk: Client id - shipped: Preparation date - viewCmr: View CMR - downloadCmrs: Download CMRs + supplier: list: payMethod: Pay method diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index e058030c0..575e2c6c7 100644 --- a/src/i18n/locale/es.yml +++ b/src/i18n/locale/es.yml @@ -49,6 +49,7 @@ globals: summary: basicData: Datos básicos daysOnward: Días adelante + daysAgo: Días atras today: Hoy yesterday: Ayer dateFormat: es-ES @@ -63,7 +64,7 @@ globals: shipped: F. envío totalEntries: Ent. totales amount: Importe - packages: Bultos + packages: Embalajes download: Descargar downloadPdf: Descargar PDF selectRows: 'Seleccionar las { numberRows } filas(s)' @@ -106,6 +107,7 @@ globals: campaign: Campaña weight: Peso error: ¡Ups! Algo salió mal + recalc: Recalcular pageTitles: logIn: Inicio de sesión addressEdit: Modificar consignatario @@ -113,7 +115,7 @@ globals: basicData: Datos básicos log: Historial parkingList: Listado de parkings - agencyList: Listado de agencias + agencyList: Agencias agency: Agencia workCenters: Centros de trabajo modes: Modos @@ -211,12 +213,13 @@ globals: roadmap: Troncales stops: Paradas routes: Rutas - cmrsList: Listado de CMRs + cmrsList: CMRs RouteList: Listado routeCreate: Nueva ruta RouteRoadmap: Troncales RouteRoadmapCreate: Crear troncal autonomous: Autónomos + RouteExtendedList: Enrutador suppliers: Proveedores supplier: Proveedor supplierCreate: Nuevo proveedor @@ -267,7 +270,7 @@ globals: tracking: Estados components: Componentes pictures: Fotos - packages: Bultos + packages: Embalajes ldap: LDAP samba: Samba twoFactor: Doble factor @@ -277,6 +280,8 @@ globals: clientsActionsMonitor: Clientes y acciones serial: Facturas por serie medical: Mutua + wasteRecalc: Recalcular mermas + operator: Operario supplier: Proveedor created: Fecha creación worker: Trabajador @@ -292,7 +297,10 @@ globals: createInvoiceIn: Crear factura recibida myAccount: Mi cuenta noOne: Nadie + maxTemperature: Máx + minTemperature: Mín params: + id: Id clientFk: Id cliente salesPersonFk: Comercial warehouseFk: Almacén @@ -300,8 +308,13 @@ globals: from: Desde To: Hasta stateFk: Estado - myTeam: Mi equipo departmentFk: Departamento + email: Correo + SSN: NSS + fi: NIF + myTeam: Mi equipo + changePass: Cambiar contraseña + deleteConfirmTitle: Eliminar los elementos seleccionados changeState: Cambiar estado errors: statusUnauthorized: Acceso denegado @@ -309,6 +322,7 @@ errors: statusBadGateway: Parece ser que el servidor ha caído statusGatewayTimeout: No se ha podido contactar con el servidor userConfig: Error al obtener configuración de usuario + updateUserConfig: Error al actualizar la configuración de usuario tokenConfig: Error al obtener configuración de token writeRequest: No se pudo completar la operación solicitada login: @@ -489,7 +503,7 @@ ticket: tracking: Estados components: Componentes pictures: Fotos - packages: Bultos + packages: Embalajes list: nickname: Alias state: Estado @@ -507,6 +521,8 @@ ticket: warehouse: Almacén customerCard: Ficha del cliente alias: Alias + ticketList: Listado de tickets + newOrder: Nuevo pedido boxing: expedition: Expedición item: Artículo @@ -528,6 +544,7 @@ ticket: landed: Entregado consigneePhone: Tel. consignatario consigneeMobile: Móv. consignatario + consigneeAddress: Dir. consignatario clientPhone: Tel. cliente clientMobile: Móv. cliente consignee: Consignatario @@ -556,6 +573,10 @@ ticket: weight: Peso goTo: Ir a summaryAmount: Resumen + purchaseRequest: Petición de compra + service: Servicio + description: Descripción + attender: Consignatario create: client: Cliente address: Dirección @@ -731,6 +752,7 @@ worker: balance: Balance formation: Formación medical: Mutua + operator: Operario list: name: Nombre email: Email @@ -819,6 +841,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 @@ -858,21 +893,6 @@ wagon: minHeightBetweenTrays: 'La distancia mínima entre bandejas es ' maxWagonHeight: 'La altura máxima del vagón es ' uncompleteTrays: Hay bandejas sin completar -route: - cmr: - list: - results: resultados - cmrFk: Id CMR - hasCmrDms: Gestdoc - 'true': Sí - 'false': 'No' - ticketFk: Id ticket - routeFk: Id ruta - country: País - clientFk: Id cliente - shipped: Fecha preparación - viewCmr: Ver CMR - downloadCmrs: Descargar CMRs supplier: list: payMethod: Método de pago diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index d1240f7a6..72c445fa9 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -10,7 +10,9 @@ import RightMenu from 'src/components/common/RightMenu.vue'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); - +const filter = { + include: { relation: 'role', scope: { fields: ['id', 'name'] } }, +}; const columns = computed(() => [ { align: 'left', @@ -22,7 +24,22 @@ const columns = computed(() => [ }, { align: 'left', - name: 'username', + name: 'roleFk', + label: t('role'), + columnFilter: { + component: 'select', + name: 'roleFk', + attrs: { + url: 'VnRoles', + optionValue: 'id', + optionLabel: 'name', + }, + }, + format: ({ role }, dashIfEmpty) => dashIfEmpty(role?.name), + }, + { + align: 'left', + name: 'nickname', label: t('Nickname'), isTitle: true, component: 'input', @@ -91,6 +108,7 @@ const exprBuilder = (param, value) => { :expr-builder="exprBuilder" :label="t('account.search')" :info="t('account.searchInfo')" + :filter="filter" /> @@ -101,6 +119,7 @@ const exprBuilder = (param, value) => { ref="tableRef" data-key="AccountUsers" url="VnUsers/preview" + :filter="filter" order="id DESC" :columns="columns" default-mode="table" diff --git a/src/pages/Account/Card/AccountDescriptorMenu.vue b/src/pages/Account/Card/AccountDescriptorMenu.vue index 0e35d25f3..6f1d2ca1f 100644 --- a/src/pages/Account/Card/AccountDescriptorMenu.vue +++ b/src/pages/Account/Card/AccountDescriptorMenu.vue @@ -4,9 +4,12 @@ import { computed, ref, toRefs } from 'vue'; import { useI18n } from 'vue-i18n'; import { useVnConfirm } from 'composables/useVnConfirm'; import { useRoute } from 'vue-router'; +import { useAcl } from 'src/composables/useAcl'; import { useArrayData } from 'src/composables/useArrayData'; import VnConfirm from 'src/components/ui/VnConfirm.vue'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; import useNotify from 'src/composables/useNotify.js'; + const $props = defineProps({ hasAccount: { type: Boolean, @@ -62,6 +65,19 @@ async function sync() { } + + + {{ t('globals.changePass') }} + {{ t('account.card.actions.sync.name') }} - diff --git a/src/pages/Customer/Card/CustomerConsumptionFilter.vue b/src/pages/Customer/Card/CustomerConsumptionFilter.vue new file mode 100644 index 000000000..289b2eb08 --- /dev/null +++ b/src/pages/Customer/Card/CustomerConsumptionFilter.vue @@ -0,0 +1,177 @@ + + + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + + + + + + + + {{ scope.opt?.name }} + {{ + scope.opt?.category?.name + }} + + + + + + + + + + + + + + + + + + {{ + t(`params.${scope.opt?.code}`) + }} + {{ + toDate(scope.opt.dated) + }} + + + + + + + + + + + + + + + + + + + + +en: + params: + item: Item id + buyer: Buyer + type: Type + category: Category + itemId: Item id + buyerId: Buyer + typeId: Type + categoryId: Category + from: From + to: To + campaignId: Campaña + valentinesDay: Valentine's Day + mothersDay: Mother's Day + allSaints: All Saints' Day +es: + params: + item: Id artículo + buyer: Comprador + type: Tipo + category: Categoría + itemId: Id Artículo + buyerId: Comprador + typeId: Tipo + categoryId: Reino + from: Desde + to: Hasta + campaignId: Campaña + valentinesDay: Día de San Valentín + mothersDay: Día de la Madre + allSaints: Día de Todos los Santos + diff --git a/src/pages/Customer/Card/CustomerNotes.vue b/src/pages/Customer/Card/CustomerNotes.vue index a9121f7f5..b85174696 100644 --- a/src/pages/Customer/Card/CustomerNotes.vue +++ b/src/pages/Customer/Card/CustomerNotes.vue @@ -22,5 +22,6 @@ const noteFilter = computed(() => { :filter="noteFilter" :body="{ clientFk: route.params.id }" style="overflow-y: auto" + :select-type="true" /> diff --git a/src/pages/Customer/Card/CustomerUnpaid.vue b/src/pages/Customer/Card/CustomerUnpaid.vue index ad00cbf59..d7f933a7f 100644 --- a/src/pages/Customer/Card/CustomerUnpaid.vue +++ b/src/pages/Customer/Card/CustomerUnpaid.vue @@ -92,7 +92,7 @@ const onSubmit = async () => { notify('globals.dataSaved', 'positive'); unpaidClient.value = true; } catch (error) { - notify('errors.create', 'negative'); + notify('errors.writeRequest', 'negative'); } finally { isLoading.value = false; } diff --git a/src/pages/Customer/Card/CustomerWebAccess.vue b/src/pages/Customer/Card/CustomerWebAccess.vue index 8d025a365..1db32c752 100644 --- a/src/pages/Customer/Card/CustomerWebAccess.vue +++ b/src/pages/Customer/Card/CustomerWebAccess.vue @@ -3,14 +3,11 @@ import { computed, ref } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute } from 'vue-router'; import axios from 'axios'; -import { useQuasar } from 'quasar'; - import VnInput from 'src/components/common/VnInput.vue'; -import CustomerChangePassword from 'src/pages/Customer/components/CustomerChangePassword.vue'; import FormModel from 'components/FormModel.vue'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; const { t } = useI18n(); -const quasar = useQuasar(); const route = useRoute(); const canChangePassword = ref(0); @@ -21,21 +18,11 @@ const filter = computed(() => { }; }); -const showChangePasswordDialog = () => { - quasar.dialog({ - component: CustomerChangePassword, - componentProps: { - id: route.params.id, - }, - }); -}; - async function hasCustomerRole() { const { data } = await axios(`Clients/${route.params.id}/hasCustomerRole`); canChangePassword.value = data; } - + - es: Enable web access: Habilitar acceso web User: Usuario Recovery email: Correo de recuperacion This email is used for user to regain access their account: Este correo electrónico se usa para que el usuario recupere el acceso a su cuenta - Change password: Cambiar contraseña diff --git a/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue b/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue index 5798e7512..27dfe48e6 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue @@ -25,19 +25,31 @@ const { notify } = useNotify(); const { t } = useI18n(); const newObservation = ref(null); +const obsId = ref(null); const onSubmit = async () => { try { - const data = $props.clients.map((item) => { - return { clientFk: item.clientFk, text: newObservation.value }; - }); - await axios.post('ClientObservations', data); + if (!obsId.value) + obsId.value = ( + await axios.get('ObservationTypes/findOne', { + params: { filter: { where: { description: 'Finance' } } }, + }) + ).data?.id; - const payload = { + const bodyObs = $props.clients.map((item) => { + return { + clientFk: item.clientFk, + text: newObservation.value, + observationTypeFk: obsId.value, + }; + }); + await axios.post('ClientObservations', bodyObs); + + const bodyObsMail = { defaulters: $props.clients, observation: newObservation.value, }; - await axios.post('Defaulters/observationEmail', payload); + await axios.post('Defaulters/observationEmail', bodyObsMail); await $props.promise(); diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index 606867388..1e5387815 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -240,39 +240,33 @@ function handleLocation(data, location) { class="row q-gutter-md q-mb-md" v-for="(note, index) in notes" > - - - - - - - - - - {{ t('Remove note') }} - - - + + + + + {{ t('Remove note') }} + + - -import { ref } from 'vue'; -import { useI18n } from 'vue-i18n'; - -import axios from 'axios'; -import { useDialogPluginComponent } from 'quasar'; - -import useNotify from 'src/composables/useNotify'; - -import VnRow from 'components/ui/VnRow.vue'; -import VnInput from 'src/components/common/VnInput.vue'; -import FetchData from 'src/components/FetchData.vue'; - -const { dialogRef } = useDialogPluginComponent(); -const { notify } = useNotify(); -const { t } = useI18n(); - -const $props = defineProps({ - id: { - type: String, - required: true, - }, - promise: { - type: Function, - required: true, - }, -}); -const userPasswords = ref({}); - -const closeButton = ref(null); -const isLoading = ref(false); -const newPassword = ref(''); -const requestPassword = ref(''); - -const onSubmit = async () => { - isLoading.value = true; - - if (newPassword.value !== requestPassword.value) { - notify(t("Passwords don't match"), 'negative'); - isLoading.value = false; - return; - } - - const payload = { - newPassword: newPassword.value, - }; - try { - await axios.patch(`Clients/${$props.id}/setPassword`, payload); - } catch (error) { - notify('errors.create', 'negative'); - } finally { - isLoading.value = false; - if (closeButton.value) closeButton.value.click(); - } -}; - - - - - (userPasswords = data[0])" - auto-load - url="UserPasswords" - /> - - - - - - - - - - - - - - {{ - t('customer.card.passwordRequirements', { - ...userPasswords, - }) - }} - - - - - - - - - - - - - - - - - - - - - -es: - New password: Nueva contraseña - Request password: Repetir contraseña - Change password: Cambiar contraseña - Passwords don't match: Las contraseñas no coinciden - diff --git a/src/pages/Customer/components/CustomerSamplesCreate.vue b/src/pages/Customer/components/CustomerSamplesCreate.vue index 3c9eb856b..8c59f28b6 100644 --- a/src/pages/Customer/components/CustomerSamplesCreate.vue +++ b/src/pages/Customer/components/CustomerSamplesCreate.vue @@ -138,7 +138,7 @@ const onSubmit = async () => { notify('globals.dataSaved', 'positive'); onDataSaved(data); } catch (error) { - notify('errors.create', 'negative'); + notify('errors.writeRequest', 'negative'); } finally { isLoading.value = false; } 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/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue new file mode 100644 index 000000000..2b5ec53f5 --- /dev/null +++ b/src/pages/Entry/EntryStockBought.vue @@ -0,0 +1,301 @@ + + + + + { + travel = data.find( + (data) => data.warehouseIn?.code.toLowerCase() === 'vnh' + ); + } + " + /> + + + + {{ t('Purchase Spaces') }}: + + + {{ travel?.m3 }} + + + + + + + + + + + + + + + + + + + + + + setFooter(data)" + :create="{ + urlCreate: 'StockBoughts', + title: t('Reserve some space'), + onDataSaved: () => tableRef.reload(), + formInitialData: { + workerFk: user.id, + dated: Date.vnNow(), + }, + }" + :columns="columns" + :user-params="userParams" + :footer="true" + table-height="80vh" + auto-load + :column-search="false" + > + + + {{ row?.worker?.user?.name }} + + + + + + {{ row?.bought }} + + + + + {{ round(tableRef.footer.reserve) }} + + + + + {{ round(tableRef.footer.bought) }} + + + + + + + + + es: + Edit travel: Editar envío + Travel: Envíos + Purchase Spaces: Espacios de compra + Buyer: Comprador + Reserve: Reservado + Bought: Comprado + Date: Fecha + View more details: Ver más detalles + Reserve some space: Reservar espacio + This buyer has already made a reservation for this date: Este comprador ya ha hecho una reserva para esta fecha + diff --git a/src/pages/Entry/EntryStockBoughtDetail.vue b/src/pages/Entry/EntryStockBoughtDetail.vue new file mode 100644 index 000000000..0fd775ee6 --- /dev/null +++ b/src/pages/Entry/EntryStockBoughtDetail.vue @@ -0,0 +1,126 @@ + + + + + + + + {{ row?.entryFk }} + + + + + + {{ row?.itemName }} + + + + + + + + + + es: + Buyer: Comprador + Reserve: Reservado + Bought: Comprado + More: Más + Date: Fecha + Entry: Entrada + Item: Artículo + Name: Nombre + Volume: Volumen + Packaging: Embalage + diff --git a/src/pages/Entry/EntryStockBoughtFilter.vue b/src/pages/Entry/EntryStockBoughtFilter.vue new file mode 100644 index 000000000..7694cfe6c --- /dev/null +++ b/src/pages/Entry/EntryStockBoughtFilter.vue @@ -0,0 +1,63 @@ + + + + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + en: + params: + dated: Date + workerFk: Worker + es: + Date: Fecha + params: + dated: Date + workerFk: Trabajador + diff --git a/src/pages/Entry/EntryWasteRecalc.vue b/src/pages/Entry/EntryWasteRecalc.vue new file mode 100644 index 000000000..cd823beb4 --- /dev/null +++ b/src/pages/Entry/EntryWasteRecalc.vue @@ -0,0 +1,72 @@ + + + + + + + + + + + + + diff --git a/src/pages/Entry/MyEntries.vue b/src/pages/Entry/MyEntries.vue index 1c56427f4..2c37c2c42 100644 --- a/src/pages/Entry/MyEntries.vue +++ b/src/pages/Entry/MyEntries.vue @@ -9,22 +9,27 @@ import VnTable from 'components/VnTable/VnTable.vue'; const { t } = useI18n(); const quasar = useQuasar(); +const params = { + daysOnward: 7, + daysAgo: 3, +}; + const columns = computed(() => [ { align: 'left', name: 'id', - label: t('customer.extendedList.tableVisibleColumns.id'), + label: t('myEntries.id'), columnFilter: false, isTitle: true, }, { visible: false, align: 'right', - label: t('shipped'), + label: t('myEntries.shipped'), name: 'shipped', columnFilter: { name: 'fromShipped', - label: t('fromShipped'), + label: t('myEntries.fromShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), @@ -32,11 +37,11 @@ const columns = computed(() => [ { visible: false, align: 'left', - label: t('shipped'), + label: t('myEntries.shipped'), name: 'shipped', columnFilter: { name: 'toShipped', - label: t('toShipped'), + label: t('myEntries.toShipped'), component: 'date', }, format: ({ shipped }) => toDate(shipped), @@ -44,14 +49,14 @@ const columns = computed(() => [ }, { align: 'right', - label: t('shipped'), + label: t('myEntries.shipped'), name: 'shipped', columnFilter: false, format: ({ shipped }) => toDate(shipped), }, { align: 'right', - label: t('landed'), + label: t('myEntries.landed'), name: 'landed', columnFilter: false, format: ({ landed }) => toDate(landed), @@ -59,26 +64,36 @@ const columns = computed(() => [ { align: 'right', - label: t('globals.wareHouseIn'), + label: t('myEntries.wareHouseIn'), name: 'warehouseInFk', - format: (row) => row.warehouseInName, + format: (row) => { + row.warehouseInName; + }, cardVisible: true, columnFilter: { + name: 'warehouseInFk', + label: t('myEntries.warehouseInFk'), component: 'select', attrs: { url: 'warehouses', fields: ['id', 'name'], optionLabel: 'name', optionValue: 'id', + alias: 't', }, - alias: 't', inWhere: true, }, }, { align: 'left', - label: t('globals.daysOnward'), - name: 'days', + label: t('myEntries.daysOnward'), + name: 'daysOnward', + visible: false, + }, + { + align: 'left', + label: t('myEntries.daysAgo'), + name: 'daysAgo', visible: false, }, { @@ -88,6 +103,7 @@ const columns = computed(() => [ { title: t('printLabels'), icon: 'print', + isPrimary: true, action: (row) => printBuys(row.id), }, ], @@ -114,9 +130,11 @@ const printBuys = (rowId) => { data-key="myEntriesList" url="Entries/filter" :columns="columns" + :user-params="params" default-mode="card" order="shipped DESC" auto-load + chip-locale="myEntries" /> diff --git a/src/pages/Entry/locale/en.yml b/src/pages/Entry/locale/en.yml index a9faa814b..f9dbd0589 100644 --- a/src/pages/Entry/locale/en.yml +++ b/src/pages/Entry/locale/en.yml @@ -6,9 +6,17 @@ entryFilter: filter: search: General search reference: Reference -landed: Landed -shipped: Shipped -fromShipped: Shipped(from) -toShipped: Shipped(to) -printLabels: Print stickers -viewLabel: View sticker +myEntries: + id: ID + landed: Landed + shipped: Shipped + fromShipped: Shipped(from) + toShipped: Shipped(to) + printLabels: Print stickers + viewLabel: View sticker + wareHouseIn: Warehouse in + warehouseInFk: Warehouse in + daysOnward: Days onward + daysAgo: Days ago +wasteRecalc: + recalcOk: The wastes were successfully recalculated diff --git a/src/pages/Entry/locale/es.yml b/src/pages/Entry/locale/es.yml index eb1e3f88a..feeea1fc9 100644 --- a/src/pages/Entry/locale/es.yml +++ b/src/pages/Entry/locale/es.yml @@ -9,10 +9,17 @@ entryFilter: filter: search: Búsqueda general reference: Referencia - -landed: F. llegada -shipped: F. salida -fromShipped: F. salida(desde) -toShipped: F. salida(hasta) -printLabels: Imprimir etiquetas -viewLabel: Ver etiqueta +myEntries: + id: ID + landed: F. llegada + shipped: F. salida + fromShipped: F. salida(desde) + toShipped: F. salida(hasta) + printLabels: Imprimir etiquetas + viewLabel: Ver etiqueta + wareHouseIn: Alm. entrada + warehouseInFk: Alm. entrada + daysOnward: Días adelante + daysAgo: Días atras +wasteRecalc: + recalcOk: Se han recalculado las mermas correctamente diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue index bf2e7db48..801a04342 100644 --- a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -274,10 +274,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`; :label="t('invoiceIn.summary.company')" :value="entity.company?.code" /> - + diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue index bf4e023a9..d1c0856b5 100644 --- a/src/pages/InvoiceIn/InvoiceInFilter.vue +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -116,7 +116,7 @@ const activities = ref([]); [ { align: 'left', name: 'isBooked', - label: t('invoiceIn.list.isBooked'), + label: t('invoiceIn.isBooked'), columnFilter: false, }, { diff --git a/src/pages/InvoiceIn/locale/en.yml b/src/pages/InvoiceIn/locale/en.yml index 824fd6e12..b09340c81 100644 --- a/src/pages/InvoiceIn/locale/en.yml +++ b/src/pages/InvoiceIn/locale/en.yml @@ -1,5 +1,6 @@ invoiceIn: serial: Serial + isBooked: Is booked list: ref: Reference supplier: Supplier @@ -7,7 +8,6 @@ invoiceIn: serial: Serial file: File issued: Issued - isBooked: Is booked awb: AWB amount: Amount card: @@ -31,7 +31,6 @@ invoiceIn: sage: Sage withholding vat: Undeductible VAT company: Company - booked: Booked expense: Expense taxableBase: Taxable base rate: Rate diff --git a/src/pages/InvoiceIn/locale/es.yml b/src/pages/InvoiceIn/locale/es.yml index 944708364..31d41fc97 100644 --- a/src/pages/InvoiceIn/locale/es.yml +++ b/src/pages/InvoiceIn/locale/es.yml @@ -1,5 +1,6 @@ invoiceIn: serial: Serie + isBooked: Contabilizada list: ref: Referencia supplier: Proveedor @@ -7,7 +8,6 @@ invoiceIn: shortIssued: F. emisión file: Fichero issued: Fecha emisión - isBooked: Conciliada awb: AWB amount: Importe card: @@ -31,7 +31,6 @@ invoiceIn: sage: Retención sage vat: Iva no deducible company: Empresa - booked: Contabilizada expense: Gasto taxableBase: Base imp. rate: Tasa diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue index 9ef0ed4b8..8e49fa16a 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue @@ -1,5 +1,6 @@ - + diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 09de62b27..91477e1a4 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -29,13 +29,16 @@ const { openReport } = usePrintService(); const columns = computed(() => [ { - align: 'left', + align: 'center', name: 'id', label: t('invoiceOutList.tableVisibleColumns.id'), chip: { condition: () => true, }, isId: true, + columnFilter: { + name: 'search', + }, }, { align: 'left', diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue index 53a1f2ec1..527da51d1 100644 --- a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -9,6 +9,7 @@ import { useArrayData } from 'src/composables/useArrayData'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import TicketDescriptorProxy from '../Ticket/Card/TicketDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; +import VnInputDate from 'components/common/VnInputDate.vue'; const { t } = useI18n(); const tableRef = ref(); @@ -64,7 +65,8 @@ const columns = computed(() => [ cardVisible: true, attrs: { url: 'Clients', - fields: ['id', 'name'], + optionLabel: 'socialName', + optionValue: 'socialName', }, columnField: { component: null, @@ -191,10 +193,33 @@ const downloadCSV = async () => { + + + + es: Download as CSV: Descargar como CSV + params: + from: Desde + to: Hasta +en: + params: + from: From + to: To diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index fddf154a2..7d4d6b896 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -514,7 +514,7 @@ function handleOnDataSave({ CrudModelRef }) { - + { > - {{ 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/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 52d442d0a..fac601735 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -139,33 +139,35 @@ const openTab = (id) => :disable-option="{ card: true }" > - - {{ $t('globals.refresh') }} - - - {{ t('salesOrdersTable.delete') }} - + + + {{ $t('globals.refresh') }} + + + {{ t('salesOrdersTable.delete') }} + + diff --git a/src/pages/Monitor/locale/en.yml b/src/pages/Monitor/locale/en.yml index 4cdd245aa..b8082f02a 100644 --- a/src/pages/Monitor/locale/en.yml +++ b/src/pages/Monitor/locale/en.yml @@ -15,7 +15,6 @@ salesOrdersTable: dateMake: Make date client: Client salesPerson: Salesperson - deleteConfirmTitle: Delete selected elements deleteConfirmMessage: All the selected elements will be deleted. Are you sure you want to continue? agency: Agency import: Import diff --git a/src/pages/Monitor/locale/es.yml b/src/pages/Monitor/locale/es.yml index 8087bb444..4ee5b90a9 100644 --- a/src/pages/Monitor/locale/es.yml +++ b/src/pages/Monitor/locale/es.yml @@ -15,7 +15,6 @@ salesOrdersTable: dateMake: Fecha de realización client: Cliente salesPerson: Comercial - deleteConfirmTitle: Eliminar los elementos seleccionados deleteConfirmMessage: Todos los elementos seleccionados serán eliminados. ¿Seguro que quieres continuar? agency: Agencia import: Importe diff --git a/src/pages/Order/Card/OrderLines.vue b/src/pages/Order/Card/OrderLines.vue index 0223aa9c6..5aec976fa 100644 --- a/src/pages/Order/Card/OrderLines.vue +++ b/src/pages/Order/Card/OrderLines.vue @@ -249,7 +249,7 @@ watch( @on-fetch="(data) => (orderSummary.vat = data)" auto-load /> - + [ { align: 'left', @@ -169,6 +170,13 @@ const getDateColor = (date) => { if (comparation == 0) return 'bg-warning'; if (comparation < 0) return 'bg-success'; }; + +onMounted(() => { + if (!route.query.createForm) return; + const clientId = route.query.createForm; + const id = JSON.parse(clientId); + fetchClientAddress(id.clientFk, id); +}); @@ -184,13 +192,14 @@ const getDateColor = (date) => { :order="['landed DESC', 'clientFk ASC', 'id DESC']" :create="{ urlCreate: 'Orders/new', - title: 'Create Order', + title: t('module.cerateOrder'), onDataSaved: (url) => { tableRef.redirect(url); }, formInitialData: { active: true, addressId: null, + clientFk: null, }, }" :user-params="{ showEmpty: false }" @@ -221,7 +230,7 @@ const getDateColor = (date) => { fetchClientAddress(id, data)" /> diff --git a/src/pages/Order/locale/en.yml b/src/pages/Order/locale/en.yml index b630a18ed..4349bc76f 100644 --- a/src/pages/Order/locale/en.yml +++ b/src/pages/Order/locale/en.yml @@ -10,6 +10,7 @@ module: total: Total salesPerson: Sales Person address: Address + cerateOrder: Create order lines: item: Item warehouse: Warehouse diff --git a/src/pages/Order/locale/es.yml b/src/pages/Order/locale/es.yml index 055d22719..cef06cb6d 100644 --- a/src/pages/Order/locale/es.yml +++ b/src/pages/Order/locale/es.yml @@ -10,6 +10,7 @@ module: total: Total salesPerson: Comercial address: Dirección + cerateOrder: Crear cesta lines: item: Artículo warehouse: Almacén diff --git a/src/pages/Route/Agency/AgencyList.vue b/src/pages/Route/Agency/AgencyList.vue index 42aede8a0..9d456c1da 100644 --- a/src/pages/Route/Agency/AgencyList.vue +++ b/src/pages/Route/Agency/AgencyList.vue @@ -27,12 +27,15 @@ const columns = computed(() => [ condition: () => true, }, isId: true, + columnFilter: false, }, { align: 'left', label: t('globals.name'), name: 'name', isTitle: true, + columnFilter: false, + columnClass: 'expand', }, { align: 'left', @@ -70,18 +73,33 @@ const columns = computed(() => [ data-key="AgencyList" :expr-builder="exprBuilder" /> - + + + + + + es: isOwn: Tiene propietario diff --git a/src/pages/Route/Agency/Card/AgencyWorkcenter.vue b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue index 1f3cab5d0..7103ea9ce 100644 --- a/src/pages/Route/Agency/Card/AgencyWorkcenter.vue +++ b/src/pages/Route/Agency/Card/AgencyWorkcenter.vue @@ -46,13 +46,13 @@ async function deleteWorCenter(id) { } + (warehouses = data)" + auto-load + /> - (warehouses = data)" - auto-load - /> { {{ props.value }} - + diff --git a/src/pages/Route/RouteExtendedList.vue b/src/pages/Route/RouteExtendedList.vue new file mode 100644 index 000000000..51da4ec12 --- /dev/null +++ b/src/pages/Route/RouteExtendedList.vue @@ -0,0 +1,360 @@ + + + + + + + + {{ t('route.Select the starting date') }} + + + + + + + + + {{ t('globals.clone') }} + + + + + + + + + + + + + + {{ t('route.Clone Selected Routes') }} + + + {{ t('route.Download selected routes as PDF') }} + + + {{ t('route.Mark as served') }} + + + + diff --git a/src/pages/Route/RouteList.vue b/src/pages/Route/RouteList.vue index 1e20df99c..d0feb9a65 100644 --- a/src/pages/Route/RouteList.vue +++ b/src/pages/Route/RouteList.vue @@ -1,34 +1,19 @@ - - - - - {{ t('Select the starting date') }} - - - - - - - - - {{ t('globals.clone') }} - - - - - - - - {{ t('Clone Selected Routes') }} - - - {{ t('Download selected routes as PDF') }} - - - {{ t('Mark as served') }} - + + + {{ row?.workerUserName }} + + - - - -en: - newRoute: New Route - hourStarted: Started hour - hourFinished: Finished hour -es: - From: Desde - To: Hasta - Worker: Trabajador - Agency: Agencia - Vehicle: Vehículo - Volume: Volumen - Date: Fecha - Description: Descripción - Hour started: Hora inicio - Hour finished: Hora fin - KmStart: Km inicio - KmEnd: Km fin - Served: Servida - newRoute: Nueva Ruta - Clone Selected Routes: Clonar rutas seleccionadas - Select the starting date: Seleccione la fecha de inicio - Stating date: Fecha de inicio - Cancel: Cancelar - Mark as served: Marcar como servidas - Download selected routes as PDF: Descargar rutas seleccionadas como PDF - Add ticket: Añadir tickets - Preview: Vista previa - Summary: Resumen - Route is closed: La ruta está cerrada - Route is not served: La ruta no está servida - hourStarted: Hora de inicio - hourFinished: Hora de fin - diff --git a/src/pages/Route/RouteRoadmap.vue b/src/pages/Route/RouteRoadmap.vue index d921dab1f..3687442f5 100644 --- a/src/pages/Route/RouteRoadmap.vue +++ b/src/pages/Route/RouteRoadmap.vue @@ -87,7 +87,7 @@ const columns = computed(() => [ actions: [ { title: t('Ver cmr'), - icon: 'visibility', + icon: 'preview', isPrimary: true, action: (row) => viewSummary(row?.id, RoadmapSummary), }, diff --git a/src/pages/Route/RouteTickets.vue b/src/pages/Route/RouteTickets.vue index 5960636b0..3bdad4fec 100644 --- a/src/pages/Route/RouteTickets.vue +++ b/src/pages/Route/RouteTickets.vue @@ -342,10 +342,7 @@ const openSmsDialog = async () => { - + {{ value }} {{ t('Open buscaman') }} @@ -353,7 +350,7 @@ const openSmsDialog = async () => { - + {{ value }} @@ -361,7 +358,7 @@ const openSmsDialog = async () => { - + {{ value }} diff --git a/src/pages/Route/locale/en.yml b/src/pages/Route/locale/en.yml new file mode 100644 index 000000000..d113fda67 --- /dev/null +++ b/src/pages/Route/locale/en.yml @@ -0,0 +1,39 @@ +route: + Worker: Worker + Agency: Agency + Vehicle: Vehicle + Description: Description + hourStarted: H.Start + hourFinished: H.End + createRoute: Create route + From: From + To: To + Date: Date + KmStart: Km start + KmEnd: Km end + Served: Served + Clone Selected Routes: Clone selected routes + Select the starting date: Select the starting date + Stating date: Starting date + Cancel: Cancel + Mark as served: Mark as served + Download selected routes as PDF: Download selected routes as PDF + Add ticket: Add ticket + Preview: Preview + Summary: Summary + Route is closed: Route is closed + Route is not served: Route is not served + cmr: + list: + results: results + cmrFk: CMR id + hasCmrDms: Attached in gestdoc + 'true': 'Yes' + 'false': 'No' + ticketFk: Ticketd id + routeFk: Route id + country: Country + clientFk: Client id + shipped: Preparation date + viewCmr: View CMR + downloadCmrs: Download CMRs diff --git a/src/pages/Route/locale/es.yml b/src/pages/Route/locale/es.yml new file mode 100644 index 000000000..a6ba4f370 --- /dev/null +++ b/src/pages/Route/locale/es.yml @@ -0,0 +1,39 @@ +route: + Worker: Trabajador + Agency: Agencia + Vehicle: Vehículo + Description: Descripción + hourStarted: H.Inicio + hourFinished: H.Fin + createRoute: Crear ruta + From: Desde + To: Hasta + Date: Fecha + KmStart: Km inicio + KmEnd: Km fin + Served: Servida + Clone Selected Routes: Clonar rutas seleccionadas + Select the starting date: Seleccione la fecha de inicio + Stating date: Fecha de inicio + Cancel: Cancelar + Mark as served: Marcar como servidas + Download selected routes as PDF: Descargar rutas seleccionadas como PDF + Add ticket: Añadir tickets + Preview: Vista previa + Summary: Resumen + Route is closed: La ruta está cerrada + Route is not served: La ruta no está servida + cmr: + list: + results: resultados + cmrFk: Id CMR + hasCmrDms: Gestdoc + 'true': Sí + 'false': 'No' + ticketFk: Id ticket + routeFk: Id ruta + country: País + clientFk: Id cliente + shipped: Fecha preparación + viewCmr: Ver CMR + downloadCmrs: Descargar CMRs diff --git a/src/pages/Ticket/Card/BasicData/BasicDataTable.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue similarity index 60% rename from src/pages/Ticket/Card/BasicData/BasicDataTable.vue rename to src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 7f2f100ad..ab96a6e75 100644 --- a/src/pages/Ticket/Card/BasicData/BasicDataTable.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -4,7 +4,6 @@ import { useI18n } from 'vue-i18n'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import FetchedTags from 'components/ui/FetchedTags.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import FetchData from 'components/FetchData.vue'; import { useStateStore } from 'stores/useStateStore'; @@ -115,7 +114,7 @@ const totalNewPrice = computed(() => { const totalDifference = computed(() => { return rows.value.reduce((acc, item) => acc + item.component?.difference || 0, 0); }); -const showMovablecolumn = computed(() => (haveDifferences.value > 0 ? ['movable'] : [])); +const showMovableColumn = computed(() => (haveDifferences.value > 0 ? ['movable'] : [])); const haveDifferences = computed(() => _ticketData.value.sale?.haveDifferences); const ticketHaveNegatives = () => { let _haveNegatives = false; @@ -145,85 +144,83 @@ onUnmounted(() => (stateStore.rightDrawer = false)); @on-fetch="(data) => (ticketUpdateActions = data)" auto-load /> - - - + + + + {{ t('basicData.total') }} + + + + + {{ t('basicData.price') }}: + {{ toCurrency(totalPrice) }} + + + + + {{ t('basicData.newPrice') }}: {{ toCurrency(totalNewPrice) }} + + + + + {{ t('basicData.difference') }}: {{ toCurrency(totalDifference) }} + + + + + + + {{ t('basicData.chargeDifference') }} + + + - - - {{ t('basicData.total') }} - - - - - {{ t('basicData.price') }}: - {{ toCurrency(totalPrice) }} - - - - - {{ t('basicData.newPrice') }}: {{ toCurrency(totalNewPrice) }} - - - - - {{ t('basicData.difference') }}: {{ toCurrency(totalDifference) }} - - - - - - - {{ t('basicData.chargeDifference') }} - - - - - - - - - - - - {{ t('basicData.withoutNegativesInfo') }} - - - - - - + + + + + + + + + {{ t('basicData.withoutNegativesInfo') }} + + + + + (stateStore.rightDrawer = false)); flat > - - + + {{ row.itemFk }} - + {{ row.item.name }} {{ row.item.subName }} diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue index 28c6fcf15..f5ce8a0f3 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue @@ -12,6 +12,8 @@ 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'; const $props = defineProps({ @@ -23,18 +25,21 @@ const $props = defineProps({ }); const emit = defineEmits(['updateForm']); - +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( @@ -43,6 +48,8 @@ watch( { deep: true } ); +onMounted(() => onFormModelInit()); + const agencyByWarehouseFilter = computed(() => ({ fields: ['id', 'name'], order: 'name ASC', @@ -51,18 +58,16 @@ const agencyByWarehouseFilter = computed(() => ({ }, })); -const zonesFilter = computed(() => ({ - fields: ['id', 'name'], - order: 'name ASC', - where: formData.value?.agencyModeFk +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 { @@ -269,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()); @on-fetch="(data) => (agenciesOptions = data)" auto-load /> - (zonesOptions = data)" - auto-load - /> onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.client')" > @@ -333,6 +342,7 @@ onMounted(() => onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.warehouse')" /> @@ -345,6 +355,7 @@ onMounted(() => onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.address')" > @@ -392,6 +403,7 @@ onMounted(() => onFormModelInit()); :label="t('basicData.alias')" v-model="formData.nickname" :required="true" + :rules="validate('basicData.alias')" /> @@ -404,6 +416,7 @@ onMounted(() => onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.company')" /> onFormModelInit()); hide-selected map-options @focus="agencyFetchRef.fetch()" + :rules="validate('basicData.agency')" /> @@ -444,16 +462,19 @@ onMounted(() => onFormModelInit()); :label="t('basicData.shipped')" v-model="formData.shipped" :required="true" + :rules="validate('basicData.shipped')" /> diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue index af47761a2..92640f898 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataView.vue @@ -3,7 +3,7 @@ import { ref, onBeforeMount } from 'vue'; import { useI18n } from 'vue-i18n'; import { useRoute, useRouter } from 'vue-router'; -import BasicDataTable from './BasicDataTable.vue'; +import TicketBasicData from './TicketBasicData.vue'; import TicketBasicDataForm from './TicketBasicDataForm.vue'; import { useVnConfirm } from 'composables/useVnConfirm'; @@ -158,7 +158,10 @@ onBeforeMount(async () => await getTicketData()); color="primary" animated keep-alive - style="max-width: 800px; margin: auto" + style="margin: auto" + :style="{ + 'max-width': step > 1 ? 'none' : '800px', + }" > await getTicketData()); /> - (formData = $event)" diff --git a/src/pages/Ticket/Card/ExpeditionNewTicket.vue b/src/pages/Ticket/Card/ExpeditionNewTicket.vue index 55ca700bc..9183ae405 100644 --- a/src/pages/Ticket/Card/ExpeditionNewTicket.vue +++ b/src/pages/Ticket/Card/ExpeditionNewTicket.vue @@ -31,6 +31,7 @@ const router = useRouter(); const { notify } = useNotify(); const newTicketFormData = reactive({}); +const date = new Date(); const createTicket = async () => { try { @@ -64,7 +65,11 @@ const createTicket = async () => { > - + [ name: 'item', align: 'left', }, + { + align: 'left', + label: t('lines.image'), + name: 'image', + columnField: { + component: VnImg, + attrs: (id) => { + return { + id, + width: '50px', + }; + }, + }, + columnFilter: false, + }, { label: t('ticketComponents.description'), name: 'description', align: 'left', + columnClass: 'expand', }, { label: t('ticketComponents.quantity'), name: 'quantity', field: 'quantity', align: 'left', - format: (val) => dashIfEmpty(val), + format: (row) => dashIfEmpty(row.quantity), }, { label: t('ticketComponents.serie'), name: 'serie', align: 'left', + format: (row) => dashIfEmpty(row.serie), }, { label: t('ticketComponents.components'), @@ -174,181 +192,166 @@ onUnmounted(() => (stateStore.rightDrawer = false)); @on-fetch="(data) => (components = data)" auto-load /> - - - + + + + {{ t('ticketComponents.total') }} + + + + {{ t('ticketComponents.baseToCommission') }}: + + {{ toCurrency(getBase) }} + + + {{ t('ticketComponents.totalWithoutVat') }}: + + {{ toCurrency(getTotal) }} + + + + + + {{ t('ticketComponents.components') }} + + + - - - {{ t('ticketComponents.total') }} - - - - {{ t('ticketComponents.baseToCommission') }}: - - {{ toCurrency(getBase) }} - - - {{ t('ticketComponents.totalWithoutVat') }}: - - {{ toCurrency(getTotal) }} - - - - - - {{ t('ticketComponents.components') }} - - - - - {{ component.name }}: - - {{ - toCurrency(component.value, 'EUR', 3) - }} - - - - - - {{ t('ticketComponents.zoneBreakdown') }} - - - - - {{ t('ticketComponents.price') }}: - - {{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }} - - - - {{ t('ticketComponents.bonus') }}: - - {{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }} - - - - {{ t('ticketComponents.zone') }}: - - - {{ dashIfEmpty(ticketData?.zone?.name) }} - - - - - - {{ t('ticketComponents.volume') }}: - - {{ ticketVolume }} - - - - {{ t('ticketComponents.packages') }}: - - {{ dashIfEmpty(ticketData?.packages) }} - - - - - - {{ t('ticketComponents.theoricalCost') }} - - - - - {{ t('ticketComponents.totalPrice') }}: - - {{ toCurrency(theoricalCost, 'EUR', 2) }} - - - - - + {{ component.name }}: + + {{ + toCurrency(component.value, 'EUR', 3) + }} + + + + + + {{ t('ticketComponents.zoneBreakdown') }} + + + + + {{ t('ticketComponents.price') }}: + + {{ toCurrency(ticketData?.zonePrice, 'EUR', 2) }} + + + + {{ t('ticketComponents.bonus') }}: + + {{ toCurrency(ticketData?.zoneBonus, 'EUR', 2) }} + + + + {{ t('ticketComponents.zone') }}: + + + {{ dashIfEmpty(ticketData?.zone?.name) }} + + + + + + {{ t('ticketComponents.volume') }}: + + {{ ticketVolume }} + + + + {{ t('ticketComponents.packages') }}: + + {{ dashIfEmpty(ticketData?.packages) }} + + + + + + {{ t('ticketComponents.theoricalCost') }} + + + + + {{ t('ticketComponents.totalPrice') }}: + + {{ toCurrency(theoricalCost, 'EUR', 2) }} + + + + - - - - {{ row.itemFk }} - - - + + + {{ row.itemFk }} + + - - - - {{ row.item.name }} - {{ row.item.subName }} - - - + + + + - - - - - {{ saleComponent.component?.componentType?.name }} - - - + + + {{ row.item.name }} + {{ row.item.subName }} + + - - - - - {{ saleComponent.component?.name }} - - - + + + + {{ saleComponent.component?.componentType?.name }} + + - - - - + + + + {{ saleComponent.component?.name }} + + + + + + + {{ toCurrency(saleComponent.value, 'EUR', 3) }} + + + + + + + {{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }} + + + - - - - - {{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }} - - - - - + + + diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index f68c897db..70fd9ab1e 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -2,13 +2,13 @@ import { ref, computed } from 'vue'; import { useRoute } from 'vue-router'; import { useI18n } from 'vue-i18n'; -import { toDate } from 'src/filters'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; import VnUserLink from 'src/components/ui/VnUserLink.vue'; +import { toDateTimeFormat } from 'src/filters/date'; const $props = defineProps({ id: { @@ -30,13 +30,24 @@ const filter = { { relation: 'address', scope: { - fields: ['id', 'name', 'mobile', 'phone'], + fields: ['id', 'name', 'mobile', 'phone', 'incotermsFk'], }, }, { relation: 'client', scope: { - fields: ['id', 'name', 'salesPersonFk', 'phone', 'mobile', 'email'], + fields: [ + 'id', + 'name', + 'salesPersonFk', + 'phone', + 'mobile', + 'email', + 'isActive', + 'isFreezed', + 'isTaxDataChecked', + 'hasElectronicInvoice', + ], include: [ { relation: 'user', @@ -87,6 +98,10 @@ const filter = { }; const data = ref(useCardDescription()); + +function ticketFilter(ticket) { + return JSON.stringify({ clientFk: ticket.clientFk }); +} @@ -128,7 +143,10 @@ const data = ref(useCardDescription()); /> - + - + + + {{ t('Client inactive') }} + + + {{ t('Client Frozen') }} + + + {{ t('Client has debt') }} + + + {{ t('Client not checked') }} + {{ t('ticket.card.customerCard') }} + + {{ t('ticket.card.ticketList') }} + + + {{ t('ticket.card.newOrder') }} + @@ -168,4 +239,8 @@ const data = ref(useCardDescription()); es: This ticket is deleted: Este ticket está eliminado Go to module index: Ir al índice del modulo + Client inactive: Cliente inactivo + Client not checked: Cliente no verificado + Client has debt: Cliente con deuda + Client Frozen: Cliente congelado diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index 4cf4e633f..834fced87 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -1,14 +1,19 @@ + + + + + + {{ t('Transfer client') }} + + + + + + + + + + {{ `#${scope.opt.id} - ` }} + {{ scope.opt.name }} + + + + + + + + + + + + + {{ t('addTurn') }} + + + + + + + + + + + + + + + + - {{ t('Open Delivery Note...') }} + {{ t('Show Delivery Note...') }} - + - {{ t('With prices') }} + {{ t('as PDF') }} - {{ t('Without prices') }} + {{ t('as PDF without prices') }} + + + {{ t('as PDF signed') }} - {{ t('As CSV') }} + {{ t('as CSV') }} @@ -213,28 +534,24 @@ function openConfirmDialog(callback) { - + - {{ t('With prices') }} + {{ t('Send PDF') }} - - {{ t('Without prices') }} + + {{ t('Send PDF to tablet') }} - {{ t('As CSV') }} + {{ t('Send CSV') }} @@ -243,8 +560,26 @@ function openConfirmDialog(callback) { - {{ t('Open Proforma Invoice') }} + {{ t('Show Proforma') }} + + + + + {{ t('Change shipped hour') }} + + + + + + + + @@ -259,24 +594,72 @@ function openConfirmDialog(callback) { {{ t('Pending payment') }} - {{ t('Minimum amount') }} + {{ t('Minimum import') }} - {{ t('Order changes') }} + {{ t('Notify changes') }} + + + + + {{ t('Make invoice') }} + + + + + + {{ + hasPdf ? t('Regenerate PDF invoice') : t('Generate PDF invoice') + }} + {{ t('To clone ticket') }} + + + + + {{ t('Recalculate components') }} + + + + + + {{ t('Refund all...') }} + + + + + + + + {{ t('with warehouse') }} + + + + + {{ t('without warehouse') }} + + + + + @@ -307,29 +690,63 @@ function openConfirmDialog(callback) { + + en: + addTurn: Add turn invoiceIds: "Invoices have been generated with the following ids: {invoiceIds}" es: - Open Delivery Note...: Abrir albarán... + Show Delivery Note...: Ver albarán... Send Delivery Note...: Enviar albarán... - With prices: Con precios - Without prices: Sin precios - As CSV: Como CSV - Open Proforma Invoice: Abrir factura proforma + as PDF: como PDF + as PDF without prices: como PDF sin precios + as CSV: Como CSV + Send PDF: Enviar PDF + Send PDF to tablet: Enviar PDF a tablet + Send CSV: Enviar CSV + Show Proforma: Ver proforma Delete ticket: Eliminar ticket - Send SMS...: Enviar SMS + Send SMS...: Enviar SMS... Pending payment: Pago pendiente - Minimum amount: Importe mínimo - Order changes: Cambios del pedido + Minimum import: Importe mínimo + Notify changes: Notificar cambios Ticket deleted: Ticket eliminado You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora To clone ticket: Clonar ticket Ticket cloned: Ticked clonado It was not able to clone the ticket: No se pudo clonar el ticket + Generate PDF invoice: Generar PDF factura + Regenerate PDF invoice: Regenerar PDF factura + The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado + Transfer client: Transferir cliente + Client: Cliente + addTurn: Añadir a turno + What is the day of receipt of the ticket?: ¿Cuál es el día de preparación del pedido? + Current ticket deleted and added to shift: Ticket actual eliminado y añadido al turno + Refund all...: Abonar todo... + with warehouse: con almacén + without warehouse: sin almacén + Make invoice: Crear factura + Change shipped hour: Cambiar hora de envío + Shipped hour: Hora de envío + Recalculate components: Recalcular componentes + Are you sure you want to recalculate components?: ¿Seguro que quieres recalcular los componentes? + Data saved: Datos guardados + Are you sure you want to invoice this ticket?: ¿Seguro que quieres facturar este ticket? + You are going to invoice this ticket: Vas a facturar este ticket + Ticket invoiced: Ticket facturado Set weight: Establecer peso Weight set: Peso establecido This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar? invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}" + This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas? + You are going to delete this ticket: Vas a eliminar este ticket + as PDF signed: como PDF firmado + Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán? 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 @@ (observationTypes = data)" + @on-fetch=" + (data) => + (observationTypes = data.map((type) => { + type.label = t(`ticketNotes.observationTypes.${type.description}`); + return type; + })) + " auto-load url="ObservationTypes" /> @@ -64,7 +82,7 @@ watch( :label="t('ticketNotes.observationType')" :options="observationTypes" hide-selected - option-label="description" + option-label="label" option-value="id" v-model="row.observationTypeFk" :disable="!!row.id" @@ -73,13 +91,14 @@ watch( :label="t('ticketNotes.description')" v-model="row.description" class="col" + @keyup.enter="handleSave" /> {{ t('ticketNotes.removeNote') }} diff --git a/src/pages/Ticket/Card/TicketPackage.vue b/src/pages/Ticket/Card/TicketPackage.vue index c071d4f7f..c0418bcf6 100644 --- a/src/pages/Ticket/Card/TicketPackage.vue +++ b/src/pages/Ticket/Card/TicketPackage.vue @@ -10,6 +10,7 @@ import FetchData from 'components/FetchData.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import { useArrayData } from 'src/composables/useArrayData'; +import VnRow from 'src/components/ui/VnRow.vue'; const route = useRoute(); const { t } = useI18n(); diff --git a/src/pages/Ticket/Card/TicketPurchaseRequest.vue b/src/pages/Ticket/Card/TicketPurchaseRequest.vue index 8d84e2b46..d0af9efd0 100644 --- a/src/pages/Ticket/Card/TicketPurchaseRequest.vue +++ b/src/pages/Ticket/Card/TicketPurchaseRequest.vue @@ -1,27 +1,35 @@ - - - - redirectToTicketSummary(row.ticketFk)" - > - - - - - - - - - {{ row.requester?.user?.nickname }} - - - - - - - - {{ row.atender?.user?.nickname }} - - - - - - - - - - - - - - - - - - {{ row.sale.itemFk }} - - - - - - - - - {{ t('globals.delete') }} - - - - - - - - - - - - (attendersOptions = data)" + /> + + + + + + + {{ row.requester?.user?.nickname }} + + + + + + {{ row.atender?.user?.nickname }} + + + + + + + + + + {{ row.price }} + + + + + + + {{ dashIfEmpty(row.sale?.itemFk) }} + + + + {{ t(getRequestState(row.isOk)) }} + + + + - - {{ t('purchaseRequest.newRequest') }} - - - + + + + + + es: New: Nueva diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index f179257f7..c786c67e3 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -16,11 +16,13 @@ import TicketSaleMoreActions from './TicketSaleMoreActions.vue'; import TicketTransfer from './TicketTransfer.vue'; import { useStateStore } from 'stores/useStateStore'; -import { toCurrency, toPercentage, dashIfEmpty } from 'src/filters'; +import { toCurrency, toPercentage } from 'src/filters'; import { useArrayData } from 'composables/useArrayData'; import { useVnConfirm } from 'composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; +import VnTable from 'src/components/VnTable/VnTable.vue'; +import VnUsesMana from 'src/components/ui/VnUsesMana.vue'; const route = useRoute(); const router = useRouter(); @@ -33,7 +35,8 @@ const stateBtnDropdownRef = ref(null); const arrayData = useArrayData('ticketData'); const { store } = arrayData; - +const selectedRows = ref([]); +const hasSelectedRows = computed(() => selectedRows.value.length > 0); const ticketConfig = ref(null); const isLocked = ref(false); const isTicketEditable = ref(false); @@ -47,94 +50,98 @@ const transfer = ref({ lastActiveTickets: [], sales: [], }); +const tableRef = ref([]); watch( () => route.params.id, - async () => await getSales() + () => tableRef.value.reload() ); const columns = computed(() => [ { - label: '', + align: 'left', name: 'statusIcons', - align: 'left', }, { - label: '', - name: 'picture', - align: 'left', + align: 'center', + label: t('lines.image'), + name: 'image', + columnField: { + component: VnImg, + attrs: (id) => { + return { + id, + width: '50px', + }; + }, + }, + columnFilter: false, }, { + align: 'left', label: t('ticketSale.visible'), name: 'visible', - field: 'visible', - align: 'left', - sortable: true, + format: (row, dashIfEmpty) => dashIfEmpty(row.visible), }, { + align: 'left', label: t('ticketSale.available'), name: 'available', - field: 'available', - align: 'left', - sortable: true, + format: (row, dashIfEmpty) => dashIfEmpty(row.available), }, { + align: 'left', label: t('ticketSale.id'), name: 'itemFk', - field: 'itemFk', - align: 'left', - sortable: true, }, { + align: 'left', label: t('ticketSale.quantity'), name: 'quantity', - field: 'quantity', - align: 'left', - sortable: true, + format: (row) => toCurrency(row.quantity), }, { + align: 'left', label: t('ticketSale.item'), name: 'item', - field: 'item', - align: 'left', - sortable: true, + format: (row) => row?.item?.name, + columnClass: 'expand', }, { + align: 'left', label: t('ticketSale.price'), name: 'price', - field: 'price', - align: 'left', - sortable: true, - format: (val) => toCurrency(val), + format: (row) => toCurrency(row.price), }, { + align: 'left', label: t('ticketSale.discount'), name: 'discount', - field: 'discount', - align: 'left', - sortable: true, + format: (row) => toPercentage(row.discount), }, { + align: 'left', label: t('ticketSale.amount'), name: 'amount', - field: 'amount', - align: 'left', - sortable: true, - format: (val) => toCurrency(val), + format: (row) => parseInt(row.amount * row.quantity), }, { + align: 'left', label: t('ticketSale.packaging'), name: 'itemPackingTypeFk', - field: 'item', - align: 'left', - sortable: true, - format: (val) => dashIfEmpty(val?.itemPackingTypeFk), + format: (row, dashIfEmpty) => dashIfEmpty(row?.item?.itemPackingTypeFk), }, { - label: '', - name: 'history', - align: 'left', - columnFilter: null, + align: 'right', + name: 'tableActions', + actions: [ + { + title: t('ticketSale.history'), + icon: 'history', + isPrimary: true, + action: (row) => goToLog(row.id), + }, + ], }, ]); @@ -155,15 +162,6 @@ const onSalesFetched = (salesData) => { for (let sale of salesData) sale.amount = getSaleTotal(sale); }; -const getSales = async () => { - try { - const { data } = await axios.get(`Tickets/${route.params.id}/getSales`); - onSalesFetched(data); - } catch (err) { - console.error('Error fetching sales', err); - } -}; - const getSaleTotal = (sale) => { if (sale.quantity == null || sale.price == null) return null; @@ -175,7 +173,7 @@ const getSaleTotal = (sale) => { const resetChanges = async () => { arrayData.fetch({ append: false }); - getSales(); + tableRef.value.reload(); }; const updateQuantity = async (sale) => { @@ -210,6 +208,7 @@ const addSale = async (sale) => { sale.item = newSale.item; notify('globals.dataSaved', 'positive'); + window.location.reload(); } catch (err) { console.error('Error adding sale', err); } @@ -259,7 +258,7 @@ const getMana = async () => { const selectedValidSales = computed(() => { if (!sales.value) return; - return selectedSales.value.filter((sale) => sale.id != undefined); + return [...selectedRows.value]; }); const onOpenEditPricePopover = async (sale) => { @@ -374,7 +373,7 @@ const changeTicketState = async (val) => { }; const removeSelectedSales = () => { - selectedSales.value.forEach((sale) => { + selectedRows.value.forEach((sale) => { const index = sales.value.indexOf(sale); sales.value.splice(index, 1); }); @@ -382,19 +381,29 @@ const removeSelectedSales = () => { const removeSales = async () => { try { - const params = { sales: selectedValidSales.value, ticketId: store.data.id }; + const params = { + sales: selectedRows.value.filter((sale) => sale.id), + ticketId: store.data.id, + }; + selectedRows.value + .filter((sale) => !sale.id) + .forEach((sale) => + tableRef.value.CrudModelRef.formData.splice(sale.$index, 1) + ); + + if (params.sales.length == 0) return; await axios.post('Sales/deleteSales', params); removeSelectedSales(); notify('globals.dataSaved', 'positive'); + window.location.reload(); } catch (err) { console.error('Error deleting sales', err); } }; -const insertRow = () => sales.value.push({ ...DEFAULT_EDIT }); - const setTransferParams = async () => { try { + selectedSales.value = selectedValidSales.value; const checkedSales = JSON.parse(JSON.stringify(selectedSales.value)); transfer.value = { lastActiveTickets: [], @@ -417,10 +426,64 @@ const setTransferParams = async () => { onMounted(async () => { stateStore.rightDrawer = true; getConfig(); - getSales(); }); onUnmounted(() => (stateStore.rightDrawer = false)); + +const items = ref([]); +const newRow = ref({}); + +const updateItem = (row) => { + const selectedItem = items.value.find((item) => item.id === row.itemFk); + if (selectedItem) { + row.item = selectedItem; + row.itemFk = selectedItem.id; + row.price = selectedItem.price; + row.discount = 0; + row.quantity = 0; + row.amount = row.price * row.quantity; + } + endNewRow(selectedItem); +}; + +function handleOnDataSave({ CrudModelRef }) { + const { copy } = addRow(CrudModelRef.formData); + CrudModelRef.insert(copy); +} + +const addRow = (original = null) => { + let copy = null; + if (!original) { + copy = { isNew: true }; + } else { + copy = { + itemFk: original.itemFk, + item: original.item, + quantity: original.quantity, + price: original.price, + discount: original.discount, + amount: original.amount, + isNew: true, + }; + } + newRow.value = copy; + return { original, copy }; +}; + +const endNewRow = (row) => { + if (row.itemFk && row.quantity) { + row.isNew = false; + } +}; + +watch( + () => newRow.value.itemFk, + (newItemFk) => { + if (newItemFk) { + updateItem(newRow.value); + } + } +); @@ -471,7 +534,7 @@ onUnmounted(() => (stateStore.rightDrawer = false)); :ticket="store.data" :is-ticket-editable="isTicketEditable" :sales="selectedValidSales" - :disable="!selectedSales.length" + :disable="!hasSelectedRows" :mana="mana" :ticket-config="ticketConfig" @get-mana="getMana()" @@ -480,7 +543,7 @@ onUnmounted(() => (stateStore.rightDrawer = false)); (stateStore.rightDrawer = false)); {{ t('Transfer lines') }} @@ -507,86 +570,99 @@ onUnmounted(() => (stateStore.rightDrawer = false)); - + - + {{ t('ticketSale.subtotal') }}: {{ toCurrency(store.data?.totalWithoutVat) }} - + {{ t('ticketSale.tax') }}: {{ toCurrency(store.data?.totalWithVat - store.data?.totalWithoutVat) }} - + {{ t('ticketSale.total') }}: {{ toCurrency(store.data?.totalWithVat) }} - - - - - - - {{ t('ticketSale.claim') }}: - {{ row.claim?.claimFk }} - - - - + + + - {{ t('ticketSale.visible') }}: {{ row.visible || 0 }} + {{ t('ticketSale.claim') }}: + {{ row.claim?.claimFk }} - - - {{ t('ticketSale.reserved') }} - - - - - {{ t('ticketSale.noVisible') }} - - - - - {{ t('ticketSale.hasComponentLack') }} - - - + + + + {{ t('ticketSale.visible') }}: {{ row.visible || 0 }} + + + + + {{ t('ticketSale.reserved') }} + + + + + {{ t('ticketSale.noVisible') }} + + + + + {{ t('ticketSale.hasComponentLack') }} + + @@ -595,158 +671,126 @@ onUnmounted(() => (stateStore.rightDrawer = false)); - - - - {{ row.visible }} - - + + + + - - - - {{ row.available }} - - + + + {{ row.visible }} + - - - - - {{ row.itemFk }} - - - - - - - - #{{ scope.opt?.id }} - {{ scope.opt?.name }} - - - - - + + + {{ row.available }} + - - - - {{ row.quantity }} - - - - - - {{ row.concept }} - {{ row.item?.subName }} - - - - - - - - - - - - {{ toCurrency(row.price) }} - - - - - - {{ toCurrency(row.price) }} - - - - - - - {{ toPercentage(row.discount / 100) }} - - - - - - {{ toPercentage(row.discount / 100) }} - - - - - - - {{ t('ticketSale.history') }} - - - - - - + - - {{ t('Add item') }} - - + + + + + {{ scope.opt?.id }} - {{ scope.opt?.name }} + + + + + + {{ row?.itemFk }} + + - + + + {{ row?.item?.name }} + + {{ row?.item?.subName.toUpperCase() }} + + + + + + + + + + + {{ row.quantity }} + + + + + {{ toCurrency(row.price) }} + + + + + + {{ toCurrency(row.price) }} + + + + + {{ toPercentage(row.discount / 100) }} + + + + + + + + + {{ toPercentage(row.discount / 100) }} + + + {{ toCurrency(row.quantity * row.price) }} + + - + {{ t('Add item to basket') }} @@ -754,6 +798,18 @@ onUnmounted(() => (stateStore.rightDrawer = false)); + + es: New item: Nuevo artículo diff --git a/src/pages/Ticket/Card/TicketSaleMoreActions.vue b/src/pages/Ticket/Card/TicketSaleMoreActions.vue index 94db67be2..2ec519d2d 100644 --- a/src/pages/Ticket/Card/TicketSaleMoreActions.vue +++ b/src/pages/Ticket/Card/TicketSaleMoreActions.vue @@ -44,7 +44,7 @@ const props = defineProps({ }, }); -const router = useRouter(); +const { push } = useRouter(); const { t } = useI18n(); const { dialog } = useQuasar(); const { notify } = useNotify(); @@ -142,7 +142,7 @@ const onCreateClaimAccepted = async () => { try { const params = { ticketId: ticket.value.id, sales: props.sales }; const { data } = await axios.post(`Claims/createFromSales`, params); - router.push({ name: 'ClaimBasicData', params: { id: data.id } }); + push({ name: 'ClaimBasicData', params: { id: data.id } }); } catch (error) { console.error('Error creating claim: ', error); } @@ -169,7 +169,7 @@ const createRefund = async (withWarehouse) => { const { data } = await axios.post('Tickets/cloneAll', params); const [refundTicket] = data; notify(t('refundTicketCreated', { ticketId: refundTicket.id }), 'positive'); - router.push({ name: 'TicketSale', params: { id: refundTicket.id } }); + push({ name: 'TicketSale', params: { id: refundTicket.id } }); } catch (error) { console.error(error); } diff --git a/src/pages/Ticket/Card/TicketSaleTracking.vue b/src/pages/Ticket/Card/TicketSaleTracking.vue index 6978d92c8..e7830bf37 100644 --- a/src/pages/Ticket/Card/TicketSaleTracking.vue +++ b/src/pages/Ticket/Card/TicketSaleTracking.vue @@ -25,7 +25,7 @@ const saleTrackingFetchDataRef = ref(null); const sales = ref([]); const saleTrackings = ref([]); -const itemShelvignsSales = ref([]); +const itemShelvingsSales = ref([]); const saleTrackingUrl = computed(() => `SaleTrackings/${route.params.id}/filter`); const oldQuantity = ref(null); @@ -88,7 +88,7 @@ const logTableColumns = computed(() => [ label: t('ticketSaleTracking.original'), name: 'original', field: 'originalQuantity', - align: 'original', + align: 'left', sortable: true, }, { @@ -177,7 +177,7 @@ const getItemShelvingSales = async (sale) => { const { data } = await axios.get(`ItemShelvingSales/filter`, { params: { filter: JSON.stringify(filter) }, }); - itemShelvignsSales.value = data; + itemShelvingsSales.value = data; } catch (error) { console.error(error); } @@ -337,7 +337,7 @@ const qCheckBoxController = (sale, action) => { :no-data-label="t('globals.noResults')" > - + { - + - - {{ row.itemFk }} - - + + + {{ row.itemFk }} + + + @@ -416,8 +418,18 @@ const qCheckBoxController = (sale, action) => { + + + {{ row.quantity }} + + + + + {{ dashIfEmpty(row.parkingFk) }} + + - + { data-key="saleTrackingLog" :rows="saleTrackings" :columns="logTableColumns" - class="q-pa-sm" + class="q-pa-sm full-width" > - - {{ row.name }} - + + + {{ row.name }} + + @@ -469,12 +483,12 @@ const qCheckBoxController = (sale, action) => { > - + { - - {{ row.name }} - + + + {{ row.name }} + + - + { - + div { + max-width: 900px; + } +} diff --git a/src/pages/Ticket/Card/TicketService.vue b/src/pages/Ticket/Card/TicketService.vue index 873051676..45a870f7f 100644 --- a/src/pages/Ticket/Card/TicketService.vue +++ b/src/pages/Ticket/Card/TicketService.vue @@ -25,7 +25,7 @@ const { notify } = useNotify(); const selected = ref([]); const defaultTaxClass = ref(null); - +const isSaving = ref(false); const crudModelFilter = computed(() => ({ where: { ticketFk: route.params.id }, })); @@ -50,7 +50,7 @@ const createRefund = async () => { if (!selected.value.length) return; const params = { - servicesIds: selected.value.map((s) => +s.ticketFk), + servicesIds: selected.value.map((s) => +s.id), withWarehouse: false, negative: true, }; @@ -104,7 +104,31 @@ const columns = computed(() => [ sortable: true, align: 'left', }, + { + label: '', + name: 'actions', + align: 'left', + columnFilter: null, + }, ]); + +async function deleteService(row) { + const serviceId = row.id; + if (!row.id) ticketServiceCrudRef.value.reset(); + else { + const { data } = await axios.delete(`TicketServices/${serviceId}`); + if (data) notify('Service deleted successfully', 'positive'); + ticketServiceCrudRef.value.reload(); + } +} + +async function handleSave() { + if (!isSaving.value) { + isSaving.value = true; + await ticketServiceCrudRef.value?.saveChanges(); + isSaving.value = false; + } +} @@ -123,6 +147,8 @@ const columns = computed(() => [ :data-required="crudModelRequiredData" auto-load v-model:selected="selected" + :order="['description ASC']" + :default-remove="false" > [ v-model.number="row.price" type="number" min="0" + @keyup.enter="handleSave" /> + + + + + {{ t('globals.delete') }} + + + + @@ -193,3 +235,4 @@ const columns = computed(() => [ /> +ñ diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 358f74af2..5fb99b849 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -1,4 +1,5 @@ (editableStates = data)" + :filter="{ fields: ['code', 'name', 'id', 'alertLevel'], order: 'name ASC' }" auto-load + @on-fetch="(data) => (editableStates = data)" /> + + + Ticket #{{ entity.id }} - {{ entity.client?.name }} ({{ @@ -122,14 +143,17 @@ async function changeState(value) { - + - - {{ entity.ticketState?.state?.name }} - + + {{ entity.ticketState.state.name }} + @@ -144,7 +168,14 @@ async function changeState(value) { :label="t('ticket.summary.agency')" :value="entity.agencyMode?.name" /> - + + + + {{ entity?.zone?.name }} + + + + - + + + + {{ entity.routeFk }} + + + + @@ -181,9 +219,9 @@ async function changeState(value) { :value="dashIfEmpty(entity.weight)" /> - + - {{ item.observationType.description }}: + ({{ + t( + `ticketNotes.observationTypes.${item.observationType.description}` + ) + }}): + {{ item.description }} - + + @@ -265,13 +307,14 @@ async function changeState(value) { /> @@ -294,11 +337,10 @@ async function changeState(value) { - + - {{ t('ticket.summary.claim') }}: - {{ props.row.claim.claimFk }} + + {{ t('ticket.summary.claim') }}: + {{ props.row.claim.claimFk }} + - {{ t('ticket.summary.claim') }}: - {{ props.row.claimBeginning.claimFk }} + + {{ t('ticket.summary.claim') }}: + {{ props.row.claimBeginning.claimFk }} + - {{ t('ticket.summary.visible') }}: - {{ props.row.visible }} + + {{ t('ticket.summary.visible') }}: + {{ props.row.visible }} + {{ t('ticket.summary.reserved') }} @@ -357,8 +398,8 @@ async function changeState(value) { {{ t('ticket.summary.itemShortage') }} @@ -367,8 +408,8 @@ async function changeState(value) { {{ t('ticket.summary.hasComponentLack') }} @@ -385,8 +426,34 @@ async function changeState(value) { /> - {{ props.row.visible }} - {{ props.row.available }} + + + {{ props.row.visible }} + + + {{ props.row.visible }} + + + + + {{ props.row.available }} + + + {{ props.row.available }} + + {{ props.row.quantity }} @@ -416,14 +483,11 @@ async function changeState(value) { - - - + + + - + {{ t('ticket.summary.created') }} {{ t('ticket.summary.package') }} {{ t('ticket.summary.quantity') }} @@ -437,13 +501,15 @@ async function changeState(value) { + + - + - + {{ t('ticket.summary.quantity') }} {{ t('globals.description') }} {{ t('ticket.summary.price') }} @@ -464,12 +530,62 @@ async function changeState(value) { + + + + + + {{ t('ticket.summary.description') }} + {{ t('ticket.summary.created') }} + {{ t('ticket.summary.requester') }} + {{ t('ticket.summary.attender') }} + {{ t('ticket.summary.quantity') }} + {{ t('ticket.summary.price') }} + {{ t('ticket.summary.item') }} + {{ t('ticket.summary.ok') }} + + + + + {{ props.row.description }} + {{ toDate(props.row.created) }} + {{ props.row.requester?.user?.username }} + {{ props.row.atender?.user?.username }} + {{ props.row.quantity }} + {{ toCurrency(props.row.price) }} + + + {{ props.row.itemFk }} + + + + + + + {{ t('Accepted') }} + + + {{ t('Denied') }} + + + + + + + es: Create training course: Crear curso de formación diff --git a/src/pages/Worker/Card/WorkerMedical.vue b/src/pages/Worker/Card/WorkerMedical.vue index 6bca4ae85..fab1416c9 100644 --- a/src/pages/Worker/Card/WorkerMedical.vue +++ b/src/pages/Worker/Card/WorkerMedical.vue @@ -65,6 +65,18 @@ const columns = [ create: true, component: 'input', }, + { + align: 'right', + name: 'tableActions', + actions: [ + { + title: t('delete'), + icon: 'delete', + action: async (row) => await tableRef.value.CrudModelRef.remove([row]), + isPrimary: true, + }, + ], + }, ]; @@ -87,5 +99,6 @@ const columns = [ :right-search="false" :is-editable="true" :use-model="true" + :default-remove="false" /> 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/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 8fee52dd3..ed34e771d 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -139,6 +139,7 @@ onBeforeMount(async () => { + diff --git a/src/pages/Worker/Card/WorkerTimeControl.vue b/src/pages/Worker/Card/WorkerTimeControl.vue index fbfd4b28d..39fb536b6 100644 --- a/src/pages/Worker/Card/WorkerTimeControl.vue +++ b/src/pages/Worker/Card/WorkerTimeControl.vue @@ -326,16 +326,20 @@ const updateData = async () => { }; const getMailStates = async (date) => { + const url = `WorkerTimeControls/${route.params.id}/getMailStates`; + const month = date.getMonth() + 1; + const prevMonth = month == 1 ? 12 : month - 1; const params = { - month: date.getMonth() + 1, + month, year: date.getFullYear(), }; - const { data } = await axios.get( - `WorkerTimeControls/${route.params.id}/getMailStates`, - { params } - ); - workerTimeControlMails.value = data; + const curMonthStates = (await axios.get(url, { params })).data; + const prevMonthStates = ( + await axios.get(url, { params: { ...params, month: prevMonth } }) + ).data; + + workerTimeControlMails.value = curMonthStates.concat(prevMonthStates); }; const showWorkerTimeForm = (propValue, formType) => { diff --git a/src/pages/Worker/WorkerFilter.vue b/src/pages/Worker/WorkerFilter.vue index 765241341..dfb5659fe 100644 --- a/src/pages/Worker/WorkerFilter.vue +++ b/src/pages/Worker/WorkerFilter.vue @@ -7,7 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -const { t } = useI18n(); +const { t, te } = useI18n(); const props = defineProps({ dataKey: { type: String, @@ -16,6 +16,11 @@ const props = defineProps({ }); const departments = ref(); + +const getLocale = (label) => { + const globalLocale = `globals.params.${label}`; + return te(globalLocale) ? t(globalLocale) : t(`params.${label}`); +}; @@ -23,7 +28,7 @@ const departments = ref(); - {{ t(`params.${tag.label}`) }}: + {{ getLocale(tag.label) }}: {{ formatFn(tag.value) }} @@ -64,10 +69,7 @@ const departments = ref(); - - - - + + + + + + [ { align: 'left', @@ -39,13 +40,31 @@ const columns = computed(() => [ }, { align: 'left', - name: 'nickname', - label: t('tableColumns.name'), + name: 'firstName', + label: t('tableColumns.firstName'), isTitle: true, columnFilter: { name: 'firstName', }, }, + { + align: 'left', + name: 'lastName', + label: t('tableColumns.lastName'), + isTitle: true, + columnFilter: { + name: 'lastName', + }, + }, + { + align: 'left', + name: 'nickname', + label: t('tableColumns.userName'), + isTitle: true, + columnFilter: { + name: 'userName', + }, + }, { align: 'left', name: 'departmentFk', @@ -66,10 +85,17 @@ const columns = computed(() => [ label: t('tableColumns.email'), cardVisible: true, columnFilter: { - alias: 'mu', - inWhere: true, + name: 'email', + }, + }, + { + align: 'left', + name: 'extension', + label: t('tableColumns.extension'), + cardVisible: true, + columnFilter: { + name: 'extension', }, - hidden: true, }, { align: 'right', @@ -93,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; @@ -152,6 +184,7 @@ async function autofillBic(worker) { auto-load /> (bankEntitiesOptions = data)" auto-load @@ -180,7 +213,7 @@ async function autofillBic(worker) { default-mode="table" redirect="worker" :right-search="false" - auto-load + :order="['id DESC']" > @@ -319,7 +352,9 @@ async function autofillBic(worker) { > bankEntitiesOptions.push(data)" + @on-data-saved=" + (_, resp) => handleNewBankEntity(data, resp) + " /> diff --git a/src/pages/Worker/locale/en.yml b/src/pages/Worker/locale/en.yml index 96df37919..8276977fd 100644 --- a/src/pages/Worker/locale/en.yml +++ b/src/pages/Worker/locale/en.yml @@ -1,6 +1,12 @@ passwordRequirements: 'The password must have at least { length } length characters, {nAlpha} alphabetic characters, {nUpper} capital letters, {nDigits} digits and {nPunct} symbols (Ex: $%&.)\n' tableColumns: id: ID - name: Name + firstName: First name + lastName: Last Name + userName: User Name department: Department email: Email + fi: FI + SSN: SSN + extension: Extension +queue: Queue diff --git a/src/pages/Worker/locale/es.yml b/src/pages/Worker/locale/es.yml index 41812345f..9c7618bc3 100644 --- a/src/pages/Worker/locale/es.yml +++ b/src/pages/Worker/locale/es.yml @@ -6,6 +6,12 @@ External: Externo passwordRequirements: 'La contraseña debe tener al menos { length } caracteres de longitud, {nAlpha} caracteres alfabéticos, {nUpper} letras mayúsculas, {nDigits} dígitos y {nPunct} símbolos (Ej: $%&.)' tableColumns: id: ID - name: Nombre + firstName: Nombre + lastName: Apellidos + userName: Nombre de usuario department: Departamento email: Email + fi: NIF + SSN: NSS + extension: Extensión +queue: Cola diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 0ba2e640a..215c12f46 100644 --- a/src/pages/Zone/Card/ZoneEventExclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventExclusionForm.vue @@ -154,7 +154,7 @@ onMounted(() => { (stateStore.rightDrawer = false)); - - - - - - {{ t('globals.collapseMenu') }} - - - - - import { onMounted, ref, computed, watch, onUnmounted } from 'vue'; 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'; @@ -144,7 +144,8 @@ watch(storeData, async (val) => { }); const reFetch = async () => { - await arrayData.fetch({ append: false }); + const { data } = await arrayData.fetch({ append: false }); + nodes.value = data; }; onMounted(async () => { @@ -182,6 +183,16 @@ onUnmounted(() => { + + + + + import('src/pages/Entry/EntryLatestBuys.vue'), }, + { + path: 'stock-Bought', + name: 'EntryStockBought', + meta: { + title: 'reserves', + icon: 'deployed_code_history', + }, + component: () => import('src/pages/Entry/EntryStockBought.vue'), + }, + { + path: 'waste-recalc', + name: 'EntryWasteRecalc', + meta: { + title: 'wasteRecalc', + icon: 'compost', + }, + component: () => import('src/pages/Entry/EntryWasteRecalc.vue'), + }, ], }, { diff --git a/src/router/modules/route.js b/src/router/modules/route.js index 3c5c860cf..9a7b16df3 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -11,7 +11,14 @@ export default { component: RouterView, redirect: { name: 'RouteMain' }, menus: { - main: ['RouteList', 'RouteAutonomous', 'RouteRoadmap', 'CmrList', 'AgencyList'], + main: [ + 'RouteList', + 'RouteExtendedList', + 'RouteAutonomous', + 'RouteRoadmap', + 'CmrList', + 'AgencyList', + ], card: ['RouteBasicData', 'RouteTickets', 'RouteLog'], }, children: [ @@ -19,9 +26,6 @@ export default { path: '/route', name: 'RouteMain', component: () => import('src/components/common/VnSectionMain.vue'), - props: { - leftDrawer: false, - }, redirect: { name: 'RouteList' }, children: [ { @@ -33,6 +37,15 @@ export default { }, component: () => import('src/pages/Route/RouteList.vue'), }, + { + path: 'extended-list', + name: 'RouteExtendedList', + meta: { + title: 'RouteExtendedList', + icon: 'format_list_bulleted', + }, + component: () => import('src/pages/Route/RouteExtendedList.vue'), + }, { path: 'create', name: 'RouteCreate', @@ -78,7 +91,7 @@ export default { name: 'AgencyList', meta: { title: 'agencyList', - icon: 'view_list', + icon: 'list', }, component: () => import('src/pages/Route/Agency/AgencyList.vue'), diff --git a/src/router/modules/ticket.js b/src/router/modules/ticket.js index dcc238f95..6e407b88b 100644 --- a/src/router/modules/ticket.js +++ b/src/router/modules/ticket.js @@ -198,7 +198,7 @@ export default { name: 'TicketPackage', meta: { title: 'packages', - icon: 'vn:bin', + icon: 'vn:bucket', }, component: () => import('src/pages/Ticket/Card/TicketPackage.vue'), }, diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index b2716474b..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: [ @@ -168,6 +169,7 @@ export default { meta: { title: 'log', icon: 'vn:History', + acls: [{ model: 'WorkerLog', props: 'find', accessType: 'READ' }], }, component: () => import('src/pages/Worker/Card/WorkerLog.vue'), }, @@ -207,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/claim/claimNotes.spec.js b/test/cypress/integration/claim/claimNotes.spec.js index a4a493cda..d7a918db1 100644 --- a/test/cypress/integration/claim/claimNotes.spec.js +++ b/test/cypress/integration/claim/claimNotes.spec.js @@ -1,4 +1,6 @@ describe('ClaimNotes', () => { + const saveBtn = '.q-field__append > .q-btn > .q-btn__content > .q-icon'; + const firstNote = '.q-infinite-scroll :nth-child(1) > .q-card__section--vert'; beforeEach(() => { cy.login('developer'); cy.visit(`/#/claim/${2}/notes`); @@ -7,7 +9,7 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; cy.get('.q-textarea').type(message); - cy.get('.q-field__append > .q-btn > .q-btn__content > .q-icon').click(); //save - cy.get(':nth-child(1) > .q-card__section--vert').should('have.text', message); + cy.get(saveBtn).click(); + cy.get(firstNote).should('have.text', message); }); }); 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/cypress/integration/entry/myEntry.spec.js b/test/cypress/integration/entry/myEntry.spec.js index dca74dec2..4addec1c4 100644 --- a/test/cypress/integration/entry/myEntry.spec.js +++ b/test/cypress/integration/entry/myEntry.spec.js @@ -11,7 +11,7 @@ describe('EntryMy when is supplier', () => { it('should open buyLabel when is supplier', () => { cy.get( - '[to="/null/2"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon' + '[to="/null/3"] > .q-card > .column > .q-btn > .q-btn__content > .q-icon' ).click(); cy.get('.q-card__actions > .q-btn').click(); cy.window().its('open').should('be.called'); diff --git a/test/cypress/integration/entry/stockBought.spec.js b/test/cypress/integration/entry/stockBought.spec.js new file mode 100644 index 000000000..66e06b79e --- /dev/null +++ b/test/cypress/integration/entry/stockBought.spec.js @@ -0,0 +1,39 @@ +describe('EntryStockBought', () => { + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('buyer'); + cy.visit(`/#/entry/stock-Bought`); + }); + it('Should edit the reserved space', () => { + cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); + cy.get('input[name="reserve"]').type('10{enter}'); + cy.get('button[title="Save"]').click(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + it('Should add a new reserved space for buyerBoss', () => { + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('input[aria-label="Reserve"]').type('1'); + cy.get('input[aria-label="Date"]').eq(1).clear(); + cy.get('input[aria-label="Date"]').eq(1).type('01-01'); + cy.get('input[aria-label="Buyer"]').type('buyerboss{downarrow}{enter}'); + cy.get('.q-notification__message').should('have.text', 'Data created'); + }); + it('Should check detail for the buyer', () => { + cy.get(':nth-child(1) > .sticky > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('tBody > tr').eq(1).its('length').should('eq', 1); + }); + it('Should check detail for the buyerBoss and had no content', () => { + cy.get(':nth-child(2) > .sticky > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('.q-table__bottom.row.items-center.q-table__bottom--nodata').should( + 'have.text', + 'warningNo data available' + ); + }); + it('Should edit travel m3 and refresh', () => { + cy.get('.vn-row > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('input[aria-label="m3"]').clear(); + cy.get('input[aria-label="m3"]').type('60'); + cy.get('.q-mt-lg > .q-btn--standard > .q-btn__content > .block').click(); + cy.get('.vn-row > div > :nth-child(2)').should('have.text', '60'); + }); +}); diff --git a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js index 6d33dbc39..353c5805b 100644 --- a/test/cypress/integration/route/agency/agencyWorkCenter.spec.js +++ b/test/cypress/integration/route/agency/agencyWorkCenter.spec.js @@ -4,12 +4,12 @@ describe('AgencyWorkCenter', () => { cy.login('developer'); cy.visit(`/#/agency/11/workCenter`); }); + const createButton = '.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon'; + const workCenterCombobox = 'input[role="combobox"]'; it('assign workCenter', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get( - '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' - ).type('workCenterOne{enter}'); + cy.get(createButton).click(); + cy.get(workCenterCombobox).type('workCenterOne{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); }); @@ -22,12 +22,10 @@ describe('AgencyWorkCenter', () => { }); it('error on duplicate workCenter', () => { - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get( - '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' - ).type('workCenterOne{enter}'); + cy.get(createButton).click(); + cy.get(workCenterCombobox).type('workCenterOne{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); - cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.get(createButton).click(); cy.get( '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container' ).type('workCenterOne{enter}'); diff --git a/test/cypress/integration/route/routeList.spec.js b/test/cypress/integration/route/routeList.spec.js index c9d7147c2..8020d3ea9 100644 --- a/test/cypress/integration/route/routeList.spec.js +++ b/test/cypress/integration/route/routeList.spec.js @@ -2,7 +2,7 @@ describe('Route', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit(`/#/route/list`); + cy.visit(`/#/route/extended-list`); }); const getVnSelect = '> :nth-child(1) > .column > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'; diff --git a/test/cypress/integration/ticket/ticketDescriptor.spec.js b/test/cypress/integration/ticket/ticketDescriptor.spec.js index 8192b7c7c..0ba2723a2 100644 --- a/test/cypress/integration/ticket/ticketDescriptor.spec.js +++ b/test/cypress/integration/ticket/ticketDescriptor.spec.js @@ -1,7 +1,8 @@ /// describe('Ticket descriptor', () => { - const toCloneOpt = '[role="menu"] .q-list > :nth-child(5)'; - const setWeightOpt = '[role="menu"] .q-list > :nth-child(6)'; + const listItem = '[role="menu"] .q-list .q-item'; + const toCloneOpt = 'To clone ticket'; + const setWeightOpt = 'Set weight'; const warehouseValue = ':nth-child(1) > :nth-child(6) > .value > span'; const summaryHeader = '.summaryHeader > div'; const weight = 25; @@ -14,7 +15,7 @@ describe('Ticket descriptor', () => { it('should clone the ticket without warehouse', () => { cy.visit('/#/ticket/1/summary'); cy.openActionsDescriptor(); - cy.get(toCloneOpt).click(); + cy.contains(listItem, toCloneOpt).click(); cy.clickConfirm(); cy.get(warehouseValue).contains('Warehouse One'); cy.get(summaryHeader) @@ -28,7 +29,7 @@ describe('Ticket descriptor', () => { it('should set the weight of the ticket', () => { cy.visit('/#/ticket/10/summary'); cy.openActionsDescriptor(); - cy.get(setWeightOpt).click(); + cy.contains(listItem, setWeightOpt).click(); cy.intercept('POST', /\/api\/Tickets\/\d+\/setWeight/).as('weight'); cy.get('.q-dialog input').type(weight); cy.clickConfirm(); diff --git a/test/cypress/integration/vnComponent/UserPanel.spec.js b/test/cypress/integration/vnComponent/UserPanel.spec.js new file mode 100644 index 000000000..e83d07954 --- /dev/null +++ b/test/cypress/integration/vnComponent/UserPanel.spec.js @@ -0,0 +1,58 @@ +/// +describe('UserPanel', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit(`/#dashboard`); + cy.waitForElement('.q-page', 6000); + }); + + it('should notify when update user warehouse', () => { + const userWarehouse = + '.q-menu .q-gutter-xs > :nth-child(3) > .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native> .q-field__input'; + + // Abro el panel + cy.openUserPanel(); + + // Compruebo la opcion inicial + cy.get(userWarehouse).should('have.value', 'VNL').click(); + + // Actualizo la opción + getOption(3); + + //Compruebo la notificación + cy.get('.q-notification').should('be.visible'); + cy.get(userWarehouse).should('have.value', 'VNH'); + + //Restauro el valor + cy.get(userWarehouse).click(); + getOption(2); + }); + it('should notify when update user company', () => { + const userCompany = + '.q-menu .q-gutter-xs > :nth-child(2) > .q-field--float > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native> .q-field__input'; + + // Abro el panel + cy.openUserPanel(); + + // Compruebo la opcion inicial + cy.get(userCompany).should('have.value', 'Warehouse One').click(); + + //Actualizo la opción + getOption(2); + + //Compruebo la notificación + cy.get('.q-notification').should('be.visible'); + cy.get(userCompany).should('have.value', 'Warehouse Two'); + + //Restauro el valor + cy.get(userCompany).click(); + getOption(1); + }); +}); + +function getOption(index) { + cy.waitForElement('[role="listbox"]'); + const option = `[role="listbox"] .q-item:nth-child(${index})`; + cy.get(option).click(); +} diff --git a/test/cypress/integration/vnComponent/vnLocation.spec.js b/test/cypress/integration/vnComponent/vnLocation.spec.js index 1872d3591..c1b0cf929 100644 --- a/test/cypress/integration/vnComponent/vnLocation.spec.js +++ b/test/cypress/integration/vnComponent/vnLocation.spec.js @@ -3,25 +3,90 @@ describe('VnLocation', () => { const dialogInputs = '.q-dialog label input'; const createLocationButton = '.q-form > .q-card > .vn-row:nth-child(6) .--add-icon'; const inputLocation = '.q-form input[aria-label="Location"]'; + const createForm = { + prefix: '.q-dialog__inner > .column > #formModel > .q-card', + sufix: ' .q-field__inner > .q-field__control', + }; + describe('CreateFormDialog ', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/supplier/567/fiscal-data', { timeout: 7000 }); + cy.waitForElement('.q-card'); + cy.get(createLocationButton).click(); + }); + it('should filter provinces based on selected country', () => { + // Select a country + cy.selectOption( + `${createForm.prefix} > :nth-child(5) > .q-field:nth-child(5)> ${createForm.sufix}`, + 'Ecuador' + ); + // Verify that provinces are filtered + cy.get( + `${createForm.prefix} > :nth-child(5) > .q-field:nth-child(3)> ${createForm.sufix}` + ).should('have.length', 1); + + // Verify that towns are filtered + cy.get( + `${createForm.prefix} > :nth-child(4) > .q-field:nth-child(3)> ${createForm.sufix}` + ).should('have.length', 1); + }); + + it('should filter towns based on selected province', () => { + // Select a country + cy.selectOption( + `${createForm.prefix} > :nth-child(5) > .q-field:nth-child(3)> ${createForm.sufix}`, + 'Ecuador' + ); + // Verify that provinces are filtered + cy.get( + `${createForm.prefix} > :nth-child(5) > .q-field:nth-child(3)> ${createForm.sufix}` + ).should('have.length', 1); + + // Verify that towns are filtered + cy.get( + `${createForm.prefix} > :nth-child(4) > .q-field:nth-child(3)> ${createForm.sufix}` + ).should('have.length', 1); + }); + it('should pass selected country', () => { + // Select a country + const country = 'Ecuador'; + const province = 'Province five'; + cy.selectOption( + `${createForm.prefix} > :nth-child(5) > .q-field:nth-child(5)> ${createForm.sufix}`, + country + ); + cy.selectOption( + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix}`, + province + ); + cy.get( + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) > .q-icon` + ).click(); + cy.get( + `#q-portal--dialog--5 > .q-dialog > ${createForm.prefix} > .vn-row > .q-select > ${createForm.sufix} > :nth-child(1) input` + ).should('have.value', province); + }); + }); describe('Worker Create', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); cy.visit('/#/worker/create', { timeout: 5000 }); cy.waitForElement('.q-card'); + cy.get(inputLocation).click(); }); it('Show all options', function () { - cy.get(inputLocation).click(); cy.get(locationOptions).should('have.length.at.least', 5); }); it('input filter location as "al"', function () { - cy.get(inputLocation).click(); + // cy.get(inputLocation).click(); cy.get(inputLocation).clear(); cy.get(inputLocation).type('al'); cy.get(locationOptions).should('have.length.at.least', 4); }); it('input filter location as "ecuador"', function () { - cy.get(inputLocation).click(); + // cy.get(inputLocation).click(); cy.get(inputLocation).clear(); cy.get(inputLocation).type('ecuador'); cy.get(locationOptions).should('have.length.at.least', 1); @@ -63,13 +128,13 @@ describe('VnLocation', () => { cy.get(dialogInputs).eq(0).clear(); cy.get(dialogInputs).eq(0).type(postCode); cy.selectOption( - '.q-dialog__inner > .column > #formModel > .q-card > :nth-child(4) > .q-select > .q-field__inner > .q-field__control ', + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix}`, province ); cy.get('.q-mt-lg > .q-btn--standard').click(); - cy.get('.q-dialog__inner > .column > #formModel > .q-card').should( - 'not.exist' - ); + cy.get(`${createForm.prefix}`).should('not.exist'); + cy.waitForElement('.q-form'); + checkVnLocation(postCode, province); }); it('Create city', () => { @@ -79,19 +144,19 @@ describe('VnLocation', () => { cy.get(dialogInputs).eq(0).type(postCode); // city create button cy.get( - '.q-dialog__inner > .column > #formModel > .q-card > :nth-child(4) > .q-select > .q-field__inner > .q-field__control > :nth-child(2) > .q-icon' + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(2) > .q-icon` ).click(); - cy.selectOption('#q-portal--dialog--2 .q-select', 'one'); - cy.get('#q-portal--dialog--2 .q-input').type(province); - cy.get('#q-portal--dialog--2 .q-btn--standard').click(); + cy.selectOption('#q-portal--dialog--3 .q-select', 'one'); + cy.get('#q-portal--dialog--3 .q-input').type(province); + cy.get('#q-portal--dialog--3 .q-btn--standard').click(); cy.get('#q-portal--dialog--1 .q-btn--standard').click(); + cy.waitForElement('.q-form'); + checkVnLocation(postCode, province); }); function checkVnLocation(postCode, province) { - cy.get('.q-dialog__inner > .column > #formModel > .q-card').should( - 'not.exist' - ); + cy.get(`${createForm.prefix}`).should('not.exist'); cy.get('.q-form > .q-card > .vn-row:nth-child(6)') .find('input') .invoke('val') diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index f895d7bb3..8d48dc71a 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -256,3 +256,9 @@ Cypress.Commands.add('validateContent', (selector, expectedValue) => { Cypress.Commands.add('openActionsDescriptor', () => { cy.get('.header > :nth-child(3) > .q-btn__content > .q-icon').click(); }); + +Cypress.Commands.add('openUserPanel', () => { + cy.get( + '.column > .q-avatar > .q-avatar__content > .q-img > .q-img__container > .q-img__image' + ).click(); +}); 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__/components/common/VnChangePassword.spec.js b/test/vitest/__tests__/components/common/VnChangePassword.spec.js new file mode 100644 index 000000000..f5a967bb5 --- /dev/null +++ b/test/vitest/__tests__/components/common/VnChangePassword.spec.js @@ -0,0 +1,70 @@ +import { createWrapper, axios } from 'app/test/vitest/helper'; +import VnChangePassword from 'src/components/common/VnChangePassword.vue'; +import { vi, beforeEach, afterEach, beforeAll, describe, expect, it } from 'vitest'; +import { Notify } from 'quasar'; + +describe('VnSmsDialog', () => { + let vm; + + beforeAll(() => { + vi.spyOn(axios, 'get').mockResolvedValue({ + data: [], + }); + vm = createWrapper(VnChangePassword, { + propsData: { + submitFn: vi.fn(), + }, + }).vm; + }); + + beforeEach(() => { + Notify.create = vi.fn(); + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + it('should notify when new password is empty', async () => { + vm.passwords.newPassword = ''; + vm.passwords.repeatPassword = 'password'; + + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'You must enter a new password', + type: 'negative', + }) + ); + }); + + it("should notify when passwords don't match", async () => { + vm.passwords.newPassword = 'password1'; + vm.passwords.repeatPassword = 'password2'; + await vm.validate(); + expect(Notify.create).toHaveBeenCalledWith( + expect.objectContaining({ + message: `Passwords don't match`, + type: 'negative', + }) + ); + }); + + describe('if passwords match', () => { + it('should call submitFn and emit password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', undefined); + }); + + it('should call submitFn and emit password and old password', async () => { + vm.passwords.newPassword = 'password'; + vm.passwords.repeatPassword = 'password'; + vm.passwords.oldPassword = 'oldPassword'; + + await vm.validate(); + expect(vm.props.submitFn).toHaveBeenCalledWith('password', 'oldPassword'); + }); + }); +}); 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); + }); +});
{{ t('route.Select the starting date') }}
{{ t('Select the starting date') }}