diff --git a/CHANGELOG.md b/CHANGELOG.md index 03812d252..e110e4cd6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,87 @@ +# Version 24.50 - 2024-12-10 + +### Added 🆕 + +- feat: add reportFileName option by:Javier Segarra +- feat: all clients just with global series by:jgallego +- feat: improve Merge branch 'test' into dev by:Javier Segarra +- feat: manual invoice in two lines by:jgallego +- feat: manualInvoice with address by:jgallego +- feat: randomize functions and example by:Javier Segarra +- feat: refs #6999 added search when user tabs on a filter with value by:Jon +- feat: refs #6999 added tab to search in VnTable filter by:Jon +- feat: refs #7346 #7346 improve form by:Javier Segarra +- feat: refs #7346 address ordered by:jgallego +- feat: refs #7346 radioButton by:jgallego +- feat: refs #7346 style radioButton by:jgallego +- feat: refs #7346 traducciones en cammelCase (7346-manualInvoice) by:jgallego +- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon +- feat: refs #8061 #8061 updates by:Javier Segarra +- feat: refs #8087 reactive data by:jorgep +- feat: refs #8087 refs#8087 Redadas en travel by:Carlos Andrés +- feat: refs #8138 add component ticket problems by:pablone +- feat: refs #8163 add max length and more tests by:wbuezas +- feat: refs #8163 add prop by:wbuezas +- feat: refs #8163 add VnInput insert functionality and e2e test by:wbuezas +- feat: refs #8163 limit with maxLength by:Javier Segarra +- feat: refs #8163 maxLength SupplierFD account by:Javier Segarra +- feat: refs #8163 maxLengthVnInput by:Javier Segarra +- feat: refs #8163 use VnAccountNumber in VnAccountNumber by:Javier Segarra +- feat: refs #8166 show notification by:jorgep + +### Changed 📦 + +- feat: refs #8038 added new functionality in VnSelect and refactor styles by:Jon +- perf: add dataCy by:Javier Segarra +- perf: refs #7346 #7346 Imrpove interface dialog by:Javier Segarra +- perf: refs #7346 #7346 use v-show instead v-if by:Javier Segarra +- perf: refs #8036 currentFilter by:alexm +- perf: refs #8061 filter autonomy by:Javier Segarra +- perf: refs #8061 solve conflicts and random posCode it by:Javier Segarra +- perf: refs #8061 use opts from VnSelect by:Javier Segarra +- perf: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra +- perf: remove console by:Javier Segarra +- perf: remove timeout by:Javier Segarra +- perf: test command fillInForm by:Javier Segarra +- refactor: refs #8162 remove comment by:wbuezas +- refactor: remove unnecesary things by:wbuezas + +### Fixed 🛠️ + +- fix: #8016 fetching data by:Javier Segarra +- fix: icons by:jgallego +- fix: refs #7229 download file by:jorgep +- fix: refs #7229 remove catch by:jorgep +- fix: refs #7229 set url by:jorgep +- fix: refs #7229 test by:jorgep +- fix: refs #7229 url by:jorgep +- fix: refs #7229 url + test by:jorgep +- fix: refs #7304 7304 clean warning by:carlossa +- fix: refs #7304 fix list by:carlossa +- fix: refs #7304 fix warning by:carlossa +- fix: refs #7346 traslations by:jgallego +- fix: refs #7529 add save by:carlossa +- fix: refs #7529 fix e2e by:carlossa +- fix: refs #7529 fix front by:carlossa +- fix: refs #7529 fix scss by:carlossa +- fix: refs #7529 fix te2e by:carlossa +- fix: refs #7529 fix workerPit e2e by:carlossa +- fix: refs #7529 front by:carlossa +- fix: refs #8036 apply exprBuilder after save filters by:alexm +- fix: refs #8036 only add where when required by:alexm +- fix: refs #8038 solve conflicts by:Jon +- fix: refs #8061 improve code dependencies (origin/8061_improve_newCP) by:Javier Segarra +- fix: refs #8138 move component from ui folder by:pablone +- fix: refs #8138 sme minor issues by:pablone +- fix: refs #8163 #8061 createNewPostCodeForm by:Javier Segarra +- fix: refs #8163 minor problem when keypress by:Javier Segarra +- fix: refs #8166 show zone error by:jorgep +- fix: removed selectedClient by:jgallego +- refs #7529 fix workerPit by:carlossa +- revert: refs #8061 test #8061 updates by:Javier Segarra +- test: fix own test by:Javier Segarra +- test: refs #8162 #8162 fix TicketList spec by:Javier Segarra + # Version 24.48 - 2024-11-25 ### Added 🆕 diff --git a/Jenkinsfile b/Jenkinsfile index 1766e3aea..c20da8ab2 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -4,7 +4,8 @@ def PROTECTED_BRANCH def BRANCH_ENV = [ test: 'test', - master: 'production' + master: 'production', + beta: 'production' ] node { @@ -15,7 +16,8 @@ node { PROTECTED_BRANCH = [ 'dev', 'test', - 'master' + 'master', + 'beta' ].contains(env.BRANCH_NAME) // https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables diff --git a/package.json b/package.json index 04b75a0b0..39d49519b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "24.50.0", + "version": "24.52.0", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -64,4 +64,4 @@ "vite": "^5.1.4", "vitest": "^0.31.1" } -} \ No newline at end of file +} diff --git a/src/boot/mainShortcutMixin.js b/src/boot/mainShortcutMixin.js new file mode 100644 index 000000000..481077e37 --- /dev/null +++ b/src/boot/mainShortcutMixin.js @@ -0,0 +1,36 @@ +import routes from 'src/router/modules'; +import { useRouter } from 'vue-router'; + +let isNotified = false; + +export default { + created: function () { + const router = useRouter(); + const keyBindingMap = routes + .filter((route) => route.meta.keyBinding) + .reduce((map, route) => { + map['Key' + route.meta.keyBinding.toUpperCase()] = route.path; + return map; + }, {}); + + const handleKeyDown = (event) => { + const { ctrlKey, altKey, code } = event; + + if (ctrlKey && altKey && keyBindingMap[code] && !isNotified) { + event.preventDefault(); + router.push(keyBindingMap[code]); + isNotified = true; + } + }; + + const handleKeyUp = (event) => { + const { ctrlKey, altKey } = event; + if (!ctrlKey || !altKey) { + isNotified = false; + } + }; + + window.addEventListener('keydown', handleKeyDown); + window.addEventListener('keyup', handleKeyUp); + }, +}; diff --git a/src/boot/qformMixin.js b/src/boot/qformMixin.js index fc7852369..8a75e1af7 100644 --- a/src/boot/qformMixin.js +++ b/src/boot/qformMixin.js @@ -1,30 +1,52 @@ -import { getCurrentInstance } from 'vue'; - +function focusFirstInput(input) { + input.focus(); + return; +} export default { mounted: function () { - const vm = getCurrentInstance(); - if (vm.type.name === 'QForm') { - if (!['searchbarForm', 'filterPanelForm'].includes(this.$el?.id)) { - // TODO: AUTOFOCUS IS NOT FOCUSING - const that = this; - this.$el.addEventListener('keyup', function (evt) { - if (evt.key === 'Enter') { - const input = evt.target; - if (input.type == 'textarea' && evt.shiftKey) { - evt.preventDefault(); - let { selectionStart, selectionEnd } = input; - input.value = - input.value.substring(0, selectionStart) + - '\n' + - input.value.substring(selectionEnd); - selectionStart = selectionEnd = selectionStart + 1; - return; - } - evt.preventDefault(); - that.onSubmit(); - } - }); + const that = this; + + const form = document.querySelector('.q-form#formModel'); + if (!form) return; + try { + const inputsFormCard = form.querySelectorAll( + `input:not([disabled]):not([type="checkbox"])` + ); + if (inputsFormCard.length) { + focusFirstInput(inputsFormCard[0]); } + const textareas = document.querySelectorAll( + 'textarea:not([disabled]), [contenteditable]:not([disabled])' + ); + if (textareas.length) { + focusFirstInput(textareas[textareas.length - 1]); + } + const inputs = document.querySelectorAll( + 'form#formModel input:not([disabled]):not([type="checkbox"])' + ); + const input = inputs[0]; + if (!input) return; + + focusFirstInput(input); + } catch (error) { + console.error(error); } + form.addEventListener('keyup', function (evt) { + if (evt.key === 'Enter') { + const input = evt.target; + if (input.type == 'textarea' && evt.shiftKey) { + evt.preventDefault(); + let { selectionStart, selectionEnd } = input; + input.value = + input.value.substring(0, selectionStart) + + '\n' + + input.value.substring(selectionEnd); + selectionStart = selectionEnd = selectionStart + 1; + return; + } + evt.preventDefault(); + that.onSubmit(); + } + }); }, }; diff --git a/src/boot/quasar.js b/src/boot/quasar.js index 01fe68d8b..547517682 100644 --- a/src/boot/quasar.js +++ b/src/boot/quasar.js @@ -1,15 +1,18 @@ +import axios from 'axios'; import { boot } from 'quasar/wrappers'; import qFormMixin from './qformMixin'; import keyShortcut from './keyShortcut'; -import useNotify from 'src/composables/useNotify.js'; -import { CanceledError } from 'axios'; - -const { notify } = useNotify(); +import { QForm } from 'quasar'; +import { QLayout } from 'quasar'; +import mainShortcutMixin from './mainShortcutMixin'; +import { useCau } from 'src/composables/useCau'; export default boot(({ app }) => { - app.mixin(qFormMixin); + QForm.mixins = [qFormMixin]; + QLayout.mixins = [mainShortcutMixin]; + app.directive('shortcut', keyShortcut); - app.config.errorHandler = (error) => { + app.config.errorHandler = async (error) => { let message; const response = error.response; const responseData = response?.data; @@ -40,12 +43,12 @@ export default boot(({ app }) => { } console.error(error); - if (error instanceof CanceledError) { + if (error instanceof axios.CanceledError) { const env = process.env.NODE_ENV; if (env && env !== 'development') return; message = 'Duplicate request'; } - notify(message ?? 'globals.error', 'negative', 'error'); + await useCau(response, message); }; }); diff --git a/src/components/CreateNewCityForm.vue b/src/components/CreateNewCityForm.vue index 1cbba42fc..bf3830522 100644 --- a/src/components/CreateNewCityForm.vue +++ b/src/components/CreateNewCityForm.vue @@ -40,6 +40,7 @@ const onDataSaved = (...args) => { url-create="towns" model="city" @on-data-saved="onDataSaved" + data-cy="newCityForm" > @@ -48,12 +49,14 @@ const onDataSaved = (...args) => { v-model="data.name" :rules="validate('city.name')" required + data-cy="cityName" /> diff --git a/src/components/CreateNewPostcodeForm.vue b/src/components/CreateNewPostcodeForm.vue index 232f86a49..c656fcb2f 100644 --- a/src/components/CreateNewPostcodeForm.vue +++ b/src/components/CreateNewPostcodeForm.vue @@ -1,5 +1,5 @@ { :rules="validate('postcode.code')" clearable required + data-cy="locationPostcode" /> setTown(value, data)" + @filter="filterTowns" :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" required + data-cy="locationTown" > @@ -171,8 +192,12 @@ const whereTowns = computed(() => { :country-fk="data.countryFk" :province-selected="data.provinceFk" @update:model-value="(value) => setProvince(value, data)" + @update:options=" + (data) => { + provincesOptions = data; + } + " v-model="data.provinceFk" - @on-province-fetched="handleProvinces" @on-province-created="onProvinceCreated" required /> @@ -191,6 +216,7 @@ const whereTowns = computed(() => { v-model="data.countryFk" :rules="validate('postcode.countryFk')" @update:model-value="(value) => setCountry(value, data)" + data-cy="locationCountry" /> diff --git a/src/components/CreateNewProvinceForm.vue b/src/components/CreateNewProvinceForm.vue index 887ca957c..d35690eeb 100644 --- a/src/components/CreateNewProvinceForm.vue +++ b/src/components/CreateNewProvinceForm.vue @@ -53,8 +53,10 @@ const where = computed(() => { v-model="data.name" :rules="validate('province.name')" required + data-cy="provinceName" /> { hide-selected option-label="label" v-model="selectedField" + data-cy="field-to-edit" /> diff --git a/src/components/FormModel.vue b/src/components/FormModel.vue index bfb470c48..2cb6ad8e6 100644 --- a/src/components/FormModel.vue +++ b/src/components/FormModel.vue @@ -110,6 +110,7 @@ const originalData = ref({}); const formData = computed(() => state.get(modelValue)); const defaultButtons = computed(() => ({ save: { + dataCy: 'saveDefaultBtn', color: 'primary', icon: 'save', label: 'globals.save', @@ -117,6 +118,7 @@ const defaultButtons = computed(() => ({ type: 'submit', }, reset: { + dataCy: 'resetDefaultBtn', color: 'primary', icon: 'restart_alt', label: 'globals.reset', @@ -207,7 +209,9 @@ async function save() { isLoading.value = true; try { formData.value = trimData(formData.value); - const body = $props.mapper ? $props.mapper(formData.value) : formData.value; + const body = $props.mapper + ? $props.mapper(formData.value, originalData.value) + : formData.value; const method = $props.urlCreate ? 'post' : 'patch'; const url = $props.urlCreate || $props.urlUpdate || $props.url || arrayData.store.url; @@ -322,6 +326,7 @@ defineExpose({ :title="t(defaultButtons.reset.label)" /> diff --git a/src/components/ItemsFilterPanel.vue b/src/components/ItemsFilterPanel.vue index 405577095..084feb377 100644 --- a/src/components/ItemsFilterPanel.vue +++ b/src/components/ItemsFilterPanel.vue @@ -9,6 +9,8 @@ import VnSelect from 'components/common/VnSelect.vue'; import VnFilterPanelChip from 'components/ui/VnFilterPanelChip.vue'; import axios from 'axios'; +import { getParamWhere } from 'src/filters'; +import { useRoute } from 'vue-router'; const { t } = useI18n(); const props = defineProps({ @@ -26,28 +28,21 @@ const props = defineProps({ }, }); -const itemCategories = ref([]); -const selectedCategoryFk = ref(null); -const selectedTypeFk = ref(null); +const route = useRoute(); + const itemTypesOptions = ref([]); const suppliersOptions = ref([]); const tagOptions = ref([]); const tagValues = ref([]); +const categoryList = ref(null); +const selectedCategoryFk = ref(getParamWhere(route.query.table, 'categoryFk', false)); +const selectedTypeFk = ref(getParamWhere(route.query.table, 'typeFk', false)); -const categoryList = computed(() => { - return (itemCategories.value || []) - .filter((category) => category.display) - .map((category) => ({ - ...category, - icon: `vn:${(category.icon || '').split('-')[1]}`, - })); -}); - -const selectedCategory = computed(() => - (itemCategories.value || []).find( +const selectedCategory = computed(() => { + return (categoryList.value || []).find( (category) => category?.id === selectedCategoryFk.value - ) -); + ); +}); const selectedType = computed(() => { return (itemTypesOptions.value || []).find( @@ -87,7 +82,7 @@ const applyTags = (params, search) => { search(); }; -const fetchItemTypes = async (id) => { +const fetchItemTypes = async (id = selectedCategoryFk.value) => { const filter = { fields: ['id', 'name', 'categoryFk'], where: { categoryFk: id }, @@ -126,15 +121,19 @@ const removeTag = (index, params, search) => { (tagValues.value || []).splice(index, 1); applyTags(params, search); }; +const setCategoryList = (data) => { + categoryList.value = (data || []) + .filter((category) => category.display) + .map((category) => ({ + ...category, + icon: `vn:${(category.icon || '').split('-')[1]}`, + })); + fetchItemTypes(); +}; - (itemCategories = data)" - /> + diff --git a/src/components/VnSelectProvince.vue b/src/components/VnSelectProvince.vue index bfc4ab7b7..d73ee964e 100644 --- a/src/components/VnSelectProvince.vue +++ b/src/components/VnSelectProvince.vue @@ -7,7 +7,7 @@ import VnSelectDialog from 'components/common/VnSelectDialog.vue'; import FetchData from 'components/FetchData.vue'; import CreateNewProvinceForm from './CreateNewProvinceForm.vue'; -const emit = defineEmits(['onProvinceCreated', 'onProvinceFetched']); +const emit = defineEmits(['onProvinceCreated', 'onProvinceFetched', 'update:options']); const $props = defineProps({ countryFk: { type: Number, @@ -41,6 +41,7 @@ async function onProvinceCreated(_, data) { } async function handleProvinces(data) { provincesOptions.value = data; + emit('update:options', data); } watch( @@ -64,11 +65,11 @@ watch( auto-load /> +import { toDateFormat } from 'src/filters/date.js'; + +defineProps({ date: { type: [Date, String], required: true } }); + +function getBadgeAttrs(date) { + let today = Date.vnNew(); + today.setHours(0, 0, 0, 0); + let timeTicket = new Date(date); + timeTicket.setHours(0, 0, 0, 0); + + let timeDiff = today - timeTicket; + + if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' }; + if (timeDiff < 0) return { color: 'success', 'text-color': 'black' }; + return { color: 'transparent', 'text-color': 'white' }; +} + +function formatShippedDate(date) { + if (!date) return '-'; + const dateSplit = date.split('T'); + const [year, month, day] = dateSplit[0].split('-'); + const newDate = new Date(year, month - 1, day); + return toDateFormat(newDate); +} + + + + {{ formatShippedDate(date) }} + + diff --git a/src/components/common/VnLocation.vue b/src/components/common/VnLocation.vue index 6de402a26..a8840f243 100644 --- a/src/components/common/VnLocation.vue +++ b/src/components/common/VnLocation.vue @@ -2,7 +2,7 @@ import CreateNewPostcode from 'src/components/CreateNewPostcodeForm.vue'; import VnSelectDialog from 'components/common/VnSelectDialog.vue'; import { useI18n } from 'vue-i18n'; -import { ref } from 'vue'; +import { computed } from 'vue'; import { useAttrs } from 'vue'; import { useRequired } from 'src/composables/useRequired'; const { t } = useI18n(); @@ -43,7 +43,7 @@ const formatLocation = (obj, properties) => { return filteredParts.join(', '); }; -const modelValue = ref( +const modelValue = computed(() => props.location ? formatLocation(props.location, locationProperties) : null ); @@ -75,7 +75,6 @@ const handleModelValue = (data) => { :input-debounce="300" :class="{ required: isRequired }" v-bind="$attrs" - clearable :emit-value="false" :tooltip="t('Create new location')" :rules="mixinRules" diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 8c71c0997..9eca3c711 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -238,6 +238,7 @@ async function openPointRecord(id, modelLog) { pointRecord.value = parseProps(propNames, locale, data); } async function setLogTree(data) { + if (!data) return; logTree.value = getLogTree(data); } diff --git a/src/components/common/VnSelect.vue b/src/components/common/VnSelect.vue index 571faad64..e5ac05231 100644 --- a/src/components/common/VnSelect.vue +++ b/src/components/common/VnSelect.vue @@ -8,7 +8,14 @@ import dataByOrder from 'src/utils/dataByOrder'; const emit = defineEmits(['update:modelValue', 'update:options', 'remove']); const $attrs = useAttrs(); const { t } = useI18n(); -const { isRequired, requiredFieldRule } = useRequired($attrs); + +const isRequired = computed(() => { + return useRequired($attrs).isRequired; +}); +const requiredFieldRule = computed(() => { + return useRequired($attrs).requiredFieldRule; +}); + const $props = defineProps({ modelValue: { type: [String, Number, Object], @@ -261,7 +268,7 @@ async function onScroll({ to, direction, from, index }) { defineExpose({ opts: myOptions }); function handleKeyDown(event) { - if (event.key === 'Tab') { + if (event.key === 'Tab' && !event.shiftKey) { event.preventDefault(); const inputValue = vnSelectRef.value?.inputValue; @@ -279,6 +286,17 @@ function handleKeyDown(event) { } vnSelectRef.value?.hidePopup(); } + + const focusableElements = document.querySelectorAll( + 'a, button, input, textarea, select, details, [tabindex]:not([tabindex="-1"])' + ); + const currentIndex = Array.prototype.indexOf.call( + focusableElements, + event.target + ); + if (currentIndex >= 0 && currentIndex < focusableElements.length - 1) { + focusableElements[currentIndex + 1].focus(); + } } } @@ -308,9 +326,9 @@ function handleKeyDown(event) { @virtual-scroll="onScroll" :data-cy="$attrs.dataCy ?? $attrs.label + '_select'" > - + { @@ -323,7 +341,22 @@ function handleKeyDown(event) { /> - + + { + value = null; + emit('remove'); + } + " + class="cursor-pointer" + size="xs" + /> + + + diff --git a/src/components/common/VnSelectDialog.vue b/src/components/common/VnSelectDialog.vue index 350aa9272..12322c3fa 100644 --- a/src/components/common/VnSelectDialog.vue +++ b/src/components/common/VnSelectDialog.vue @@ -43,6 +43,7 @@ const isAllowedToCreate = computed(() => { > +import { computed, useAttrs } from 'vue'; +import VnSelect from 'components/common/VnSelect.vue'; +import VnAvatar from 'src/components/ui/VnAvatar.vue'; + +const emit = defineEmits(['update:modelValue']); +const $props = defineProps({ + hasAvatar: { + type: Boolean, + default: false, + }, + hasInfo: { + type: Boolean, + default: false, + }, + modelValue: { + type: [String, Number, Object], + default: null, + }, +}); + +const $attrs = useAttrs(); + +const value = computed({ + get() { + return $props.modelValue; + }, + set(val) { + emit('update:modelValue', val); + }, +}); + +const url = computed(() => { + let url = 'Workers/search'; + const { departmentCodes } = $attrs.params ?? {}; + if (!departmentCodes) return url; + const params = new URLSearchParams({ + departmentCodes: JSON.stringify(departmentCodes), + }); + + return url.concat(`?${params.toString()}`); +}); + + + + + + + + + + {{ $t($props.hasInfo) }} + + + + + + + {{ scope.opt.name }} + + + {{ scope.opt.nickname }} + + + {{ scope.opt.nickname }}, {{ scope.opt.code }} + + + + + + + + +es: + Responsible for approving invoices: Responsable de aprobar las facturas + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 83af77442..f4c0091d2 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -222,8 +222,8 @@ const toModule = computed(() => /> - - diff --git a/src/pages/Item/Card/ItemShelving.vue b/src/pages/Item/Card/ItemShelving.vue index 27e265e6b..7ad60c9e0 100644 --- a/src/pages/Item/Card/ItemShelving.vue +++ b/src/pages/Item/Card/ItemShelving.vue @@ -1,19 +1,15 @@ @@ -203,7 +152,7 @@ onMounted(async () => { { - - - - - - - - - - - - {{ row.longName }} + + + {{ row.longName }} - + - + diff --git a/src/pages/Item/Card/ItemSummary.vue b/src/pages/Item/Card/ItemSummary.vue index db90ba06f..e1b97d7c9 100644 --- a/src/pages/Item/Card/ItemSummary.vue +++ b/src/pages/Item/Card/ItemSummary.vue @@ -46,7 +46,7 @@ const getUrl = (id, param) => `#/Item/${id}/${param}`; `#/Item/${id}/${param}`; { itemTagsRef.value.formData[itemTagsRef.value.formData.length - 1].priority = getHighestPriority(rows); }; + +const submitTags = async (data) => { + itemTagsRef.value.onSubmit(data); +}; (tagOptions = data)" auto-load /> @@ -76,7 +80,6 @@ const insertTag = (rows) => { data-key="ItemTags" model="ItemTags" url="ItemTags" - update-url="Tags/onSubmit" :data-required="{ $index: undefined, itemFk: route.params.id, @@ -137,6 +140,7 @@ const insertTag = (rows) => { :required="false" :rules="validate('itemTag.tagFk')" :use-like="false" + sort-by="value" /> { v-model="row.priority" :required="true" :rules="validate('itemTag.priority')" + @keyup.enter.stop="submitTags(row)" /> { + + +es: + Tags can not be repeated: Las etiquetas no pueden repetirse + diff --git a/src/pages/Item/Card/ItemTax.vue b/src/pages/Item/Card/ItemTax.vue index 84b5f63f4..8060481f0 100644 --- a/src/pages/Item/Card/ItemTax.vue +++ b/src/pages/Item/Card/ItemTax.vue @@ -28,7 +28,7 @@ const taxesFilter = { ], }; -const ItemTaxRef = ref(null); +const ItemTaxRef = ref(); const taxesOptions = ref([]); const submitTaxes = async (data) => { @@ -36,7 +36,10 @@ const submitTaxes = async (data) => { id: tax.id, taxClassFk: tax.taxClassFk, })); - + if (payload.some((item) => item.taxClassFk === null)) { + notify(t('Tax class cannot be blank'), 'negative'); + return; + } await axios.post(`Items/updateTaxes`, payload); notify(t('globals.dataSaved'), 'positive'); }; diff --git a/src/pages/Item/ItemFixedPrice.vue b/src/pages/Item/ItemFixedPrice.vue index 4e57d18ca..d2cf89d65 100644 --- a/src/pages/Item/ItemFixedPrice.vue +++ b/src/pages/Item/ItemFixedPrice.vue @@ -1,5 +1,5 @@ + + + + + { auto-load :disable-option="{ card: true }" chip-locale="item.params" + :right-search="false" > @@ -306,30 +315,28 @@ onMounted(async () => { /> - - - - {{ row.response }} - - - - - {{ t('Discard') }} - - - + + + {{ row.response }} + + + + + {{ t('Discard') }} + + diff --git a/src/pages/Item/ItemRequestFilter.vue b/src/pages/Item/ItemRequestFilter.vue index 64bc0e575..ea1a6e760 100644 --- a/src/pages/Item/ItemRequestFilter.vue +++ b/src/pages/Item/ItemRequestFilter.vue @@ -1,5 +1,5 @@ @@ -145,33 +161,17 @@ const decrement = (paramsObj, key) => { - - - - - {{ scope.opt?.name }} - {{ scope.opt?.nickname }}, - {{ scope.opt?.code }} - - - - + /> diff --git a/src/pages/Item/ItemType/ItemTypeSearchbar.vue b/src/pages/Item/ItemType/ItemTypeSearchbar.vue index 87903a517..749033d43 100644 --- a/src/pages/Item/ItemType/ItemTypeSearchbar.vue +++ b/src/pages/Item/ItemType/ItemTypeSearchbar.vue @@ -10,7 +10,6 @@ const { t } = useI18n(); url="ItemTypes" :label="t('Search item type')" :info="t('Search itemType by id, name or code')" - search-url="table" /> diff --git a/src/pages/Item/ItemTypeList.vue b/src/pages/Item/ItemTypeList.vue index 149de482d..4cea931e2 100644 --- a/src/pages/Item/ItemTypeList.vue +++ b/src/pages/Item/ItemTypeList.vue @@ -6,6 +6,7 @@ import VnTable from 'components/VnTable/VnTable.vue'; import FetchData from 'components/FetchData.vue'; import RightMenu from 'src/components/common/RightMenu.vue'; import ItemTypeFilter from './ItemType/ItemTypeFilter.vue'; +import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; const { t } = useI18n(); const tableRef = ref(); @@ -31,13 +32,14 @@ const columns = computed(() => [ { align: 'left', name: 'name', - label: t('name'), + label: t('globals.name'), cardVisible: true, create: true, }, { align: 'left', label: t('worker'), + name: 'workerFk', create: true, component: 'select', attrs: { @@ -45,20 +47,20 @@ const columns = computed(() => [ optionLabel: 'nickname', optionValue: 'id', }, + format: (row) => row.worker?.user?.name, cardVisible: true, - visible: true, - columnField: { - component: 'userLink', - attrs: ({ row }) => { - return { - workerId: row?.worker?.id, - name: row.worker?.user?.name, - defaultName: true, - }; - }, - }, + columnField: { component: null }, columnFilter: { - name: 'workerFk', + attrs: { + url: 'Workers/activeWithInheritedRole', + fields: ['id', 'name'], + where: { role: 'buyer' }, + optionFilter: 'firstName', + optionLabel: 'name', + optionValue: 'id', + useLike: false, + }, + inWhere: true, }, }, { @@ -135,24 +137,27 @@ const columns = computed(() => [ :columns="columns" auto-load :right-search="false" - :is-editable="false" - :use-model="true" redirect="item/item-type" - /> + > + + + {{ row.worker?.user?.name }} + + + + es: id: Id code: Código - name: Nombre worker: Trabajador ItemCategory: Reino Temperature: Temperatura Create ItemTypes: Crear familia en: code: Code - name: Name worker: Worker ItemCategory: ItemCategory Temperature: Temperature diff --git a/src/pages/Item/locale/en.yml b/src/pages/Item/locale/en.yml index 761feeee8..74feb512b 100644 --- a/src/pages/Item/locale/en.yml +++ b/src/pages/Item/locale/en.yml @@ -66,6 +66,7 @@ lastEntries: package: Package freight: Freight comission: Comission + printedStickers: Pri. itemTags: removeTag: Remove tag addTag: Add tag @@ -95,6 +96,15 @@ item: mine: For me state: State myTeam: My team + shipped: Shipped + description: Description + quantity: Quantity + price: Price + item: Item + achieved: Achieved + concept: Concept + denyOptions: Deny + scopeDays: Scope days searchbar: label: Search item descriptor: @@ -112,7 +122,7 @@ item: title: All its properties will be copied subTitle: Do you want to clone this item? list: - id: Identifier + id: Id grouping: Grouping packing: Packing description: Description @@ -122,8 +132,9 @@ item: intrastat: Intrastat isActive: Active size: Size - origin: Origin + origin: Orig. userName: Buyer + weight: Weight weightByPiece: Weight/stem stemMultiplier: Multiplier producer: Producer diff --git a/src/pages/Item/locale/es.yml b/src/pages/Item/locale/es.yml index 6eaf3d9d9..3f2a06f1f 100644 --- a/src/pages/Item/locale/es.yml +++ b/src/pages/Item/locale/es.yml @@ -56,7 +56,7 @@ lastEntries: landed: F. Entrega entry: Entrada pvp: PVP - label: Etiquetas + label: Eti. grouping: Grouping quantity: Cantidad cost: Coste @@ -66,6 +66,7 @@ lastEntries: package: Embalaje freight: Porte comission: Comisión + printedStickers: Imp. itemTags: removeTag: Quitar etiqueta addTag: Añadir etiqueta @@ -97,6 +98,15 @@ item: mine: Para mi state: Estado myTeam: Mi equipo + shipped: Enviado + description: Descripción + quantity: Cantidad + price: Precio + item: Artículo + achieved: Conseguido + concept: Concepto + denyOptions: Denegado + scopeDays: Días en adelante searchbar: label: Buscar artículo descriptor: @@ -114,7 +124,7 @@ item: title: Todas sus propiedades serán copiadas subTitle: ¿Desea clonar este artículo? list: - id: Identificador + id: Id grouping: Grouping packing: Packing description: Descripción @@ -124,7 +134,8 @@ item: intrastat: Intrastat isActive: Activo size: Medida - origin: Origen + origin: Orig. + weight: Peso weightByPiece: Peso/tallo userName: Comprador stemMultiplier: Multiplicador diff --git a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue index 3247da014..82578a61f 100644 --- a/src/pages/Monitor/Ticket/MonitorTicketFilter.vue +++ b/src/pages/Monitor/Ticket/MonitorTicketFilter.vue @@ -9,6 +9,7 @@ import VnInput from 'src/components/common/VnInput.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import FetchData from 'src/components/FetchData.vue'; import { dateRange } from 'src/filters'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; defineProps({ dataKey: { type: String, required: true } }); const { t, te } = useI18n(); @@ -59,7 +60,11 @@ const getLocale = (label) => { @@ -108,33 +113,16 @@ const getLocale = (label) => { - - - - - {{ opt.name }} - - {{ `${opt.nickname}, ${opt.code}` }} - - - - - + @@ -146,6 +134,7 @@ const getLocale = (label) => { /> + { /> + + + + + { /> + + + + + + + + + + [ align: 'left', format: (row) => row.practicalHour, columnFilter: false, + dense: true, }, { label: t('salesTicketsTable.preparation'), @@ -190,6 +196,7 @@ const columns = computed(() => [ 'false-value': 0, 'true-value': 1, }, + component: false, }, { label: t('salesTicketsTable.zone'), @@ -206,6 +213,12 @@ const columns = computed(() => [ }, }, }, + { + label: t('salesTicketsTable.payMethod'), + name: 'payMethod', + align: 'left', + columnFilter: false, + }, { label: t('salesTicketsTable.total'), name: 'totalWithVat', @@ -219,6 +232,36 @@ const columns = computed(() => [ }, }, }, + { + label: t('salesTicketsTable.department'), + name: 'department', + align: 'left', + columnFilter: { + component: 'select', + url: 'Departments', + attrs: { + options: DepartmentOpts.value, + optionValue: 'name', + optionLabel: 'name', + dense: true, + }, + }, + }, + { + label: t('salesTicketsTable.packing'), + name: 'packing', + align: 'left', + columnFilter: { + component: 'select', + url: 'ItemPackingTypes', + attrs: { + options: ItemPackingTypeOpts.value, + 'option-value': 'code', + 'option-label': 'code', + dense: true, + }, + }, + }, { align: 'right', name: 'tableActions', @@ -250,19 +293,6 @@ const columns = computed(() => [ }, ]); -const getBadgeAttrs = (date) => { - let today = Date.vnNew(); - today.setHours(0, 0, 0, 0); - let timeTicket = new Date(date); - timeTicket.setHours(0, 0, 0, 0); - - let timeDiff = today - timeTicket; - - if (timeDiff == 0) return { color: 'warning', 'text-color': 'black' }; - if (timeDiff < 0) return { color: 'success', 'text-color': 'black' }; - return { color: 'transparent', 'text-color': 'white' }; -}; - let refreshTimer = null; const autoRefreshHandler = (value) => { @@ -279,14 +309,6 @@ const totalPriceColor = (ticket) => { if (total > 0 && total < 50) return 'warning'; }; -const formatShippedDate = (date) => { - if (!date) return '-'; - const dateSplit = date.split('T'); - const [year, month, day] = dateSplit[0].split('-'); - const newDate = new Date(year, month - 1, day); - return toDateFormat(newDate); -}; - const openTab = (id) => window.open(`#/ticket/${id}/sale`, '_blank', 'noopener, noreferrer'); @@ -318,6 +340,24 @@ const openTab = (id) => auto-load @on-fetch="(data) => (zoneOpts = data)" /> + (ItemPackingTypeOpts = data)" + /> + (DepartmentOpts = data)" + /> @@ -337,7 +377,7 @@ const openTab = (id) => auto-load :row-click="({ id }) => openTab(id)" :disable-option="{ card: true }" - :user-params="{ from, to, scopeDays: 0 }" + :user-params="{ from, to, scopeDays: 0, packing }" > - - {{ formatShippedDate(row.shippedDate) }} - + diff --git a/src/pages/Monitor/locale/en.yml b/src/pages/Monitor/locale/en.yml index ff4031654..e61a24979 100644 --- a/src/pages/Monitor/locale/en.yml +++ b/src/pages/Monitor/locale/en.yml @@ -26,8 +26,8 @@ salesTicketsTable: componentLack: Component lack tooLittle: Ticket too little identifier: Identifier - theoretical: Theoretical - practical: Practical + theoretical: H.Theor + practical: H.Prac province: Province state: State isFragile: Is fragile @@ -35,7 +35,10 @@ salesTicketsTable: goToLines: Go to lines preview: Preview total: Total - preparation: Preparation + preparation: H.Prep + payMethod: Pay method + department: Department + packing: ITP searchBar: label: Search tickets info: Search tickets by id or alias diff --git a/src/pages/Monitor/locale/es.yml b/src/pages/Monitor/locale/es.yml index a2ed3bb1a..30afb1904 100644 --- a/src/pages/Monitor/locale/es.yml +++ b/src/pages/Monitor/locale/es.yml @@ -26,8 +26,8 @@ salesTicketsTable: componentLack: Faltan componentes tooLittle: Ticket demasiado pequeño identifier: Identificador - theoretical: Teórica - practical: Práctica + theoretical: H.Teór + practical: H.Prác province: Provincia state: Estado isFragile: Es frágil @@ -35,7 +35,10 @@ salesTicketsTable: goToLines: Ir a líneas preview: Vista previa total: Total - preparation: Preparación + preparation: H.Prep + payMethod: Método de pago + department: Departamento + packing: ITP searchBar: label: Buscar tickets info: Buscar tickets por identificador o alias diff --git a/src/pages/Order/Card/OrderCatalog.vue b/src/pages/Order/Card/OrderCatalog.vue index a71065521..b7af615bb 100644 --- a/src/pages/Order/Card/OrderCatalog.vue +++ b/src/pages/Order/Card/OrderCatalog.vue @@ -1,7 +1,7 @@ @@ -44,30 +72,33 @@ const canAddToOrder = () => { - + - {{ item.warehouse }} + {{ price.warehouse }} { - item.quantity += item.grouping; + price.quantity -= price.grouping; + } + " + @click.exact=" + () => { + price.quantity += price.grouping; } " > - {{ item.grouping }} + {{ price.grouping }} - x {{ toCurrency(item.price) }} + x {{ toCurrency(price.price) }} - diff --git a/src/pages/Order/Card/OrderCreateDialog.vue b/src/pages/Order/Card/OrderCreateDialog.vue index c78b04d7f..9a53b9e30 100644 --- a/src/pages/Order/Card/OrderCreateDialog.vue +++ b/src/pages/Order/Card/OrderCreateDialog.vue @@ -1,9 +1,8 @@ @@ -90,10 +64,9 @@ onMounted(async () => { option-value="id" option-label="name" :filter="{ - fields: ['id', 'name', 'defaultAddressFk'], + fields: ['id', 'name'], }" hide-selected - @update:model-value="onClientChange" > @@ -110,7 +83,7 @@ onMounted(async () => { :label="t('order.form.addressFk')" v-model="data.addressId" url="addresses" - :fields="['id', 'nickname', 'defaultAddressFk', 'street', 'city']" + :fields="['id', 'nickname', 'street', 'city']" sort-by="id" option-value="id" option-label="street" diff --git a/src/pages/Order/Card/OrderDescriptor.vue b/src/pages/Order/Card/OrderDescriptor.vue index 138fcc40f..e0c613aed 100644 --- a/src/pages/Order/Card/OrderDescriptor.vue +++ b/src/pages/Order/Card/OrderDescriptor.vue @@ -63,21 +63,26 @@ const setData = (entity) => { if (!entity) return; getTotalRef.value && getTotalRef.value.fetch(); data.value = useCardDescription(entity?.client?.name, entity?.id); - state.set('orderData', entity); + state.set('orderTotal', total); }; const getConfirmationValue = (isConfirmed) => { return t(isConfirmed ? 'globals.confirmed' : 'order.summary.notConfirmed'); }; -const total = ref(null); +const orderTotal = computed(() => state.get('orderTotal') ?? 0); +const total = ref(0); (total = response)" + @on-fetch=" + (response) => { + total = response; + } + " /> - + diff --git a/src/pages/Order/Card/OrderFilter.vue b/src/pages/Order/Card/OrderFilter.vue index 917369919..dc86600ac 100644 --- a/src/pages/Order/Card/OrderFilter.vue +++ b/src/pages/Order/Card/OrderFilter.vue @@ -6,6 +6,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnSelect from 'components/common/VnSelect.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -61,28 +62,16 @@ const sourceList = ref([]); outlined rounded /> - - - - - {{ opt.name }} - - {{ opt.nickname }},{{ opt.code }} - - - - - + /> import { useI18n } from 'vue-i18n'; -import { computed, onMounted, ref } from 'vue'; +import { computed, ref, onMounted } from 'vue'; import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; import { toDateTimeFormat } from 'src/filters/date'; import { useSummaryDialog } from 'src/composables/useSummaryDialog'; @@ -17,14 +17,13 @@ import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; import VnTable from 'src/components/VnTable/VnTable.vue'; import VnInputDate from 'src/components/common/VnInputDate.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; -import dataByOrder from 'src/utils/dataByOrder'; const { t } = useI18n(); const { viewSummary } = useSummaryDialog(); const tableRef = ref(); const agencyList = ref([]); -const addressesList = ref([]); const route = useRoute(); +const addressOptions = ref([]); const columns = computed(() => [ { align: 'left', @@ -150,16 +149,12 @@ onMounted(() => { const id = JSON.parse(clientId); fetchClientAddress(id.clientFk); }); + async function fetchClientAddress(id, formData = {}) { - const { data } = await axios.get(`Clients/${id}`, { - params: { - filter: { - order: ['isDefaultAddress DESC', 'isActive DESC', 'nickname ASC'], - include: { relation: 'addresses' }, - }, - }, - }); - addressesList.value = data.addresses; + const { data } = await axios.get( + `Clients/${id}/addresses?filter[order]=isActive DESC` + ); + addressOptions.value = data; formData.addressId = data.defaultAddressFk; fetchAgencies(formData); } @@ -170,7 +165,7 @@ async function fetchAgencies({ landed, addressId }) { const { data } = await axios.get('Agencies/landsThatDay', { params: { addressFk: addressId, landed }, }); - agencyList.value = dataByOrder(data, 'agencyMode ASC'); + agencyList.value = data; } const getDateColor = (date) => { @@ -254,34 +249,29 @@ const getDateColor = (date) => { fetchAgencies(data)" > - - - - + - - {{ scope.opt.nickname }} - - - {{ `${scope.opt.street}, ${scope.opt.city}` }} + + {{ + `${ + !scope.opt?.isActive + ? t('basicData.inactive') + : '' + } ` + }} + {{ scope.opt?.nickname }}: {{ scope.opt?.street }}, + {{ scope.opt?.city }} diff --git a/src/pages/Route/Card/RouteFilter.vue b/src/pages/Route/Card/RouteFilter.vue index a6cd149f1..6f65313d3 100644 --- a/src/pages/Route/Card/RouteFilter.vue +++ b/src/pages/Route/Card/RouteFilter.vue @@ -4,6 +4,7 @@ import VnFilterPanel from 'components/ui/VnFilterPanel.vue'; import VnSelect from 'components/common/VnSelect.vue'; import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const props = defineProps({ @@ -31,29 +32,13 @@ const emit = defineEmits(['search']); - - - - - {{ opt.name }} - - {{ opt.nickname }},{{ opt.code }} - - - - - + /> diff --git a/src/pages/Route/Card/RouteForm.vue b/src/pages/Route/Card/RouteForm.vue index 8c89718fa..aa5caf1ef 100644 --- a/src/pages/Route/Card/RouteForm.vue +++ b/src/pages/Route/Card/RouteForm.vue @@ -11,6 +11,7 @@ import VnInputDate from 'components/common/VnInputDate.vue'; import VnInput from 'components/common/VnInput.vue'; import axios from 'axios'; import VnInputTime from 'components/common/VnInputTime.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const route = useRoute(); @@ -94,26 +95,7 @@ const onSave = (data, response) => { > - - - - - {{ opt.name }} - - {{ opt.nickname }}, {{ opt.code }} - - - - - + { +const cloneRoutes = async () => { if (!selectedRows.value.length || !startingDate.value) return; - axios.post('Routes/clone', { - created: startingDate.value, + await axios.post('Routes/clone', { + dated: startingDate.value, ids: selectedRows.value.map((row) => row?.id), }); startingDate.value = null; @@ -274,7 +274,6 @@ const openTicketsDialog = (id) => { {{ t('route.Select the starting date') }} - { await bankEntitiesRef.value.fetch(); rowData.bankEntityFk = dataSaved.id; }; -const onChangesSaved = () => { +const onChangesSaved = async () => { if (supplier.value.payMethodFk !== wireTransferFk.value) quasar .dialog({ @@ -55,12 +56,35 @@ const setWireTransfer = async () => { await axios.patch(`Suppliers/${route.params.id}`, params); notify('globals.dataSaved', 'positive'); }; + +function findBankFk(value, row) { + row.bankEntityFk = null; + if (!value) return; + + const bankEntityFk = bankEntitiesOptions.value.find((b) => b.id == value.slice(4, 8)); + if (bankEntityFk) row.bankEntityFk = bankEntityFk.id; +} + +function bankEntityFilter(val, update) { + update(() => { + const needle = val.toLowerCase(); + filteredBankEntitiesOptions.value = bankEntitiesOptions.value.filter( + (bank) => + bank.bic.toLowerCase().startsWith(needle) || + bank.name.toLowerCase().includes(needle) + ); + }); +} (bankEntitiesOptions = data)" + @on-fetch=" + (data) => { + (bankEntitiesOptions = data), (filteredBankEntitiesOptions = data); + } + " auto-load /> { findBankFk(value, row)" :required="true" > @@ -109,7 +134,9 @@ const setWireTransfer = async () => { bankEntityFilter(val, update)" option-label="bic" option-value="id" hide-selected diff --git a/src/pages/Supplier/Card/SupplierBasicData.vue b/src/pages/Supplier/Card/SupplierBasicData.vue index 70f6432dd..842109656 100644 --- a/src/pages/Supplier/Card/SupplierBasicData.vue +++ b/src/pages/Supplier/Card/SupplierBasicData.vue @@ -5,6 +5,7 @@ import FormModel from 'components/FormModel.vue'; import VnRow from 'components/ui/VnRow.vue'; import VnInput from 'src/components/common/VnInput.vue'; import VnSelect from 'src/components/common/VnSelect.vue'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const route = useRoute(); const { t } = useI18n(); @@ -30,31 +31,11 @@ const companySizes = [ :rules="validate('supplier.nickname')" clearable /> - - - - {{ - t('Responsible for approving invoices') - }} - - - - - - {{ scope.opt?.name }} - - {{ scope.opt?.nickname }}, {{ scope.opt?.id }} - - - - - + /> es: - Responsible for approving invoices: Responsable de aprobar las facturas Small(1-5), Medium(6-50), Big(> 50): Pequeño(1-5), Mediano(6-50), Grande(> 50) diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue index 1f52181d1..c0748af87 100644 --- a/src/pages/Supplier/SupplierList.vue +++ b/src/pages/Supplier/SupplierList.vue @@ -129,8 +129,7 @@ const columns = computed(() => [ -en: - Search suppliers: Search suppliers -es: + es: Search suppliers: Buscar proveedores + Create Supplier: Crear proveedor diff --git a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue index ab96a6e75..0f6cc5772 100644 --- a/src/pages/Ticket/Card/BasicData/TicketBasicData.vue +++ b/src/pages/Ticket/Card/BasicData/TicketBasicData.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/Ticket/Card/TicketComponents.vue b/src/pages/Ticket/Card/TicketComponents.vue index f45bde2d1..8fab1968b 100644 --- a/src/pages/Ticket/Card/TicketComponents.vue +++ b/src/pages/Ticket/Card/TicketComponents.vue @@ -1,5 +1,5 @@ @@ -321,10 +319,6 @@ onUnmounted(() => (stateStore.rightDrawer = false)); {{ toCurrency(saleComponent.value * row.quantity, 'EUR', 3) }} - diff --git a/src/pages/Ticket/Card/TicketCreateTracking.vue b/src/pages/Ticket/Card/TicketCreateTracking.vue index 3ea762c6c..5c1e916f2 100644 --- a/src/pages/Ticket/Card/TicketCreateTracking.vue +++ b/src/pages/Ticket/Card/TicketCreateTracking.vue @@ -9,6 +9,7 @@ import VnSelect from 'src/components/common/VnSelect.vue'; import FetchData from 'components/FetchData.vue'; import { useState } from 'src/composables/useState'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const emit = defineEmits(['onRequestCreated']); @@ -46,29 +47,7 @@ const onStateFkChange = (formData) => (formData.userFk = user.value.id); option-label="name" option-value="id" /> - - - - - - {{ opt.name }} - - - {{ opt.nickname }}, {{ opt.code }} - - - - + diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index b8e64cb79..38e2af612 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -1,6 +1,6 @@ {{ t('Show Proforma') }} + + + + + {{ t('Restore ticket') }} + @@ -741,10 +805,13 @@ es: Ticket invoiced: Ticket facturado Set weight: Establecer peso Weight set: Peso establecido - This ticket may be invoiced, do you want to continue?: Es posible que se facture este ticket, desea continuar? invoiceIds: "Se han generado las facturas con los siguientes ids: {invoiceIds}" This ticket will be removed from current route! Continue anyway?: ¡Se eliminará el ticket de la ruta actual! ¿Continuar de todas formas? You are going to delete this ticket: Vas a eliminar este ticket as PDF signed: como PDF firmado Are you sure you want to replace this delivery note?: ¿Seguro que quieres reemplazar este albarán? + Restore ticket: Restaurar ticket + Are you sure you want to restore the ticket?: ¿Seguro que quieres restaurar el ticket? + You are going to restore this ticket: Vas a restaurar este ticket + Ticket restored: Ticket restaurado diff --git a/src/pages/Ticket/Card/TicketExpedition.vue b/src/pages/Ticket/Card/TicketExpedition.vue index b7f1f4dd0..38010a997 100644 --- a/src/pages/Ticket/Card/TicketExpedition.vue +++ b/src/pages/Ticket/Card/TicketExpedition.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/Ticket/Card/TicketSale.vue b/src/pages/Ticket/Card/TicketSale.vue index deeaaa28d..a7e0f6171 100644 --- a/src/pages/Ticket/Card/TicketSale.vue +++ b/src/pages/Ticket/Card/TicketSale.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/Ticket/TicketAdvanceFilter.vue b/src/pages/Ticket/TicketAdvanceFilter.vue index a1d301f35..6528bf380 100644 --- a/src/pages/Ticket/TicketAdvanceFilter.vue +++ b/src/pages/Ticket/TicketAdvanceFilter.vue @@ -57,7 +57,6 @@ onMounted(async () => await getItemPackingTypes()); search-url="advanceTickets" :data-key="props.dataKey" :search-button="true" - :hidden-tags="['search']" :unremovable-params="['warehouseFk', 'dateFuture', 'dateToAdvance']" > diff --git a/src/pages/Ticket/TicketFutureFilter.vue b/src/pages/Ticket/TicketFutureFilter.vue index ffe967272..d28b0af71 100644 --- a/src/pages/Ticket/TicketFutureFilter.vue +++ b/src/pages/Ticket/TicketFutureFilter.vue @@ -59,7 +59,6 @@ onMounted(async () => { /> diff --git a/src/pages/Ticket/TicketWeekly.vue b/src/pages/Ticket/TicketWeekly.vue index 306f414df..0e18fe028 100644 --- a/src/pages/Ticket/TicketWeekly.vue +++ b/src/pages/Ticket/TicketWeekly.vue @@ -1,5 +1,5 @@ diff --git a/src/pages/Travel/Card/TravelBasicData.vue b/src/pages/Travel/Card/TravelBasicData.vue index fd02b28cd..4b9aa28ed 100644 --- a/src/pages/Travel/Card/TravelBasicData.vue +++ b/src/pages/Travel/Card/TravelBasicData.vue @@ -104,7 +104,7 @@ const warehousesOptionsIn = ref([]); es: - raidDays: Si se marca "Redada", la fecha de entrega se moverá automáticamente los días indicados. + raidDays: El travel se desplaza automáticamente cada día para estar desde hoy al número de días indicado. Si se deja vacio no se moverá en: - raidDays: If "Raid" is checked, the landing date will automatically shift by the specified number of days. + raidDays: The travel adjusts itself daily to match the number of days set, starting from today. If left blank, it won’t move diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue index 6e61ecfc4..4e48e6b53 100644 --- a/src/pages/Travel/Card/TravelCard.vue +++ b/src/pages/Travel/Card/TravelCard.vue @@ -36,13 +36,13 @@ const filter = { data-key="Travel" base-url="Travels" search-data-key="TravelList" + :filter="filter" :descriptor="TravelDescriptor" :filter-panel="TravelFilter" - :filter="filter" :searchbar-props="{ - url: 'Travels', + url: 'Travels/filter', + searchUrl: 'table', label: 'Search travel', - info: 'You can search by travel id or name', }" /> diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue index 9286f5e3e..675a44979 100644 --- a/src/pages/Travel/ExtraCommunity.vue +++ b/src/pages/Travel/ExtraCommunity.vue @@ -57,7 +57,7 @@ const travelKgPercentages = ref([]); const tableColumnComponents = { id: { component: QBtn, - attrs: { flat: true, color: 'primary' }, + attrs: { flat: true, color: 'primary', dense: true }, }, cargoSupplierNickname: { component: QBtn, @@ -178,6 +178,7 @@ const columns = computed(() => [ align: 'left', showValue: false, sortable: true, + style: 'min-width: 170px;', }, { label: t('globals.packages'), @@ -237,7 +238,7 @@ const columns = computed(() => [ format: (value) => toDate(value), }, { - label: t('globals.wareHhuseIn'), + label: t('globals.warehouseIn'), field: 'warehouseInName', name: 'warehouseInName', align: 'left', @@ -506,7 +507,7 @@ const getColor = (percentage) => { :key="col.name" :props="props" @click="stopEventPropagation($event, col)" - auto-width + :style="col.style" > { }" > - {{ entry.id }} + {{ entry.id }} @@ -637,6 +638,18 @@ const getColor = (percentage) => { :deep(.q-table) { border-collapse: collapse; + + th { + padding: 0; + } + tbody tr td { + &:nth-child(1) { + max-width: 65px; + } + &:nth-child(4) { + padding: 0; + } + } } .q-td :deep(input) { @@ -684,7 +697,6 @@ const getColor = (percentage) => { width: max-content; } - en: searchExtraCommunity: Search for extra community shipping diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue index 1c11261f4..12c880b00 100644 --- a/src/pages/Travel/TravelList.vue +++ b/src/pages/Travel/TravelList.vue @@ -205,6 +205,7 @@ const columns = computed(() => [ ref="tableRef" data-key="TravelList" url="Travels/filter" + redirect="travel" :create="{ urlCreate: 'Travels', title: t('Create Travels'), @@ -217,9 +218,7 @@ const columns = computed(() => [ :user-params="{ daysOnward: 7 }" order="landed DESC" :columns="columns" - redirect="travel" :is-editable="false" - :use-model="true" > diff --git a/src/pages/Worker/Card/WorkerBalance.vue b/src/pages/Worker/Card/WorkerBalance.vue index 25ab92c9b..95e0b986e 100644 --- a/src/pages/Worker/Card/WorkerBalance.vue +++ b/src/pages/Worker/Card/WorkerBalance.vue @@ -15,6 +15,9 @@ const columns = computed(() => [ name: 'paymentDate', label: t('worker.balance.tableVisibleColumns.paymentDate'), create: true, + columnCreate: { + required: true, + }, component: 'date', field: 'paymentDate', cardVisible: true, @@ -24,6 +27,9 @@ const columns = computed(() => [ name: 'incomeTypeFk', label: t('worker.balance.tableVisibleColumns.incomeType'), create: true, + columnCreate: { + required: true, + }, component: 'select', attrs: { options: payrollComponents, @@ -37,6 +43,9 @@ const columns = computed(() => [ name: 'debit', label: t('worker.balance.tableVisibleColumns.debit'), create: true, + columnCreate: { + required: true, + }, component: 'input', field: 'debit', cardVisible: true, @@ -46,6 +55,9 @@ const columns = computed(() => [ name: 'credit', label: t('worker.balance.tableVisibleColumns.credit'), create: true, + columnCreate: { + required: true, + }, component: 'input', field: 'credit', cardVisible: true, diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 13f9e9795..f09aec816 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -10,6 +10,7 @@ import { useState } from 'src/composables/useState'; import axios from 'axios'; import VnImg from 'src/components/ui/VnImg.vue'; import EditPictureForm from 'components/EditPictureForm.vue'; +import DepartmentDescriptorProxy from 'src/pages/Department/Card/DepartmentDescriptorProxy.vue'; const $props = defineProps({ id: { @@ -143,10 +144,14 @@ const handlePhotoUpdated = (evt = false) => { :value="entity.user?.emailUser?.email" copy /> - + + + + + + {{ t('globals.phone') }} @@ -206,6 +211,8 @@ const handlePhotoUpdated = (evt = false) => { es: + Go to client: Ir a cliente + Go to user: Ir al usuario Click to allow the user to be disabled: Marcar para deshabilitar Click to exclude the user from getting disabled: Marcar para no deshabilitar diff --git a/src/pages/Worker/Card/WorkerPda.vue b/src/pages/Worker/Card/WorkerPda.vue index 94f4e0d95..3ceee2493 100644 --- a/src/pages/Worker/Card/WorkerPda.vue +++ b/src/pages/Worker/Card/WorkerPda.vue @@ -133,6 +133,7 @@ function reloadData() { option-value="id" id="deviceProductionFk" hide-selected + data-cy="pda-dialog-select" > diff --git a/src/pages/Worker/WorkerCreate.vue b/src/pages/Worker/WorkerCreate.vue index 5676837dd..a4c6c2a06 100644 --- a/src/pages/Worker/WorkerCreate.vue +++ b/src/pages/Worker/WorkerCreate.vue @@ -14,6 +14,7 @@ import FormModel from 'components/FormModel.vue'; import CreateBankEntityForm from 'src/components/CreateBankEntityForm.vue'; import VnRadio from 'src/components/common/VnRadio.vue'; import { useState } from 'src/composables/useState'; +import VnSelectWorker from 'src/components/common/VnSelectWorker.vue'; const { t } = useI18n(); const user = useState().getUser(); @@ -149,27 +150,11 @@ async function autofillBic(worker) { hide-selected :rules="validate('Worker.company')" /> - - - - - {{ scope.opt.name }} - {{ scope.opt.nickname }}, - {{ scope.opt.code }} - - - - - + /> - - - - - {{ scope.opt.name }} - {{ scope.opt.nickname }}, - {{ scope.opt.code }} - - - - - + /> @@ -377,6 +362,7 @@ async function autofillBic(worker) { es: + Create worker: Crear trabajador Search worker: Buscar trabajador You can search by worker id or name: Puedes buscar por id o nombre del trabajador diff --git a/src/pages/Zone/Card/ZoneBasicData.vue b/src/pages/Zone/Card/ZoneBasicData.vue index 535f2393d..2d65476ce 100644 --- a/src/pages/Zone/Card/ZoneBasicData.vue +++ b/src/pages/Zone/Card/ZoneBasicData.vue @@ -32,7 +32,12 @@ const agencyOptions = ref([]); - + @@ -103,7 +108,20 @@ const agencyOptions = ref([]); clearable /> - + + + diff --git a/src/pages/Zone/Card/ZoneEventExclusionForm.vue b/src/pages/Zone/Card/ZoneEventExclusionForm.vue index 4b54f6743..0882036c1 100644 --- a/src/pages/Zone/Card/ZoneEventExclusionForm.vue +++ b/src/pages/Zone/Card/ZoneEventExclusionForm.vue @@ -1,5 +1,5 @@ @@ -51,7 +50,6 @@ onUnmounted(() => (stateStore.rightDrawer = false)); :last-day="lastDay" :events="events" v-model:formModeName="formModeName" - @open-zone-form="openForm" /> @@ -65,7 +63,7 @@ onUnmounted(() => (stateStore.rightDrawer = false)); /> @@ -78,9 +76,12 @@ onUnmounted(() => (stateStore.rightDrawer = false)); [], }, - formModeName: { - type: String, - required: true, - default: 'include', - }, }); -const emit = defineEmits(['openZoneForm', 'update:formModeName']); - const { t } = useI18n(); const route = useRoute(); const weekdayStore = useWeekdayStore(); const { openConfirmationModal } = useVnConfirm(); -const formName = computed({ - get: () => props.formModeName, - set: (value) => emit('update:formModeName', value), -}); - const params = computed(() => ({ zoneFk: route.params.id, started: props.firstDay, @@ -88,16 +80,6 @@ const deleteEvent = async (id) => { await fetchData(); }; -const openInclusionForm = (event) => { - formName.value = 'include'; - emit('openZoneForm', { - date: event.dated, - event, - isNewMode: false, - eventType: 'event', - }); -}; - onMounted(async () => { weekdayStore.initStore(); }); @@ -110,13 +92,13 @@ onMounted(async () => { t('eventsPanel.editMode') }} (agencies = data)" auto-load /> - + {{ t(`filterPanel.${tag.label}`) }}: diff --git a/src/pages/Zone/locale/en.yml b/src/pages/Zone/locale/en.yml index bf7d8f0de..ba4982311 100644 --- a/src/pages/Zone/locale/en.yml +++ b/src/pages/Zone/locale/en.yml @@ -61,6 +61,8 @@ eventsPanel: events: Events everyday: Everyday delete: Delete + deleteTitle: This item will be deleted + deleteSubtitle: Are you sure you want to continue? eventsExclusionForm: addExclusion: Add exclusion editExclusion: Edit exclusion @@ -76,6 +78,7 @@ eventsInclusionForm: rangeOfDates: Range of dates from: From to: To + day: Day upcomingDeliveries: province: Province closing: Closing diff --git a/src/pages/Zone/locale/es.yml b/src/pages/Zone/locale/es.yml index 91541bea3..d0bab83f4 100644 --- a/src/pages/Zone/locale/es.yml +++ b/src/pages/Zone/locale/es.yml @@ -61,6 +61,8 @@ eventsPanel: events: Eventos everyday: Todos los días delete: Eliminar + deleteTitle: Eliminar evento + deleteSubtitle: ¿Seguro que quieres eliminar este evento? eventsExclusionForm: addExclusion: Añadir exclusión editExclusion: Editar exclusión @@ -76,5 +78,6 @@ eventsInclusionForm: rangeOfDates: Rango de fechas from: Desde to: Hasta + day: Día upcomingDeliveries: province: Provincia diff --git a/src/router/modules/travel.js b/src/router/modules/travel.js index 627692be8..dff693d2f 100644 --- a/src/router/modules/travel.js +++ b/src/router/modules/travel.js @@ -75,9 +75,9 @@ export default { }, { name: 'TravelHistory', - path: 'history', + path: 'log', meta: { - title: 'history', + title: 'log', icon: 'history', }, component: () => import('src/pages/Travel/Card/TravelLog.vue'), diff --git a/src/router/modules/zone.js b/src/router/modules/zone.js index cac1f2bd7..fc8161fb3 100644 --- a/src/router/modules/zone.js +++ b/src/router/modules/zone.js @@ -106,7 +106,7 @@ export default { }, { name: 'ZoneHistory', - path: 'history', + path: 'log', meta: { title: 'log', icon: 'history', diff --git a/src/utils/dataByOrder.js b/src/utils/dataByOrder.js index 1bdedb8a1..eb4a4f586 100644 --- a/src/utils/dataByOrder.js +++ b/src/utils/dataByOrder.js @@ -1,6 +1,7 @@ function orderData(data, order) { if (typeof order === 'function') return data.sort(data); if (typeof order === 'string') order = [order]; + if (!Array.isArray(data)) return []; if (Array.isArray(order)) { let orderComp = []; diff --git a/test/cypress/integration/client/clientBasicData.spec.js b/test/cypress/integration/client/clientBasicData.spec.js index 7b0a19828..bed28dc22 100644 --- a/test/cypress/integration/client/clientBasicData.spec.js +++ b/test/cypress/integration/client/clientBasicData.spec.js @@ -3,11 +3,17 @@ describe('Client basic data', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1110/basic-data', { - timeout: 5000, - }); + cy.visit('#/customer/1102/basic-data'); }); it('Should load layout', () => { cy.get('.q-card').should('be.visible'); + cy.dataCy('customerPhone').find('input').should('be.visible'); + cy.dataCy('customerPhone').find('input').type('123456789'); + cy.get('.q-btn-group > .q-btn--standard').click(); + cy.intercept('PATCH', '/api/Clients/1102', (req) => { + const { body } = req; + cy.wrap(body).should('have.property', 'phone', '123456789'); + }); + cy.get('.q-notification__message').should('have.text', 'Data saved'); }); }); diff --git a/test/cypress/integration/client/clientBillingData.spec.js b/test/cypress/integration/client/clientBillingData.spec.js index 00af82e39..16e8fc0fc 100644 --- a/test/cypress/integration/client/clientBillingData.spec.js +++ b/test/cypress/integration/client/clientBillingData.spec.js @@ -8,6 +8,6 @@ describe('Client billing data', () => { }); }); it('Should load layout', () => { - cy.get('.q-card').should('be.visible'); + cy.get('.q-page').should('be.visible'); }); }); diff --git a/test/cypress/integration/client/clientFiscalData.spec.js b/test/cypress/integration/client/clientFiscalData.spec.js index e337c26f8..05e0772e9 100644 --- a/test/cypress/integration/client/clientFiscalData.spec.js +++ b/test/cypress/integration/client/clientFiscalData.spec.js @@ -3,11 +3,16 @@ describe('Client fiscal data', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1110/fiscal-data', { + cy.visit('#/customer/1107/fiscal-data', { timeout: 5000, }); }); - it('Should load layout', () => { + it('Should change required value when change customer', () => { cy.get('.q-card').should('be.visible'); + cy.dataCy('sageTaxTypeFk').filter('input').should('not.have.attr', 'required'); + cy.get('#searchbar input').clear(); + cy.get('#searchbar input').type('1{enter}'); + cy.get('.q-item > .q-item__label').should('have.text', ' #1'); + cy.dataCy('sageTaxTypeFk').filter('input').should('have.attr', 'required'); }); }); diff --git a/test/cypress/integration/client/clientList.spec.js b/test/cypress/integration/client/clientList.spec.js index 431ac0548..ce07deb16 100644 --- a/test/cypress/integration/client/clientList.spec.js +++ b/test/cypress/integration/client/clientList.spec.js @@ -17,13 +17,15 @@ describe('Client list', () => { it('Client list create new client', () => { cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + const randomInt = Math.floor(Math.random() * 90) + 10; + const data = { - Name: { val: 'Name 1' }, - 'Social name': { val: 'TEST 1' }, - 'Tax number': { val: '20852113Z' }, - 'Web user': { val: 'user_test_1' }, - Street: { val: 'C/ STREET 1' }, - Email: { val: 'user.test@1.com' }, + Name: { val: `Name ${randomInt}` }, + 'Social name': { val: `TEST ${randomInt}` }, + 'Tax number': { val: `20852${randomInt}3Z` }, + 'Web user': { val: `user_test_${randomInt}` }, + Street: { val: `C/ STREET ${randomInt}` }, + Email: { val: `user.test${randomInt}@cypress.com` }, 'Sales person': { val: 'employee', type: 'select' }, Location: { val: '46000, Valencia(Province one), España', type: 'select' }, 'Business type': { val: 'Otros', type: 'select' }, @@ -32,7 +34,7 @@ describe('Client list', () => { cy.get('.q-mt-lg > .q-btn--standard').click(); - cy.checkNotification('created'); + cy.checkNotification('Data created'); cy.url().should('include', '/summary'); }); it('Client list search client', () => { @@ -54,8 +56,8 @@ describe('Client list', () => { cy.openActionDescriptor('Create ticket'); cy.waitForElement('#formModel'); cy.waitForElement('.q-form'); - cy.checkValueSelectForm(1, search); - cy.checkValueSelectForm(2, search); + cy.checkValueForm(1, search); + cy.checkValueForm(2, search); }); it('Client founded create order', () => { const search = 'Jessica Jones'; diff --git a/test/cypress/integration/client/clientSms.spec.js b/test/cypress/integration/client/clientSms.spec.js new file mode 100644 index 000000000..731522a5c --- /dev/null +++ b/test/cypress/integration/client/clientSms.spec.js @@ -0,0 +1,12 @@ +/// +describe('Client notes', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('#/customer/1101/sms'); + }); + it('Should load layout', () => { + cy.get('.q-page').should('be.visible'); + cy.get('.q-page > :nth-child(2) > :nth-child(1)').should('be.visible'); + }); +}); diff --git a/test/cypress/integration/client/clientWebAccess.spec.js b/test/cypress/integration/client/clientWebAccess.spec.js index 47f9efa4c..6803336a3 100644 --- a/test/cypress/integration/client/clientWebAccess.spec.js +++ b/test/cypress/integration/client/clientWebAccess.spec.js @@ -3,11 +3,26 @@ describe('Client web-access', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1110/web-access', { - timeout: 5000, - }); }); - it('Should load layout', () => { + it('Should test buttons ', () => { + cy.visit('#/customer/1101/web-access'); + cy.get('.q-page').should('be.visible'); + cy.get('#formModel').should('be.visible'); cy.get('.q-card').should('be.visible'); + cy.get('.q-btn-group > :nth-child(1)').should('not.be.disabled'); + cy.get('.q-checkbox__inner').click(); + cy.get('.q-btn-group > .q-btn--standard.q-btn--actionable').should( + 'not.be.disabled' + ); + cy.get('.q-btn-group > .q-btn--flat').should('not.be.disabled'); + cy.get('.q-btn-group > :nth-child(1)').click(); + cy.get('.q-dialog__inner > .q-card > :nth-child(1)') + .should('be.visible') + .find('.text-h6') + .should('have.text', 'Change password'); + }); + it('Should disabled buttons', () => { + cy.visit('#/customer/1110/web-access'); + cy.get('.q-btn-group > :nth-child(1)').should('be.disabled'); }); }); diff --git a/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js b/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js index 3c35d5ed0..1c2f935fc 100644 --- a/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js +++ b/test/cypress/integration/client/credit-management/clientCreditContracts.spec.js @@ -1,5 +1,5 @@ /// -describe('Client credit opinion', () => { +describe('Client credit contracts', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); @@ -8,6 +8,6 @@ describe('Client credit opinion', () => { }); }); it('Should load layout', () => { - cy.get('.q-card').should('be.visible'); + cy.get('.q-page').should('be.visible'); }); }); diff --git a/test/cypress/integration/client/others/clientUnpaid.spec.js b/test/cypress/integration/client/others/clientUnpaid.spec.js index 9972ba0e9..ab6bc18bf 100644 --- a/test/cypress/integration/client/others/clientUnpaid.spec.js +++ b/test/cypress/integration/client/others/clientUnpaid.spec.js @@ -3,11 +3,17 @@ describe('Client unpaid', () => { beforeEach(() => { cy.viewport(1280, 720); cy.login('developer'); - cy.visit('#/customer/1110/others/unpaid', { - timeout: 5000, - }); }); - it('Should load layout', () => { + it('Should add unpaid', () => { + cy.visit('#/customer/1102/others/unpaid'); cy.get('.q-card').should('be.visible'); + cy.get('.q-checkbox__inner').click(); + cy.dataCy('customerUnpaidAmount').find('input').type('100'); + cy.dataCy('customerUnpaidDate').find('input').type('01/01/2001'); + cy.get('.q-btn-group > .q-btn--standard').click(); + cy.reload(); + cy.get('.q-checkbox__inner') + .should('be.visible') + .and('not.have.class', 'q-checkbox__inner--active'); }); }); diff --git a/test/cypress/integration/item/ItemFixedPrice.spec.js b/test/cypress/integration/item/ItemFixedPrice.spec.js new file mode 100644 index 000000000..824ecf7a0 --- /dev/null +++ b/test/cypress/integration/item/ItemFixedPrice.spec.js @@ -0,0 +1,63 @@ +/// +function goTo(n = 1) { + return `.q-virtual-scroll__content > :nth-child(${n})`; +} +const firstRow = goTo(); +`.q-virtual-scroll__content > :nth-child(2)`; +describe('Handle Items FixedPrice', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/item/fixed-price', { timeout: 5000 }); + cy.waitForElement('.q-table'); + cy.get( + '.q-header > .q-toolbar > :nth-child(1) > .q-btn__content > .q-icon' + ).click(); + }); + it('filter', function () { + cy.get('.category-filter > :nth-child(1) > .q-btn__content > .q-icon').click(); + cy.selectOption('.list > :nth-child(2)', 'Alstroemeria'); + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); + + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.selectOption(`${firstRow} > :nth-child(2)`, '#13'); + cy.get(`${firstRow} > :nth-child(4)`).find('input').type(1); + cy.get(`${firstRow} > :nth-child(5)`).find('input').type('2'); + cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One'); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + /* ==== End Cypress Studio ==== */ + }); + it('Create and delete ', function () { + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); + cy.selectOption(`${firstRow} > :nth-child(2)`, '#11'); + cy.get(`${firstRow} > :nth-child(4)`).type('1'); + cy.get(`${firstRow} > :nth-child(5)`).type('2'); + cy.selectOption(`${firstRow} > :nth-child(9)`, 'Warehouse One'); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + cy.get('.q-gutter-x-sm > .q-btn > .q-btn__content > .q-icon').click(); + cy.get(`${firstRow} > .text-right > .q-btn > .q-btn__content > .q-icon`).click(); + cy.get( + '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' + ).click(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('Massive edit', function () { + cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click(); + cy.get('#subToolbar > .q-btn--standard').click(); + cy.selectOption("[data-cy='field-to-edit']", 'Min price'); + cy.dataCy('value-to-edit').find('input').type('1'); + cy.get('.countLines').should('have.text', ' 1 '); + cy.get('.q-mt-lg > .q-btn--standard').click(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + it('Massive remove', function () { + cy.get(' .bg-header > :nth-child(1) > .q-checkbox > .q-checkbox__inner ').click(); + cy.get('#subToolbar > .q-btn--flat').click(); + cy.get( + '.q-card__actions > .q-btn--unelevated > .q-btn__content > .block' + ).click(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); +}); diff --git a/test/cypress/integration/item/itemLastEntries.spec.js b/test/cypress/integration/item/itemLastEntries.spec.js new file mode 100644 index 000000000..c94cfa480 --- /dev/null +++ b/test/cypress/integration/item/itemLastEntries.spec.js @@ -0,0 +1,20 @@ +describe('ItemLastEntries', () => { + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('buyer'); + cy.visit('/#/item/1/last-entries'); + cy.intercept('GET', /.*lastEntriesFilter/).as('item'); + cy.waitForElement('tbody'); + }); + + it('should filter by agency', () => { + cy.get('tbody > tr') + .its('length') + .then((rowCount) => { + cy.get('[data-cy="hideInventory"]').click(); + cy.wait('@item'); + cy.waitForElement('tbody'); + cy.get('tbody > tr').should('have.length.greaterThan', rowCount); + }); + }); +}); diff --git a/test/cypress/integration/ticket/ticketList.spec.js b/test/cypress/integration/ticket/ticketList.spec.js index bbdbcea92..c1d1a0655 100644 --- a/test/cypress/integration/ticket/ticketList.spec.js +++ b/test/cypress/integration/ticket/ticketList.spec.js @@ -37,7 +37,7 @@ describe('TicketList', () => { cy.dataCy('ticketSummary').should('exist'); }); - it('Client list create new client', () => { + it.only('Client list create new client', () => { cy.dataCy('vnTableCreateBtn').should('exist'); cy.dataCy('vnTableCreateBtn').click(); const data = { @@ -47,7 +47,8 @@ describe('TicketList', () => { Landed: { val: '01-01-2024', type: 'date' }, }; cy.fillInForm(data); - cy.get('.q-mt-lg > .q-btn--standard').click(); + cy.dataCy('Agency_select').click(); + cy.dataCy('FormModelPopup_save').click(); cy.checkNotification('Data created'); cy.url().should('match', /\/ticket\/\d+\/summary/); }); diff --git a/test/cypress/integration/vnComponent/VnBreadcrumbs.spec.js b/test/cypress/integration/vnComponent/VnBreadcrumbs.spec.js index 347dae7df..8e37d8c9c 100644 --- a/test/cypress/integration/vnComponent/VnBreadcrumbs.spec.js +++ b/test/cypress/integration/vnComponent/VnBreadcrumbs.spec.js @@ -2,6 +2,7 @@ describe('VnBreadcrumbs', () => { const lastBreadcrumb = '.q-breadcrumbs--last > .q-breadcrumbs__el'; beforeEach(() => { + cy.viewport(1920, 1080); cy.login('developer'); cy.visit('/'); }); diff --git a/test/cypress/integration/vnComponent/VnLocation.spec.js b/test/cypress/integration/vnComponent/VnLocation.spec.js index b98d42fdd..82d12a1e4 100644 --- a/test/cypress/integration/vnComponent/VnLocation.spec.js +++ b/test/cypress/integration/vnComponent/VnLocation.spec.js @@ -43,11 +43,9 @@ describe('VnLocation', () => { province ); cy.get( - `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) > .q-icon` + `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(3) ` ).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); + cy.dataCy('locationProvince').should('have.value', province); }); }); describe('Worker Create', () => { @@ -123,32 +121,16 @@ describe('VnLocation', () => { const province = randomString({ length: 4 }); cy.get(createLocationButton).click(); cy.get(dialogInputs).eq(0).type(postCode); - cy.get( - `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(2) > .q-icon` - ).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.dataCy('City_icon').click(); + cy.selectOption('[data-cy="locationProvince"]:last', 'Province one'); + cy.dataCy('cityName').type(province); + cy.dataCy('FormModelPopup_save').eq(1).click(); + cy.dataCy('FormModelPopup_save').eq(0).click(); + cy.waitForElement('.q-form'); checkVnLocation(postCode, province); }); - it('Create province without country', () => { - const provinceName = 'Saskatchew'.concat(Math.random(1 * 100)); - cy.get(createLocationButton).click(); - cy.get( - `${createForm.prefix} > :nth-child(5) > .q-select > ${createForm.sufix} > :nth-child(2) ` - ) - .eq(0) - .click(); - cy.selectOption('#q-portal--dialog--3 .q-select', 'one'); - cy.countSelectOptions('#q-portal--dialog--3 .q-select', 4); - cy.get('#q-portal--dialog--3 .q-input').type(provinceName); - - cy.get('#q-portal--dialog--3 .q-btn--standard').click(); - }); - it('Create city with country', () => { const cityName = 'Saskatchew'.concat(Math.random(1 * 100)); cy.get(createLocationButton).click(); @@ -156,14 +138,23 @@ describe('VnLocation', () => { `${createForm.prefix} > :nth-child(5) > :nth-child(3) `, 'Italia' ); - cy.get( - `${createForm.prefix} > :nth-child(4) > .q-select > ${createForm.sufix} > :nth-child(2) > .q-icon` - ).click(); - cy.selectOption('#q-portal--dialog--4 .q-select', 'Province four'); - cy.countSelectOptions('#q-portal--dialog--4 .q-select', 1); + cy.dataCy('City_icon').click(); + cy.selectOption('[data-cy="locationProvince"]:last', 'Province four'); + cy.countSelectOptions('[data-cy="locationProvince"]:last', 1); - cy.get('#q-portal--dialog--4 .q-input').type(cityName); - cy.get('#q-portal--dialog--4 .q-btn--standard').click(); + cy.dataCy('cityName').type(cityName); + cy.dataCy('FormModelPopup_save').eq(1).click(); + }); + + it('Create province without country', () => { + const provinceName = 'Saskatchew'.concat(Math.random(1 * 100)); + cy.get(createLocationButton).click(); + cy.dataCy('Province_icon').click(); + cy.selectOption('[data-cy="autonomyProvince"] ', 'Autonomy one'); + cy.countSelectOptions('[data-cy="autonomyProvince"]', 4); + cy.dataCy('provinceName').type(provinceName); + + cy.dataCy('FormModelPopup_save').eq(1).click(); }); it('Create province with country', () => { @@ -173,17 +164,13 @@ describe('VnLocation', () => { `${createForm.prefix} > :nth-child(5) > :nth-child(3) `, 'España' ); - cy.get( - `${createForm.prefix} > :nth-child(5) > .q-select > ${createForm.sufix} > :nth-child(2) ` - ) - .eq(0) - .click(); + cy.dataCy('Province_icon').click(); - cy.selectOption('#q-portal--dialog--4 .q-select', 'one'); - cy.countSelectOptions('#q-portal--dialog--4 .q-select', 2); + cy.selectOption('[data-cy="autonomyProvince"] ', 'Autonomy one'); + cy.countSelectOptions('[data-cy="autonomyProvince"]', 2); - cy.get('#q-portal--dialog--4 .q-input').type(provinceName); - cy.get('#q-portal--dialog--4 .q-btn--standard').click(); + cy.dataCy('provinceName').type(provinceName); + cy.dataCy('FormModelPopup_save').eq(1).click(); }); function checkVnLocation(postCode, province) { diff --git a/test/cypress/integration/worker/workerPda.spec.js b/test/cypress/integration/worker/workerPda.spec.js index fe8efa834..dc1ca6224 100644 --- a/test/cypress/integration/worker/workerPda.spec.js +++ b/test/cypress/integration/worker/workerPda.spec.js @@ -1,6 +1,5 @@ describe('WorkerPda', () => { - const deviceProductionField = - '.vn-row > .q-field > .q-field__inner > .q-field__control > .q-field__control-container'; + const select = '[data-cy="pda-dialog-select"]'; beforeEach(() => { cy.viewport(1920, 1080); cy.login('developer'); @@ -9,7 +8,8 @@ describe('WorkerPda', () => { it('assign pda', () => { cy.get('.q-page-sticky > div > .q-btn > .q-btn__content > .q-icon').click(); - cy.get(deviceProductionField).type('{downArrow}{enter}'); + cy.get(select).click(); + cy.get(select).type('{downArrow}{enter}'); cy.get('.q-notification__message').should('have.text', 'Data created'); }); diff --git a/test/cypress/integration/zone/zoneBasicData.spec.js b/test/cypress/integration/zone/zoneBasicData.spec.js index c6151a49b..6229039b7 100644 --- a/test/cypress/integration/zone/zoneBasicData.spec.js +++ b/test/cypress/integration/zone/zoneBasicData.spec.js @@ -1,5 +1,6 @@ describe('ZoneBasicData', () => { const notification = '.q-notification__message'; + const priceBasicData = '[data-cy="Price_input"]'; beforeEach(() => { cy.viewport(1280, 720); @@ -8,14 +9,20 @@ describe('ZoneBasicData', () => { }); it('should throw an error if the name is empty', () => { - cy.get('.q-card > :nth-child(1)').clear(); + cy.get('[data-cy="zone-basic-data-name"] input').type('{selectall}{backspace}'); cy.get('.q-btn-group > .q-btn--standard').click(); cy.get(notification).should('contains.text', "can't be blank"); }); + it('should throw an error if the price is empty', () => { + cy.get(priceBasicData).clear(); + cy.get('.q-btn-group > .q-btn--standard').click(); + cy.get(notification).should('contains.text', 'cannot be blank'); + }); + it("should edit the basicData's zone", () => { cy.get('.q-card > :nth-child(1)').type(' modified'); cy.get('.q-btn-group > .q-btn--standard').click(); - cy.get(notification).should('contains.text', 'Data saved'); + cy.checkNotification('Data saved'); }); }); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index dbaa7871c..2b13a7144 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -110,14 +110,14 @@ Cypress.Commands.add('fillInForm', (obj, form = '.q-form > .q-card') => { const { type, val } = field; switch (type) { case 'select': - cy.wrap(el).type(val); + cy.get(el).click(); cy.get('.q-menu .q-item').contains(val).click(); break; case 'date': - cy.wrap(el).type(val.split('-').join('')); + cy.get(el).type(val.split('-').join('')); break; case 'time': - cy.wrap(el).click(); + cy.get(el).click(); cy.get('.q-time .q-time__clock').contains(val.h).click(); cy.get('.q-time .q-time__clock').contains(val.m).click(); cy.get('.q-time .q-time__link').contains(val.x).click(); @@ -260,6 +260,7 @@ Cypress.Commands.add('writeSearchbar', (value) => { value ); }); + Cypress.Commands.add('validateContent', (selector, expectedValue) => { cy.get(selector).should('have.text', expectedValue); }); @@ -281,16 +282,38 @@ Cypress.Commands.add('clickButtonsDescriptor', (id) => { .click(); }); +Cypress.Commands.add('openActionDescriptor', (opt) => { + cy.openActionsDescriptor(); + const listItem = '[role="menu"] .q-list .q-item'; + cy.contains(listItem, opt).click(); + 1; +}); + +Cypress.Commands.add('clickButtonsDescriptor', (id) => { + cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) + .invoke('removeAttr', 'target') + .click(); +}); + +Cypress.Commands.add('openActionDescriptor', (opt) => { + cy.openActionsDescriptor(); + const listItem = '[role="menu"] .q-list .q-item'; + cy.contains(listItem, opt).click(); + 1; +}); + +Cypress.Commands.add('clickButtonsDescriptor', (id) => { + cy.get(`.actions > .q-card__actions> .q-btn:nth-child(${id})`) + .invoke('removeAttr', 'target') + .click(); +}); + Cypress.Commands.add('openUserPanel', () => { cy.get( '.column > .q-avatar > .q-avatar__content > .q-img > .q-img__container > .q-img__image' ).click(); }); -Cypress.Commands.add('openActions', (row) => { - cy.get('tbody > tr').eq(row).find('.actions > .q-btn').click(); -}); - Cypress.Commands.add('checkNotification', (text) => { cy.get('.q-notification') .should('be.visible') @@ -301,10 +324,14 @@ Cypress.Commands.add('checkNotification', (text) => { }); }); +Cypress.Commands.add('openActions', (row) => { + cy.get('tbody > tr').eq(row).find('.actions > .q-btn').click(); +}); + Cypress.Commands.add('checkValueForm', (id, search) => { - cy.get( - `.grid-create > :nth-child(${id}) > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > .q-field__input` - ).should('have.value', search); + cy.get(`.grid-create > :nth-child(${id}) `) + .find('input') + .should('have.value', search); }); Cypress.Commands.add('checkValueSelectForm', (id, search) => { @@ -317,6 +344,6 @@ Cypress.Commands.add('searchByLabel', (label, value) => { cy.get(`[label="${label}"] > .q-field > .q-field__inner`).type(`${value}{enter}`); }); -Cypress.Commands.add('dataCy', (dataTestId, attr = 'data-cy') => { - return cy.get(`[${attr}="${dataTestId}"]`); +Cypress.Commands.add('dataCy', (tag, attr = 'data-cy') => { + return cy.get(`[${attr}="${tag}"]`); }); diff --git a/test/vitest/__tests__/components/common/VnDiscount.spec.js b/test/vitest/__tests__/components/common/VnDiscount.spec.js new file mode 100644 index 000000000..5d5be61ac --- /dev/null +++ b/test/vitest/__tests__/components/common/VnDiscount.spec.js @@ -0,0 +1,28 @@ +import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; +import { createWrapper } from 'app/test/vitest/helper'; +import VnDiscount from 'components/common/vnDiscount.vue'; + +describe('VnDiscount', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(VnDiscount, { + props: { + data: {}, + price: 100, + quantity: 2, + discount: 10, + } + }).vm; + }); + + afterEach(() => { + vi.clearAllMocks(); + }); + + describe('total', () => { + it('should calculate total correctly', () => { + expect(vm.total).toBe(180); + }); + }); +}); \ No newline at end of file diff --git a/test/vitest/__tests__/components/common/VnLinkPhone.spec.js b/test/vitest/__tests__/components/common/VnLinkPhone.spec.js index e460ab2fc..a34ef90a5 100644 --- a/test/vitest/__tests__/components/common/VnLinkPhone.spec.js +++ b/test/vitest/__tests__/components/common/VnLinkPhone.spec.js @@ -1,29 +1,50 @@ -import { describe, it, expect } from 'vitest'; +import { describe, it, expect, beforeAll, vi } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; import parsePhone from 'src/filters/parsePhone'; describe('parsePhone filter', () => { - it("adds prefix +34 if it doesn't have one", () => { - const resultado = parsePhone('123456789', '34'); - expect(resultado).toBe('34123456789'); + beforeAll(async () => { + vi.spyOn(axios, 'get').mockReturnValue({ data: { prefix: '34' } }); }); - it('maintains prefix +34 if it is already correct', () => { - const resultado = parsePhone('+34123456789', '34'); - expect(resultado).toBe('34123456789'); + it('no phone', async () => { + const phone = await parsePhone(null, '34'); + expect(phone).toBe(undefined); }); - it('converts prefix 0034 to +34', () => { - const resultado = parsePhone('0034123456789', '34'); - expect(resultado).toBe('34123456789'); + it("adds prefix +34 if it doesn't have one", async () => { + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('34123456789'); }); - it('converts prefix 34 without symbol to +34', () => { - const resultado = parsePhone('34123456789', '34'); - expect(resultado).toBe('34123456789'); + it('maintains prefix +34 if it is already correct', async () => { + const phone = await parsePhone('+34123456789', '34'); + expect(phone).toBe('34123456789'); }); - it('replaces incorrect prefix with the correct one', () => { - const resultado = parsePhone('+44123456789', '34'); - expect(resultado).toBe('44123456789'); + it('converts prefix 0034 to +34', async () => { + const phone = await parsePhone('0034123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('converts prefix 34 without symbol to +34', async () => { + const phone = await parsePhone('34123456789', '34'); + expect(phone).toBe('34123456789'); + }); + + it('replaces incorrect prefix with the correct one', async () => { + const phone = await parsePhone('+44123456789', '34'); + expect(phone).toBe('44123456789'); + }); + + it('adds default prefix on error', async () => { + vi.spyOn(axios, 'get').mockImplementation((url) => { + if (url.includes('Prefixes')) + return Promise.reject(new Error('Network error')); + else if (url.includes('PbxConfigs')) + return Promise.resolve({ data: { defaultPrefix: '39' } }); + }); + const phone = await parsePhone('123456789', '34'); + expect(phone).toBe('39123456789'); }); }); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 4bfae5dc8..ce057c7c3 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -44,7 +44,18 @@ vi.mock('vue-router', () => ({ vi.mock('axios'); vi.spyOn(useValidator, 'useValidator').mockImplementation(() => { - return { validate: vi.fn() }; + return { + validate: vi.fn(), + validations: () => ({ + format: vi.fn(), + presence: vi.fn(), + required: vi.fn(), + length: vi.fn(), + numericality: vi.fn(), + min: vi.fn(), + custom: vi.fn(), + }), + }; }); class FormDataMock {
{{ t('route.Select the starting date') }}