diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d10b26e..88abb9ae0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,183 @@ +# Version 24.40 - 2024-10-02 + +### Added 🆕 + +- chore: refs #4074 admit several acls by:jorgep +- chore: refs #4074 drop workerCreate by:jorgep +- chore: refs #4074 fix tests by:jorgep +- chore: refs #4074 wip replace useRole for useAcl by:jorgep +- chore: refs #7155 remove console.log by:alexm +- chore: refs #7155 typo by:alexm +- chore: refs #7663 add test by:jorgep +- chore: refs #7663 create test wip by:jorgep +- chore: refs #7663 drop useless code (origin/7663-setWeight) by:jorgep +- chore: refs #7828 fix e2e by:jorgep +- feat(AccountBasicData): add twoFactorFk by:alexm +- feat: add max rule by:Javier Segarra +- feat: add shortcut add event in some subSections by:Javier Segarra +- feat: add shortcut more buttons (origin/add_shortcut_add_subSections) by:Javier Segarra +- feat: add tooltip CustomerNewCustomAgent by:Javier Segarra +- feat: apply color when today by:Javier Segarra +- feat: change label because its more natural by:Javier Segarra +- feat: change order by:Javier Segarra +- feat: change QBadge color by:Javier Segarra +- feat: change url CustomerList by:Javier Segarra +- feat: copy customer countryFk by:Javier Segarra +- feat: create VnSelectEnum and add in AccountBasicData and ClaimBasicData by:alexm +- feat: CustomerBalance by:Javier Segarra +- feat: CustomerConsumptionFilter by:Javier Segarra +- feat: customer consumption (origin/7830-customerDesplegables, 7830-customerDesplegables) by:alexm +- feat: CustomerCreateTicket by:Javier Segarra +- feat: CustomerCredit section by:Javier Segarra +- feat: CustomerGreuges by:Javier Segarra +- feat: CustomerSample to VnTable by:Javier Segarra +- feat: global handler (origin/fix_global_handler, fix_global_handler) by:alexm +- feat: goToSupplier by:Javier Segarra +- feat: handle newValue by:Javier Segarra +- feat: handle same multiple CP by:Javier Segarra +- feat: hide menus on small view (origin/hideMenu) by:jorgep +- feat: minor changes by:Javier Segarra +- feat: orderCreateDialog by:Javier Segarra +- feat: refs #4074 drop useless code by:jorgep +- feat: refs #4074 useAcl in vnSelectDialog by:jorgep +- feat: refs #6346 new wagon type section by:Jon +- feat: refs #7404 add m3 and fix detail by:pablone +- feat: refs #7404 add some style to the form and reorganize fields by:pablone +- feat: refs #7404 add travel m3 to reserves form by:pablone +- feat: refs #7404 style dynamic text color by:pablone +- feat: refs #7404 travel m3 form by:pablone +- feat: refs #7500 added VnImg to show files by:Jon +- feat: refs #7663 add setWeight menu opt (wip) by:jorgep +- feat: refs #7663 fine tunning by:jorgep +- feat: refs #7828 create axios instance which no manage errors (origin/7828-makeCorrectCalls) by:jorgep +- feat: refs #7828 useAcl & cherry pick mail data worker by:jorgep +- feat: remove cli warnings by:Javier Segarra +- feat: show preparation field by:Javier Segarra +- feat: stateGroupedFilter by:Javier Segarra +- feat: translations fixed by:jgallego +- feat(TravelList): add daysOnward by:alexm +- feat: travel m3 by:pablone +- feat: use disableInifiniteScroll property by:Javier Segarra +- feat: VnImg draggable by:Javier Segarra +- feat: vnLocation changes by:Javier Segarra +- feat: vnSelect exprBuilder by:Javier Segarra +- fix: refs #7404 remove some style by:pablone +- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone +- fix: refs #7404 translates and some minor style fixes by:pablone +- fix: styles by:Javier Segarra +- perf: improve style by:Javier Segarra + +### Changed 📦 + +- perf: CustomerBalance by:Javier Segarra +- perf: CustomerBasicData by:Javier Segarra +- perf: CustomerBasicData.salesPersonFk by:Javier Segarra +- perf: CustomerSummary by:Javier Segarra +- perf: customerSummaryTable by:Javier Segarra +- perf: disable card option by:Javier Segarra +- perf: i18n by:Javier Segarra +- perf: improve by:Javier Segarra +- perf: improve style by:Javier Segarra +- perf: imrpove exprBuilder by:Javier Segarra +- perf: minor comments by:Javier Segarra +- perf: refs #6346 previous changes by:Jon +- perf: sendEmail customerConsumption by:Javier Segarra +- perf: solve reload CardSummary component by:Javier Segarra +- perf: update CustommerDescriptor by:Javier Segarra +- refactor: refs #4074 accept array by:jorgep +- refactor: refs #4074 rollback by:jorgep +- refactor: refs #4074 use acl & drop useless roles by:jorgep +- refactor: refs #4074 useAcl in navigationStore & router by:jorgep +- refactor: refs #4074 use fn (origin/4074-useAcls) by:jorgep +- refactor: refs #4074 use VnTitle by:jorgep +- refactor: refs #6346 deleted front error checking by:Jon +- refactor: refs #6346 requested changes by:Jon +- refactor: refs #6346 wagons to VnTable by:Jon +- refactor: refs #7500 deleted useless code by:Jon +- refactor: refs #7500 refactor vnimg when storage is dms by:Jon +- refactor: refs #7828 wip by:jorgep + +### Fixed 🛠️ + +- chore: refs #4074 fix tests by:jorgep +- chore: refs #7828 fix e2e by:jorgep +- feat: refs #7404 add m3 and fix detail by:pablone +- feat: translations fixed by:jgallego +- fix: #5938 grouped filter by:Javier Segarra +- fix: #6943 fix customerSummaryTable by:Javier Segarra +- fix: #6943 show nickname salesPerson by:Javier Segarra +- fix: address-create i18n by:Javier Segarra +- fix: comments (origin/6943_fix_customer_module, 6943_fix_customer_module) by:Javier Segarra +- fix: CusomerSummary to Address by:Javier Segarra +- fix: CustomerAddress mobile by:Javier Segarra +- fix: CustomerBillingData by:Javier Segarra +- fix: Customerconsumption by:Javier Segarra +- fix: customer credit opinion by:alexm +- fix: CustomerCreditOpinion workerDescriptor by:Javier Segarra +- fix: CustomerDescriptorAccount by:Javier Segarra +- fix: CustomerDescriptor.bussinessTypeFk by:Javier Segarra +- fix: CustomerFilter by:Javier Segarra +- fix: CustomerGreuges by:Javier Segarra +- fix: CustomerMandates by:Javier Segarra +- fix: Customer module find salesPersons out of first get by:Javier Segarra +- fix: CustomerRecovery transalate label by:Javier Segarra +- fix: CustomerSamples by:Javier Segarra +- fix: customerSummaryToTicketList button by:Javier Segarra +- fix: CustomerWebPayment by:Javier Segarra +- fix: CustommerSummaryTable Proxy by:Javier Segarra +- fix: deleted code by:Jon +- fix: duplicate code by:alexm +- fix: emit:updateModelValue by:Javier Segarra +- fix: fixed wagon tests by:Jon +- fix: fix wagon list reload by:Jon +- fix: i18n en preparation label by:Javier Segarra +- fix: infiniteScroll by:Javier Segarra +- fix: isFullMovable checkbox by:Javier Segarra +- fix: merge conflicts by:Javier Segarra +- fix: merge in dev by:alexm +- fix: missing code by:Jon +- fix: Options VnSelect properties by:Javier Segarra +- fix: refs #4074 await to watch by:jorgep +- fix: refs #4074 drop wrong acl by:jorgep +- fix: refs #4074 workerCard data-key by:jorgep +- fix: refs #6346 fix list and create by:pablone +- fix: refs #6943 prevent null (origin/6943-warmfix-preventNull) by:jorgep +- fix: refs #7155 remove userParams in watcher (7155-travel_daysOnward) by:alexm +- fix: refs #7155 use chip-locale (origin/7155-travel_daysOnward_2, 7155-travel_daysOnward_2) by:alexm +- fix: refs #7404 remove console.log by:pablone +- fix: refs #7404 remove from test by:pablone +- fix: refs #7404 remove some style by:pablone +- fix: refs #7404 revert commit prevent production access by:pablone +- fix: refs #7404 style non center pop up (origin/7404-fixFront) by:pablone +- fix: refs #7404 translates and some minor style fixes by:pablone +- fix: refs #7500 fixed e2e test by:Jon +- fix: refs #7500 fixed showing images wrongly by:Jon +- fix: refs #7830 customer credit by:pablone +- fix: refs #7830 remove console.log by:pablone +- fix: remove console.log by:pablone +- fix: remove FetchData by:Javier Segarra +- fix: remove FIXME (origin/6943_fix_customerSummaryTable) by:Javier Segarra +- fix: remove print variable by:Javier Segarra +- fix: remove promise execution by:Javier Segarra +- fix: reset VnTable scroll properties by:Javier Segarra +- fix: rule by:Javier Segarra +- fix: solve conflicts from master to test by:Javier Segarra +- fix: split params (origin/warmfix-addSearchUrl) by:jorgep +- fix: state cell by:Javier Segarra +- fix: stop call back event hasMoreData by:Javier Segarra +- fix: styles by:Javier Segarra +- fix: SupplierFiscalData VnLocation (origin/fix_supplierFD_location) by:Javier Segarra +- fix: VnLocation test by:Javier Segarra +- fix(VnTable): header background-color by:alexm +- fix(VnTable): sanitizer value is defined by:carlossa +- fix: wagon reload (origin/FixWagonRedirect) by:Jon +- fix: workerDms filter workerFk by:alexm +- fix(WorkerList): add type email by:alexm +- Merge remote-tracking branch 'origin/7830-customerDesplegables' into 6943_fix_customerSummaryTable by:Javier Segarra +- refs #7155 scopeDays fix (origin/7155-scopeDays) by:carlossa +- revert: vnUSerLink change by:Javier Segarra +- test: fix test (7677_vnLocation_perf) by:Javier Segarra + # Version 24.38 - 2024-09-17 ### Added 🆕 diff --git a/package.json b/package.json index 92ae2dae6..866c00170 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/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 cf3a64bde..f0f833ebe 100644 --- a/src/components/CreateBankEntityForm.vue +++ b/src/components/CreateBankEntityForm.vue @@ -20,7 +20,7 @@ const customer = computed(() => state.get('customer')); const bankEntityFormData = reactive({ name: null, bic: null, - countryFk: customer?.value?.countryFk, + countryFk: customer.value?.countryFk, id: null, }); @@ -30,8 +30,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 a0881a1c2..50063741a 100644 --- a/src/components/CreateNewCityForm.vue +++ b/src/components/CreateNewCityForm.vue @@ -1,5 +1,5 @@ - (provincesOptions = data)" - auto-load - url="Provinces" - /> { - + diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index addd81c62..99cba5360 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -1,5 +1,225 @@ - + + + + + + + + + + + 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="true" + > + + + + {{ opt.name }} + + {{ opt.province.name }}, + {{ opt.province.country.name }} + + + + + + + onCityCreated(requestResponse, 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 + Country: País + Postcode: Código postal + diff --git a/src/components/CreateNewProvinceForm.vue b/src/components/CreateNewProvinceForm.vue index affa718e7..3d1cbe121 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,6 +40,11 @@ const onDataSaved = (dataSaved, requestResponse) => { (autonomiesOptions = data)" auto-load + :filter="{ + where: { + countryFk: $props.countryFk, + }, + }" url="Autonomies/location" /> { 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 01629dacb..05aca954c 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/VnTable/VnVisibleColumn.vue b/src/components/VnTable/VnVisibleColumn.vue index a4d15e627..2566ed0ea 100644 --- a/src/components/VnTable/VnVisibleColumn.vue +++ b/src/components/VnTable/VnVisibleColumn.vue @@ -135,7 +135,7 @@ onMounted(async () => { }); - + 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 2ffb525a4..6a497057e 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 2f816e6f5..71711a966 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 5831c94c1..3a297c99b 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 e6d1c3024..6a93e5553 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -12,14 +12,46 @@ 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') { + return obj[prop]; + } else if (typeof prop === 'function') { + return prop(obj); + } + return null; + }); + + const filteredParts = parts.filter( + (part) => part !== null && part !== undefined && part !== '' + ); + + return filteredParts.join(', '); +}; + const modelValue = ref( - props.location - ? `${props.location?.postcode} - ${props.location?.city}(${props.location?.province?.name}), ${props.location?.country?.name}` - : null + props.location ? formatLocation(props.location, locationProperties) : null ); + function showLabel(data) { - return `${data.code} - ${data.town}(${data.province}), ${data.country}`; + const dataProperties = [ + 'code', + (obj) => (obj.town ? `${obj.town}(${obj.province})` : null), + 'country', + ]; + return formatLocation(data, dataProperties); } + const handleModelValue = (data) => { emit('update:model-value', data); }; @@ -41,6 +73,7 @@ const handleModelValue = (data) => { v-bind="$attrs" clearable :emit-value="false" + :tooltip="t('Create new location')" > { } - + +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/VnLog.vue b/src/components/common/VnLog.vue index 0c937d1bf..8aa23ced0 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -406,6 +406,7 @@ watch( :skeleton="false" auto-load @on-fetch="setLogTree" + search-url="logs" > (stateStore.leftDrawer = $props.leftDrawer)); +onMounted( + () => (stateStore.leftDrawer = useQuasar().screen.gt.xs ? $props.leftDrawer : false) +); diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 8edeb91a6..2b4d7a8f9 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -38,6 +38,10 @@ const $props = defineProps({ type: [Array], default: () => [], }, + exprBuilder: { + type: Function, + default: null, + }, isClearable: { type: Boolean, default: true, @@ -183,6 +187,7 @@ async function fetchFilter(val) { }, {}); } else defaultWhere = { [key]: getVal(val) }; const where = { ...(val ? defaultWhere : {}), ...$props.where }; + $props.exprBuilder && Object.assign(where, $props.exprBuilder(key, val)); const fetchOptions = { where, include, limit }; if (fields) fetchOptions.fields = fields; if (sortBy) fetchOptions.order = sortBy; @@ -226,6 +231,8 @@ function nullishToTrue(value) { } const getVal = (val) => ($props.useLike ? { like: `%${val}%` } : val); + +defineExpose({ opts: myOptions }); diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue index 2474fb1ba..2a67f0be8 100644 --- a/src/components/common/VnSelectDialog.vue +++ b/src/components/common/VnSelectDialog.vue @@ -1,5 +1,5 @@ + + + + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index c8a89495d..5d6a00097 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/CatalogItem.vue b/src/components/ui/CatalogItem.vue index 175fcb657..e1f6a3246 100644 --- a/src/components/ui/CatalogItem.vue +++ b/src/components/ui/CatalogItem.vue @@ -31,7 +31,7 @@ const dialog = ref(null); - + - + 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 7094c036c..e2065e113 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/VnPaginate.vue b/src/components/ui/VnPaginate.vue index 4a2297a0c..1fc354de0 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -56,7 +56,7 @@ const props = defineProps({ }, offset: { type: Number, - default: 0, + default: undefined, }, skeleton: { type: Boolean, @@ -119,10 +119,9 @@ watch( ); watch( - () => [props.url, props.filter, props.userParams], - ([url, filter, userParams]) => mounted.value && fetch({ url, filter, userParams }) + () => [props.url, props.filter], + ([url, filter]) => mounted.value && fetch({ url, filter }) ); - const addFilter = async (filter, params) => { await arrayData.addFilter({ filter, params }); }; @@ -131,9 +130,8 @@ async function fetch(params) { useArrayData(props.dataKey, params); arrayData.reset(['filter.skip', 'skip']); await arrayData.fetch({ append: false }); - if (!store.hasMoreData) { - isLoading.value = false; - } + if (!store.hasMoreData) isLoading.value = false; + emit('onFetch', store.data); return store.data; } diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index f3b37a749..b637c9916 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/composables/useArrayData.js b/src/composables/useArrayData.js index 71818bcdd..7e83ddf7c 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -24,7 +24,8 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) { const params = JSON.parse(query[searchUrl]); const filter = params?.filter && JSON.parse(params?.filter ?? '{}'); delete params.filter; - store.userParams = { ...params, ...store.userParams }; + + store.userParams = { ...store.userParams, ...params }; store.userFilter = { ...filter, ...store.userFilter }; if (filter?.order) store.order = filter.order; } @@ -111,7 +112,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/composables/usePrintService.js b/src/composables/usePrintService.js index 68a009d7b..c6c212ad8 100644 --- a/src/composables/usePrintService.js +++ b/src/composables/usePrintService.js @@ -1,15 +1,18 @@ import { useSession } from './useSession'; import axios from 'axios'; import { useQuasar } from 'quasar'; +import { useI18n } from 'vue-i18n'; export function usePrintService() { const quasar = useQuasar(); + const { t } = useI18n(); + const { getTokenMultimedia } = useSession(); function sendEmail(path, params) { return axios.post(path, params).then(() => quasar.notify({ - message: 'Notification sent', + message: t('globals.notificationSent'), type: 'positive', icon: 'check', }) diff --git a/src/css/quasar.variables.scss b/src/css/quasar.variables.scss index aeac483be..9f7c62848 100644 --- a/src/css/quasar.variables.scss +++ b/src/css/quasar.variables.scss @@ -36,7 +36,6 @@ $color-font-secondary: #777; .bg-success { background-color: $positive; } - .bg-notice { background-color: $info; } diff --git a/src/i18n/locale/en.yml b/src/i18n/locale/en.yml index 2e2365f7b..b73395df2 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,8 @@ globals: clientsActionsMonitor: Clients and actions serial: Serial medical: Mutual + RouteExtendedList: Router + wasteRecalc: Waste recaclulate supplier: Supplier created: Created worker: Worker @@ -288,7 +292,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,12 +303,19 @@ globals: from: From To: To stateFk: State + departmentFk: Department + email: Email + SSN: SSN + fi: FI + changePass: Change password + deleteConfirmTitle: Delete selected elements errors: statusUnauthorized: Access denied statusInternalServerError: An internal server error has ocurred 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: @@ -495,6 +509,8 @@ ticket: warehouse: Warehouse customerCard: Customer card alias: Alias + ticketList: Ticket List + newOrder: New Order boxing: expedition: Expedition item: Item @@ -516,6 +532,7 @@ ticket: landed: Landed consigneePhone: Consignee phone consigneeMobile: Consignee mobile + consigneeAddress: Consignee address clientPhone: Client phone clientMobile: Client mobile consignee: Consignee @@ -545,6 +562,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 @@ -568,7 +590,6 @@ invoiceOut: client: Client company: Company customerCard: Customer card - ticketList: Ticket List summary: issued: Issued created: Created @@ -794,14 +815,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 @@ -858,34 +879,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 @@ -901,6 +895,7 @@ supplier: account: Account payMethod: Pay Method payDay: Pay Day + country: Country summary: responsible: Responsible notes: Notes @@ -999,6 +994,7 @@ travel: warehouseOut: Warehouse out totalEntries: Total entries totalEntriesTooltip: Total entries + daysOnward: Landed days onwards summary: confirmed: Confirmed entryId: Entry Id @@ -1074,7 +1070,7 @@ item: producer: Producer landed: Landed fixedPrice: - itemId: Item ID + itemFk: Item ID groupingPrice: Grouping price packingPrice: Packing price hasMinPrice: Has min price diff --git a/src/i18n/locale/es.yml b/src/i18n/locale/es.yml index 53b487e9f..2552c9549 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 @@ -162,6 +164,7 @@ globals: dms: Gestión documental entryCreate: Nueva entrada latestBuys: Últimas compras + reserves: Reservas tickets: Tickets ticketCreate: Nuevo ticket boxing: Encajado @@ -210,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 @@ -266,7 +270,7 @@ globals: tracking: Estados components: Componentes pictures: Fotos - packages: Bultos + packages: Embalajes ldap: LDAP samba: Samba twoFactor: Doble factor @@ -276,6 +280,7 @@ globals: clientsActionsMonitor: Clientes y acciones serial: Facturas por serie medical: Mutua + wasteRecalc: Recalcular mermas supplier: Proveedor created: Fecha creación worker: Trabajador @@ -291,7 +296,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 @@ -299,12 +307,19 @@ globals: from: Desde To: Hasta stateFk: Estado + departmentFk: Departamento + email: Correo + SSN: NSS + fi: NIF + changePass: Cambiar contraseña + deleteConfirmTitle: Eliminar los elementos seleccionados errors: statusUnauthorized: Acceso denegado statusInternalServerError: Ha ocurrido un error interno del servidor 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: @@ -485,7 +500,7 @@ ticket: tracking: Estados components: Componentes pictures: Fotos - packages: Bultos + packages: Embalajes list: nickname: Alias state: Estado @@ -503,6 +518,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 @@ -524,6 +541,7 @@ ticket: landed: Entregado consigneePhone: Tel. consignatario consigneeMobile: Móv. consignatario + consigneeAddress: Dir. consignatario clientPhone: Tel. cliente clientMobile: Móv. cliente consignee: Consignatario @@ -553,6 +571,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 @@ -855,21 +877,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 @@ -885,6 +892,7 @@ supplier: account: Cuenta payMethod: Método de pago payDay: Dia de pago + country: País summary: responsible: Responsable notes: Notas @@ -983,6 +991,7 @@ travel: warehouseOut: Alm.entrada totalEntries: ∑ totalEntriesTooltip: Entradas totales + daysOnward: Días de llegada en adelante summary: confirmed: Confirmado entryId: Id entrada @@ -1058,7 +1067,7 @@ item: producer: Productor landed: F. entrega fixedPrice: - itemId: ID Artículo + itemFk: ID Artículo groupingPrice: Precio grouping packingPrice: Precio packing hasMinPrice: Tiene precio mínimo diff --git a/src/pages/Account/AccountList.vue b/src/pages/Account/AccountList.vue index c0f47f452..92e65f98c 100644 --- a/src/pages/Account/AccountList.vue +++ b/src/pages/Account/AccountList.vue @@ -5,11 +5,14 @@ import VnTable from 'src/components/VnTable/VnTable.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import AccountSummary from './Card/AccountSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; - +import AccountFilter from './AccountFilter.vue'; +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', @@ -18,27 +21,31 @@ const columns = computed(() => [ isId: true, field: 'id', cardVisible: true, - columnFilter: { - component: 'select', - name: 'search', - attrs: { - url: 'VnUsers/preview', - fields: ['id', 'name'], - }, - }, }, { 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', columnField: { component: null, }, - columnFilter: { - inWhere: true, - }, cardVisible: true, create: true, }, @@ -50,9 +57,6 @@ const columns = computed(() => [ columnField: { component: null, }, - columnFilter: { - inWhere: true, - }, cardVisible: true, create: true, }, @@ -104,17 +108,25 @@ const exprBuilder = (param, value) => { :expr-builder="exprBuilder" :label="t('account.search')" :info="t('account.searchInfo')" + :filter="filter" /> - + + + + + diff --git a/src/pages/Account/Card/AccountCard.vue b/src/pages/Account/Card/AccountCard.vue index e13694da1..3756ea4f7 100644 --- a/src/pages/Account/Card/AccountCard.vue +++ b/src/pages/Account/Card/AccountCard.vue @@ -10,7 +10,7 @@ const { t } = useI18n(); + + {{ t('globals.changePass') }} + {{ t('account.card.actions.sync.name') }} - diff --git a/src/pages/Customer/Card/CustomerBalance.vue b/src/pages/Customer/Card/CustomerBalance.vue index 318aac69d..6f9d5ed07 100644 --- a/src/pages/Customer/Card/CustomerBalance.vue +++ b/src/pages/Customer/Card/CustomerBalance.vue @@ -16,7 +16,7 @@ import { useVnConfirm } from 'src/composables/useVnConfirm'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; -import VnFilter from 'src/components/VnTable/VnFilter.vue'; +import VnSelect from 'src/src/components/common/VnSelect.vue'; import CustomerNewPayment from 'src/pages/Customer/components/CustomerNewPayment.vue'; import InvoiceOutDescriptorProxy from 'src/pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; @@ -33,9 +33,9 @@ const stateStore = useStateStore(); const user = state.getUser(); const clientRisk = ref([]); +const companies = ref([]); const tableRef = ref(); const companyId = ref(user.value.companyFk); -const companyLastId = ref(user.value.companyFk); const balances = ref([]); const vnFilterRef = ref({}); const filter = computed(() => { @@ -45,33 +45,6 @@ const filter = computed(() => { }; }); -const companyFilterColumn = { - align: 'left', - name: 'companyId', - label: t('Company'), - component: 'select', - attrs: { - url: 'Companies', - optionLabel: 'code', - sortBy: 'code', - limit: 0, - }, - columnFilter: { - event: { - remove: () => (companyId.value = null), - 'update:modelValue': (newCompanyFk) => { - if (!newCompanyFk) return; - vnFilterRef.value.addFilter(newCompanyFk); - companyLastId.value = newCompanyFk; - }, - blur: () => - !companyId.value && - (companyId.value = companyLastId.value ?? user.value.companyFk), - }, - }, - visible: false, -}; - const columns = computed(() => [ { align: 'right', @@ -166,6 +139,11 @@ onBeforeMount(() => { }); async function getCurrentBalance(data) { + currentBalance.value[companyId.value] = { + amount: 0, + code: companies.value.find((c) => c.id === companyId.value)?.code, + }; + for (const balance of data) { currentBalance.value[balance.companyFk] = { code: balance.company.code, @@ -192,14 +170,21 @@ const showBalancePdf = ({ id }) => { (companies = data)" + > + + @@ -212,13 +197,15 @@ const showBalancePdf = ({ id }) => { - + :options="companies" + option-label="code" + option-value="id" + > diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index 32e9256ac..0ea911a56 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -16,6 +16,20 @@ const { t } = useI18n(); const businessTypes = ref([]); const contactChannels = ref([]); const title = ref(); +const handleSalesModelValue = (val) => ({ + or: [ + { id: val }, + { name: val }, + { nickname: { like: '%' + val + '%' } }, + { code: { like: `${val}%` } }, + ], +}); + +const exprBuilder = (param, value) => { + return { + and: [{ active: { neq: false } }, handleSalesModelValue(value)], + }; +}; 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/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 57fdf8a80..7a9591d68 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -190,6 +190,18 @@ const setData = (entity) => (data.value = useCardDescription(entity?.name, entit > {{ t('Go to user') }} + + {{ t('Go to supplier') }} + @@ -204,6 +216,7 @@ es: Customer ticket list: Listado de tickets del cliente Customer invoice out list: Listado de facturas del cliente Go to user: Ir al usuario + Go to supplier: Ir al proveedor Customer unpaid: Cliente impago Unpaid: Impagado unpaidDated: 'Fecha {dated}' diff --git a/src/pages/Customer/Card/CustomerFiscalData.vue b/src/pages/Customer/Card/CustomerFiscalData.vue index 37a8f370a..a0ea4c8f3 100644 --- a/src/pages/Customer/Card/CustomerFiscalData.vue +++ b/src/pages/Customer/Card/CustomerFiscalData.vue @@ -93,6 +93,7 @@ function handleLocation(data, location) { handleLocation(data, location)" @@ -134,15 +135,17 @@ function handleLocation(data, location) { - + - 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/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index 94e84a885..1c31941d0 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -13,6 +13,7 @@ import VnTitle from 'src/components/common/VnTitle.vue'; import VnRow from 'src/components/ui/VnRow.vue'; const route = useRoute(); const { t } = useI18n(); +const grafanaUrl = 'https://grafana.verdnatura.es'; const $props = defineProps({ id: { diff --git a/src/pages/Customer/Card/CustomerUnpaid.vue b/src/pages/Customer/Card/CustomerUnpaid.vue index b5e298742..2e9602c55 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 4d2d7ac17..e05dd062d 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 'src/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; } - + @@ -85,5 +82,4 @@ es: 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/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 82a22c1da..7234e69fc 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -1,5 +1,4 @@ - (provinces = data)" auto-load /> - (zones = data)" auto-load /> - (workers = data)" - auto-load - /> - + {{ t(`params.${tag.label}`) }}: @@ -65,15 +52,14 @@ const zones = ref(); - - - - + - - - - - @@ -135,25 +119,21 @@ const zones = ref(); - - - - - - + diff --git a/src/pages/Customer/CustomerList.vue b/src/pages/Customer/CustomerList.vue index 746d6a51b..e652fa0b5 100644 --- a/src/pages/Customer/CustomerList.vue +++ b/src/pages/Customer/CustomerList.vue @@ -8,10 +8,10 @@ import VnLocation from 'src/components/common/VnLocation.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import CustomerSummary from './Card/CustomerSummary.vue'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; - +import RightMenu from 'src/components/common/RightMenu.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; - import { toDate } from 'src/filters'; +import CustomerFilter from './CustomerFilter.vue'; const { t } = useI18n(); const router = useRouter(); @@ -397,6 +397,11 @@ function handleLocation(data, location) { :label="t('Search customer')" data-key="Customer" /> + + + + + diff --git a/src/pages/Customer/Defaulter/CustomerDefaulter.vue b/src/pages/Customer/Defaulter/CustomerDefaulter.vue index 53c4d607d..7ef317326 100644 --- a/src/pages/Customer/Defaulter/CustomerDefaulter.vue +++ b/src/pages/Customer/Defaulter/CustomerDefaulter.vue @@ -3,7 +3,6 @@ import { ref, computed } from 'vue'; import { useI18n } from 'vue-i18n'; import { useQuasar } from 'quasar'; import { toCurrency, toDate, dateRange } from 'filters/index'; -import CustomerNotificationsFilter from './CustomerDefaulterFilter.vue'; import CustomerBalanceDueTotal from './CustomerBalanceDueTotal.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; @@ -11,7 +10,6 @@ import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnInput from 'src/components/common/VnInput.vue'; import CustomerDefaulterAddObservation from './CustomerDefaulterAddObservation.vue'; import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; const { t } = useI18n(); @@ -192,11 +190,6 @@ function exprBuilder(param, value) { - - - - - diff --git a/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue b/src/pages/Customer/Defaulter/CustomerDefaulterAddObservation.vue index 65b6e30ac..4e8b27f24 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/CustomerAddressCreate.vue b/src/pages/Customer/components/CustomerAddressCreate.vue index aca4a346c..aa7fdee42 100644 --- a/src/pages/Customer/components/CustomerAddressCreate.vue +++ b/src/pages/Customer/components/CustomerAddressCreate.vue @@ -134,6 +134,7 @@ function handleLocation(data, location) { option-label="fiscalName" option-value="id" v-model="data.customsAgentFk" + :tooltip="t('Create a new expense')" > diff --git a/src/pages/Customer/components/CustomerAddressEdit.vue b/src/pages/Customer/components/CustomerAddressEdit.vue index ea039044e..fc16537c7 100644 --- a/src/pages/Customer/components/CustomerAddressEdit.vue +++ b/src/pages/Customer/components/CustomerAddressEdit.vue @@ -11,7 +11,7 @@ import VnRow from 'src/components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelectDialog from 'src/components/common/VnSelectDialog.vue'; -import CustomsNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; +import CustomerNewCustomsAgent from 'src/pages/Customer/components/CustomerNewCustomsAgent.vue'; const { t } = useI18n(); const route = useRoute(); @@ -226,9 +226,10 @@ function handleLocation(data, location) { option-label="fiscalName" option-value="id" v-model="data.customsAgentFk" + :tooltip="t('New customs agent')" > - + @@ -239,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 'src/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); - await $props.promise(); - } 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 44d0ef840..bf7e17a70 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/locale/es.yml b/src/pages/Customer/locale/es.yml index 4fcbe3fa2..451784425 100644 --- a/src/pages/Customer/locale/es.yml +++ b/src/pages/Customer/locale/es.yml @@ -58,7 +58,7 @@ customer: vies: VIES payMethod: Método de pago bankAccount: Cuenta bancaria - dueDay: Día de pago + dueDay: Vencimiento hasLcr: Recibido LCR hasCoreVnl: Recibido core VNL hasB2BVnl: Recibido B2B VNL diff --git a/src/pages/Department/Card/DepartmentBasicData.vue b/src/pages/Department/Card/DepartmentBasicData.vue index 14a43d006..e861d46d9 100644 --- a/src/pages/Department/Card/DepartmentBasicData.vue +++ b/src/pages/Department/Card/DepartmentBasicData.vue @@ -1,5 +1,4 @@ - (workersOptions = data)" - auto-load - /> - (clientsOptions = data)" auto-load /> { > { - + { { { > - + {{ col.value }} diff --git a/src/pages/Entry/EntryLatestBuys.vue b/src/pages/Entry/EntryLatestBuys.vue index 1aeebb63e..46c9d404b 100644 --- a/src/pages/Entry/EntryLatestBuys.vue +++ b/src/pages/Entry/EntryLatestBuys.vue @@ -1,16 +1,18 @@ diff --git a/src/pages/Entry/EntryStockBought.vue b/src/pages/Entry/EntryStockBought.vue index 5e6940f90..0313c8234 100644 --- a/src/pages/Entry/EntryStockBought.vue +++ b/src/pages/Entry/EntryStockBought.vue @@ -45,19 +45,19 @@ const columns = [ optionValue: 'id', useLike: false, }, + columnFilter: false, }, { - align: 'left', + align: 'center', label: t('Reserve'), name: 'reserve', columnFilter: false, create: true, component: 'number', summation: true, - cardVisible: true, }, { - align: 'left', + align: 'center', label: t('Bought'), name: 'bought', summation: true, @@ -77,7 +77,7 @@ const columns = [ name: 'tableActions', actions: [ { - title: t('More'), + title: t('View more details'), icon: 'search', isPrimary: true, action: (row) => { @@ -101,14 +101,9 @@ const travel = ref(null); const userParams = ref({ dated: Date.vnNew(), }); + const filter = ref({ - where: { - shipped: (userParams.value.dated - ? new Date(userParams.value.dated) - : Date.vnNew() - ).setHours(0, 0, 0, 0), - m3: { neq: null }, - }, + fields: ['id', 'm3', 'warehouseInFk'], include: [ { relation: 'warehouseIn', @@ -117,6 +112,13 @@ const filter = ref({ }, }, ], + where: { + shipped: (userParams.value.dated + ? new Date(userParams.value.dated) + : Date.vnNew() + ).setHours(0, 0, 0, 0), + m3: { neq: null }, + }, }); const setUserParams = async ({ dated }) => { @@ -128,6 +130,22 @@ const setUserParams = async ({ dated }) => { function openDialog() { travelDialogRef.value = true; } + +function setFooter(data) { + const footer = { + bought: 0, + reserve: 0, + }; + data.forEach((row) => { + footer.bought += row?.bought; + footer.reserve += row?.reserve; + }); + tableRef.value.footer = footer; +} + +function round(value) { + return Math.round(value * 100) / 100; +} @@ -139,20 +157,22 @@ function openDialog() { :filter="filter" @on-fetch=" (data) => { - travel = data.filter((data) => data.warehouseIn.code === 'VNH'); + travel = data.find( + (data) => data.warehouseIn?.code.toLowerCase() === 'vnh' + ); } " /> - {{ t('Booked trucks') }}: + {{ t('Purchase Spaces') }}: - {{ travel[0]?.m3 }} + {{ travel?.m3 }} @@ -192,42 +212,79 @@ function openDialog() { /> - - - - - {{ row?.worker?.user?.name }} - - - - - + + + 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) }} + + + + + - en: @@ -240,12 +297,12 @@ en: es: Edit travel: Editar envío Travel: Envíos - Booked trucks: Camiones reservados + Purchase Spaces: Espacios de compra Buyer: Comprador Reserve: Reservado Bought: Comprado - More: Más 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 index 08e68866f..0d3161f20 100644 --- a/src/pages/Entry/EntryStockBoughtDetail.vue +++ b/src/pages/Entry/EntryStockBoughtDetail.vue @@ -22,7 +22,7 @@ const customUrl = `StockBoughts/getStockBoughtDetail?workerFk=${$props.workerFk} const columns = [ { align: 'left', - label: 'Entry', + label: t('Entry'), name: 'entryFk', isTitle: true, isId: true, @@ -41,29 +41,33 @@ const columns = [ create: true, columnClass: 'expand', columnFilter: false, + cardVisible: true, }, { align: 'left', name: 'volume', label: t('Volume'), columnFilter: false, + cardVisible: true, }, { align: 'left', label: t('Packaging'), name: 'packagingFk', columnFilter: false, + cardVisible: true, }, { align: 'left', label: 'Packing', name: 'packing', columnFilter: false, + cardVisible: true, }, ]; - + - + {{ row?.entryFk }} - + {{ row?.itemName }} @@ -93,14 +97,18 @@ const columns = [ - @@ -110,4 +118,9 @@ es: Bought: Comprado More: Más Date: Fecha + Entry: Entrada + Item: Artículo + Name: Nombre + Volume: Volumen + Packaging: Embalage 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 39039ee47..5b8bc0c97 100644 --- a/src/pages/Entry/MyEntries.vue +++ b/src/pages/Entry/MyEntries.vue @@ -9,22 +9,27 @@ import VnTable from 'src/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 d1fb036ab..0ba24a618 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 75e1a9630..94aebb355 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/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue index b53c671bc..ef3f02882 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue @@ -9,7 +9,6 @@ import RefundInvoiceForm from 'src/components/RefundInvoiceForm.vue'; import SendEmailDialog from 'src/components/common/SendEmailDialog.vue'; import useNotify from 'src/composables/useNotify'; -import { useSession } from 'src/composables/useSession'; import { usePrintService } from 'src/composables/usePrintService'; import { useVnConfirm } from 'src/composables/useVnConfirm'; import { getUrl } from 'src/composables/getUrl'; @@ -30,8 +29,6 @@ const $props = defineProps({ const { notify } = useNotify(); const router = useRouter(); -const session = useSession(); -const token = session.getToken(); const { t } = useI18n(); const { openReport, sendEmail } = usePrintService(); const { openConfirmationModal } = useVnConfirm(); 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/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index 59c367c90..5ff511494 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -15,25 +15,10 @@ const props = defineProps({ required: true, }, }); - -const workers = ref(); -const workersCopy = ref(); const states = ref(); - -function setWorkers(data) { - workers.value = data; - workersCopy.value = data; -} - (states = data)" auto-load /> - diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 3b020c0c9..14477c677 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -13,6 +13,8 @@ import { toCurrency, toDate } from 'src/filters/index'; import { useStateStore } from 'stores/useStateStore'; import { QBtn } from 'quasar'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; +import RightMenu from 'src/components/common/RightMenu.vue'; +import InvoiceOutFilter from './InvoiceOutFilter.vue'; const { t } = useI18n(); const stateStore = useStateStore(); @@ -27,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', @@ -179,6 +184,11 @@ watchEffect(selectedRows); :label="t('searchInvoice')" data-key="invoiceOut" /> + + + + + [ cardVisible: true, attrs: { url: 'Clients', - fields: ['id', 'name'], + optionLabel: 'socialName', + optionValue: 'socialName', }, columnField: { component: null, @@ -168,7 +170,6 @@ const downloadCSV = async () => { } } " - :limit="0" :columns="columns" auto-load :is-editable="false" @@ -192,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/Card/ItemTags.vue b/src/pages/Item/Card/ItemTags.vue index afc1d5cc3..a23626ca2 100644 --- a/src/pages/Item/Card/ItemTags.vue +++ b/src/pages/Item/Card/ItemTags.vue @@ -171,7 +171,6 @@ const insertTag = (rows) => { { stateStore.rightDrawer = true; params.warehouseFk = user.value.warehouseFk; }); onUnmounted(() => (stateStore.rightDrawer = false)); + +const defaultColumnAttrs = { + align: 'left', + sortable: true, +}; const columns = computed(() => [ { - label: t('item.fixedPrice.itemId'), - name: 'itemId', + label: t('item.fixedPrice.itemFk'), + name: 'itemFk', ...defaultColumnAttrs, isId: true, cardVisible: true, @@ -426,7 +426,7 @@ function handleOnDataSave({ CrudModelRef }) { :default-save="false" data-key="ItemFixedPrices" url="FixedPrices/filter" - :order="['itemFk ASC']" + :order="['description DESC']" save-url="FixedPrices/crud" :user-params="{ warehouseFk: user.warehouseFk }" ref="tableRef" @@ -460,7 +460,7 @@ function handleOnDataSave({ CrudModelRef }) { - + - + (stateStore.rightDrawer = false)); diff --git a/src/pages/Item/ItemTypeCreate.vue b/src/pages/Item/ItemTypeCreate.vue index ae935a9ca..dc5c8db57 100644 --- a/src/pages/Item/ItemTypeCreate.vue +++ b/src/pages/Item/ItemTypeCreate.vue @@ -15,7 +15,6 @@ const router = useRouter(); const newItemTypeForm = reactive({}); -const workersOptions = ref([]); const categoriesOptions = ref([]); const temperaturesOptions = ref([]); @@ -25,12 +24,6 @@ const redirectToItemTypeBasicData = (_, { id }) => { - (workersOptions = data)" - :filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }" - auto-load - /> (categoriesOptions = data)" @@ -61,7 +54,9 @@ const redirectToItemTypeBasicData = (_, { id }) => { - (workersOptions = data)" - :filter="{ order: 'firstName ASC', fields: ['id', 'firstName'] }" - auto-load - /> (categoriesOptions = data)" @@ -50,7 +43,9 @@ const temperaturesOptions = ref([]); [ diff --git a/src/pages/Monitor/MonitorOrders.vue b/src/pages/Monitor/MonitorOrders.vue index 512eed8e1..d4ad01466 100644 --- a/src/pages/Monitor/MonitorOrders.vue +++ b/src/pages/Monitor/MonitorOrders.vue @@ -132,7 +132,6 @@ const openTab = (id) => :table="{ 'row-key': 'id', selection: 'multiple', - 'hide-bottom': true, }" default-mode="table" :row-click="({ id }) => openTab(id)" @@ -140,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/Ticket/MonitorTickets.vue b/src/pages/Monitor/Ticket/MonitorTickets.vue index 396bed482..c710d22e4 100644 --- a/src/pages/Monitor/Ticket/MonitorTickets.vue +++ b/src/pages/Monitor/Ticket/MonitorTickets.vue @@ -25,7 +25,11 @@ const stateOpts = ref([]); const zoneOpts = ref([]); const visibleColumns = ref([]); const { viewSummary } = useSummaryDialog(); -const [from, to] = dateRange(Date.vnNew()); +const from = Date.vnNew(); +from.setHours(0, 0, 0, 0); +const to = new Date(from.getTime()); +to.setDate(to.getDate() + 1); +to.setHours(23, 59, 59, 999); function exprBuilder(param, value) { switch (param) { @@ -54,7 +58,6 @@ const columns = computed(() => [ label: t('salesTicketsTable.problems'), name: 'totalProblems', align: 'left', - columnFilter: false, attrs: { dense: true, @@ -65,7 +68,6 @@ const columns = computed(() => [ name: 'id', field: 'id', align: 'left', - columnFilter: { component: 'number', name: 'id', @@ -108,9 +110,7 @@ const columns = computed(() => [ { label: t('salesTicketsTable.date'), name: 'shippedDate', - style: { 'max-width': '100px' }, align: 'left', - columnFilter: { component: 'date', name: 'shippedDate', @@ -164,7 +164,6 @@ const columns = computed(() => [ label: t('salesTicketsTable.state'), name: 'state', align: 'left', - style: { 'max-width': '100px' }, columnFilter: { component: 'select', name: 'stateFk', @@ -193,7 +192,6 @@ const columns = computed(() => [ label: t('salesTicketsTable.zone'), name: 'zoneFk', align: 'left', - columnFilter: { component: 'select', name: 'zoneFk', @@ -210,8 +208,6 @@ const columns = computed(() => [ name: 'totalWithVat', field: 'totalWithVat', align: 'left', - style: { 'max-width': '75px' }, - columnFilter: { component: 'number', name: 'totalWithVat', @@ -370,7 +366,7 @@ const openTab = (id) => - + > {{ $t('salesTicketsTable.tooLittle') }} - + - - - {{ row.id }} - - - + + {{ row.id }} + + - - {{ row.nickname }} + + - + - + - + - - - {{ formatShippedDate(row.shippedDate) }} - - + + {{ formatShippedDate(row.shippedDate) }} + - - - + - + {{ row.refFk }} @@ -477,32 +467,33 @@ const openTab = (id) => > {{ row.state }} - + - - - {{ $t('salesTicketsTable.isFragile') }} - - + + {{ $t('salesTicketsTable.isFragile') }} + - + {{ row.zoneName }} - + - - - {{ toCurrency(row.totalWithVat) }} - - + + {{ toCurrency(row.totalWithVat) }} + + 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/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index 53f81f722..858a7dcc0 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -130,4 +130,5 @@ function extractValueTags(items) { es: You can search items by name or id: Puedes buscar items por nombre o id + Search items: Buscar items diff --git a/src/pages/Order/Card/OrderCatalogFilter.vue b/src/pages/Order/Card/OrderCatalogFilter.vue index c1e1cb78f..e2b139013 100644 --- a/src/pages/Order/Card/OrderCatalogFilter.vue +++ b/src/pages/Order/Card/OrderCatalogFilter.vue @@ -247,6 +247,7 @@ function addOrder(value, field, params) { rounded emit-value use-input + sort-by="name ASC" :disable="!selectedCategoryFk" @update:model-value=" (value) => { diff --git a/src/pages/Order/Card/OrderCatalogItemDialog.vue b/src/pages/Order/Card/OrderCatalogItemDialog.vue index 2065e6a41..6a9b53e45 100644 --- a/src/pages/Order/Card/OrderCatalogItemDialog.vue +++ b/src/pages/Order/Card/OrderCatalogItemDialog.vue @@ -20,8 +20,10 @@ const props = defineProps({ const fields = ref((props.prices || []).map((item) => ({ ...item, quantity: 0 }))); const descriptorData = useArrayData('orderData'); - +const isLoading = ref(false); const addToOrder = async () => { + if (isLoading.value) return; + isLoading.value = true; const items = (fields.value || []).filter((item) => Number(item.quantity) > 0); await axios.post('/OrderRows/addToOrder', { items, @@ -30,6 +32,10 @@ const addToOrder = async () => { notify(t('globals.dataSaved'), 'positive'); emit('added'); descriptorData.fetch({}); + isLoading.value = false; +}; +const canAddToOrder = () => { + return (fields.value || []).some((item) => Number(item.quantity) > 0); }; @@ -68,7 +74,11 @@ const addToOrder = async () => { - + {{ t('globals.add') }} @@ -81,3 +91,10 @@ const addToOrder = async () => { width: 200px; } + + +es: + noAmount: La cantidad no puede ser 0 +en: + noAmount: Amount can not be 0 + diff --git a/src/pages/Order/Card/OrderCreateDialog.vue b/src/pages/Order/Card/OrderCreateDialog.vue index 33d5a0ef5..a479ca2c7 100644 --- a/src/pages/Order/Card/OrderCreateDialog.vue +++ b/src/pages/Order/Card/OrderCreateDialog.vue @@ -1,5 +1,5 @@ - (clientOptions = data)" - :filter="{ fields: ['id', 'name', 'defaultAddressFk'], order: 'id' }" - auto-load - /> { diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue index 5ee8df971..6ac0625bc 100644 --- a/src/pages/Order/Card/OrderFilter.vue +++ b/src/pages/Order/Card/OrderFilter.vue @@ -17,10 +17,6 @@ const props = defineProps({ const agencyFilter = { fields: ['id', 'name'] }; const agencyList = ref(null); -const salesPersonFilter = { - fields: ['id', 'nickname'], -}; -const salesPersonList = ref(null); const sourceList = ref([]); @@ -32,14 +28,6 @@ const sourceList = ref([]); auto-load @on-fetch="(data) => (agencyList = data)" /> - (salesPersonList = data)" - :params="{ departmentCodes: ['VT'] }" - auto-load - /> (orderSummary.vat = data)" auto-load /> - + - + diff --git a/src/pages/Order/OrderList.vue b/src/pages/Order/OrderList.vue index d96a33ef5..6b6b41828 100644 --- a/src/pages/Order/OrderList.vue +++ b/src/pages/Order/OrderList.vue @@ -14,14 +14,15 @@ import OrderFilter from './Card/OrderFilter.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import { toDateTimeFormat } from 'src/filters/date'; +import { onMounted } from 'vue'; +import { useRoute } from 'vue-router'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); const agencyList = ref([]); const addressesList = ref([]); -const clientId = ref(); - +const route = useRoute(); const columns = computed(() => [ { 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 798402d06..88c1c8428 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 b1d9a46ca..79f247bb9 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 0b64f2542..562910fab 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 93016a2b8..683966290 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 b0c66819b..ce05fbb18 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/Shelving/Card/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue index 6ec673f63..b55cc4dec 100644 --- a/src/pages/Shelving/Card/ShelvingFilter.vue +++ b/src/pages/Shelving/Card/ShelvingFilter.vue @@ -1,7 +1,5 @@ - { :key="index" class="row q-gutter-md q-mb-md" > - + {{ t('components.iban_tooltip') }} @@ -113,6 +117,8 @@ const setWireTransfer = async () => { option-label="bic" option-value="id" hide-selected + :required="true" + :roles-allowed-to-create="['financial']" > + + + + + [ return data; }, }" + :right-search="false" order="id ASC" :columns="columns" auto-load diff --git a/src/pages/Ticket/Card/BasicData/BasicDataTable.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue similarity index 59% rename from src/pages/Ticket/Card/BasicData/BasicDataTable.vue rename to src/pages/Ticket/Card/BasicData/TicketBasicData.vue index 5f2647234..1685580ce 100644 --- a/src/pages/Ticket/Card/BasicData/BasicDataTable.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -4,11 +4,10 @@ import { useI18n } from 'vue-i18n'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import FetchedTags from 'src/components/ui/FetchedTags.vue'; -import RightMenu from 'src/components/common/RightMenu.vue'; import FetchData from 'src/components/FetchData.vue'; -import { useStateStore } from 'stores/useStateStore'; -import { toCurrency } from 'filters/index'; +import { useStateStore } from 'src/stores/useStateStore'; +import { toCurrency } from 'src/filters/index'; import { useRole } from 'src/composables/useRole'; const $props = defineProps({ @@ -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 d3a29e0bd..150312182 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicDataForm.vue @@ -12,6 +12,7 @@ import VnInputTime from 'src/components/common/VnInputTime.vue'; import axios from 'axios'; import useNotify from 'src/composables/useNotify.js'; +import { useValidator } from 'src/composables/useValidator'; import { toTimeFormat } from 'filters/date.js'; const $props = defineProps({ @@ -23,7 +24,7 @@ const $props = defineProps({ }); const emit = defineEmits(['updateForm']); - +const { validate } = useValidator(); const { notify } = useNotify(); const router = useRouter(); const { t } = useI18n(); @@ -51,18 +52,18 @@ const agencyByWarehouseFilter = computed(() => ({ }, })); -const zonesFilter = computed(() => ({ - fields: ['id', 'name'], - order: 'name ASC', - where: formData.value?.agencyModeFk - ? { - shipped: formData.value?.shipped, - addressFk: formData.value?.addressFk, - agencyModeFk: formData.value?.agencyModeFk, - warehouseFk: formData.value?.warehouseFk, - } - : {}, -})); +function zoneWhere() { + if (formData?.value?.agencyModeFk) { + return formData.value?.agencyModeFk + ? { + shipped: formData.value?.shipped, + addressFk: formData.value?.addressFk, + agencyModeFk: formData.value?.agencyModeFk, + warehouseFk: formData.value?.warehouseFk, + } + : {}; + } +} const getLanded = async (params) => { try { @@ -293,13 +294,6 @@ onMounted(() => 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 +328,7 @@ onMounted(() => onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.warehouse')" /> @@ -345,6 +341,7 @@ onMounted(() => onFormModelInit()); hide-selected map-options :required="true" + :rules="validate('basicData.address')" > @@ -392,6 +389,7 @@ onMounted(() => onFormModelInit()); :label="t('basicData.alias')" v-model="formData.nickname" :required="true" + :rules="validate('basicData.alias')" /> @@ -404,6 +402,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 +448,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 e3d393323..a26d5d979 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 'src/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 cabf2ebd9..43d2aa192 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 2d120bd2e..173ef40c1 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 'src/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 4b84db26a..81b942372 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -1,6 +1,6 @@ + + + + + + {{ 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 CSV') }} + {{ t('as CSV') }} @@ -220,21 +485,21 @@ function openConfirmDialog(callback) { v-ripple clickable > - {{ t('With prices') }} + {{ t('Send PDF') }} - {{ t('Without prices') }} + {{ t('Send PDF to tablet') }} - {{ t('As CSV') }} + {{ t('Send CSV') }} @@ -243,8 +508,26 @@ function openConfirmDialog(callback) { - {{ t('Open Proforma Invoice') }} + {{ t('Show Proforma') }} + + + + + {{ t('Change shipped hour') }} + + + + + + + + @@ -259,24 +542,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 +638,61 @@ 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 diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index dcdf828cf..6895966af 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -1,39 +1,35 @@ (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 1d2adb5a5..90fb210af 100644 --- a/src/pages/Ticket/Card/TicketPackage.vue +++ b/src/pages/Ticket/Card/TicketPackage.vue @@ -10,6 +10,7 @@ import FetchData from 'src/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 02ca5c3ec..bdd4030f6 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: diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index 2407a83a7..d50d08a35 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -16,11 +16,12 @@ 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 'src/composables/useArrayData'; import { useVnConfirm } from 'src/composables/useVnConfirm'; import useNotify from 'src/composables/useNotify.js'; import axios from 'axios'; +import VnTable from 'src/components/VnTable/VnTable.vue'; const route = useRoute(); const router = useRouter(); @@ -33,7 +34,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 +49,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 +161,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 +172,7 @@ const getSaleTotal = (sale) => { const resetChanges = async () => { arrayData.fetch({ append: false }); - getSales(); + tableRef.value.reload(); }; const updateQuantity = async (sale) => { @@ -210,6 +207,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 +257,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 +372,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 +380,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 +425,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 +533,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 +542,7 @@ onUnmounted(() => (stateStore.rightDrawer = false)); (stateStore.rightDrawer = false)); {{ t('Transfer lines') }} @@ -507,246 +569,222 @@ 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') }} + + - + - - - - {{ 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 +792,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 a5d924fdf..455167b6f 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 f52fa0d5f..5300f0234 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 07826e7c2..c46767fc8 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 e32d74fd6..89c0910c2 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -1,6 +1,7 @@ (editableStates = data)" + :filter="{ fields: ['code', 'name', 'id', 'alertLevel'], order: 'name ASC' }" auto-load + @on-fetch="(data) => (editableStates = data)" /> - - - - - {{ item.name }} - - - - + - + - - {{ entity.ticketState?.state?.name }} - + + {{ entity.ticketState.state.name }} + @@ -148,7 +154,14 @@ async function changeState(value) { :label="t('ticket.summary.agency')" :value="entity.agencyMode?.name" /> - + + + + {{ entity?.zone?.name }} + + + + - + + + + {{ entity.routeFk }} + + + + @@ -185,9 +205,9 @@ async function changeState(value) { :value="dashIfEmpty(entity.weight)" /> - + - - - - - + + + ({{ + t( + `ticketNotes.observationTypes.${item.observationType.description}` + ) + }}): + + + {{ item.description }} + + @@ -263,13 +293,14 @@ async function changeState(value) { /> @@ -292,11 +323,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') }} @@ -355,8 +384,8 @@ async function changeState(value) { {{ t('ticket.summary.itemShortage') }} @@ -365,8 +394,8 @@ async function changeState(value) { {{ t('ticket.summary.hasComponentLack') }} @@ -383,8 +412,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 }} @@ -414,14 +469,11 @@ async function changeState(value) { - - - + + + - + {{ t('ticket.summary.created') }} {{ t('ticket.summary.package') }} {{ t('ticket.summary.quantity') }} @@ -435,13 +487,15 @@ async function changeState(value) { + + - + - + {{ t('ticket.summary.quantity') }} {{ t('globals.description') }} {{ t('ticket.summary.price') }} @@ -462,12 +516,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 aaf170957..741086a4f 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/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 2a6b64e6a..3d0e334d9 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 d9e394b37..a658b5ca5 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 7de4344a4..09151c21b 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']" > @@ -214,6 +247,7 @@ async function autofillBic(worker) { @@ -262,6 +296,7 @@ async function autofillBic(worker) { handleLocation(data, location)" @@ -317,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/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 63e2cb85f..c0ea3cd14 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -64,6 +64,15 @@ const agencyOptions = ref([]); type="number" min="0" /> + + @@ -128,4 +137,5 @@ es: Bonus: Bonificación Inflation: Inflación Volumetric: Volumétrico + Max length m³: Medida máxima tumbado diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 516207302..ee10c2c9b 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 'src/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/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 073cbcf55..da242684c 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..7258881be 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -168,6 +168,7 @@ export default { meta: { title: 'log', icon: 'vn:History', + acls: [{ model: 'WorkerLog', props: 'find', accessType: 'READ' }], }, component: () => import('src/pages/Worker/Card/WorkerLog.vue'), }, diff --git a/test/cypress/integration/claim/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js index 3b73a24d9..eb39f340a 100755 --- a/test/cypress/integration/claim/claimDevelopment.spec.js +++ b/test/cypress/integration/claim/claimDevelopment.spec.js @@ -33,11 +33,12 @@ 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(); - cy.get(thirdRow).should('exist'); + cy.waitForElement(thirdRow); const rowData = [ false, 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/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 index b93afa520..66e06b79e 100644 --- a/test/cypress/integration/entry/stockBought.spec.js +++ b/test/cypress/integration/entry/stockBought.spec.js @@ -6,7 +6,6 @@ describe('EntryStockBought', () => { }); it('Should edit the reserved space', () => { cy.get('.q-field__native.q-placeholder').should('have.value', '01/01/2001'); - cy.get('tBody > tr').its('length').should('eq', 2); cy.get('input[name="reserve"]').type('10{enter}'); cy.get('button[title="Save"]').click(); cy.get('.q-notification__message').should('have.text', 'Data saved'); @@ -18,7 +17,6 @@ describe('EntryStockBought', () => { 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'); - cy.get('tBody > tr').its('length').should('eq', 3); }); it('Should check detail for the buyer', () => { cy.get(':nth-child(1) > .sticky > .q-btn > .q-btn__content > .q-icon').click(); 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 3533a3c1f..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); @@ -34,22 +99,25 @@ describe('VnLocation', () => { cy.visit('/#/supplier/567/fiscal-data', { timeout: 7000 }); cy.waitForElement('.q-form'); }); - it('Fin by postalCode', () => { + it('Find by postalCode', () => { const postCode = '46600'; + const postCodeLabel = '46600, Valencia(Province one), España'; const firstOption = '[role="listbox"] .q-item:nth-child(1)'; cy.get(inputLocation).click(); cy.get(inputLocation).clear(); cy.get(inputLocation).type(postCode); - cy.get(locationOptions).should('have.length.at.least', 2); + cy.get(locationOptions) + .get(':nth-child(1)') + .should('have.length.at.least', 2); + cy.get( + firstOption.concat(' > .q-item__section > .q-item__label--caption') + ).should('have.text', postCodeLabel); cy.get(firstOption).click(); cy.get('.q-btn-group > .q-btn--standard > .q-btn__content > .q-icon').click(); cy.reload(); cy.waitForElement('.q-form'); - cy.get(inputLocation).should( - 'have.value', - '46600 - Valencia(Province one), España' - ); + cy.get(inputLocation).should('have.value', postCodeLabel); }); it('Create postCode', () => { @@ -60,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', () => { @@ -76,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/integration/wagon/wagonCreate.spec.js b/test/cypress/integration/wagon/wagonCreate.spec.js index 9aff3a819..cd248d1bb 100644 --- a/test/cypress/integration/wagon/wagonCreate.spec.js +++ b/test/cypress/integration/wagon/wagonCreate.spec.js @@ -11,7 +11,9 @@ describe('WagonCreate', () => { cy.get('input').eq(1).type('1234ABCD'); cy.get('input').eq(2).type('100'); cy.get('input').eq(3).click(); - cy.get('div[role="listbox"]').find('div.q-item').click(); + cy.get('.q-select > .q-field__inner > .q-field__control').type( + '{downarrow}{enter}' + ); // Save cy.get('button[type="submit"]').click(); @@ -19,12 +21,22 @@ describe('WagonCreate', () => { // Check data has been saved successfully cy.waitForElement('.q-card'); - cy.get('.title').should('have.text', '1234'); - cy.get('[title-label="Plate"] > .value > span').should('have.text', '1234ABCD'); - cy.get(':nth-child(2) > .value > span').should('have.text', '100'); - cy.get(':nth-child(3) > .value > span').should('have.text', 'Wagon Type #1'); + cy.get( + '[to="/null/1"] > .q-card > .no-padding > .q-py-none > .cursor-text' + ).should('have.text', '1234'); + cy.get( + '[to="/null/1"] > .q-card > .no-padding > .q-pr-lg > :nth-child(1) > .vn-label-value > .value > :nth-child(1) > .row > span' + ).should('have.text', '1234ABCD'); + cy.get( + '[to="/null/1"] > .q-card > .no-padding > .q-pr-lg > :nth-child(2) > .vn-label-value > .value > :nth-child(1) > .row > span' + ).should('have.text', '100'); + cy.get( + '[to="/null/1"] > .q-card > .no-padding > .q-pr-lg > :nth-child(3) > .vn-label-value > .value > :nth-child(1) > .row > span' + ).should('have.text', 'Wagon Type #1'); // Delete wagon type created - cy.get('.actions > .q-btn--standard').click(); + cy.get( + '[to="/null/2"] > .q-card > .column > [title="Remove"] > .q-btn__content > .q-icon' + ).click(); }); }); diff --git a/test/cypress/integration/wagonType/wagonTypeCreate.spec.js b/test/cypress/integration/wagonType/wagonTypeCreate.spec.js index cd7ffa58f..0ad98e597 100644 --- a/test/cypress/integration/wagonType/wagonTypeCreate.spec.js +++ b/test/cypress/integration/wagonType/wagonTypeCreate.spec.js @@ -13,7 +13,7 @@ describe('WagonTypeCreate', () => { }); it('delete a wagon type', () => { cy.get( - ':nth-child(2) > :nth-child(1) > .card-list-body > .actions > .q-btn--standard' + '[to="/null/2"] > .q-card > .column > [title="Remove"] > .q-btn__content > .q-icon' ).click(); }); }); diff --git a/test/cypress/integration/wagonType/wagonTypeEdit.spec.js b/test/cypress/integration/wagonType/wagonTypeEdit.spec.js index 6e5816e51..36dd83411 100644 --- a/test/cypress/integration/wagonType/wagonTypeEdit.spec.js +++ b/test/cypress/integration/wagonType/wagonTypeEdit.spec.js @@ -4,7 +4,7 @@ describe('WagonTypeEdit', () => { beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); - cy.visit('/#/wagon/type/2/edit'); + cy.visit('/#/wagon/type/1/edit'); }); it('should edit the name and the divisible field of the wagon type', () => { @@ -14,14 +14,14 @@ describe('WagonTypeEdit', () => { cy.get('.q-btn--standard').click(); }); - it('should create a tray', () => { - cy.get('.action-button > .q-btn > .q-btn__content > .q-icon').click(); - cy.get('input').last().type('150'); - cy.get(trayColorRow).type('{downArrow}{downArrow}{enter}'); - }); - it('should delete a tray', () => { cy.get('.action-button > .q-btn > .q-btn__content > .q-icon').first().click(); cy.reload(); }); + + it('should create a tray', () => { + cy.get('.action-button > .q-btn > .q-btn__content > .q-icon').last().click(); + cy.get('input').last().type('150'); + cy.get(trayColorRow).type('{downArrow}{downArrow}{downArrow}{enter}'); + }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index a9a405313..83f45b721 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -140,6 +140,7 @@ Cypress.Commands.add('removeCard', () => { }); Cypress.Commands.add('addCard', () => { cy.waitForElement('tbody'); + cy.waitForElement('.q-page-sticky > div > .q-btn'); cy.get('.q-page-sticky > div > .q-btn').click(); }); Cypress.Commands.add('clickConfirm', () => { @@ -247,3 +248,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__/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'); + }); + }); +});
{{ t('route.Select the starting date') }}
{{ t('Select the starting date') }}