diff --git a/.vscode/settings.json b/.vscode/settings.json index ecc1d50d7..5026b7d3b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -13,5 +13,6 @@ ], "[vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode" - } + }, + "cSpell.words": ["axios"] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 243d67a34..1a679cdfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [2400.01] - 2024-01-04 + +### Added + +### Changed + +### Fixed + +## [2350.01] - 2023-12-14 + +### Added + +- (Carros) => Se añade contador de carros. #6545 +- (Reclamaciones) => Se añade la sección para hacer acciones sobre una reclamación. #5654 + +### Changed + +### Fixed + +- (Reclamaciones) => Se corrige el color de la barra según el tema y el evento de actualziar cantidades #6334 + ## [2253.01] - 2023-01-05 ### Added diff --git a/cypress.config.js b/cypress.config.js index 2b5b40d08..1934f833e 100644 --- a/cypress.config.js +++ b/cypress.config.js @@ -8,7 +8,7 @@ module.exports = defineConfig({ supportFile: 'test/cypress/support/index.js', videosFolder: 'test/cypress/videos', video: false, - specPattern: 'test/cypress/integration/*.spec.js', + specPattern: 'test/cypress/integration/**/*.spec.js', experimentalRunAllSpecs: true, component: { componentFolder: 'src', diff --git a/package-lock.json b/package-lock.json index a3a9dcc63..9db93eff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,14 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "23.52.01", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "salix-front", - "version": "0.0.1", + "version": "23.52.01", "dependencies": { - "@quasar/cli": "^2.2.1", + "@quasar/cli": "^2.3.0", "@quasar/extras": "^1.16.4", "axios": "^1.4.0", "chromium": "^3.0.3", @@ -946,9 +946,9 @@ } }, "node_modules/@quasar/cli": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.2.1.tgz", - "integrity": "sha512-PMwJ76IeeNRRBw+08hUMjhqGC6JKJ/t1zIb+IOiyR5D4rkBR26Ha/Z46OD3KfwUprq4Q8s4ieB1+d3VY8FhPKg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@quasar/cli/-/cli-2.3.0.tgz", + "integrity": "sha512-DNFDemicj3jXe5+Ib+5w9Bwj1U3yoHQkqn0bU/qysIl/p0MmGA1yqOfUF0V4fw/5or1dfCvStIA/oZxUcC+2pQ==", "dependencies": { "@quasar/ssl-certificate": "^1.0.0", "ci-info": "^3.8.0", diff --git a/package.json b/package.json index 3e26b483b..7c966dbb1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "23.40.01", + "version": "24.00.01", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -9,13 +9,13 @@ "lint": "eslint --ext .js,.vue ./", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "test:e2e": "cypress open", - "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run --browser chromium", + "test:e2e:ci": "cd ../salix && gulp docker && cd ../salix-front && cypress run", "test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test:unit": "vitest", "test:unit:ci": "vitest run" }, "dependencies": { - "@quasar/cli": "^2.2.1", + "@quasar/cli": "^2.3.0", "@quasar/extras": "^1.16.4", "axios": "^1.4.0", "chromium": "^3.0.3", diff --git a/quasar.config.js b/quasar.config.js index cbcbae4dc..755e96bd3 100644 --- a/quasar.config.js +++ b/quasar.config.js @@ -29,7 +29,7 @@ module.exports = configure(function (/* ctx */) { // app boot file (/src/boot) // --> boot files are part of "main.js" // https://v2.quasar.dev/quasar-cli/boot-files - boot: ['i18n', 'axios', 'vnDate'], + boot: ['i18n', 'axios', 'vnDate', 'validations'], // https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#css css: ['app.scss'], @@ -66,7 +66,9 @@ module.exports = configure(function (/* ctx */) { // publicPath: '/', // analyze: true, // env: {}, - // rawDefine: {} + rawDefine: { + 'process.env.NODE_ENV': JSON.stringify(process.env.NODE_ENV) + }, // ignorePublicFolder: true, // minify: false, // polyfillModulePreload: true, @@ -89,11 +91,12 @@ module.exports = configure(function (/* ctx */) { vitePlugins: [ [ - VueI18nPlugin, + VueI18nPlugin({ + runtimeOnly: false + }), { // if you want to use Vue I18n Legacy API, you need to set `compositionOnly: false` // compositionOnly: false, - // you need to set i18n resource including paths ! include: path.resolve(__dirname, './src/i18n/**'), }, diff --git a/src/boot/validations.js b/src/boot/validations.js new file mode 100644 index 000000000..31e232f86 --- /dev/null +++ b/src/boot/validations.js @@ -0,0 +1,6 @@ +import { boot } from 'quasar/wrappers'; +import { useValidationsStore } from 'src/stores/useValidationsStore'; + +export default boot(async ({ store }) => { + await useValidationsStore(store).fetchModels(); +}); diff --git a/src/boot/vnDate.js b/src/boot/vnDate.js index c78886b57..33d5ac27f 100644 --- a/src/boot/vnDate.js +++ b/src/boot/vnDate.js @@ -15,4 +15,14 @@ export default boot(() => { Date.vnNow = () => { return new Date(Date.vnUTC()).getTime(); }; + + Date.vnFirstDayOfMonth = () => { + const date = new Date(Date.vnUTC()); + return new Date(date.getFullYear(), date.getMonth(), 1); + }; + + Date.vnLastDayOfMonth = () => { + const date = new Date(Date.vnUTC()); + return new Date(date.getFullYear(), date.getMonth() + 1, 0); + }; }); diff --git a/src/components/CrudModel.vue b/src/components/CrudModel.vue index 2a4982fce..75353a35a 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -89,6 +89,7 @@ async function fetch(data) { watch(formData, () => (hasChanges.value = true), { deep: true }); emit('onFetch', data); + return data; } async function reset() { @@ -135,6 +136,10 @@ async function saveChanges(data) { hasChanges.value = false; isLoading.value = false; emit('saveChanges', data); + quasar.notify({ + type: 'positive', + message: t('globals.dataSaved'), + }); } async function insert() { @@ -268,7 +273,7 @@ watch(formUrl, async () => { - + {}, + }, + observeFormChanges: { + type: Boolean, + default: true, + description: + 'Esto se usa principalmente para permitir guardar sin hacer cambios (Útil para la feature de clonar ya que en este caso queremos poder guardar de primeras)', + }, }); const emit = defineEmits(['onFetch']); @@ -43,44 +63,73 @@ defineExpose({ save, }); -onMounted(async () => await fetch()); +onMounted(async () => { + // Podemos enviarle al form la estructura de data inicial sin necesidad de fetchearla + if ($props.formInitialData && !$props.autoLoad) { + state.set($props.model, $props.formInitialData); + } else { + await fetch(); + } + + // Disparamos el watcher del form después de que se haya cargado la data inicial, si así se desea + if ($props.observeFormChanges) { + startFormWatcher(); + } +}); onUnmounted(() => { state.unset($props.model); }); const isLoading = ref(false); -const hasChanges = ref(false); -const originalData = ref(); +// Si elegimos observar los cambios del form significa que inicialmente las actions estaran deshabilitadas +const hasChanges = ref(!$props.observeFormChanges); +const originalData = ref({...$props.formInitialData}); const formData = computed(() => state.get($props.model)); const formUrl = computed(() => $props.url); +const startFormWatcher = () => { + watch( + () => formData.value, + (val) => { + if (val) hasChanges.value = true; + }, + { deep: true } + ); +}; + function tMobile(...args) { if (!quasar.platform.is.mobile) return t(...args); } async function fetch() { const { data } = await axios.get($props.url, { - params: { filter: $props.filter }, + params: { filter: JSON.stringify($props.filter) }, }); state.set($props.model, data); originalData.value = data && JSON.parse(JSON.stringify(data)); - watch(formData.value, () => (hasChanges.value = true)); - emit('onFetch', state.get($props.model)); } async function save() { if (!hasChanges.value) { - return quasar.notify({ - type: 'negative', - message: t('globals.noChanges'), - }); + notify('globals.noChanges', 'negative'); + return; } isLoading.value = true; - await axios.patch($props.urlUpdate || $props.url, formData.value); + + try { + if ($props.urlCreate) { + await axios.post($props.urlCreate, formData.value); + notify('globals.dataCreated', 'positive'); + } else { + await axios.patch($props.urlUpdate || $props.url, formData.value); + } + } catch (err) { + notify('errors.create', 'negative'); + } originalData.value = JSON.parse(JSON.stringify(formData.value)); hasChanges.value = false; @@ -91,11 +140,12 @@ function reset() { state.set($props.model, originalData.value); originalData.value = JSON.parse(JSON.stringify(originalData.value)); - watch(formData.value, () => (hasChanges.value = true)); - emit('onFetch', state.get($props.model)); - hasChanges.value = false; + if ($props.observeFormChanges) { + hasChanges.value = false; + } } + // eslint-disable-next-line vue/no-dupe-keys function filter(value, update, filterOptions) { update( @@ -118,13 +168,28 @@ watch(formUrl, async () => { }); - + {{ t('globals.changesToSave') }} - - - + + + + + + + @@ -156,3 +221,13 @@ watch(formUrl, async () => { color="primary" /> + diff --git a/src/components/NavBar.vue b/src/components/NavBar.vue index 4e3e241b8..2368e078e 100644 --- a/src/components/NavBar.vue +++ b/src/components/NavBar.vue @@ -7,6 +7,7 @@ import { useStateStore } from 'stores/useStateStore'; import { useQuasar } from 'quasar'; import PinnedModules from './PinnedModules.vue'; import UserPanel from 'components/UserPanel.vue'; +import VnBreadcrumbs from './common/VnBreadcrumbs.vue'; const { t } = useI18n(); const session = useSession(); @@ -24,27 +25,17 @@ const pinnedModulesRef = ref(); - - + + {{ t('globals.collapseMenu') }} - + - - {{ appName }} - - + @@ -112,6 +100,7 @@ const pinnedModulesRef = ref(); + diff --git a/src/components/common/SendEmailDialog.vue b/src/components/common/SendEmailDialog.vue index ab83c0949..0f574a795 100644 --- a/src/components/common/SendEmailDialog.vue +++ b/src/components/common/SendEmailDialog.vue @@ -24,12 +24,13 @@ const address = ref(props.data.address); const isLoading = ref(false); async function confirm() { - const response = { address }; - + const response = { address: address.value }; if (props.promise) { isLoading.value = true; + const { address: _address, ...restData } = props.data; + try { - Object.assign(response, props.data); + Object.assign(response, restData); await props.promise(response); } finally { isLoading.value = false; diff --git a/src/components/common/VnAccountNumber.vue b/src/components/common/VnAccountNumber.vue new file mode 100644 index 000000000..f7273a72d --- /dev/null +++ b/src/components/common/VnAccountNumber.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/components/common/VnBreadcrumbs.vue b/src/components/common/VnBreadcrumbs.vue new file mode 100644 index 000000000..792671e21 --- /dev/null +++ b/src/components/common/VnBreadcrumbs.vue @@ -0,0 +1,82 @@ + + + + + + + + + + diff --git a/src/components/common/VnInputDate.vue b/src/components/common/VnInputDate.vue new file mode 100644 index 000000000..a8e0d4a43 --- /dev/null +++ b/src/components/common/VnInputDate.vue @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + diff --git a/src/components/common/VnJsonValue.vue b/src/components/common/VnJsonValue.vue new file mode 100644 index 000000000..a2e858d0d --- /dev/null +++ b/src/components/common/VnJsonValue.vue @@ -0,0 +1,88 @@ + + + + + {{ t }} + + + + diff --git a/src/components/common/VnLog.vue b/src/components/common/VnLog.vue index 1213c8bbc..0949fb5cb 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,128 +1,634 @@ - - - {{ t('Audit logs') }} - - - - - - {{ log.userName }} - - {{ - toDate(log.created, { - dateStyle: 'medium', - timeStyle: 'short', - }) - }} - - - - {{ t(`actions.${log.action}`) }} - - {{ t(`models.${log.model}`) }} - - (workers = data)" + auto-load + /> + + (actions = data.map((item) => { + return { + locale: useFirstUpper(validations[item.changedModel].locale.name), + value: item.changedModel, + }; + })) + " + auto-load + /> + + + + {{ useFirstUpper(validations[props.model].locale.name) }} + #{{ originLog.originFk }} + + + + + + + + + + + + + + + + + - - - + #{{ modelLog.id }} + + {{ modelLog.showValue }} + + + + + + + + + + {{ toRelativeDate(log.creationDate) }} + + + - {{ t(col.label) }} - - - - - - - - - + + + + {{ modelLog.modelI18n }} + #{{ modelLog.id }} + + + + + {{ value.nameI18n }}: + + + + + + + + + + + + + + + + + + {{ prop.nameI18n }}: + + + , + + + + + + + {{ prop.nameI18n }}: + + + + #{{ prop.val.id }} + + + ← + + + #{{ prop.old.id }} + + + + + + + {{ log.description }} + + + + + + + - + {{ t('globals.collapseMenu') }} @@ -131,28 +637,390 @@ function actionColor(action) { - + + + + selectFilter('search')" + @focusout="() => selectFilter('search')" + @clear="() => selectFilter('search')" + > + + + {{ t('tooltips.search') }} + + + + + + selectFilter('action')" + hide-selected + /> + + + + + {{ t(`Users.${label}`) }} + + + + + + + + + + + + + + + + {{ opt.name }} + {{ opt.nickname }} + + + + + + + + + + + {{ + t('tooltips.changes') + }} + + + + + + + + + evt.target.blur()" + @clear="selectFilter('date', 'to')" + v-model="dateFrom" + clearable + clear-icon="close" + /> + + + evt.target.blur()" + @clear="selectFilter('date', 'from')" + v-model="dateTo" + clearable + clear-icon="close" + /> + + + + { + dateFromDialog = false; + dateFrom = date.formatDate(value, 'DD-MM-YYYY'); + selectFilter('date', 'from'); + } + " + /> + + + { + dateToDialog = false; + dateTo = date.formatDate(value, 'DD-MM-YYYY'); + selectFilter('date', 'to'); + } + " + /> + + + + - en: + to: To + pointRecord: View record at this point in time + recordChanges: show all record changes + tooltips: + search: Search by id or concept + changes: Search by changes actions: - insert: Creates - update: Updates - delete: Deletes - models: - Claim: Claim - ClaimDms: Document - ClaimBeginning: Claimed Sales - ClaimObservation: Observation + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: Claim ID @@ -172,6 +1040,12 @@ en: responsibility: Responsibility packages: Packages es: + to: Hasta + pointRecord: Ver el registro en este punto + recordChanges: Mostrar todos los cambios realizados en el registro + tooltips: + search: Buscar por identificador o concepto + changes: Buscar por cambios. Los atributos deben buscarse por su nombre interno, para obtenerlo situar el cursor sobre el atributo. Audit logs: Registros de auditoría Property: Propiedad Before: Antes @@ -179,14 +1053,14 @@ es: Yes: Si Nothing: Nada actions: - insert: Crea - update: Actualiza - delete: Elimina - models: - Claim: Reclamación - ClaimDms: Documento - ClaimBeginning: Línea reclamada - ClaimObservation: Observación + Creates: Crea + Edits: Modifica + Deletes: Elimina + Accesses: Accede + Users: + User: Usuario + All: Todo + System: Sistema properties: id: ID claimFk: ID reclamación diff --git a/src/components/common/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 55395f260..568da613f 100644 --- a/src/components/common/VnSelectFilter.vue +++ b/src/components/common/VnSelectFilter.vue @@ -4,7 +4,7 @@ const emit = defineEmits(['update:modelValue', 'update:options']); const $props = defineProps({ modelValue: { - type: [String, Number], + type: [String, Number, Object], default: null, }, options: { @@ -15,6 +15,14 @@ const $props = defineProps({ type: String, default: '', }, + filterOptions: { + type: Array, + default: () => [], + }, + isClearable: { + type: Boolean, + default: true, + }, }); const { optionLabel, options } = toRefs($props); const myOptions = ref([]); @@ -28,18 +36,22 @@ function setOptions(data) { setOptions(options.value); const filter = (val, options) => { - const search = val.toLowerCase(); + const search = val.toString().toLowerCase(); - if (val === '') return options; + if (!search) return options; return options.filter((row) => { + if ($props.filterOptions.length) { + return $props.filterOptions.some((prop) => { + const propValue = String(row[prop]).toLowerCase(); + return propValue.includes(search); + }); + } + const id = row.id; - const name = row[$props.optionLabel].toLowerCase(); + const optionLabel = String(row[$props.optionLabel]).toLowerCase(); - const idMatches = id == search; - const nameMatches = name.indexOf(search) > -1; - - return idMatches || nameMatches; + return id == search || optionLabel.includes(search); }); }; @@ -85,11 +97,16 @@ const value = computed({ fill-input ref="vnSelectRef" > - - + + - + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f63b75de6..2669b53a9 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,8 +1,10 @@ - - - - - {{ t('components.cardDescriptor.mainList') }} - - - + + + + {{ t('components.smartCard.openSummary') }} + + {{ t('components.cardDescriptor.summary') }} - {{ t('components.cardDescriptor.moreOptions') }} @@ -211,8 +234,6 @@ watch($props, async () => { width: 256px; .header { display: flex; - justify-content: space-between; - align-items: stretch; } .icons { margin: 0 10px; diff --git a/src/components/ui/CardList.vue b/src/components/ui/CardList.vue index ed1df7668..7d7cee23e 100644 --- a/src/components/ui/CardList.vue +++ b/src/components/ui/CardList.vue @@ -1,66 +1,109 @@ + - - {{ $props.title ?? `#${$props.id}` }} + + + + {{ $props.title }} + + + {{ t('ID') }}: {{ $props.id }} + + + - - + + - + + @@ -73,11 +116,11 @@ const $props = defineProps({ background-color: var(--vn-gray); } .list-items { - width: 90%; -} -@media (max-width: $breakpoint-xs) { - .list-items { - width: 85%; - } + width: 75%; } + + + es: + ID: ID + diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index 9f3d9756b..33a6980d5 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -27,7 +27,7 @@ defineExpose({ async function fetch() { const params = {}; - if (props.filter) params.filter = props.filter; + if (props.filter) params.filter = JSON.stringify(props.filter); const { data } = await axios.get(props.url, { params }); entity.value = data; @@ -73,6 +73,7 @@ watch(props, async () => { .cardSummary { width: 100%; + .summaryHeader { text-align: center; font-size: 20px; @@ -82,11 +83,14 @@ watch(props, async () => { .summaryBody { display: flex; flex-direction: row; + flex-wrap: wrap; justify-content: space-evenly; gap: 15px; padding: 15px; + background-color: var(--vn-gray); > .q-card.vn-one { + width: 350px; flex: 1; } > .q-card.vn-two { diff --git a/src/components/ui/VnFilterPanel.vue b/src/components/ui/VnFilterPanel.vue index d78c3ba13..9cd5a052f 100644 --- a/src/components/ui/VnFilterPanel.vue +++ b/src/components/ui/VnFilterPanel.vue @@ -26,7 +26,7 @@ const props = defineProps({ }, }); -const emit = defineEmits(['refresh', 'clear']); +const emit = defineEmits(['refresh', 'clear', 'search']); const arrayData = useArrayData(props.dataKey); const store = arrayData.store; @@ -41,18 +41,15 @@ onMounted(() => { const isLoading = ref(false); async function search() { - for (const param in userParams.value) { - if (userParams.value[param] === '' || userParams.value[param] === null) { - delete userParams.value[param]; - delete store.userParams[param]; - } - } - const params = { ...userParams.value }; isLoading.value = true; - await arrayData.addFilter({ params }); + const params = { ...userParams.value }; + const { params: newParams } = await arrayData.addFilter({ params }); + userParams.value = newParams; + if (!props.showAll && !Object.values(params).length) store.data = []; isLoading.value = false; + emit('search'); } async function reload() { @@ -78,10 +75,11 @@ async function clearFilters() { const tags = computed(() => { const params = []; - for (const param in store.userParams) { + for (const param in userParams.value) { + if (!userParams.value[param]) continue; params.push({ label: param, - value: store.userParams[param], + value: userParams.value[param], }); } @@ -89,8 +87,7 @@ const tags = computed(() => { }); async function remove(key) { - delete userParams.value[key]; - delete store.userParams[key]; + userParams.value[key] = null; await search(); } @@ -106,6 +103,7 @@ function formatValue(value) { return `"${value}"`; } + @@ -119,32 +117,32 @@ function formatValue(value) { {{ t('Remove filters') }} {{ t('Refresh') }} - + @@ -172,35 +170,35 @@ function formatValue(value) { - - - - - - - - + + + + + + + + -es: +es: No filters applied: No se han aplicado filtros Applied filters: Filtros aplicados Remove filters: Eliminar filtros diff --git a/src/components/ui/VnLinkPhone.vue b/src/components/ui/VnLinkPhone.vue new file mode 100644 index 000000000..4445b99c9 --- /dev/null +++ b/src/components/ui/VnLinkPhone.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index 72375e804..1565fc8b4 100644 --- a/src/components/ui/VnLv.vue +++ b/src/components/ui/VnLv.vue @@ -4,9 +4,10 @@ import { dashIfEmpty } from 'src/filters'; const $props = defineProps({ label: { type: String, default: null }, - value: { type: [Number, String, Boolean], default: null }, - titleLabel: { type: String, default: null }, - titleValue: { type: [Number, String, Boolean], default: null }, + value: { + type: [String, Boolean], + default: null, + }, info: { type: String, default: null }, dash: { type: Boolean, default: true }, }); @@ -23,7 +24,7 @@ const isBooleanValue = computed(() => typeof $props.value === 'boolean'); - {{ $props.label }} + {{ $props.label }} diff --git a/src/components/ui/VnPaginate.vue b/src/components/ui/VnPaginate.vue index d21d073f2..c75761462 100644 --- a/src/components/ui/VnPaginate.vue +++ b/src/components/ui/VnPaginate.vue @@ -50,6 +50,10 @@ const props = defineProps({ type: Boolean, default: true, }, + exprBuilder: { + type: Function, + default: null, + }, }); const emit = defineEmits(['onFetch', 'onPaginate']); @@ -68,6 +72,7 @@ const arrayData = useArrayData(props.dataKey, { limit: props.limit, order: props.order, userParams: props.userParams, + exprBuilder: props.exprBuilder, }); const store = arrayData.store; diff --git a/src/components/ui/VnRow.vue b/src/components/ui/VnRow.vue new file mode 100644 index 000000000..c3c951528 --- /dev/null +++ b/src/components/ui/VnRow.vue @@ -0,0 +1,12 @@ + + + + + + diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index af6999b5b..35f6c1548 100644 --- a/src/components/ui/VnSearchbar.vue +++ b/src/components/ui/VnSearchbar.vue @@ -76,9 +76,9 @@ async function search() { const module = route.matched[1]; if (rows.length === 1) { const [firstRow] = rows; - await router.push({ path: `/${module.name}/${firstRow.id}` }); + await router.push({ path: `${module.path}/${firstRow.id}` }); } else if (route.matched.length > 3) { - await router.push({ path: `/${module.name}` }); + await router.push({ path: `/${module.path}` }); arrayData.updateStateParams(); } } @@ -105,7 +105,11 @@ async function search() { class="cursor-pointer" /> - + {{ props.info }} diff --git a/src/composables/downloadFile.js b/src/composables/downloadFile.js new file mode 100644 index 000000000..877d2a254 --- /dev/null +++ b/src/composables/downloadFile.js @@ -0,0 +1,11 @@ +import { useSession } from 'src/composables/useSession'; +import { getUrl } from './getUrl'; + +const session = useSession(); +const token = session.getToken(); + +export async function downloadFile(dmsId) { + let appUrl = await getUrl('', 'lilium'); + appUrl = appUrl.replace('/#/', ''); + window.open(`${appUrl}/api/dms/${dmsId}/downloadFile?access_token=${token}`); +} diff --git a/src/composables/getUrl.js b/src/composables/getUrl.js index f2bd9ddb9..1e6aec4c6 100644 --- a/src/composables/getUrl.js +++ b/src/composables/getUrl.js @@ -1,11 +1,10 @@ import axios from 'axios'; -export async function getUrl(route, appName = 'salix') { - const filter = { - where: { and: [{ appName: appName }, { environment: process.env.NODE_ENV }] }, - }; +export async function getUrl(route, app = 'salix') { + let url; - const { data } = await axios.get('Urls/findOne', { params: { filter } }); - const url = data.url; - return route ? url + route : url; + await axios.get('Urls/getUrl', { params: { app } }).then((res) => { + url = res.data + route; + }); + return url; } diff --git a/src/composables/useArrayData.js b/src/composables/useArrayData.js index 9aff0eaa8..523340cc9 100644 --- a/src/composables/useArrayData.js +++ b/src/composables/useArrayData.js @@ -2,6 +2,7 @@ import { onMounted, ref, computed } from 'vue'; import { useRouter, useRoute } from 'vue-router'; import axios from 'axios'; import { useArrayDataStore } from 'stores/useArrayDataStore'; +import { buildFilter } from 'filters/filterPanel'; const arrayDataStore = useArrayDataStore(); @@ -29,6 +30,10 @@ export function useArrayData(key, userOptions) { } }); + if (key && userOptions) { + setOptions(); + } + function setOptions() { const allowedOptions = [ 'url', @@ -39,10 +44,11 @@ export function useArrayData(key, userOptions) { 'skip', 'userParams', 'userFilter', + 'exprBuilder', ]; if (typeof userOptions === 'object') { for (const option in userOptions) { - const isEmpty = userOptions[option] == null || userOptions[option] == ''; + const isEmpty = userOptions[option] == null || userOptions[option] === ''; if (isEmpty || !allowedOptions.includes(option)) continue; if (Object.prototype.hasOwnProperty.call(store, option)) { @@ -64,16 +70,27 @@ export function useArrayData(key, userOptions) { skip: store.skip, }; - Object.assign(filter, store.userFilter); - Object.assign(store.filter, filter); + let exprFilter; + let userParams = { ...store.userParams }; + if (store?.exprBuilder) { + const where = buildFilter(userParams, (param, value) => { + const res = store.exprBuilder(param, value); + if (res) delete userParams[param]; + return res; + }); + exprFilter = where ? { where } : null; + } + Object.assign(filter, store.userFilter, exprFilter); + Object.assign(store.filter, filter); const params = { filter: JSON.stringify(store.filter), }; - Object.assign(params, store.userParams); + Object.assign(params, userParams); store.isLoading = true; + const response = await axios.get(store.url, { signal: canceller.signal, params, @@ -97,6 +114,7 @@ export function useArrayData(key, userOptions) { store.isLoading = false; canceller = null; + return response; } function destroy() { @@ -121,9 +139,30 @@ export function useArrayData(key, userOptions) { async function addFilter({ filter, params }) { if (filter) store.userFilter = Object.assign(store.userFilter, filter); - if (params) store.userParams = Object.assign(store.userParams, params); + + let userParams = Object.assign({}, store.userParams, params); + userParams = sanitizerParams(userParams, store?.exprBuilder); + + store.userParams = userParams; await fetch({ append: false }); + return { filter, params }; + } + + function sanitizerParams(params, exprBuilder) { + for (const param in params) { + if (params[param] === '' || params[param] === null) { + delete store.userParams[param]; + delete params[param]; + if (store.filter?.where) { + delete store.filter.where[Object.keys(exprBuilder ? exprBuilder(param) : param)[0]]; + if (Object.keys(store.filter.where).length === 0) { + delete store.filter.where; + } + } + } + } + return params; } async function loadMore() { @@ -147,10 +186,11 @@ export function useArrayData(key, userOptions) { if (store.userParams && Object.keys(store.userParams).length !== 0) query.params = JSON.stringify(store.userParams); - router.replace({ - path: route.path, - query: query, - }); + if (router) + router.replace({ + path: route.path, + query: query, + }); } const totalRows = computed(() => (store.data && store.data.length) || 0); diff --git a/src/composables/useCamelCase.js b/src/composables/useCamelCase.js new file mode 100644 index 000000000..5285b022a --- /dev/null +++ b/src/composables/useCamelCase.js @@ -0,0 +1,3 @@ +export function useCamelCase(value) { + return value.replace(/[-_](.)/g, (_, char) => char.toUpperCase()); +} diff --git a/src/composables/useColor.js b/src/composables/useColor.js new file mode 100644 index 000000000..b325e985f --- /dev/null +++ b/src/composables/useColor.js @@ -0,0 +1,35 @@ +export function djb2a(string) { + let hash = 5381; + for (let i = 0; i < string.length; i++) + hash = ((hash << 5) + hash) ^ string.charCodeAt(i); + return hash >>> 0; +} + +export function useColor(value) { + return '#' + colors[djb2a(value || '') % colors.length]; +} + +const colors = [ + 'b5b941', // Yellow + 'ae9681', // Peach + 'd78767', // Salmon + 'cc7000', // Orange bright + 'e2553d', // Coral + '8B0000', // Red dark + 'de4362', // Red crimson + 'FF1493', // Ping intense + 'be39a2', // Pink light + 'b754cf', // Purple middle + 'a87ba8', // Pink + '8a69cd', // Blue lavender + 'ab20ab', // Purple dark + '00b5b8', // Turquoise + '1fa8a1', // Green ocean + '5681cf', // Blue steel + '3399fe', // Blue sky + '6d9c3e', // Green chartreuse + '51bb51', // Green lime + '518b8b', // Gray board + '7e7e7e', // Gray + '5d5d5d', // Gray dark +]; diff --git a/src/composables/useFirstUpper.js b/src/composables/useFirstUpper.js new file mode 100644 index 000000000..36378c05f --- /dev/null +++ b/src/composables/useFirstUpper.js @@ -0,0 +1,3 @@ +export function useFirstUpper(str) { + return str && str.charAt(0).toUpperCase() + str.substr(1); +} diff --git a/src/composables/useNotify.js b/src/composables/useNotify.js new file mode 100644 index 000000000..2f0e1c257 --- /dev/null +++ b/src/composables/useNotify.js @@ -0,0 +1,22 @@ +import { Notify } from 'quasar'; +import { i18n } from 'src/boot/i18n'; + +export default function useNotify() { + const notify = (message, type, icon) => { + const defaultIcons = { + warning: 'warning', + negative: 'error', + positive: 'check', + }; + + Notify.create({ + message: i18n.global.t(message), + type: type, + icon: icon ? icon : defaultIcons[type], + }); + }; + + return { + notify, + }; +} diff --git a/src/composables/useState.js b/src/composables/useState.js index f0ff830ab..1c797e992 100644 --- a/src/composables/useState.js +++ b/src/composables/useState.js @@ -8,6 +8,7 @@ const user = ref({ nickname: '', lang: '', darkMode: null, + companyFk: null, }); const roles = ref([]); @@ -23,6 +24,7 @@ export function useState() { nickname: user.value.nickname, lang: user.value.lang, darkMode: user.value.darkMode, + companyFk: user.value.companyFk, }; }); } @@ -34,6 +36,7 @@ export function useState() { nickname: data.nickname, lang: data.lang, darkMode: data.darkMode, + companyFk: data.companyFk, }; } @@ -59,7 +62,6 @@ export function useState() { delete state.value[name]; } - return { getUser, setUser, @@ -69,6 +71,6 @@ export function useState() { get, unset, drawer, - headerMounted + headerMounted, }; } diff --git a/src/composables/useUserConfig.js b/src/composables/useUserConfig.js index 767ffb54e..692943978 100644 --- a/src/composables/useUserConfig.js +++ b/src/composables/useUserConfig.js @@ -1,14 +1,24 @@ import axios from 'axios'; import { useState } from './useState'; +import useNotify from './useNotify'; export function useUserConfig() { const state = useState(); + const { notify } = useNotify(); async function fetch() { - const { data } = await axios.get('UserConfigs/getUserConfig'); - const user = state.getUser().value; - user.darkMode = data.darkMode; - state.setUser(user); + try { + const { data } = await axios.get('UserConfigs/getUserConfig'); + const user = state.getUser().value; + user.darkMode = data.darkMode; + user.companyFk = data.companyFk; + state.setUser(user); + + return data; + } catch (error) { + notify('globals.errors.userConfig', 'negative'); + console.error('Error fetching user config:', error); + } } return { diff --git a/src/composables/useValidator.js b/src/composables/useValidator.js index bc48332a2..3f9f00367 100644 --- a/src/composables/useValidator.js +++ b/src/composables/useValidator.js @@ -1,19 +1,12 @@ -import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; -import axios from 'axios'; import validator from 'validator'; - -const models = ref(null); +import { useValidationsStore } from 'src/stores/useValidationsStore'; export function useValidator() { - if (!models.value) fetch(); - - function fetch() { - axios.get('Schemas/ModelInfo').then((response) => (models.value = response.data)); - } + const models = useValidationsStore().validations; function validate(propertyRule) { - const modelInfo = models.value; + const modelInfo = models; if (!modelInfo || !propertyRule) return; const rule = propertyRule.split('.'); @@ -75,5 +68,6 @@ export function useValidator() { return { validate, + models, }; } diff --git a/src/css/app.scss b/src/css/app.scss index 0f04c9ad8..02d4f8946 100644 --- a/src/css/app.scss +++ b/src/css/app.scss @@ -33,6 +33,7 @@ body.body--light { --vn-gray: #f5f5f5; --vn-label: #5f5f5f; --vn-dark: white; + --vn-light-gray: #e7e3e3; } body.body--dark { @@ -40,8 +41,15 @@ body.body--dark { --vn-gray: #313131; --vn-label: #a8a8a8; --vn-dark: #292929; + --vn-light-gray: #424242; } .bg-vn-dark { background-color: var(--vn-dark); } + +.vn-card { + background-color: var(--vn-gray); + color: var(--vn-text); + border-radius: 8px; +} diff --git a/src/filters/filterPanel.js b/src/filters/filterPanel.js new file mode 100644 index 000000000..ee91e6749 --- /dev/null +++ b/src/filters/filterPanel.js @@ -0,0 +1,94 @@ +/** + * Passes a loopback fields filter to an object. + * + * @param {Object} fields The fields object or array + * @return {Object} The fields as object + */ +function fieldsToObject(fields) { + let fieldsObj = {}; + + if (Array.isArray(fields)) { + for (let field of fields) fieldsObj[field] = true; + } else if (typeof fields == 'object') { + for (let field in fields) { + if (fields[field]) fieldsObj[field] = true; + } + } + + return fieldsObj; +} + +/** + * Merges two loopback fields filters. + * + * @param {Object|Array} src The source fields + * @param {Object|Array} dst The destination fields + * @return {Array} The merged fields as an array + */ +function mergeFields(src, dst) { + let fields = {}; + Object.assign(fields, fieldsToObject(src), fieldsToObject(dst)); + return Object.keys(fields); +} + +/** + * Merges two loopback where filters. + * + * @param {Object|Array} src The source where + * @param {Object|Array} dst The destination where + * @return {Array} The merged wheres + */ +function mergeWhere(src, dst) { + let and = []; + if (src) and.push(src); + if (dst) and.push(dst); + return simplifyOperation(and, 'and'); +} + +/** + * Merges two loopback filters returning the merged filter. + * + * @param {Object} src The source filter + * @param {Object} dst The destination filter + * @return {Object} The result filter + */ +function mergeFilters(src, dst) { + let res = Object.assign({}, dst); + + if (!src) return res; + + if (src.fields) res.fields = mergeFields(src.fields, res.fields); + if (src.where) res.where = mergeWhere(res.where, src.where); + if (src.include) res.include = src.include; + if (src.order) res.order = src.order; + if (src.limit) res.limit = src.limit; + if (src.offset) res.offset = src.offset; + if (src.skip) res.skip = src.skip; + + return res; +} + +function simplifyOperation(operation, operator) { + switch (operation.length) { + case 0: + return undefined; + case 1: + return operation[0]; + default: + return { [operator]: operation }; + } +} + +function buildFilter(params, builderFunc) { + let and = []; + + for (let param in params) { + let value = params[param]; + if (value == null) continue; + let expr = builderFunc(param, value); + if (expr) and.push(expr); + } + return simplifyOperation(and, 'and'); +} + +export { fieldsToObject, mergeFields, mergeWhere, mergeFilters, buildFilter }; diff --git a/src/filters/index.js b/src/filters/index.js index 158ce1009..b0c441641 100644 --- a/src/filters/index.js +++ b/src/filters/index.js @@ -2,6 +2,7 @@ import toLowerCase from './toLowerCase'; import toDate from './toDate'; import toDateString from './toDateString'; import toDateHour from './toDateHour'; +import toRelativeDate from './toRelativeDate'; import toCurrency from './toCurrency'; import toPercentage from './toPercentage'; import toLowerCamel from './toLowerCamel'; @@ -13,6 +14,7 @@ export { toDate, toDateString, toDateHour, + toRelativeDate, toCurrency, toPercentage, dashIfEmpty, diff --git a/src/filters/toCurrency.js b/src/filters/toCurrency.js index 5005e95bc..f820c0127 100644 --- a/src/filters/toCurrency.js +++ b/src/filters/toCurrency.js @@ -9,13 +9,10 @@ export default function (value, symbol = 'EUR', fractionSize = 2) { style: 'currency', currency: symbol, minimumFractionDigits: fractionSize, - maximumFractionDigits: fractionSize + maximumFractionDigits: fractionSize, }; const lang = locale.value == 'es' ? 'de' : locale.value; - return new Intl.NumberFormat(lang, options) - .format(value); - - -} \ No newline at end of file + return new Intl.NumberFormat(lang, options).format(value); +} diff --git a/src/filters/toRelativeDate.js b/src/filters/toRelativeDate.js new file mode 100644 index 000000000..76e67dbea --- /dev/null +++ b/src/filters/toRelativeDate.js @@ -0,0 +1,32 @@ +import { useI18n } from 'vue-i18n'; + +export default function formatDate(dateVal) { + const { t } = useI18n(); + const today = new Date(); + if (dateVal == null) return ''; + + const date = new Date(dateVal); + const dateZeroTime = new Date(dateVal); + dateZeroTime.setHours(0, 0, 0, 0); + const diff = Math.trunc( + (today.getTime() - dateZeroTime.getTime()) / (1000 * 3600 * 24) + ); + let format; + if (diff === 0) format = t('globals.today'); + else if (diff === 1) format = t('globals.yesterday'); + else if (diff > 1 && diff < 7) { + const options = { weekday: 'short' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } else if (today.getFullYear() === date.getFullYear()) { + const options = { day: 'numeric', month: 'short' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } else { + const options = { year: 'numeric', month: '2-digit', day: '2-digit' }; + format = date.toLocaleDateString(t('globals.dateFormat'), options); + } + + // Formatear la hora en HH:mm + const hours = date.getHours().toString().padStart(2, '0'); + const minutes = date.getMinutes().toString().padStart(2, '0'); + return `${format} ${hours}:${minutes}`; +} diff --git a/src/i18n/en/index.js b/src/i18n/en/index.js index 948332d46..5e7e116c5 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -5,6 +5,9 @@ export default { en: 'English', }, language: 'Language', + entity: 'Entity', + user: 'User', + details: 'Details', collapseMenu: 'Collapse left menu', backToDashboard: 'Return to dashboard', notifications: 'Notifications', @@ -13,8 +16,12 @@ export default { pinnedModules: 'Pinned modules', darkMode: 'Dark mode', logOut: 'Log out', + date: 'Date', dataSaved: 'Data saved', dataDeleted: 'Data deleted', + search: 'Search', + changes: 'Changes', + dataCreated: 'Data created', add: 'Add', create: 'Create', save: 'Save', @@ -36,13 +43,34 @@ export default { summary: { basicData: 'Basic data', }, + today: 'Today', + yesterday: 'Yesterday', + dateFormat: 'en-GB', + microsip: 'Open in MicroSIP', noSelectedRows: `You don't have any line selected`, + downloadCSVSuccess: 'CSV downloaded successfully', + // labels compartidos entre vistas + reference: 'Reference', + agency: 'Agency', + wareHouseOut: 'Warehouse Out', + wareHouseIn: 'Warehouse In', + landed: 'Landed', + shipped: 'Shipped', + totalEntries: 'Total entries', + amount: 'Amount', + packages: 'Packages', + download: 'Download', + selectRows: 'Select all { numberRows } row(s)', + allRows: 'All { numberRows } row(s)', + markAll: 'Mark all', }, 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', + create: 'Error during creation', }, login: { title: 'Login', @@ -225,7 +253,6 @@ export default { invoice: 'Invoice', shipped: 'Shipped', landed: 'Landed', - packages: 'Packages', consigneePhone: 'Consignee phone', consigneeMobile: 'Consignee mobile', clientPhone: 'Client phone', @@ -242,7 +269,6 @@ export default { description: 'Description', price: 'Price', discount: 'Discount', - amount: 'Amount', packing: 'Packing', hasComponentLack: 'Component lack', itemShortage: 'Not visible', @@ -274,6 +300,7 @@ export default { development: 'Development', log: 'Audit logs', notes: 'Notes', + action: 'Action', }, list: { customer: 'Customer', @@ -334,7 +361,6 @@ export default { assignedTo: 'Assigned', created: 'Created', state: 'State', - packages: 'Packages', picked: 'Picked', returnOfMaterial: 'Return of material authorization (RMA)', }, @@ -346,8 +372,10 @@ export default { }, invoiceOut: { pageTitles: { - invoiceOuts: 'Invoices Out', + invoiceOuts: 'Invoice out', list: 'List', + negativeBases: 'Negative Bases', + globalInvoicing: 'Global invoicing', createInvoiceOut: 'Create invoice out', summary: 'Summary', basicData: 'Basic Data', @@ -356,7 +384,6 @@ export default { ref: 'Reference', issued: 'Issued', shortIssued: 'Issued', - amount: 'Amount', client: 'Client', created: 'Created', shortCreated: 'Created', @@ -366,7 +393,6 @@ export default { }, card: { issued: 'Issued', - amount: 'Amount', client: 'Client', company: 'Company', customerCard: 'Customer card', @@ -389,6 +415,138 @@ export default { shipped: 'Shipped', totalWithVat: 'Amount', }, + globalInvoices: { + errors: { + chooseValidClient: 'Choose a valid client', + chooseValidCompany: 'Choose a valid company', + chooseValidPrinter: 'Choose a valid printer', + fillDates: 'Invoice date and the max date should be filled', + invoiceDateLessThanMaxDate: 'Invoice date can not be less than max date', + invoiceWithFutureDate: 'Exists an invoice with a future date', + noTicketsToInvoice: 'There are not clients to invoice', + criticalInvoiceError: 'Critical invoicing error, process stopped', + }, + table: { + client: 'Client', + addressId: 'Address id', + streetAddress: 'Street', + }, + statusCard: { + percentageText: '{getPercentage}% {getAddressNumber} of {getNAddresses}', + pdfsNumberText: '{nPdfs} of {totalPdfs} PDFs', + }, + }, + negativeBases: { + from: 'From', + to: 'To', + company: 'Company', + country: 'Country', + clientId: 'Client Id', + client: 'Client', + amount: 'Amount', + base: 'Base', + ticketId: 'Ticket Id', + active: 'Active', + hasToInvoice: 'Has to Invoice', + verifiedData: 'Verified Data', + comercial: 'Comercial', + errors: { + downloadCsvFailed: 'CSV download failed', + }, + }, + }, + shelving: { + pageTitles: { + shelving: 'Shelving', + shelvingList: 'Shelving List', + create: 'Create', + summary: 'Summary', + basicData: 'Basic Data', + log: 'Logs', + }, + list: { + parking: 'Parking', + priority: 'Priority', + newShelving: 'New Shelving', + }, + summary: { + code: 'Code', + parking: 'Parking', + priority: 'Priority', + worker: 'Worker', + recyclable: 'Recyclable', + }, + basicData: { + code: 'Code', + parking: 'Parking', + priority: 'Priority', + recyclable: 'Recyclable', + }, + }, + invoiceIn: { + pageTitles: { + invoiceIns: 'Invoices In', + list: 'List', + createInvoiceIn: 'Create invoice in', + summary: 'Summary', + basicData: 'Basic data', + vat: 'VAT', + dueDay: 'Due day', + intrastat: 'Intrastat', + log: 'Logs', + }, + list: { + ref: 'Reference', + supplier: 'Supplier', + supplierRef: 'Supplier ref.', + serialNumber: 'Serial number', + serial: 'Serial', + file: 'File', + issued: 'Issued', + isBooked: 'Is booked', + awb: 'AWB', + amount: 'Amount', + }, + card: { + issued: 'Issued', + amount: 'Amount', + client: 'Client', + company: 'Company', + customerCard: 'Customer card', + ticketList: 'Ticket List', + vat: 'Vat', + dueDay: 'Due day', + intrastat: 'Intrastat', + }, + summary: { + supplier: 'Supplier', + supplierRef: 'Supplier ref.', + currency: 'Currency', + docNumber: 'Doc number', + issued: 'Expedition date', + operated: 'Operation date', + bookEntried: 'Entry date', + bookedDate: 'Booked date', + sage: 'Sage withholding', + vat: 'Undeductible VAT', + company: 'Company', + booked: 'Booked', + expense: 'Expense', + taxableBase: 'Taxable base', + rate: 'Rate', + sageVat: 'Sage vat', + sageTransaction: 'Sage transaction', + dueDay: 'Date', + bank: 'Bank', + amount: 'Amount', + foreignValue: 'Foreign value', + dueTotal: 'Due day', + noMatch: 'Do not match', + code: 'Code', + net: 'Net', + stems: 'Stems', + country: 'Country', + }, }, worker: { pageTitles: { @@ -448,6 +606,7 @@ export default { typesList: 'Types List', typeCreate: 'Create type', typeEdit: 'Edit type', + wagonCounter: 'Trolley counter', }, type: { name: 'Name', @@ -504,6 +663,81 @@ export default { }, }, }, + supplier: { + pageTitles: { + suppliers: 'Suppliers', + supplier: 'Supplier', + list: 'List', + create: 'Create', + summary: 'Summary', + }, + list: { + payMethod: 'Pay method', + payDeadline: 'Pay deadline', + payDay: 'Pay day', + account: 'Account', + newSupplier: 'New supplier', + }, + summary: { + responsible: 'Responsible', + notes: 'Notes', + verified: 'Verified', + isActive: 'Is active', + billingData: 'Billing data', + payMethod: 'Pay method', + payDeadline: 'Pay deadline', + payDay: 'Día de pago', + account: 'Account', + fiscalData: 'Fiscal data', + sageTaxType: 'Sage tax type', + sageTransactionType: 'Sage transaction type', + sageWithholding: 'Sage withholding', + supplierActivity: 'Supplier activity', + healthRegister: 'Healt register', + fiscalAddress: 'Fiscal address', + socialName: 'Social name', + taxNumber: 'Tax number', + street: 'Street', + city: 'City', + postCode: 'Postcode', + province: 'Province', + country: 'Country', + }, + create: { + supplierName: 'Supplier name', + }, + }, + travel: { + pageTitles: { + travel: 'Travels', + list: 'List', + create: 'Create', + summary: 'Summary', + extraCommunity: 'Extra community', + }, + summary: { + confirmed: 'Confirmed', + entryId: 'Entry Id', + freight: 'Freight', + package: 'Package', + delivered: 'Delivered', + received: 'Received', + entries: 'Entries', + cloneShipping: 'Clone travel', + CloneTravelAndEntries: 'Clone travel and his entries', + AddEntry: 'Add entry', + }, + variables: { + search: 'Id/Reference', + agencyModeFk: 'Agency', + warehouseInFk: ' Warehouse In', + warehouseOutFk: 'Warehouse Out', + landedFrom: 'Landed from', + landedTo: 'Landed to', + continent: 'Continent out', + totalEntries: 'Total entries', + }, + }, components: { topbar: {}, userPanel: { @@ -511,9 +745,11 @@ export default { logOut: 'Log Out', }, smartCard: { - openCard: 'View card', - openSummary: 'Open summary', - viewDescription: 'View description', + downloadFile: 'Download file', + clone: 'Clone', + openCard: 'View', + openSummary: 'Summary', + viewDescription: 'Description', }, cardDescriptor: { mainList: 'Main list', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index 9b452ab22..4e570e7b4 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -5,6 +5,9 @@ export default { en: 'Inglés', }, language: 'Idioma', + entity: 'Entidad', + user: 'Usuario', + details: 'Detalles', collapseMenu: 'Contraer menú lateral', backToDashboard: 'Volver al tablón', notifications: 'Notificaciones', @@ -13,8 +16,12 @@ export default { pinnedModules: 'Módulos fijados', darkMode: 'Modo oscuro', logOut: 'Cerrar sesión', + date: 'Fecha', dataSaved: 'Datos guardados', dataDeleted: 'Datos eliminados', + search: 'Buscar', + changes: 'Cambios', + dataCreated: 'Datos creados', add: 'Añadir', create: 'Crear', save: 'Guardar', @@ -36,13 +43,33 @@ export default { summary: { basicData: 'Datos básicos', }, + today: 'Hoy', + yesterday: 'Ayer', + dateFormat: 'es-ES', noSelectedRows: `No tienes ninguna línea seleccionada`, + microsip: 'Abrir en MicroSIP', + downloadCSVSuccess: 'Descarga de CSV exitosa', + reference: 'Referencia', + agency: 'Agencia', + wareHouseOut: 'Alm. salida', + wareHouseIn: 'Alm. entrada', + landed: 'F. entrega', + shipped: 'F. envío', + totalEntries: 'Ent. totales', + amount: 'Importe', + packages: 'Bultos', + download: 'Descargar', + selectRows: 'Seleccionar las { numberRows } filas(s)', + allRows: 'Todo { numberRows } filas(s)', + markAll: 'Marcar todo', }, 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', + create: 'Error al crear', }, login: { title: 'Inicio de sesión', @@ -224,7 +251,6 @@ export default { invoice: 'Factura', shipped: 'Enviado', landed: 'Entregado', - packages: 'Bultos', consigneePhone: 'Tel. consignatario', consigneeMobile: 'Móv. consignatario', clientPhone: 'Tel. cliente', @@ -241,7 +267,6 @@ export default { description: 'Descripción', price: 'Precio', discount: 'Descuento', - amount: 'Importe', packing: 'Encajado', hasComponentLack: 'Faltan componentes', itemShortage: 'No visible', @@ -273,6 +298,7 @@ export default { photos: 'Fotos', log: 'Registros de auditoría', notes: 'Notas', + action: 'Acción', }, list: { customer: 'Cliente', @@ -333,7 +359,6 @@ export default { assignedTo: 'Asignada a', created: 'Creada', state: 'Estado', - packages: 'Bultos', picked: 'Recogida', returnOfMaterial: 'Autorización de retorno de materiales (RMA)', }, @@ -348,6 +373,8 @@ export default { pageTitles: { invoiceOuts: 'Fact. emitidas', list: 'Listado', + negativeBases: 'Bases Negativas', + globalInvoicing: 'Facturación global', createInvoiceOut: 'Crear fact. emitida', summary: 'Resumen', basicData: 'Datos básicos', @@ -356,7 +383,6 @@ export default { ref: 'Referencia', issued: 'Fecha emisión', shortIssued: 'F. emisión', - amount: 'Importe', client: 'Cliente', created: 'Fecha creación', shortCreated: 'F. creación', @@ -366,7 +392,6 @@ export default { }, card: { issued: 'Fecha emisión', - amount: 'Importe', client: 'Cliente', company: 'Empresa', customerCard: 'Ficha del cliente', @@ -389,6 +414,138 @@ export default { shipped: 'F. envío', totalWithVat: 'Importe', }, + globalInvoices: { + errors: { + chooseValidClient: 'Selecciona un cliente válido', + chooseValidCompany: 'Selecciona una empresa válida', + chooseValidPrinter: 'Selecciona una impresora válida', + fillDates: + 'La fecha de la factura y la fecha máxima deben estar completas', + invoiceDateLessThanMaxDate: + 'La fecha de la factura no puede ser menor que la fecha máxima', + invoiceWithFutureDate: 'Existe una factura con una fecha futura', + noTicketsToInvoice: 'No hay clientes para facturar', + criticalInvoiceError: 'Error crítico en la facturación, proceso detenido', + }, + table: { + client: 'Cliente', + addressId: 'Id dirección', + streetAddress: 'Dirección fiscal', + }, + statusCard: { + percentageText: '{getPercentage}% {getAddressNumber} de {getNAddresses}', + pdfsNumberText: '{nPdfs} de {totalPdfs} PDFs', + }, + }, + negativeBases: { + from: 'Desde', + to: 'Hasta', + company: 'Empresa', + country: 'País', + clientId: 'Id cliente', + client: 'Cliente', + amount: 'Importe', + base: 'Base', + ticketId: 'Id ticket', + active: 'Activo', + hasToInvoice: 'Facturar', + verifiedData: 'Datos comprobados', + comercial: 'Comercial', + errors: { + downloadCsvFailed: 'Error al descargar CSV', + }, + }, + }, + shelving: { + pageTitles: { + shelving: 'Carros', + shelvingList: 'Listado de carros', + create: 'Crear', + summary: 'Resumen', + basicData: 'Datos básicos', + log: 'Registros de auditoría', + }, + list: { + parking: 'Parking', + priority: 'Prioridad', + newShelving: 'Nuevo Carro', + }, + summary: { + code: 'Código', + parking: 'Parking', + priority: 'Prioridad', + worker: 'Trabajador', + recyclable: 'Reciclable', + }, + basicData: { + code: 'Código', + parking: 'Parking', + priority: 'Prioridad', + recyclable: 'Reciclable', + }, + }, + invoiceIn: { + pageTitles: { + invoiceIns: 'Fact. recibidas', + list: 'Listado', + createInvoiceIn: 'Crear fact. recibida', + summary: 'Resumen', + basicData: 'Datos básicos', + vat: 'IVA', + dueDay: 'Vencimiento', + intrastat: 'Intrastat', + log: 'Registros de auditoría', + }, + list: { + ref: 'Referencia', + supplier: 'Proveedor', + supplierRef: 'Ref. proveedor', + serialNumber: 'Num. serie', + shortIssued: 'F. emisión', + serial: 'Serie', + file: 'Fichero', + issued: 'Fecha emisión', + isBooked: 'Conciliada', + awb: 'AWB', + amount: 'Importe', + }, + card: { + issued: 'Fecha emisión', + amount: 'Importe', + client: 'Cliente', + company: 'Empresa', + customerCard: 'Ficha del cliente', + ticketList: 'Listado de tickets', + vat: 'Iva', + dueDay: 'Fecha de vencimiento', + }, + summary: { + supplier: 'Proveedor', + supplierRef: 'Ref. proveedor', + currency: 'Divisa', + docNumber: 'Número documento', + issued: 'Fecha de expedición', + operated: 'Fecha operación', + bookEntried: 'Fecha asiento', + bookedDate: 'Fecha contable', + sage: 'Retención sage', + vat: 'Iva no deducible', + company: 'Empresa', + booked: 'Contabilizada', + expense: 'Gasto', + taxableBase: 'Base imp.', + rate: 'Tasa', + sageTransaction: 'Sage transación', + dueDay: 'Fecha', + bank: 'Caja', + amount: 'Importe', + foreignValue: 'Divisa', + dueTotal: 'Vencimiento', + code: 'Código', + net: 'Neto', + stems: 'Tallos', + country: 'País', + }, }, worker: { pageTitles: { @@ -448,6 +605,7 @@ export default { typesList: 'Listado tipos', typeCreate: 'Crear tipo', typeEdit: 'Editar tipo', + wagonCounter: 'Contador de carros', }, type: { name: 'Nombre', @@ -504,6 +662,81 @@ export default { }, }, }, + supplier: { + pageTitles: { + suppliers: 'Proveedores', + supplier: 'Proveedor', + list: 'Listado', + create: 'Crear', + summary: 'Resumen', + }, + list: { + payMethod: 'Método de pago', + payDeadline: 'Plazo de pago', + payDay: 'Día de pago', + account: 'Cuenta', + newSupplier: 'Nuevo proveedor', + }, + summary: { + responsible: 'Responsable', + notes: 'Notas', + verified: 'Verificado', + isActive: 'Está activo', + billingData: 'Forma de pago', + payMethod: 'Método de pago', + payDeadline: 'Plazo de pago', + payDay: 'Día de pago', + account: 'Cuenta', + fiscalData: 'Data fiscal', + sageTaxType: 'Tipo de impuesto Sage', + sageTransactionType: 'Tipo de transacción Sage', + sageWithholding: 'Retención sage', + supplierActivity: 'Actividad proveedor', + healthRegister: 'Pasaporte sanitario', + fiscalAddress: 'Dirección fiscal', + socialName: 'Razón social', + taxNumber: 'NIF/CIF', + street: 'Dirección', + city: 'Población', + postCode: 'Código postal', + province: 'Provincia', + country: 'País', + }, + create: { + supplierName: 'Nombre del proveedor', + }, + }, + travel: { + pageTitles: { + travel: 'Envíos', + list: 'Listado', + create: 'Crear', + summary: 'Resumen', + extraCommunity: 'Extra comunitarios', + }, + summary: { + confirmed: 'Confirmado', + entryId: 'Id entrada', + freight: 'Porte', + package: 'Embalaje', + delivered: 'Enviada', + received: 'Recibida', + entries: 'Entradas', + cloneShipping: 'Clonar envío', + CloneTravelAndEntries: 'Clonar travel y sus entradas', + AddEntry: 'Añadir entrada', + }, + variables: { + search: 'Id/Referencia', + agencyModeFk: 'Agencia', + warehouseInFk: 'Alm. entrada', + warehouseOutFk: ' Alm. salida', + landedFrom: 'Llegada desde', + landedTo: 'Llegada hasta', + continent: 'Cont. Salida', + totalEntries: 'Ent. totales', + }, + }, components: { topbar: {}, userPanel: { @@ -511,9 +744,11 @@ export default { logOut: 'Cerrar sesión', }, smartCard: { - openCard: 'Ver ficha', - openSummary: 'Abrir detalles', - viewDescription: 'Ver descripción', + downloadFile: 'Descargar archivo', + clone: 'Clonar', + openCard: 'Ficha', + openSummary: 'Detalles', + viewDescription: 'Descripción', }, cardDescriptor: { mainList: 'Listado principal', diff --git a/src/pages/Claim/Card/ClaimAction.vue b/src/pages/Claim/Card/ClaimAction.vue new file mode 100644 index 000000000..44575f1d6 --- /dev/null +++ b/src/pages/Claim/Card/ClaimAction.vue @@ -0,0 +1,518 @@ + + + (claim = data)" + auto-load + /> + (resolvedStateId = data.id)" + auto-load + :where="{ code: 'resolved' }" + /> + (destinationTypes = data)" + /> + + + + + + {{ t('globals.collapseMenu') }} + + + + + + + + {{ `${t('Total claimed')}: ${toCurrency(totalClaimed)}` }} + + + + + + {{ t('claim.summary.actions') }} + + save({ responsibility: value })" + label-always + color="primary" + markers + :marker-labels="marker_labels" + :min="DEFAULT_MIN_RESPONSABILITY" + :max="DEFAULT_MAX_RESPONSABILITY" + /> + + + + + save({ isChargedToMana: value })" + /> + {{ t('mana') }} + + + + + + + + + + {{ value }} + + + + + + + updateDestination(value, row)" + /> + + + + + {{ toCurrency(value) }} + + + + + {{ toCurrency(value) }} + + + + + + + + + + + + + + + + + {{ column.label }} + + + + + {{ column.value.description }} + + + {{ column.value }} + + + + + + + + + + + + + + + + + + + + + + {{ t('dialog title') }} + + + + + + + + + + + + + + + + + +en: + mana: Is paid with mana + dialog title: Change destination to all selected rows + confirmGreuges: Do you want to insert complaints? + confirmGreugesMessage: Insert complaints into the client's record + +es: + mana: Cargado al maná + Delivered: Descripción + Quantity: Cantidad + Claimed: Rec + Description: Descripción + Price: Precio + Discount: Dto. + Destination: Destino + Landed: F.entrega + Remove line: Eliminar línea + Total claimed: Total reclamado + Regularize: Regularizar + Change destination: Cambiar destino + Import claim: Importar reclamación + dialog title: Cambiar destino en todas las filas seleccionadas + Remove: Eliminar + dialogGreuge title: Insertar greuges en la ficha del cliente + ClaimGreugeDescription: Id reclamación + Id item: Id artículo + confirmGreuges: ¿Desea insertar greuges? + confirmGreugesMessage: Insertar greuges en la ficha del cliente + diff --git a/src/pages/Claim/Card/ClaimBasicData.vue b/src/pages/Claim/Card/ClaimBasicData.vue index ddf669dd0..0f067f281 100644 --- a/src/pages/Claim/Card/ClaimBasicData.vue +++ b/src/pages/Claim/Card/ClaimBasicData.vue @@ -6,6 +6,8 @@ import { useI18n } from 'vue-i18n'; import { useSession } from 'src/composables/useSession'; import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; +import VnInputDate from "components/common/VnInputDate.vue"; const route = useRoute(); const { t } = useI18n(); @@ -90,138 +92,94 @@ const statesFilter = { auto-load /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - filter(value, update, workerFilter) - " - :rules="validate('claim.claimStateFk')" - :input-debounce="0" - > - - - - - - - - - filter(value, update, statesFilter) - " - :rules="validate('claim.claimStateFk')" - :input-debounce="0" - > - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + filter(value, update, workerFilter)" + :rules="validate('claim.claimStateFk')" + :input-debounce="0" + > + + + + + + + + + filter(value, update, statesFilter)" + :rules="validate('claim.claimStateFk')" + :input-debounce="0" + > + + + + + + + + + + + + + + + + + + - - diff --git a/src/pages/Claim/Card/ClaimCard.vue b/src/pages/Claim/Card/ClaimCard.vue index 03b9889f0..a8c832967 100644 --- a/src/pages/Claim/Card/ClaimCard.vue +++ b/src/pages/Claim/Card/ClaimCard.vue @@ -22,11 +22,6 @@ const $props = defineProps({ const entityId = computed(() => { return $props.id || route.params.id; }); - -let salixUrl; -onMounted(async () => { - salixUrl = await getUrl(`claim/${entityId.value}`); -}); @@ -42,18 +37,6 @@ onMounted(async () => { - - - - - {{ t('Action') }} - - diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index af7e84d38..85ae9f7e1 100644 --- a/src/pages/Claim/Card/ClaimDescriptor.vue +++ b/src/pages/Claim/Card/ClaimDescriptor.vue @@ -7,6 +7,7 @@ import { useState } from 'src/composables/useState'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; +import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; import CardDescriptor from 'components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; @@ -62,13 +63,18 @@ const filter = { ], }; +const STATE_COLOR = { + pending: 'positive', + managed: 'warning', + resolved: 'negative', +}; + function stateColor(code) { - if (code === 'pending') return 'positive'; - if (code === 'managed') return 'warning'; - if (code === 'resolved') return 'negative'; + return STATE_COLOR[code]; } const data = ref(useCardDescription()); const setData = (entity) => { + if (!entity) return; data.value = useCardDescription(entity.client.name, entity.id); state.set('ClaimDescriptor', entity); }; @@ -83,6 +89,7 @@ const setData = (entity) => { :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + data-key="claimData" > @@ -113,23 +120,23 @@ const setData = (entity) => { {{ entity.worker.user.name }} - + - {{ entity.client.salesPersonUser.name }} - + {{ entity.client?.salesPersonUser?.name }} + - + diff --git a/src/pages/Claim/Card/ClaimDescriptorMenu.vue b/src/pages/Claim/Card/ClaimDescriptorMenu.vue index 5688613d6..d88c3d120 100644 --- a/src/pages/Claim/Card/ClaimDescriptorMenu.vue +++ b/src/pages/Claim/Card/ClaimDescriptorMenu.vue @@ -37,7 +37,7 @@ function confirmPickupOrder() { data: { address: customer.email, }, - send: sendPickupOrder, + promise: sendPickupOrder, }, }); } diff --git a/src/pages/Claim/Card/ClaimDevelopment.vue b/src/pages/Claim/Card/ClaimDevelopment.vue index 0c83bdadd..0ab3c6c90 100644 --- a/src/pages/Claim/Card/ClaimDevelopment.vue +++ b/src/pages/Claim/Card/ClaimDevelopment.vue @@ -7,6 +7,7 @@ import FetchData from 'components/FetchData.vue'; import VnSelectFilter from 'components/common/VnSelectFilter.vue'; import { getUrl } from 'composables/getUrl'; import { tMobile } from 'composables/tMobile'; +import router from 'src/router'; const route = useRoute(); @@ -19,7 +20,7 @@ const claimResponsibles = ref([]); const claimRedeliveries = ref([]); const workers = ref([]); const selected = ref([]); -const insertButtonRef = ref(); +const saveButtonRef = ref(); let salixUrl; onMounted(async () => { @@ -102,10 +103,6 @@ const columns = computed(() => [ tabIndex: 5, }, ]); - -function goToAction() { - location.href = `${salixUrl}/action`; -} @@ -175,6 +172,7 @@ function goToAction() { :option-label="col.optionLabel" :autofocus="col.tabIndex == 1" input-debounce="0" + hide-selected > @@ -213,6 +211,7 @@ function goToAction() { dense input-debounce="0" :autofocus="col.tabIndex == 1" + hide-selected /> @@ -236,13 +235,11 @@ function goToAction() { @@ -251,9 +248,6 @@ function goToAction() { .grid-style-transition { transition: transform 0.28s, background-color 0.28s; } -.maxwidth { - width: 100%; -} diff --git a/src/pages/Claim/Card/ClaimLines.vue b/src/pages/Claim/Card/ClaimLines.vue index c03291b85..fa7fb123f 100644 --- a/src/pages/Claim/Card/ClaimLines.vue +++ b/src/pages/Claim/Card/ClaimLines.vue @@ -46,7 +46,7 @@ async function onFetchClaim(data) { const amount = ref(0); const amountClaimed = ref(0); async function onFetch(rows) { - if (!rows || rows.length) return; + if (!rows || !rows.length) return; amount.value = rows.reduce( (acumulator, { sale }) => acumulator + sale.price * sale.quantity, 0 @@ -155,7 +155,7 @@ function showImportDialog() { - + {{ t('Amount') }} diff --git a/src/pages/Claim/Card/ClaimPhoto.vue b/src/pages/Claim/Card/ClaimPhoto.vue index 483dbffc1..6ac116ce0 100644 --- a/src/pages/Claim/Card/ClaimPhoto.vue +++ b/src/pages/Claim/Card/ClaimPhoto.vue @@ -26,6 +26,7 @@ const client = ref({}); const inputFile = ref(); const files = ref({}); +const spinnerRef = ref(); const claimDmsRef = ref(); const dmsType = ref({}); const config = ref({}); @@ -118,11 +119,11 @@ async function create() { clientId: client.value.id, }).toUpperCase(), }; - + spinnerRef.value.show(); await axios.post(query, formData, { params: dms, }); - + spinnerRef.value.hide(); quasar.notify({ message: t('globals.dataSaved'), type: 'positive', @@ -234,7 +235,9 @@ function onDrag() { - + + + diff --git a/src/pages/Claim/Card/ClaimSummary.vue b/src/pages/Claim/Card/ClaimSummary.vue index dc5ec9544..d6a7f1333 100644 --- a/src/pages/Claim/Card/ClaimSummary.vue +++ b/src/pages/Claim/Card/ClaimSummary.vue @@ -1,5 +1,5 @@ @@ -77,32 +89,37 @@ function viewSummary(id) { > - - + + + + {{ row.clientName }} + + + + + + + + {{ row.workerName }} + + + + - + {{ row.stateDescription }} @@ -110,26 +127,26 @@ function viewSummary(id) { + - - {{ t('components.smartCard.openCard') }} - - - - - {{ t('components.smartCard.openSummary') }} - - - - - {{ t('components.smartCard.viewDescription') }} - - + diff --git a/src/pages/Customer/Card/CustomerBasicData.vue b/src/pages/Customer/Card/CustomerBasicData.vue index ffd3c3476..47cb6e337 100644 --- a/src/pages/Customer/Card/CustomerBasicData.vue +++ b/src/pages/Customer/Card/CustomerBasicData.vue @@ -6,6 +6,7 @@ import { useI18n } from 'vue-i18n'; import { useSession } from 'src/composables/useSession'; import FetchData from 'components/FetchData.vue'; import FormModel from 'components/FormModel.vue'; +import VnRow from 'components/ui/VnRow.vue'; const route = useRoute(); const { t } = useI18n(); @@ -58,121 +59,109 @@ const filterOptions = { @on-fetch="(data) => (businessTypes = data)" auto-load /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - filter(value, update, filterOptions) - " - :rules="validate('client.salesPersonFk')" - :input-debounce="0" - > - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + filter(value, update, filterOptions)" + :rules="validate('client.salesPersonFk')" + :input-debounce="0" + > + + + + + + + + + + + + + + diff --git a/src/pages/Customer/Card/CustomerDescriptor.vue b/src/pages/Customer/Card/CustomerDescriptor.vue index 23e6228ee..282a4b958 100644 --- a/src/pages/Customer/Card/CustomerDescriptor.vue +++ b/src/pages/Customer/Card/CustomerDescriptor.vue @@ -14,6 +14,10 @@ const $props = defineProps({ required: false, default: null, }, + summary: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -34,6 +38,8 @@ const setData = (entity) => (data.value = useCardDescription(entity.name, entity :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + :summary="$props.summary" + data-key="customerData" > diff --git a/src/pages/Customer/Card/CustomerDescriptorProxy.vue b/src/pages/Customer/Card/CustomerDescriptorProxy.vue index 84a69c026..0341f66a0 100644 --- a/src/pages/Customer/Card/CustomerDescriptorProxy.vue +++ b/src/pages/Customer/Card/CustomerDescriptorProxy.vue @@ -1,5 +1,6 @@ + - + diff --git a/src/pages/Customer/Card/CustomerSummary.vue b/src/pages/Customer/Card/CustomerSummary.vue index 081bdd157..6693274ac 100644 --- a/src/pages/Customer/Card/CustomerSummary.vue +++ b/src/pages/Customer/Card/CustomerSummary.vue @@ -6,6 +6,7 @@ import { toCurrency, toPercentage, toDate } from 'src/filters'; import CardSummary from 'components/ui/CardSummary.vue'; import { getUrl } from 'src/composables/getUrl'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; const route = useRoute(); const { t } = useI18n(); @@ -68,8 +69,18 @@ const creditWarning = computed(() => { - - + + + {{ t('customer.summary.phone') }} + + + + + + {{ t('customer.summary.mobile') }} + + + -import { reactive, watch } from 'vue'; - -const customer = reactive({ - name: '', -}); - -watch(() => customer.name); - - - - - - - - - - - - - - - - - - - - diff --git a/src/pages/Customer/CustomerFilter.vue b/src/pages/Customer/CustomerFilter.vue index 113e0cc7c..c276b0d3e 100644 --- a/src/pages/Customer/CustomerFilter.vue +++ b/src/pages/Customer/CustomerFilter.vue @@ -3,6 +3,7 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnSelectFilter from 'components/common/VnSelectFilter.vue'; const { t } = useI18n(); const props = defineProps({ @@ -63,7 +64,7 @@ const zones = ref(); - - @@ -124,7 +126,7 @@ const zones = ref(); - - - + + + {{ t('customer.list.phone') }} + + + - - {{ t('components.smartCard.openCard') }} - - + class="bg-vn-dark" + outline + /> - - {{ t('components.smartCard.openSummary') }} - - + color="primary" + style="margin-top: 15px" + /> diff --git a/src/pages/Customer/CustomerPaymentsFilter.vue b/src/pages/Customer/CustomerPaymentsFilter.vue index 56fb52d79..7ee584d7f 100644 --- a/src/pages/Customer/CustomerPaymentsFilter.vue +++ b/src/pages/Customer/CustomerPaymentsFilter.vue @@ -1,6 +1,7 @@ + + (suppliers = data)" + /> + (currencies = data)" + /> + (companies = data)" + /> + (dmsTypes = data)" + /> + (warehouses = data)" + /> + (allowedContentTypes = data)" + /> + (userConfig = data)" + auto-load + /> + + + + + + + + + {{ + `${scope.opt.id} - ${scope.opt.nickname}` + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('Edit document') }} + + + {{ t('Create document') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('Edit document') }} + + + + + + + + + + + + + + + + + + + + + + {{ t('Select a file') }} + + + + + {{ + `${t( + 'Allowed content types' + )}: ${allowedContentTypes.join(', ')}` + }} + + + + + + + + + + + + + + + + + + + + + + {{ t('Create document') }} + + + + + + + + + + + + + + + + + + + + + + {{ t('Select a file') }} + + + + + {{ + `${t( + 'Allowed content types' + )}: ${allowedContentTypes.join(', ')}` + }} + + + + + + + + + + + + + + + + + + + en: + supplierFk: Supplier + es: + supplierFk: Proveedor + Supplier ref: Ref. proveedor + Expedition date: Fecha expedición + Operation date: Fecha operación + Undeductible VAT: Iva no deducible + Document: Documento + Download file: Descargar archivo + Entry date: Fecha asiento + Accounted date: Fecha contable + Currency: Moneda + Company: Empresa + Edit document: Editar documento + Reference: Referencia + Type: Tipo + Description: Descripción + Generate identifier for original file: Generar identificador para archivo original + Required field: Campo obligatorio + File: Fichero + Create document: Crear documento + Select a file: Seleccione un fichero + Allowed content types: Tipos de archivo permitidos + The company can't be empty: La empresa no puede estar vacía + The warehouse can't be empty: El almacén no puede estar vacío + The DMS Type can't be empty: El dms no puede estar vacío + The description can't be empty: La descripción no puede estar vacía + The files can't be empty: Los archivos no pueden estar vacíos + diff --git a/src/pages/InvoiceIn/Card/InvoiceInCard.vue b/src/pages/InvoiceIn/Card/InvoiceInCard.vue new file mode 100644 index 000000000..4d06cb41e --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInCard.vue @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + es: + Search invoice: Buscar factura emitida + You can search by invoice reference: Puedes buscar por referencia de la factura + diff --git a/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue new file mode 100644 index 000000000..4e37102e9 --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInDescriptor.vue @@ -0,0 +1,327 @@ + + + + (config = data)" + /> + + + + + {{ t('To book') }} + + + {{ t('Delete invoice') }} + + + {{ t('Clone invoice') }} + + + {{ t('Show agricultural receipt as PDF') }} + + + {{ t('Send agricultural receipt as PDF') }}... + + + {{ t('components.smartCard.downloadFile') }} + + + + + + + + + + + + + + {{ t('invoiceOut.card.ticketList') }} + + + + + + + +es: + To book: Contabilizar + Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura? + Delete invoice: Eliminar factura + Are you sure you want to delete this invoice?: Estas seguro de querer eliminar esta factura? + Invoice deleted: Factura eliminada + Clone invoice: Clonar factura + Invoice cloned: Factura clonada + Show agricultural receipt as PDF: Ver recibo agrícola como PDF + Send agricultural receipt as PDF: Enviar recibo agrícola como PDF + Are you sure you want to send it?: Estás seguro que quieres enviarlo? + Send PDF invoice: Enviar factura a PDF + diff --git a/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue new file mode 100644 index 000000000..e240e9a8c --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInDueDay.vue @@ -0,0 +1,294 @@ + + + (banks = data)" /> + (areRows = !!data.length)" + > + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + `${scope.opt.id}: ${scope.opt.bank}` + }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ + `${scope.opt.id}: ${scope.opt.bank}` + }} + + + + + + + + + + + + + + + + + + + + + + + + + es: + Date: Fecha + Bank: Caja + Amount: Importe + Foreign value: Divisa + diff --git a/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue new file mode 100644 index 000000000..58f521534 --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue @@ -0,0 +1,280 @@ + + + (countries = data)" + sort-by="country" + /> + (intrastats = data)" + /> + + + + + + + + + + + + + + + + + (invoceInIntrastat = data)" + > + + + + + + + + + + + + + {{ `${scope.opt.id}: ${scope.opt.description}` }} + + + + + + + + + + + + + + + + + + + + + + + {{ + `${scope.opt.id}: ${scope.opt.description}` + }} + + + + + + + + + + + + + + + + + + + + + + + + + + en: + amount: Amount + net: Net + stems: Stems + country: Country + es: + Code: Código + amount: Cantidad + net: Neto + stems: Tallos + country: País + Total amount: Total importe + Total net: Total neto + Total stems: Total tallos + diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummary.vue b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue new file mode 100644 index 000000000..d7039fe3d --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInSummary.vue @@ -0,0 +1,428 @@ + + + + setData(data)" + > + + {{ invoiceIn.id }} - {{ invoiceIn.supplier.name }} + + + + + + + {{ t('invoiceIn.pageTitles.basicData') }} + + + + + + + + + + + + {{ t('invoiceIn.pageTitles.basicData') }} + + + + + + + + + + + + {{ t('invoiceIn.pageTitles.basicData') }} + + + + + + + + + + + + {{ t('invoiceIn.pageTitles.basicData') }} + + + + + + + + + + + {{ toCurrency(invoiceIn.totals.totalDueDay) }} + + + + + + + + + + {{ t('invoiceIn.card.vat') }} + + + + + + + {{ t(col.label) }} + + + + + + + {{ toCurrency(invoiceIn.totals.totalTaxableBase) }} + + + {{ + toCurrency(getTaxTotal(invoiceIn.invoiceInTax)) + }} + + + + + + + + + {{ t('invoiceIn.card.dueDay') }} + + + + + + + {{ t(col.label) }} + + + + + + + + {{ toCurrency(invoiceIn.totals.totalDueDay) }} + + + + + + + + + {{ t('invoiceIn.card.intrastat') }} + + + + + + + {{ t(col.label) }} + + + + + + + {{ toCurrency(intrastatTotals.amount) }} + {{ intrastatTotals.net }} + {{ intrastatTotals.stems }} + + + + + + + + + + + es: + Search invoice: Buscar factura emitida + You can search by invoice reference: Puedes buscar por referencia de la factura + diff --git a/src/pages/InvoiceIn/Card/InvoiceInSummaryDialog.vue b/src/pages/InvoiceIn/Card/InvoiceInSummaryDialog.vue new file mode 100644 index 000000000..8657bf6ac --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInSummaryDialog.vue @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/src/pages/InvoiceIn/Card/InvoiceInVat.vue b/src/pages/InvoiceIn/Card/InvoiceInVat.vue new file mode 100644 index 000000000..d8e742706 --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInVat.vue @@ -0,0 +1,500 @@ + + + (expenses = data)" + /> + (sageTaxTypes = data)" /> + (sageTransactionTypes = data)" + /> + + + + + + + + + {{ `${scope.opt.id}: ${scope.opt.name}` }} + + + + + + + {{ t('Create expense') }} + + + + + + + + + + + + + + + + + + + + + + {{ scope.opt.vat }} + + {{ `#${scope.opt.id}` }} + + + + + + + + + + + + + + {{ + scope.opt.transaction + }} + + {{ `#${scope.opt.id}` }} + + + + + + + + + + + + + + + + + + + + + + + + + {{ `${scope.opt.id}: ${scope.opt.name}` }} + + + + + + + + + + + + + + + + + {{ + scope.opt.vat + }} + + {{ `#${scope.opt.id}` }} + + + + + + + + + + + + {{ + scope.opt.transaction + }} + + {{ `#${scope.opt.id}` }} + + + + + + + + {{ toCurrency(taxRate(props.row)) }} + + + + + + + + + + + + + + + + + + {{ t('New expense') }} + + + + + + + + + + + + + + + + + + + + + + + + + +es: + Expense: Gasto + Create expense: Crear gasto + Add tax: Crear gasto + Taxable base: Base imp. + Sage tax: Sage iva + Sage transaction: Sage transacción + Rate: Tasa + Foreign value: Divisa + New expense: Nuevo gasto + Code: Código + It's a withholding: Es una retención + Descripction: Descripción + The code can't be empty: El código no puede estar vacío + The description can't be empty: La descripción no puede estar vacía + The code have to be a number: El código debe ser un número. + diff --git a/src/pages/InvoiceIn/InvoiceInFilter.vue b/src/pages/InvoiceIn/InvoiceInFilter.vue new file mode 100644 index 000000000..6348d4167 --- /dev/null +++ b/src/pages/InvoiceIn/InvoiceInFilter.vue @@ -0,0 +1,307 @@ + + + + (suppliers = data)" + /> + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +en: + params: + search: ID + supplierRef: Supplier ref. + supplierFk: Supplier + fi: Supplier fiscal id + clientFk: Customer + amount: Amount + created: Created + awb: AWB + dued: Dued + serialNumber: Serial Number + serial: Serial + account: Account + isBooked: is booked +es: + params: + search: Contiene + supplierRef: Ref. proveedor + supplierFk: Proveedor + clientFk: Cliente + fi: CIF proveedor + serialNumber: Num. serie + serial: Serie + awb: AWB + amount: Importe + issued: Emitida + isBooked: Conciliada + account: Cuenta + created: Creada + dued: Vencida + From: Desde + To: Hasta + Amount: Importe + Issued: Fecha factura + Id or supplier: Id o proveedor + More options: Más opciones + diff --git a/src/pages/InvoiceIn/InvoiceInList.vue b/src/pages/InvoiceIn/InvoiceInList.vue new file mode 100644 index 000000000..692251bff --- /dev/null +++ b/src/pages/InvoiceIn/InvoiceInList.vue @@ -0,0 +1,179 @@ + + + + + + + + + + + + {{ t('globals.collapseMenu') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('components.smartCard.openCard') }} + + + + + {{ t('components.smartCard.openSummary') }} + + + + + {{ t('components.smartCard.downloadFile') }} + + + + + + + + + + + + + + + + +es: + Search invoice: Buscar factura emitida + You can search by invoice reference: Puedes buscar por referencia de la factura + diff --git a/src/pages/InvoiceIn/InvoiceInMain.vue b/src/pages/InvoiceIn/InvoiceInMain.vue new file mode 100644 index 000000000..66ce78f23 --- /dev/null +++ b/src/pages/InvoiceIn/InvoiceInMain.vue @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue index f6abd77e0..9c1bb3649 100644 --- a/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptor.vue @@ -7,6 +7,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; +import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; const $props = defineProps({ id: { @@ -57,13 +58,14 @@ const setData = (entity) => (data.value = useCardDescription(entity.ref, entity. :title="data.title" :subtitle="data.subtitle" @on-fetch="setData" + data-key="invoiceOutData" > + + + - + diff --git a/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue new file mode 100644 index 000000000..ab8b6470b --- /dev/null +++ b/src/pages/InvoiceOut/Card/InvoiceOutDescriptorMenu.vue @@ -0,0 +1,40 @@ + + + + + {{ t('Transfer invoice to') }} + + + {{ t('See invoice') }} + + + {{ t('Send invoice') }} + + + {{ t('Delete invoice') }} + + + {{ t('Post invoice') }} + + + {{ t('Regenerate invoice PDF') }} + + + {{ t('Pass') }} + + + + +es: + Transfer invoice to: Transferir factura a + See invoice: Ver factura + Send invoice: Enviar factura + Delete invoice: Eliminar factura + Post invoice: Asentar factura + Regenerate invoice PDF: Regenerar PDF factura + Pass: Abono + diff --git a/src/pages/InvoiceOut/InvoiceOutFilter.vue b/src/pages/InvoiceOut/InvoiceOutFilter.vue index 1ecaf828b..5d965816f 100644 --- a/src/pages/InvoiceOut/InvoiceOutFilter.vue +++ b/src/pages/InvoiceOut/InvoiceOutFilter.vue @@ -3,6 +3,7 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnInputDate from 'components/common/VnInputDate.vue'; const { t } = useI18n(); const props = defineProps({ @@ -43,66 +44,72 @@ function setWorkers(data) { - - - - - - - - - - - - - - - - - - - - - - + outlined + rounded + v-model="params.clientFk" + /> + + + + + + + + + + - - - - + /> - - - - + /> @@ -110,115 +117,35 @@ function setWorkers(data) { - - - - - - - - - - - - - - + :label="t('Issued')" + dense + outlined + rounded + /> - - - - - - - - - - - - - - + :label="t('Created')" + dense + outlined + rounded + /> - - - - - - - - - - - - - - + diff --git a/src/pages/InvoiceOut/InvoiceOutGlobal.vue b/src/pages/InvoiceOut/InvoiceOutGlobal.vue new file mode 100644 index 000000000..7284ca04e --- /dev/null +++ b/src/pages/InvoiceOut/InvoiceOutGlobal.vue @@ -0,0 +1,198 @@ + + + + + + + + + + + + + {{ t(`status.${status}`) }} + {{ + t('invoiceOut.globalInvoices.statusCard.percentageText', { + getPercentage: getPercentage, + getAddressNumber: getAddressNumber, + getNAddresses: getNAddresses, + }) + }} + {{ + t('invoiceOut.globalInvoices.statusCard.pdfsNumberText', { + nPdfs: nPdfs, + totalPdfs: totalPdfs, + }) + }} + + + + + + + + {{ props.value }} + + + + + + + + + + + + + +en: + status: + packageInvoicing: Build packaging tickets + invoicing: Invoicing client + stopping: Stopping process + done: Ended process + of: of + +es: + status: + packageInvoicing: Generación de tickets de empaque + invoicing: Facturando a cliente + stopping: Deteniendo proceso + done: Proceso detenido + of: de + diff --git a/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue new file mode 100644 index 000000000..c15d56671 --- /dev/null +++ b/src/pages/InvoiceOut/InvoiceOutGlobalForm.vue @@ -0,0 +1,201 @@ + + + + onFetchCompanies(data)" auto-load /> + onFetchPrinters(data)" auto-load /> + onFetchClients(data)" auto-load /> + + + + + + + + + + + + + + + + + + + + +en: + invoiceDate: Invoice date + maxShipped: Max date + allClients: All clients + oneClient: One client + company: Company + printer: Printer + invoiceOut: Invoice out + client: Client + stop: Stop + +es: + invoiceDate: Fecha de factura + maxShipped: Fecha límite + allClients: Todos los clientes + oneClient: Un solo cliente + company: Empresa + printer: Impresora + invoiceOut: Facturar + client: Cliente + stop: Parar + diff --git a/src/pages/InvoiceOut/InvoiceOutList.vue b/src/pages/InvoiceOut/InvoiceOutList.vue index 137dedba8..292fa2e0a 100644 --- a/src/pages/InvoiceOut/InvoiceOutList.vue +++ b/src/pages/InvoiceOut/InvoiceOutList.vue @@ -1,8 +1,8 @@ {{ t('globals.collapseMenu') }} @@ -64,71 +121,128 @@ function viewSummary(id) { - - - - - - - - - - - - - - - - - - {{ t('components.smartCard.openCard') }} - - - - - {{ t('components.smartCard.openSummary') }} - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -140,7 +254,14 @@ function viewSummary(id) { +en: + searchInvoice: Search issued invoice + fileDenied: Browser denied file download... + fileAllowed: Successful download of CSV file + youCanSearchByInvoiceReference: You can search by invoice reference es: - Search invoice: Buscar factura emitida - You can search by invoice reference: Puedes buscar por referencia de la factura + searchInvoice: Buscar factura emitida + fileDenied: El navegador denegó la descarga de archivos... + fileAllowed: Descarga exitosa de archivo CSV + youCanSearchByInvoiceReference: Puedes buscar por referencia de la factura diff --git a/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue new file mode 100644 index 000000000..63385380e --- /dev/null +++ b/src/pages/InvoiceOut/InvoiceOutNegativeBases.vue @@ -0,0 +1,356 @@ + + + + + + + + + + + + + + + + {{ rows.length }} {{ t('results') }} + + + + + + + + + + {{ t(`invoiceOut.negativeBases.${col.label}`) }} + + + + + + + + + {{ props.value }} + + + + + + + + + + + + + diff --git a/src/pages/Route/Cmr/CmrFilter.vue b/src/pages/Route/Cmr/CmrFilter.vue index b8948652a..bab300672 100644 --- a/src/pages/Route/Cmr/CmrFilter.vue +++ b/src/pages/Route/Cmr/CmrFilter.vue @@ -3,6 +3,7 @@ import { ref } from 'vue'; import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; +import VnInputDate from "components/common/VnInputDate.vue"; const { t } = useI18n(); const props = defineProps({ @@ -111,35 +112,7 @@ const countries = ref(); - - - - - - - - - - - - - + @@ -156,7 +129,7 @@ const countries = ref(); country: Country clientFk: Client id shipped: Preparation date - + es: params: cmrFk: Id cmr diff --git a/src/pages/Shelving/Card/ShelvingCard.vue b/src/pages/Shelving/Card/ShelvingCard.vue new file mode 100644 index 000000000..221740486 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingCard.vue @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + diff --git a/src/pages/Shelving/Card/ShelvingDescriptor.vue b/src/pages/Shelving/Card/ShelvingDescriptor.vue new file mode 100644 index 000000000..34c374117 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingDescriptor.vue @@ -0,0 +1,71 @@ + + + + + + + + + + + {{ entity.worker?.user?.nickname }} + + + + + + + + + + diff --git a/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue b/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue new file mode 100644 index 000000000..6290cda75 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingDescriptorMenu.vue @@ -0,0 +1,61 @@ + + + + + + + {{ t('deleteShelving') }} + + + + +{ + "en": { + "deleteShelving": "Delete Shelving" + }, + "es": { + "deleteShelving": "Eliminar carro" + } +} + diff --git a/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue b/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue new file mode 100644 index 000000000..8023271b0 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingDescriptorProxy.vue @@ -0,0 +1,15 @@ + + + + + + diff --git a/src/pages/Shelving/Card/ShelvingFilter.vue b/src/pages/Shelving/Card/ShelvingFilter.vue new file mode 100644 index 000000000..423402f89 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingFilter.vue @@ -0,0 +1,120 @@ + + + + + + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +en: + params: + parkingFk: Parking + userFk: Worker + isRecyclable: Recyclable +es: + params: + parkingFk: Parking + userFk: Trabajador + isRecyclable: Reciclable + diff --git a/src/pages/Shelving/Card/ShelvingForm.vue b/src/pages/Shelving/Card/ShelvingForm.vue new file mode 100644 index 000000000..e30ac1939 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingForm.vue @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + filter(value, update, parkingSelectFilter) + " + :rules="validate('Shelving.parkingFk')" + :input-debounce="0" + /> + + + + + + + + + + + + + diff --git a/src/pages/Shelving/Card/ShelvingLog.vue b/src/pages/Shelving/Card/ShelvingLog.vue new file mode 100644 index 000000000..8f355d2ca --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingLog.vue @@ -0,0 +1,6 @@ + + + + diff --git a/src/pages/Shelving/Card/ShelvingSearchbar.vue b/src/pages/Shelving/Card/ShelvingSearchbar.vue new file mode 100644 index 000000000..89a4de01e --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingSearchbar.vue @@ -0,0 +1,20 @@ + + + + + + + + +es: + Search shelving: Buscar carros + You can search by shelving reference: Puedes buscar por referencia del carro + diff --git a/src/pages/Shelving/Card/ShelvingSummary.vue b/src/pages/Shelving/Card/ShelvingSummary.vue new file mode 100644 index 000000000..f1f914385 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingSummary.vue @@ -0,0 +1,110 @@ + + + + + + + + + {{ t('globals.collapseMenu') }} + + + + + + + + + {{ entity.code }} + + + + + {{ t('shelving.pageTitles.basicData') }} + + + + + + + + + + + + + + + + diff --git a/src/pages/Shelving/Card/ShelvingSummaryDialog.vue b/src/pages/Shelving/Card/ShelvingSummaryDialog.vue new file mode 100644 index 000000000..783e47395 --- /dev/null +++ b/src/pages/Shelving/Card/ShelvingSummaryDialog.vue @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/src/pages/Shelving/ShelvingList.vue b/src/pages/Shelving/ShelvingList.vue new file mode 100644 index 000000000..71b3a6ccf --- /dev/null +++ b/src/pages/Shelving/ShelvingList.vue @@ -0,0 +1,138 @@ + + + + + + + + + + + + {{ t('globals.collapseMenu') }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('shelving.list.newShelving') }} + + + + + + + diff --git a/src/pages/Shelving/ShelvingMain.vue b/src/pages/Shelving/ShelvingMain.vue new file mode 100644 index 000000000..66ce78f23 --- /dev/null +++ b/src/pages/Shelving/ShelvingMain.vue @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/pages/Supplier/Card/SupplierCard.vue b/src/pages/Supplier/Card/SupplierCard.vue new file mode 100644 index 000000000..cf94c9e8d --- /dev/null +++ b/src/pages/Supplier/Card/SupplierCard.vue @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Supplier/Card/SupplierDescriptor.vue b/src/pages/Supplier/Card/SupplierDescriptor.vue new file mode 100644 index 000000000..910d36489 --- /dev/null +++ b/src/pages/Supplier/Card/SupplierDescriptor.vue @@ -0,0 +1,94 @@ + + + + + + + + + + + + + + + + + diff --git a/src/pages/Supplier/Card/SupplierDescriptorProxy.vue b/src/pages/Supplier/Card/SupplierDescriptorProxy.vue new file mode 100644 index 000000000..b730a39dd --- /dev/null +++ b/src/pages/Supplier/Card/SupplierDescriptorProxy.vue @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/pages/Supplier/Card/SupplierSummary.vue b/src/pages/Supplier/Card/SupplierSummary.vue new file mode 100644 index 000000000..e9fbffe96 --- /dev/null +++ b/src/pages/Supplier/Card/SupplierSummary.vue @@ -0,0 +1,188 @@ + + + + setData(data)" + > + + + + + + + {{ supplier.name }} - {{ supplier.id }} + + + + + + {{ t('globals.summary.basicData') }} + + + {{ t('globals.summary.basicData') }} + + + + + + {{ dashIfEmpty(supplier.worker?.user?.nickname) }} + + + + + + + {{ dashIfEmpty(supplier.note) }} + + + + + + + + + + + + + + + + + {{ t('supplier.summary.billingData') }} + + + {{ t('supplier.summary.billingData') }} + + + + + + + + {{ t('supplier.summary.fiscalData') }} + + + {{ t('supplier.summary.fiscalData') }} + + + + + + + + + {{ t('supplier.summary.fiscalAddress') }} + + + {{ t('supplier.summary.fiscalAddress') }} + + + + + + + + + + + + diff --git a/src/pages/Supplier/Card/SupplierSummaryDialog.vue b/src/pages/Supplier/Card/SupplierSummaryDialog.vue new file mode 100644 index 000000000..6353caa38 --- /dev/null +++ b/src/pages/Supplier/Card/SupplierSummaryDialog.vue @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/src/pages/Supplier/SupplierCreate.vue b/src/pages/Supplier/SupplierCreate.vue new file mode 100644 index 000000000..9b792483b --- /dev/null +++ b/src/pages/Supplier/SupplierCreate.vue @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Supplier/SupplierList.vue b/src/pages/Supplier/SupplierList.vue new file mode 100644 index 000000000..ea7bc3cb0 --- /dev/null +++ b/src/pages/Supplier/SupplierList.vue @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('supplier.list.newSupplier') }} + + + + + + + + +en: + Search suppliers: Search suppliers + +es: + Search suppliers: Buscar proveedores + diff --git a/src/pages/Supplier/SupplierMain.vue b/src/pages/Supplier/SupplierMain.vue new file mode 100644 index 000000000..66ce78f23 --- /dev/null +++ b/src/pages/Supplier/SupplierMain.vue @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/pages/Ticket/Card/TicketDescriptor.vue b/src/pages/Ticket/Card/TicketDescriptor.vue index 25c9dfb1c..d2a407874 100644 --- a/src/pages/Ticket/Card/TicketDescriptor.vue +++ b/src/pages/Ticket/Card/TicketDescriptor.vue @@ -8,6 +8,7 @@ import CardDescriptor from 'components/ui/CardDescriptor.vue'; import TicketDescriptorMenu from './TicketDescriptorMenu.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import useCardDescription from 'src/composables/useCardDescription'; +import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; const $props = defineProps({ id: { @@ -80,6 +81,7 @@ const setData = (entity) => :filter="filter" :title="data.title" :subtitle="data.subtitle" + data-key="ticketData" @on-fetch="setData" > diff --git a/src/pages/Ticket/Card/TicketDescriptorMenu.vue b/src/pages/Ticket/Card/TicketDescriptorMenu.vue index f55229bcf..95f6a94d9 100644 --- a/src/pages/Ticket/Card/TicketDescriptorMenu.vue +++ b/src/pages/Ticket/Card/TicketDescriptorMenu.vue @@ -87,7 +87,7 @@ function showSmsDialog(template, customData) { componentProps: { phone: phone, template: template, - locale: client.user.lang, + locale: client?.user?.lang ?? 'default_locale', data: data, promise: sendSms, }, diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index ce46d1d47..cfc35c032 100644 --- a/src/pages/Ticket/Card/TicketSummary.vue +++ b/src/pages/Ticket/Card/TicketSummary.vue @@ -10,6 +10,7 @@ import FetchedTags from 'components/ui/FetchedTags.vue'; import InvoiceOutDescriptorProxy from 'pages/InvoiceOut/Card/InvoiceOutDescriptorProxy.vue'; import WorkerDescriptorProxy from 'pages/Worker/Card/WorkerDescriptorProxy.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import { getUrl } from 'src/composables/getUrl'; onUpdated(() => summaryRef.value.fetch()); @@ -172,7 +173,7 @@ async function changeState(value) { :label="t('ticket.summary.agency')" :value="ticket.agencyMode.name" /> - + - + {{ dashIfEmpty(ticket.refFk) }} - - - - - + + + + {{ t('ticket.summary.consigneePhone') }} + + + + + + {{ t('ticket.summary.consigneeMobile') }} + + + + + + {{ t('ticket.summary.clientPhone') }} + + + + + + {{ t('ticket.summary.clientMobile') }} + + + {{ t('ticket.summary.description') }} {{ t('ticket.summary.price') }} {{ t('ticket.summary.discount') }} - {{ t('ticket.summary.amount') }} + {{ t('globals.amount') }} {{ t('ticket.summary.packing') }} @@ -391,7 +400,7 @@ async function changeState(value) { v-if="ticket.packagings.length > 0 || ticket.services.length > 0" > - {{ t('ticket.summary.packages') }} + {{ t('globals.packages') }} @@ -422,7 +431,7 @@ async function changeState(value) { {{ t('ticket.summary.description') }} {{ t('ticket.summary.price') }} {{ t('ticket.summary.taxClass') }} - {{ t('ticket.summary.amount') }} + {{ t('globals.amount') }} diff --git a/src/pages/Ticket/TicketFilter.vue b/src/pages/Ticket/TicketFilter.vue index aa98b9564..69f0b1c11 100644 --- a/src/pages/Ticket/TicketFilter.vue +++ b/src/pages/Ticket/TicketFilter.vue @@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n'; import FetchData from 'components/FetchData.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import toDateString from 'filters/toDateString'; +import VnInputDate from "components/common/VnInputDate.vue"; const { t } = useI18n(); const props = defineProps({ @@ -71,69 +72,10 @@ const warehouses = ref(); - - - - - - - - - - - - - - + - - - - - - - - - - - - - - + diff --git a/src/pages/Ticket/TicketList.vue b/src/pages/Ticket/TicketList.vue index b9f0f57e7..7d7429630 100644 --- a/src/pages/Ticket/TicketList.vue +++ b/src/pages/Ticket/TicketList.vue @@ -68,7 +68,7 @@ function viewSummary(id) { - + @@ -87,6 +87,7 @@ function viewSummary(id) { v-for="row of rows" :key="row.id" :id="row.id" + :title="`${row.nickname} (${row.id})`" @click="navigate(row.id)" > @@ -120,11 +121,11 @@ function viewSummary(id) { /> - - - {{ t('components.smartCard.openSummary') }} - - + diff --git a/src/pages/Travel/Card/TravelCard.vue b/src/pages/Travel/Card/TravelCard.vue new file mode 100644 index 000000000..6309c14ab --- /dev/null +++ b/src/pages/Travel/Card/TravelCard.vue @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Travel/Card/TravelDescriptor.vue b/src/pages/Travel/Card/TravelDescriptor.vue new file mode 100644 index 000000000..6a45710ad --- /dev/null +++ b/src/pages/Travel/Card/TravelDescriptor.vue @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + diff --git a/src/pages/Travel/Card/TravelDescriptorProxy.vue b/src/pages/Travel/Card/TravelDescriptorProxy.vue new file mode 100644 index 000000000..ab5c42d7e --- /dev/null +++ b/src/pages/Travel/Card/TravelDescriptorProxy.vue @@ -0,0 +1,16 @@ + + + + + + + diff --git a/src/pages/Travel/Card/TravelSummary.vue b/src/pages/Travel/Card/TravelSummary.vue new file mode 100644 index 000000000..ad4659b11 --- /dev/null +++ b/src/pages/Travel/Card/TravelSummary.vue @@ -0,0 +1,323 @@ + + + + setTravelData(data)" + > + + + + + + + {{ travel.ref }} - {{ travel.id }} + + + + + + {{ t('components.cardDescriptor.moreOptions') }} + + + + + {{ option.name }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('travel.summary.entries') }} + + + + + + + {{ props.value }} + {{ + props.col.toolTip + }} + + + + + + + + + diff --git a/src/pages/Travel/Card/TravelSummaryDialog.vue b/src/pages/Travel/Card/TravelSummaryDialog.vue new file mode 100644 index 000000000..8351d6e5b --- /dev/null +++ b/src/pages/Travel/Card/TravelSummaryDialog.vue @@ -0,0 +1,29 @@ + + + + + + + + + diff --git a/src/pages/Travel/ExtraCommunity.vue b/src/pages/Travel/ExtraCommunity.vue new file mode 100644 index 000000000..cef3ed998 --- /dev/null +++ b/src/pages/Travel/ExtraCommunity.vue @@ -0,0 +1,419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ col.value }} + + + + + + + + {{ col.value }} + + + + + + + + + + {{ entry.id }} + + + + {{ + entry.supplierName + }} + + + + {{ + toCurrency(entry.invoiceAmount) + }} + {{ entry.reference }} + {{ entry.stickers }} + + {{ entry.loadedkg }} + {{ entry.volumeKg }} + + + + + + + + + + + + + +en: + searchExtraCommunity: Search for extra community shipping + kg: BI. KG + physicKg: Phy. KG + shipped: W. shipped + landed: W. landed + +es: + searchExtraCommunity: Buscar por envío extra comunitario + kg: KG Bloq. + physicKg: KG físico + shipped: F. envío + landed: F. llegada + diff --git a/src/pages/Travel/ExtraCommunityFilter.vue b/src/pages/Travel/ExtraCommunityFilter.vue new file mode 100644 index 000000000..2d3629de4 --- /dev/null +++ b/src/pages/Travel/ExtraCommunityFilter.vue @@ -0,0 +1,263 @@ + + + + updateFilterOptions(data, 'warehouses')" + auto-load + /> + updateFilterOptions(data, 'continents')" + auto-load + /> + updateFilterOptions(data, 'agencies')" + auto-load + /> + updateFilterOptions(data, 'suppliers')" + auto-load + /> + + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +en: + params: + ref: Reference + totalEntries: Total entries + agencyModeFk: Agency + warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out + shippedFrom: Shipped from + landedTo: Landed to + cargoSupplierFk: Supplier + continent: Continent out +es: + params: + ref: Referencia + totalEntries: Ent. totales + agencyModeFk: Agencia + warehouseInFk: Alm. entrada + warehouseOutFk: Alm. salida + shippedFrom: Llegada desde + landedTo: Llegada hasta + cargoSupplierFk: Proveedor + continent: Cont. Salida + diff --git a/src/pages/Travel/TravelCreate.vue b/src/pages/Travel/TravelCreate.vue new file mode 100644 index 000000000..434bbb238 --- /dev/null +++ b/src/pages/Travel/TravelCreate.vue @@ -0,0 +1,179 @@ + + + + onFetchAgencies(data)" auto-load /> + onFetchWarehouses(data)" auto-load /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/pages/Travel/TravelFilter.vue b/src/pages/Travel/TravelFilter.vue new file mode 100644 index 000000000..2c2d80488 --- /dev/null +++ b/src/pages/Travel/TravelFilter.vue @@ -0,0 +1,311 @@ + + + + updateFilterOptions(data, 'warehouses')" + auto-load + /> + updateFilterOptions(data, 'continents')" + auto-load + /> + updateFilterOptions(data, 'agencies')" + auto-load + /> + + + + + {{ t(`params.${tag.label}`) }}: + {{ formatFn(tag.value) }} + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +en: + params: + search: Id/Reference + agencyModeFk: Agency + warehouseInFk: Warehouse In + warehouseOutFk: Warehouse Out + scopeDays: Days onward + landedFrom: Landed from + landedTo: Landed to + continent: Continent out + totalEntries: Total entries +es: + params: + search: Id/Referencia + agencyModeFk: Agencia + warehouseInFk: Alm. entrada + warehouseOutFk: Alm. salida + scopeDays: Días adelante + landedFrom: Llegada desde + landedTo: Llegada hasta + continent: Cont. Salida + totalEntries: Ent. totales + + diff --git a/src/pages/Travel/TravelList.vue b/src/pages/Travel/TravelList.vue new file mode 100644 index 000000000..d62485a4c --- /dev/null +++ b/src/pages/Travel/TravelList.vue @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ t('supplier.list.newSupplier') }} + + + + + + + + +en: + addEntry: Add entry + + +es: + addEntry: Añadir entrada + + + diff --git a/src/pages/Travel/TravelMain.vue b/src/pages/Travel/TravelMain.vue new file mode 100644 index 000000000..66ce78f23 --- /dev/null +++ b/src/pages/Travel/TravelMain.vue @@ -0,0 +1,17 @@ + + + + + + + + + + + + diff --git a/src/pages/Wagon/Card/WagonCard.vue b/src/pages/Wagon/Card/WagonCard.vue new file mode 100644 index 000000000..18ec121e3 --- /dev/null +++ b/src/pages/Wagon/Card/WagonCard.vue @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + +es: + Search customer: Buscar cliente + You can search by customer id or name: Puedes buscar por id o nombre del cliente + diff --git a/src/pages/Wagon/Type/WagonTypeCreate.vue b/src/pages/Wagon/Type/WagonTypeCreate.vue index adfd41049..50be4169c 100644 --- a/src/pages/Wagon/Type/WagonTypeCreate.vue +++ b/src/pages/Wagon/Type/WagonTypeCreate.vue @@ -239,8 +239,8 @@ function exceedMaxHeight(pos) { - - + + - + - - - - - - + + + + + + @@ -357,45 +357,55 @@ function exceedMaxHeight(pos) { justify-content: center; align-items: flex-start; } -.q-card { + +.q-form { width: 70%; } + .q-dialog { .q-card { width: 100%; } } + .wheels { margin-left: 5%; display: flex; justify-content: space-around; } + .wagon-tray { display: flex; height: 6rem; + .position { - width: 15%; + width: 20%; border-right: 1rem solid gray; display: flex; align-items: flex-end; justify-content: flex-end; padding-right: 1rem; } + .shelving { display: flex; width: 75%; + .shelving-half { width: 50%; height: 100%; + .shelving-up { height: 80%; width: 100%; } + .shelving-down { height: 20%; width: 100%; } } + .shelving-divisible { width: 1%; height: 100%; @@ -403,6 +413,7 @@ function exceedMaxHeight(pos) { border-right: 0.5rem dashed grey; } } + .action-button { width: 10%; border-left: 1rem solid gray; diff --git a/src/pages/Wagon/Type/WagonTypeList.vue b/src/pages/Wagon/Type/WagonTypeList.vue index f364da008..8e4ace744 100644 --- a/src/pages/Wagon/Type/WagonTypeList.vue +++ b/src/pages/Wagon/Type/WagonTypeList.vue @@ -5,6 +5,8 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import CardList from 'components/ui/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; const quasar = useQuasar(); const arrayData = useArrayData('WagonTypeList'); @@ -48,43 +50,28 @@ async function remove(row) { auto-load > - - - - {{ row.name }} - #{{ row.id }} - - - - - - {{ t('components.smartCard.openCard') }} - - - - - {{ t('wagon.list.remove') }} - - - - - + + + + + + diff --git a/src/pages/Wagon/WagonCounter.vue b/src/pages/Wagon/WagonCounter.vue new file mode 100644 index 000000000..bd5d2ca67 --- /dev/null +++ b/src/pages/Wagon/WagonCounter.vue @@ -0,0 +1,154 @@ + + + + + + + {{ props.title }} + + + + {{ props.count }} + + + + {{ t('Add 30') }} + + + {{ t('Add 10') }} + + + + + {{ t('Subtract 1') }} + + + {{ t('Flush') }} + + + + + + + + + +es: + Subtract 1: Quitar 1 + Add 30: Añadir 30 + Add 10: Añadir 10 + Flush: Vaciar + Are you sure?: ¿Estás seguro? + It will set to 0: Se pondrá a 0 + The counter will be reset to zero: Se pondrá el contador a cero + diff --git a/src/pages/Wagon/WagonCreate.vue b/src/pages/Wagon/WagonCreate.vue index 123e01d36..0bd6f87be 100644 --- a/src/pages/Wagon/WagonCreate.vue +++ b/src/pages/Wagon/WagonCreate.vue @@ -86,9 +86,9 @@ function filterType(val, update) { - - - + + + - + - - - - - - + + + + + + @@ -176,7 +176,8 @@ function filterType(val, update) { justify-content: center; align-items: flex-start; } -.q-card { + +.q-form { width: 70%; } diff --git a/src/pages/Wagon/WagonList.vue b/src/pages/Wagon/WagonList.vue index 5e512aa92..77d9da0bf 100644 --- a/src/pages/Wagon/WagonList.vue +++ b/src/pages/Wagon/WagonList.vue @@ -5,6 +5,8 @@ import VnPaginate from 'src/components/ui/VnPaginate.vue'; import { useArrayData } from 'src/composables/useArrayData'; import { useI18n } from 'vue-i18n'; import { useRouter } from 'vue-router'; +import CardList from 'components/ui/CardList.vue'; +import VnLv from 'components/ui/VnLv.vue'; const quasar = useQuasar(); const arrayData = useArrayData('WagonList'); @@ -55,69 +57,40 @@ async function remove(row) { auto-load > - - - - {{ row.label }} - #{{ row.id }} - - - - - {{ t('wagon.list.plate') }} - - {{ row.plate }} - - - - - - {{ t('wagon.list.volume') }} - - {{ row.volume }} - - - - - - {{ t('wagon.list.type') }} - - {{ row.type.name }} - - - - - - - - - {{ t('components.smartCard.openCard') }} - - - - - {{ t('wagon.list.remove') }} - - - - - + + + + + + + + + + + diff --git a/src/pages/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index ba64a5abb..f089c0022 100644 --- a/src/pages/Worker/Card/WorkerDescriptor.vue +++ b/src/pages/Worker/Card/WorkerDescriptor.vue @@ -5,13 +5,19 @@ import { useI18n } from 'vue-i18n'; import { useSession } from 'src/composables/useSession'; import CardDescriptor from 'src/components/ui/CardDescriptor.vue'; import VnLv from 'src/components/ui/VnLv.vue'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import useCardDescription from 'src/composables/useCardDescription'; + const $props = defineProps({ id: { type: Number, required: false, default: null, }, + summary: { + type: Object, + default: null, + }, }); const route = useRoute(); @@ -47,23 +53,27 @@ const filter = { ], }; -const sip = computed(() => worker.value.sip && worker.value.sip.extension); +const sip = computed(() => worker.value?.sip && worker.value.sip.extension); function getWorkerAvatar() { const token = getToken(); - return `/api/Images/user/160x160/${route.params.id}/download?access_token=${token}`; + return `/api/Images/user/160x160/${entityId.value}/download?access_token=${token}`; } const data = ref(useCardDescription()); -const setData = (entity) => - (data.value = useCardDescription(entity.user.nickname, entity.id)); +const setData = (entity) => { + if (!entity) return; + data.value = useCardDescription(entity.user.nickname, entity.id); +}; { worker = data; @@ -90,14 +100,24 @@ const setData = (entity) => - - + + - - + + + {{ t('worker.card.phone') }} + + + + + + {{ t('worker.summary.sipExtension') }} + + + diff --git a/src/pages/Worker/Card/WorkerDescriptorProxy.vue b/src/pages/Worker/Card/WorkerDescriptorProxy.vue index 8d372b6de..98b06ad6e 100644 --- a/src/pages/Worker/Card/WorkerDescriptorProxy.vue +++ b/src/pages/Worker/Card/WorkerDescriptorProxy.vue @@ -1,5 +1,6 @@ + - - - - - - {{ t('worker.notificationsManager.activeNotifications') }} - - - - - - - - - - {{ t('worker.notificationsManager.availableNotifications') }} - - - + + + + + - - {{ notification.name }} - {{ - notification.description - }} - - + + + + + + {{ notification.name }} + + {{ notification.description }} + + - - - - - - - + + + + + + + + + + diff --git a/src/pages/Worker/Card/WorkerSummary.vue b/src/pages/Worker/Card/WorkerSummary.vue index 7c8accc5d..970a0dee4 100644 --- a/src/pages/Worker/Card/WorkerSummary.vue +++ b/src/pages/Worker/Card/WorkerSummary.vue @@ -7,6 +7,7 @@ import { getUrl } from 'src/composables/getUrl'; import VnLv from 'src/components/ui/VnLv.vue'; import WorkerDescriptorProxy from './WorkerDescriptorProxy.vue'; import { dashIfEmpty } from 'src/filters'; +import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; const route = useRoute(); const { t } = useI18n(); @@ -91,15 +92,24 @@ const filter = { - - - + + + {{ t('worker.summary.phoneExtension') }} + + + + + + {{ t('worker.summary.entPhone') }} + + + + + + {{ t('worker.summary.personalPhone') }} + + + @@ -109,10 +119,12 @@ const filter = { - + + + {{ t('worker.summary.sipExtension') }} + + + diff --git a/src/pages/Worker/WorkerList.vue b/src/pages/Worker/WorkerList.vue index cb75cdee3..a09c370ef 100644 --- a/src/pages/Worker/WorkerList.vue +++ b/src/pages/Worker/WorkerList.vue @@ -71,11 +71,11 @@ function viewSummary(id) { - - - {{ t('components.smartCard.openCard') }} - - - - - {{ t('components.smartCard.openSummary') }} - - + class="bg-vn-dark" + outline + /> + diff --git a/src/router/modules/Supplier.js b/src/router/modules/Supplier.js new file mode 100644 index 000000000..198a9581e --- /dev/null +++ b/src/router/modules/Supplier.js @@ -0,0 +1,61 @@ +import { RouterView } from 'vue-router'; + +export default { + path: '/supplier', + name: 'Supplier', + meta: { + title: 'suppliers', + icon: 'vn:supplier', + }, + component: RouterView, + redirect: { name: 'SupplierMain' }, + menus: { + main: ['SupplierList'], + card: [], + }, + children: [ + { + path: '', + name: 'SupplierMain', + component: () => import('src/pages/Supplier/SupplierMain.vue'), + redirect: { name: 'SupplierList' }, + children: [ + { + path: 'list', + name: 'SupplierList', + meta: { + title: 'list', + icon: 'view_list', + }, + component: () => import('src/pages/Supplier/SupplierList.vue'), + }, + { + path: 'create', + name: 'SupplierCreate', + meta: { + title: 'create', + }, + component: () => import('src/pages/Supplier/SupplierCreate.vue'), + }, + ], + }, + { + name: 'SupplierCard', + path: ':id', + component: () => import('src/pages/Supplier/Card/SupplierCard.vue'), + redirect: { name: 'SupplierSummary' }, + children: [ + { + name: 'SupplierSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'launch', + }, + component: () => + import('src/pages/Supplier/Card/SupplierSummary.vue'), + }, + ], + }, + ], +}; diff --git a/src/router/modules/claim.js b/src/router/modules/claim.js index 9df1dd64e..1dfd75cff 100644 --- a/src/router/modules/claim.js +++ b/src/router/modules/claim.js @@ -19,6 +19,7 @@ export default { 'ClaimLog', 'ClaimNotes', 'ClaimDevelopment', + 'ClaimAction', ], }, children: [ @@ -130,6 +131,15 @@ export default { }, component: () => import('src/pages/Claim/Card/ClaimNotes.vue'), }, + { + name: 'ClaimAction', + path: 'action', + meta: { + title: 'action', + icon: 'vn:actions', + }, + component: () => import('src/pages/Claim/Card/ClaimAction.vue'), + }, ], }, ], diff --git a/src/router/modules/customer.js b/src/router/modules/customer.js index c7e978eec..832a1e0fd 100644 --- a/src/router/modules/customer.js +++ b/src/router/modules/customer.js @@ -10,7 +10,7 @@ export default { component: RouterView, redirect: { name: 'CustomerMain' }, menus: { - main: ['CustomerList', 'CustomerPayments', 'CustomerCreate'], + main: ['CustomerList', 'CustomerPayments'], card: ['CustomerBasicData'], }, children: [ @@ -27,7 +27,7 @@ export default { title: 'list', icon: 'view_list', }, - component: () => import('src/pages/Customer/CustomerList.vue') + component: () => import('src/pages/Customer/CustomerList.vue'), }, { path: 'payments', @@ -36,17 +36,7 @@ export default { title: 'webPayments', icon: 'vn:onlinepayment', }, - component: () => import('src/pages/Customer/CustomerPayments.vue') - }, - { - path: 'create', - name: 'CustomerCreate', - meta: { - title: 'createCustomer', - icon: 'vn:addperson', - roles: ['developer'], - }, - component: () => import('src/pages/Customer/CustomerCreate.vue'), + component: () => import('src/pages/Customer/CustomerPayments.vue'), }, ], }, @@ -63,7 +53,8 @@ export default { title: 'summary', icon: 'launch', }, - component: () => import('src/pages/Customer/Card/CustomerSummary.vue'), + component: () => + import('src/pages/Customer/Card/CustomerSummary.vue'), }, { path: 'basic-data', @@ -72,7 +63,8 @@ export default { title: 'basicData', icon: 'vn:settings', }, - component: () => import('src/pages/Customer/Card/CustomerBasicData.vue'), + component: () => + import('src/pages/Customer/Card/CustomerBasicData.vue'), }, ], }, diff --git a/src/router/modules/index.js b/src/router/modules/index.js index 2916b98ef..0ab5bf2b2 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -2,9 +2,13 @@ import Customer from './customer'; import Ticket from './ticket'; import Claim from './claim'; import InvoiceOut from './invoiceOut'; +import invoiceIn from './invoiceIn'; import Worker from './worker'; +import Shelving from './shelving'; import Wagon from './wagon'; import Route from './route'; +import Supplier from './Supplier'; +import Travel from './travel'; export default [ Customer, @@ -12,6 +16,10 @@ export default [ Claim, InvoiceOut, Worker, + Shelving, Wagon, - Route -] + Route, + Supplier, + Travel, + invoiceIn, +]; diff --git a/src/router/modules/invoiceIn.js b/src/router/modules/invoiceIn.js new file mode 100644 index 000000000..a5509908c --- /dev/null +++ b/src/router/modules/invoiceIn.js @@ -0,0 +1,98 @@ +import { RouterView } from 'vue-router'; + +export default { + path: '/invoice-in', + name: 'InvoiceIn', + meta: { + title: 'invoiceIns', + icon: 'vn:invoice-in', + }, + component: RouterView, + redirect: { name: 'InvoiceInMain' }, + menus: { + main: ['InvoiceInList'], + card: [ + 'InvoiceInBasicData', + 'InvoiceInVat', + 'InvoiceInDueDay', + 'InvoiceInIntrastat', + ], + }, + children: [ + { + path: '', + name: 'InvoiceInMain', + component: () => import('src/pages/InvoiceIn/InvoiceInMain.vue'), + redirect: { name: 'InvoiceInList' }, + children: [ + { + path: 'list', + name: 'InvoiceInList', + meta: { + title: 'list', + icon: 'view_list', + }, + component: () => import('src/pages/InvoiceIn/InvoiceInList.vue'), + }, + ], + }, + { + name: 'InvoiceInCard', + path: ':id', + component: () => import('src/pages/InvoiceIn/Card/InvoiceInCard.vue'), + redirect: { name: 'InvoiceInSummary' }, + children: [ + { + name: 'InvoiceInSummary', + path: 'summary', + meta: { + title: 'summary', + icon: 'view_list', + }, + component: () => + import('src/pages/InvoiceIn/Card/InvoiceInSummary.vue'), + }, + { + name: 'InvoiceInBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + roles: ['salesPerson'], + }, + component: () => + import('src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'), + }, + { + name: 'InvoiceInVat', + path: 'vat', + meta: { + title: 'vat', + icon: 'vn:tax', + }, + component: () => import('src/pages/InvoiceIn/Card/InvoiceInVat.vue'), + }, + { + name: 'InvoiceInDueDay', + path: 'due-day', + meta: { + title: 'dueDay', + icon: 'vn:calendar', + }, + component: () => + import('src/pages/InvoiceIn/Card/InvoiceInDueDay.vue'), + }, + { + name: 'InvoiceInIntrastat', + path: 'intrastat', + meta: { + title: 'intrastat', + icon: 'vn:lines', + }, + component: () => + import('src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'), + }, + ], + }, + ], +}; diff --git a/src/router/modules/invoiceOut.js b/src/router/modules/invoiceOut.js index daf5041e9..084cf6ac3 100644 --- a/src/router/modules/invoiceOut.js +++ b/src/router/modules/invoiceOut.js @@ -5,12 +5,12 @@ export default { name: 'InvoiceOut', meta: { title: 'invoiceOuts', - icon: 'vn:invoice-out' + icon: 'vn:invoice-out', }, component: RouterView, redirect: { name: 'InvoiceOutMain' }, menus: { - main: ['InvoiceOutList'], + main: ['InvoiceOutList', 'InvoiceOutGlobal', 'InvoiceOutNegativeBases'], card: [], }, children: [ @@ -28,8 +28,27 @@ export default { icon: 'view_list', }, component: () => import('src/pages/InvoiceOut/InvoiceOutList.vue'), - } - ] + }, + { + path: 'global-invoicing', + name: 'InvoiceOutGlobal', + meta: { + title: 'globalInvoicing', + icon: 'contact_support', + }, + component: () => import('src/pages/InvoiceOut/InvoiceOutGlobal.vue'), + }, + { + path: 'negative-bases', + name: 'InvoiceOutNegativeBases', + meta: { + title: 'negativeBases', + icon: 'view_list', + }, + component: () => + import('src/pages/InvoiceOut/InvoiceOutNegativeBases.vue'), + }, + ], }, { name: 'InvoiceOutCard', @@ -41,11 +60,12 @@ export default { name: 'InvoiceOutSummary', path: 'summary', meta: { - title: 'summary' + title: 'summary', }, - component: () => import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'), - } - ] + component: () => + import('src/pages/InvoiceOut/Card/InvoiceOutSummary.vue'), + }, + ], }, - ] + ], }; diff --git a/src/router/modules/route.js b/src/router/modules/route.js index a3550885c..acda898de 100644 --- a/src/router/modules/route.js +++ b/src/router/modules/route.js @@ -21,13 +21,13 @@ export default { redirect: { name: 'CmrList' }, children: [ { - path: 'cmr/list', + path: 'cmr', name: 'CmrList', meta: { title: 'cmrsList', icon: 'fact_check', }, - component: () => import('src/pages/Route/Cmr/CmrList.vue') + component: () => import('src/pages/Route/Cmr/CmrList.vue'), }, ], }, diff --git a/src/router/modules/shelving.js b/src/router/modules/shelving.js new file mode 100644 index 000000000..494f286c3 --- /dev/null +++ b/src/router/modules/shelving.js @@ -0,0 +1,80 @@ +import {RouterView} from "vue-router"; + +export default { + path: '/shelving', + name: 'Shelving', + meta: { + title: 'shelving', + icon: 'vn:inventory' + }, + component: RouterView, + redirect: { name: 'ShelvingMain' }, + menus: { + main: ['ShelvingList'], + card: ['ShelvingBasicData', 'ShelvingLog'] + }, + children: [ + { + path: '', + name: 'ShelvingMain', + component: () => import('src/pages/Shelving/ShelvingMain.vue'), + redirect: { name: 'ShelvingList' }, + children: [ + { + path: 'list', + name: 'ShelvingList', + meta: { + title: 'shelvingList', + icon: 'view_list', + }, + component: () => import('src/pages/Shelving/ShelvingList.vue'), + }, + { + path: 'create', + name: 'ShelvingCreate', + meta: { + title: 'create', + }, + component: () => import('src/pages/Shelving/Card/ShelvingForm.vue'), + }, + ], + }, + { + name: 'ShelvingLayout', + path: ':id', + component: () => import('pages/Shelving/Card/ShelvingCard.vue'), + redirect: { name: 'ShelvingSummary' }, + children: [ + { + name: 'ShelvingSummary', + path: 'summary', + meta: { + title: 'summary', + }, + component: () => + import('pages/Shelving/Card/ShelvingSummary.vue'), + }, + { + name: 'ShelvingBasicData', + path: 'basic-data', + meta: { + title: 'basicData', + icon: 'vn:settings', + roles: ['salesPerson'], + }, + component: () => import('pages/Shelving/Card/ShelvingForm.vue'), + }, + { + name: 'ShelvingLog', + path: 'log', + meta: { + title: 'log', + icon: 'history', + }, + component: () => import('src/pages/Shelving/Card/ShelvingLog.vue'), + }, + ], + }, + ] +}; + diff --git a/src/router/modules/travel.js b/src/router/modules/travel.js new file mode 100644 index 000000000..bd0a2067d --- /dev/null +++ b/src/router/modules/travel.js @@ -0,0 +1,69 @@ +import { RouterView } from 'vue-router'; + +export default { + path: '/travel', + name: 'Travel', + meta: { + title: 'travel', + icon: 'local_airport', + }, + component: RouterView, + redirect: { name: 'TravelMain' }, + menus: { + main: ['TravelList', 'ExtraCommunity'], + card: [], + }, + children: [ + { + path: '', + name: 'TravelMain', + component: () => import('src/pages/Travel/TravelMain.vue'), + redirect: { name: 'TravelList' }, + children: [ + { + path: 'list', + name: 'TravelList', + meta: { + title: 'list', + icon: 'view_list', + }, + component: () => import('src/pages/Travel/TravelList.vue'), + }, + { + path: 'extra-community', + name: 'ExtraCommunity', + meta: { + title: 'extraCommunity', + icon: 'vn:shipment-01', + }, + component: () => import('src/pages/Travel/ExtraCommunity.vue'), + }, + { + path: 'create', + name: 'TravelCreate', + meta: { + title: 'extraCommunity', + icon: '', + }, + component: () => import('src/pages/Travel/TravelCreate.vue'), + }, + ], + }, + { + name: 'TravelCard', + path: ':id', + component: () => import('src/pages/Travel/Card/TravelCard.vue'), + redirect: { name: 'TravelSummary' }, + children: [ + { + name: 'TravelSummary', + path: 'summary', + meta: { + title: 'summary', + }, + component: () => import('src/pages/Travel/Card/TravelSummary.vue'), + }, + ], + }, + ], +}; diff --git a/src/router/modules/wagon.js b/src/router/modules/wagon.js index 02513d5a8..238e482dd 100644 --- a/src/router/modules/wagon.js +++ b/src/router/modules/wagon.js @@ -10,7 +10,7 @@ export default { component: RouterView, redirect: { name: 'WagonMain' }, menus: { - main: ['WagonList', 'WagonTypeList'], + main: ['WagonList', 'WagonTypeList', 'WagonCounter'], card: [], }, children: [ @@ -27,7 +27,7 @@ export default { title: 'wagonsList', icon: 'vn:trolley', }, - component: () => import('src/pages/Wagon/WagonList.vue') + component: () => import('src/pages/Wagon/WagonList.vue'), }, { path: 'create', @@ -36,7 +36,7 @@ export default { title: 'wagonCreate', icon: 'create', }, - component: () => import('src/pages/Wagon/WagonCreate.vue') + component: () => import('src/pages/Wagon/WagonCreate.vue'), }, { path: ':id/edit', @@ -45,7 +45,16 @@ export default { title: 'wagonEdit', icon: 'edit', }, - component: () => import('src/pages/Wagon/WagonCreate.vue') + component: () => import('src/pages/Wagon/WagonCreate.vue'), + }, + { + path: 'counter', + name: 'WagonCounter', + meta: { + title: 'wagonCounter', + icon: 'add_circle', + }, + component: () => import('src/pages/Wagon/WagonCounter.vue'), }, ], }, @@ -62,7 +71,7 @@ export default { title: 'typesList', icon: 'view_list', }, - component: () => import('src/pages/Wagon/Type/WagonTypeList.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeList.vue'), }, { path: 'create', @@ -71,7 +80,7 @@ export default { title: 'typeCreate', icon: 'create', }, - component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'), }, { path: ':id/edit', @@ -80,9 +89,9 @@ export default { title: 'typeEdit', icon: 'edit', }, - component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue') + component: () => import('src/pages/Wagon/Type/WagonTypeCreate.vue'), }, ], - } + }, ], }; diff --git a/src/router/modules/worker.js b/src/router/modules/worker.js index f79d7f06d..e5ee7c1a2 100644 --- a/src/router/modules/worker.js +++ b/src/router/modules/worker.js @@ -11,7 +11,7 @@ export default { redirect: { name: 'WorkerMain' }, menus: { main: ['WorkerList'], - // card: ['WorkerNotificationsManager'], + card: ['WorkerNotificationsManager'], }, children: [ { @@ -46,15 +46,16 @@ export default { }, component: () => import('src/pages/Worker/Card/WorkerSummary.vue'), }, - // { - // name: 'WorkerNotificationsManager', - // path: 'notifications', - // meta: { - // title: 'notifications', - // icon: 'notifications', - // }, - // component: () => import('src/pages/Worker/Card/WorkerNotificationsManager.vue'), - // }, + { + name: 'WorkerNotificationsManager', + path: 'notifications', + meta: { + title: 'notifications', + icon: 'notifications', + }, + component: () => + import('src/pages/Worker/Card/WorkerNotificationsManager.vue'), + }, ], }, ], diff --git a/src/router/routes.js b/src/router/routes.js index e3aa22a06..550fcf64e 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -3,8 +3,12 @@ import ticket from './modules/ticket'; import claim from './modules/claim'; import worker from './modules/worker'; import invoiceOut from './modules/invoiceOut'; +import invoiceIn from './modules/invoiceIn'; import wagon from './modules/wagon'; +import supplier from './modules/Supplier'; import route from './modules/route'; +import travel from './modules/travel'; +import shelving from 'src/router/modules/shelving'; const routes = [ { @@ -48,14 +52,18 @@ const routes = [ ticket, claim, worker, + shelving, invoiceOut, + invoiceIn, + wagon, + route, + supplier, + travel, { path: '/:catchAll(.*)*', name: 'NotFound', component: () => import('../pages/NotFound.vue'), }, - wagon, - route, ], }, ]; diff --git a/src/services/invoiceOut.service.js b/src/services/invoiceOut.service.js new file mode 100644 index 000000000..6232f319d --- /dev/null +++ b/src/services/invoiceOut.service.js @@ -0,0 +1,49 @@ +import axios from 'axios'; + +const request = async (method, url, params = {}) => { + try { + let response; + + if (method === 'GET') { + response = await axios.get(url, { params }); + } else if (method === 'POST') { + response = await axios.post(url, params); + } + return response.data; + } catch (err) { + console.error(`Error with ${method} request to ${url}`, err); + return err.response; + } +}; + +const invoiceOutService = { + getNegativeBases: async (params) => { + return await request('GET', 'InvoiceOuts/negativeBases', params); + }, + + getNegativeBasesCsv: async (params) => { + return await request('GET', 'InvoiceOuts/negativeBasesCsv', params); + }, + + getInvoiceDate: async (params) => { + return await request('GET', 'InvoiceOuts/getInvoiceDate', params); + }, + + getFindOne: async (params) => { + return await request('GET', 'InvoiceOutConfigs/findOne', params); + }, + + getClientsToInvoice: async (params) => { + return await request('POST', 'InvoiceOuts/clientsToInvoice', params); + }, + + invoiceClient: async (params) => { + return await request('POST', 'InvoiceOuts/invoiceClient', params); + }, + + makePdfAndNotify: async (invoiceId, params) => { + return await request('POST', `InvoiceOuts/${invoiceId}/makePdfAndNotify`, params); + }, +}; + +export default invoiceOutService; diff --git a/src/services/travel.service.js b/src/services/travel.service.js new file mode 100644 index 000000000..469b8b237 --- /dev/null +++ b/src/services/travel.service.js @@ -0,0 +1,23 @@ +import axios from 'axios'; + +const travelService = { + getTravelEntries: async (param) => { + try { + return await axios.get(`Travels/${param}/getEntries`); + } catch (err) { + console.error(`Error fetching travel entries`, err); + return err.response; + } + }, + + updateTravel: async (id, params) => { + try { + return await axios.patch(`Travels/${id}`, params); + } catch (err) { + console.error(`Error updating travel`, err); + return err.response; + } + }, +}; + +export default travelService; diff --git a/src/stores/invoiceOutGlobal.js b/src/stores/invoiceOutGlobal.js new file mode 100644 index 000000000..d449a577d --- /dev/null +++ b/src/stores/invoiceOutGlobal.js @@ -0,0 +1,264 @@ +import { defineStore } from 'pinia'; +import { useUserConfig } from 'src/composables/useUserConfig'; +import invoiceOutService from 'src/services/invoiceOut.service'; +import useNotify from 'src/composables/useNotify.js'; +import { exportFile } from 'quasar'; + +const { notify } = useNotify(); + +export const useInvoiceOutGlobalStore = defineStore({ + id: 'invoiceOutGlobal', + + state: () => ({ + initialDataLoading: true, + formInitialData: { + companyFk: null, + invoiceDate: null, + maxShipped: null, + clientId: null, + printer: null, + }, + addresses: [], + minInvoicingDate: null, + parallelism: null, + invoicing: false, + isInvoicing: false, + status: null, + addressIndex: 0, + errors: [], + printer: null, + nRequests: 0, + nPdfs: 0, + totalPdfs: 0, + }), + actions: { + async init() { + await this.fetchAllData(); + }, + + async fetchAllData() { + try { + const userInfo = await useUserConfig().fetch(); + const date = Date.vnNew(); + this.formInitialData.maxShipped = new Date( + date.getFullYear(), + date.getMonth(), + 0 + ) + .toISOString() + .substring(0, 10); + + await Promise.all([ + this.fetchParallelism(), + this.fetchInvoiceOutConfig(userInfo.companyFk), + ]); + + this.initialDataLoading = false; + } catch (err) { + console.error('Error fetching invoice out global initial data'); + } + }, + + async fetchInvoiceOutConfig(companyFk) { + this.formInitialData.companyFk = companyFk; + const params = { companyFk: companyFk }; + const { issued } = await invoiceOutService.getInvoiceDate(params); + const stringDate = issued.substring(0, 10); + this.minInvoicingDate = stringDate; + this.formInitialData.invoiceDate = stringDate; + }, + + async fetchParallelism() { + const filter = { fields: ['parallelism'] }; + const { parallelism } = await invoiceOutService.getFindOne(filter); + this.parallelism = parallelism; + }, + + async makeInvoice(formData, clientsToInvoice) { + this.invoicing = true; + this.status = 'packageInvoicing'; + try { + this.printer = formData.printer; + const params = { + invoiceDate: new Date(formData.invoiceDate), + maxShipped: new Date(formData.maxShipped), + clientId: formData.clientId ? formData.clientId : null, + companyFk: formData.companyFk, + }; + + this.validateMakeInvoceParams(params, clientsToInvoice); + + if (clientsToInvoice == 'all') params.clientId = undefined; + + const addressesResponse = await invoiceOutService.getClientsToInvoice( + params + ); + + this.addresses = addressesResponse; + + if (!this.addresses || !this.addresses.length > 0) { + notify( + 'invoiceOut.globalInvoices.errors.noTicketsToInvoice', + 'negative' + ); + throw new Error("There aren't addresses to invoice"); + } + + this.addresses.forEach(async (address) => { + await this.invoiceClient(address, formData); + }); + } catch (err) { + this.handleError(err); + } + }, + + validateMakeInvoceParams(params, clientsToInvoice) { + if (clientsToInvoice === 'one' && !params.clientId) { + notify('invoiceOut.globalInvoices.errors.chooseValidClient', 'negative'); + throw new Error('Invalid client'); + } + if (!params.invoiceDate || !params.maxShipped) { + notify('invoiceOut.globalInvoices.errors.fillDates', 'negative'); + throw new Error('Missing dates'); + } + if (params.invoiceDate < params.maxShipped) { + notify( + 'invoiceOut.globalInvoices.errors.invoiceDateLessThanMaxDate', + 'negative' + ); + throw new Error('Invalid date range'); + } + + const invoiceDateTime = new Date(params.invoiceDate).getTime(); + const minInvoiceDateTime = new Date(this.minInvoicingDate).getTime(); + + if (this.minInvoicingDate && invoiceDateTime < minInvoiceDateTime) { + notify( + 'invoiceOut.globalInvoices.errors.invoiceWithFutureDate', + 'negative' + ); + throw new Error('Invoice date in the future'); + } + + if (!params.companyFk) { + notify('invoiceOut.globalInvoices.errors.chooseValidCompany', 'negative'); + throw new Error('Invalid company'); + } + if (!this.printer) { + notify('invoiceOut.globalInvoices.errors.chooseValidPrinter', 'negative'); + throw new Error('Invalid printer'); + } + }, + + async invoiceClient(address, formData) { + if (this.nRequests === this.parallelism || this.isInvoicing) return; + + if (this.status === 'stopping') { + if (this.nRequests) return; + this.invoicing = false; + this.status = 'done'; + return; + } + + const params = { + clientId: address.clientId, + addressId: address.id, + invoiceDate: new Date(formData.invoiceDate), + maxShipped: new Date(formData.maxShipped), + companyFk: formData.companyFk, + }; + + this.status = 'invoicing'; + this.invoicing = true; + + const invoiceResponse = await invoiceOutService.invoiceClient(params); + + if (invoiceResponse.data.error) { + if (invoiceResponse.status >= 400 && invoiceResponse.status < 500) { + this.invoiceClientError(address, invoiceResponse); + this.addressIndex++; + return; + } else { + this.invoicing = false; + this.status = 'done'; + notify( + 'invoiceOut.globalInvoices.errors.criticalInvoiceError', + 'negative' + ); + throw new Error('Critical invoicing error, process stopped'); + } + } else { + this.isInvoicing = false; + if (invoiceResponse.data) { + this.makePdfAndNotify(invoiceResponse.data, address); + } + } + }, + + async makePdfAndNotify(invoiceId, client) { + try { + this.nRequests++; + this.totalPdfs++; + const params = { printerFk: this.printer }; + await invoiceOutService.makePdfAndNotify(invoiceId, params); + this.nPdfs++; + this.nRequests--; + } catch (err) { + this.invoiceClientError(client, err, true); + } + }, + + invoiceClientError(client, response, isWarning) { + const message = response.data?.error?.message || response.message; + this.errors.unshift({ client, message, isWarning }); + }, + + handleError(err) { + this.invoicing = false; + this.status = null; + throw err; + }, + + async getNegativeBasesCsv(from, to) { + try { + const params = { from: from, to: to }; + const CSVResponse = await invoiceOutService.getNegativeBasesCsv(params); + + if (CSVResponse.data && CSVResponse.data.error) throw new Error(); + + const status = exportFile('negativeBases.csv', CSVResponse, { + encoding: 'windows-1252', + mimeType: 'text/csv;charset=windows-1252;', + }); + + if (status) { + notify('globals.downloadCSVSuccess', 'positive'); + } + } catch (err) { + notify('invoiceOut.negativeBases.errors.downloadCsvFailed', 'negative'); + } + }, + + // State mutations actions + + setStatusValue(status) { + this.status = status; + }, + }, + + getters: { + getNAddresses(state) { + return state.addresses.length; + }, + getPercentage(state) { + if (this.getNAdresses <= 0 || !state.addressIndex) { + return 0; + } + let porcentaje = (state.addressIndex / this.getNAddresses) * 100; + return porcentaje; + }, + getAddressNumber(state) { + return state.addressIndex; + }, + }, +}); diff --git a/src/stores/useArrayDataStore.js b/src/stores/useArrayDataStore.js index f9a32a6fa..223406a33 100644 --- a/src/stores/useArrayDataStore.js +++ b/src/stores/useArrayDataStore.js @@ -18,7 +18,8 @@ export const useArrayDataStore = defineStore('arrayDataStore', () => { skip: 0, order: '', data: ref(), - isLoading: false + isLoading: false, + exprBuilder: null, }; } diff --git a/src/stores/useNavigationStore.js b/src/stores/useNavigationStore.js index 168c1f9dc..b5947f87b 100644 --- a/src/stores/useNavigationStore.js +++ b/src/stores/useNavigationStore.js @@ -6,7 +6,19 @@ import { useRole } from 'src/composables/useRole'; import routes from 'src/router/modules'; export const useNavigationStore = defineStore('navigationStore', () => { - const modules = ['customer', 'claim', 'ticket', 'invoiceOut', 'worker', 'wagon', 'route']; + const modules = [ + 'customer', + 'claim', + 'ticket', + 'invoiceOut', + 'worker', + 'shelving', + 'wagon', + 'route', + 'supplier', + 'travel', + 'invoiceIn', + ]; const pinnedModules = ref([]); const role = useRole(); diff --git a/src/stores/useValidationsStore.js b/src/stores/useValidationsStore.js new file mode 100644 index 000000000..e00812379 --- /dev/null +++ b/src/stores/useValidationsStore.js @@ -0,0 +1,18 @@ +import axios from 'axios'; +import { defineStore } from 'pinia'; + +export const useValidationsStore = defineStore('validationsStore', { + state: () => ({ + validations: null, + }), + actions: { + async fetchModels() { + try { + const { data } = await axios.get('Schemas/modelinfo'); + this.validations = data; + } catch (error) { + console.error('Error al obtener las validaciones:', error); + } + }, + }, +}); diff --git a/test/cypress/integration/claim/claimAction.spec.js b/test/cypress/integration/claim/claimAction.spec.js new file mode 100644 index 000000000..f181722fa --- /dev/null +++ b/test/cypress/integration/claim/claimAction.spec.js @@ -0,0 +1,45 @@ +/// +describe('ClaimAction', () => { + const claimId = 2; + + const firstRow = 'tbody > :nth-child(1)'; + const destinationRow = '.q-item__section > .q-field'; + + beforeEach(() => { + cy.viewport(1920, 1080); + cy.login('developer'); + cy.visit(`/#/claim/${claimId}/action`); + }); + + it('should import claim', () => { + cy.get('[title="Import claim"]').click(); + }); + + it('should change destination', () => { + const rowData = [true, null, null, 'Bueno']; + cy.fillRow(firstRow, rowData); + }); + + it('should change destination from other button', () => { + const rowData = [true]; + + cy.fillRow(firstRow, rowData); + cy.get('[title="Change destination"]').click(); + cy.selectOption(destinationRow, 'Confeccion'); + cy.get('.q-card > .q-card__actions > .q-btn--standard').click(); + }); + + it('should regularize', () => { + cy.get('[title="Regularize"]').click(); + cy.clickConfirm(); + }); + + it('should remove the line', () => { + cy.fillRow(firstRow, [true]); + cy.removeCard(); + cy.clickConfirm(); + + cy.reload(); + cy.get(firstRow).should('not.exist'); + }); +}); diff --git a/test/cypress/integration/claimDevelopment.spec.js b/test/cypress/integration/claim/claimDevelopment.spec.js similarity index 100% rename from test/cypress/integration/claimDevelopment.spec.js rename to test/cypress/integration/claim/claimDevelopment.spec.js diff --git a/test/cypress/integration/ClaimNotes.spec.js b/test/cypress/integration/claim/claimNotes.spec.js similarity index 90% rename from test/cypress/integration/ClaimNotes.spec.js rename to test/cypress/integration/claim/claimNotes.spec.js index 5b52dd339..0a0f28fe7 100644 --- a/test/cypress/integration/ClaimNotes.spec.js +++ b/test/cypress/integration/claim/claimNotes.spec.js @@ -7,7 +7,7 @@ describe('ClaimNotes', () => { it('should add a new note', () => { const message = 'This is a new message.'; - cy.get('.q-page-sticky button').click(); + cy.get('.q-page-sticky > div > button').click(); cy.get('.q-dialog .q-card__section:nth-child(2)').type(message); cy.get('.q-card__actions button:nth-child(2)').click(); cy.get('.q-card .q-card__section:nth-child(2)') diff --git a/test/cypress/integration/claimPhoto.spec.js b/test/cypress/integration/claim/claimPhoto.spec.js similarity index 100% rename from test/cypress/integration/claimPhoto.spec.js rename to test/cypress/integration/claim/claimPhoto.spec.js diff --git a/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js new file mode 100644 index 000000000..0013df343 --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInBasicData.spec.js @@ -0,0 +1,53 @@ +/// +describe('InvoiceInBasicData', () => { + const selects = '.q-form .q-select'; + const appendBtns = 'label button'; + const dialogAppendBtns = '.q-dialog label button'; + const dialogInputs = '.q-dialog input'; + const dialogActionBtns = '.q-card__actions button'; + + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/invoice-in/1/basic-data`); + }); + + it('should edit the provideer and supplier ref', () => { + cy.get(selects).eq(0).click(); + cy.get(selects).eq(0).type('Bros'); + cy.get(selects).eq(0).type('{enter}'); + + cy.get(appendBtns).eq(0).click(); + cy.get('input').eq(2).type(4739); + cy.saveCard(); + + cy.get(`${selects} input`).eq(0).invoke('val').should('eq', 'Bros nick'); + cy.get('input').eq(2).invoke('val').should('eq', '4739'); + }); + + it('should edit the dms data', () => { + const firtsInput = 'Ticket:65'; + const secondInput = "I don't know what posting here!"; + + cy.get(appendBtns).eq(3).click(); + cy.get(dialogAppendBtns).eq(0).click(); + cy.get(dialogInputs).eq(0).type(firtsInput); + cy.get(dialogAppendBtns).eq(1).click(); + cy.get('textarea').type(secondInput); + cy.get(dialogActionBtns).eq(1).click(); + + cy.get(appendBtns).eq(3).click(); + + cy.get(dialogInputs).eq(0).invoke('val').should('eq', firtsInput); + cy.get('textarea').invoke('val').should('eq', secondInput); + }); + + it('should throw an error creating a new dms if a file is not attached', () => { + cy.get(appendBtns).eq(2).click(); + cy.get(appendBtns).eq(1).click(); + cy.get(dialogActionBtns).eq(1).click(); + cy.get('.q-notification__message').should( + 'have.text', + "The files can't be empty" + ); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js new file mode 100644 index 000000000..ee5ffe08d --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInDueDay.spec.js @@ -0,0 +1,30 @@ +/// +describe('InvoiceInDueDay', () => { + const inputs = 'label input'; + const inputBtns = 'label button'; + const addBtn = '.q-page-sticky > div > .q-btn > .q-btn__content'; + + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/invoice-in/6/due-day`); + }); + + it('should update the amount', () => { + cy.get(inputBtns).eq(1).click(); + cy.get(inputs).eq(3).type(23); + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should remove the first line', () => { + cy.removeRow(1); + }); + + it('should add a new row ', () => { + cy.waitForElement('thead'); + cy.get(addBtn).click(); + + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js new file mode 100644 index 000000000..5024b2f1c --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInIntrastat.spec.js @@ -0,0 +1,38 @@ +/// +describe('InvoiceInIntrastat', () => { + const inputBtns = 'label button'; + const thirdRow = 'tbody > :nth-child(3)'; + const firstLineCode = 'tbody > :nth-child(1) > :nth-child(2)'; + + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/invoice-in/1/intrastat`); + }); + + it('should edit the first line', () => { + cy.selectOption(firstLineCode, 'Plantas vivas: Esqueje/injerto, Vid'); + + cy.get(inputBtns).eq(1).click(); + + cy.saveCard(); + cy.visit(`/#/invoice-in/1/intrastat`); + + cy.getValue(firstLineCode).should( + 'have.value', + 'Plantas vivas: Esqueje/injerto, Vid' + ); + }); + + it('should add a new row', () => { + const rowData = [false, 'Plantas vivas: Esqueje/injerto, Vid', 30, 10, 5, 'FR']; + + cy.addRow(); + cy.fillRow(thirdRow, rowData); + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should remove the first line', () => { + cy.removeRow(1); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInList.spec.js b/test/cypress/integration/invoiceIn/invoiceInList.spec.js new file mode 100644 index 000000000..bd722edab --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInList.spec.js @@ -0,0 +1,28 @@ +/// +describe('InvoiceInList', () => { + const firstCard = '.q-card:nth-child(1)'; + const firstId = + '.q-card:nth-child(1) .list-items > .vn-label-value:first-child > .value > span'; + const firstDetailBtn = '.q-card:nth-child(1) .q-btn:nth-child(2)'; + const summaryHeaders = '.summaryBody .header'; + + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/invoice-in`); + }); + + it('should redirect on clicking a invoice', () => { + cy.get(firstId) + .invoke('text') + .then((id) => { + cy.get(firstCard).click(); + cy.url().should('include', `/invoice-in/${id}/summary`); + }); + }); + + it('should open the details', () => { + cy.get(firstDetailBtn).click(); + cy.get(summaryHeaders).eq(1).contains('Basic data'); + cy.get(summaryHeaders).eq(4).contains('Vat'); + }); +}); diff --git a/test/cypress/integration/invoiceIn/invoiceInVat.spec.js b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js new file mode 100644 index 000000000..26c7750ad --- /dev/null +++ b/test/cypress/integration/invoiceIn/invoiceInVat.spec.js @@ -0,0 +1,55 @@ +/// +describe('InvoiceInVat', () => { + const inputs = 'label input'; + const inputBtns = 'label button'; + const thirdRow = 'tbody > :nth-child(3)'; + const firstLineVat = 'tbody > :nth-child(1) > :nth-child(4)'; + const dialogInputs = '.q-dialog label input'; + const dialogBtns = '.q-dialog button'; + const randomInt = Math.floor(Math.random() * 100); + + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/invoice-in/1/vat`); + }); + + it('should edit the first line', () => { + cy.get(inputBtns).eq(1).click(); + cy.get(inputs).eq(2).type(23); + cy.selectOption(firstLineVat, 'H.P. IVA 21% CEE'); + + cy.saveCard(); + cy.visit(`/#/invoice-in/1/vat`); + + cy.getValue(firstLineVat).should('have.value', 'H.P. IVA 21% CEE'); + }); + + it('should add a new row', () => { + cy.addRow(); + cy.fillRow(thirdRow, [true, 2000000001, 30, 'H.P. IVA 10']); + cy.saveCard(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); + + it('should remove the first line', () => { + cy.removeRow(1); + }); + + it('should throw an error if there are fields undefined', () => { + cy.get(inputBtns).eq(0).click(); + cy.get(dialogBtns).eq(2).click(); + cy.get('.q-notification__message').should('have.text', "The code can't be empty"); + }); + + it('should correctly handle expense addition', () => { + cy.get(inputBtns).eq(0).click(); + + cy.get(dialogInputs).eq(0).click(); + cy.get(dialogInputs).eq(0).type(randomInt); + cy.get(dialogInputs).eq(1).click(); + cy.get(dialogInputs).eq(1).type('This is a dummy expense'); + + cy.get(dialogBtns).eq(2).click(); + cy.get('.q-notification__message').should('have.text', 'Data saved'); + }); +}); diff --git a/test/cypress/integration/ticketBoxing.spec.js b/test/cypress/integration/ticket/ticketBoxing.spec.js similarity index 59% rename from test/cypress/integration/ticketBoxing.spec.js rename to test/cypress/integration/ticket/ticketBoxing.spec.js index 7b3bd36cc..c4d85e945 100755 --- a/test/cypress/integration/ticketBoxing.spec.js +++ b/test/cypress/integration/ticket/ticketBoxing.spec.js @@ -17,18 +17,4 @@ describe('TicketBoxing', () => { cy.get('div[class="q-item__label text-h6"]').eq(0).click(); cy.get('.q-notification__message').should('have.text', 'No videos available'); }); - - it('should show select time and video if have video list', () => { - cy.intercept( - { - method: 'GET', - url: '/api/Boxings/*', - }, - ['2022-01-01T01-01-00.mp4', '2022-02-02T02-02-00.mp4', '2022-03-03T03-03-00.mp4'] - ).as('getVideoList'); - cy.get('.q-list').eq(3).find('.q-item').eq(2).click(); - - cy.get('.q-list').eq(3).find('.q-item').eq(0).find('.q-range'); - cy.get('.q-list').eq(3).find('.q-item').eq(1).find('.q-select'); - }); }); diff --git a/test/cypress/integration/vnBreadcrumbs.spec.js b/test/cypress/integration/vnBreadcrumbs.spec.js new file mode 100644 index 000000000..3c839c1c7 --- /dev/null +++ b/test/cypress/integration/vnBreadcrumbs.spec.js @@ -0,0 +1,21 @@ +/// +describe('VnBreadcrumbs', () => { + const firstCard = '.q-infinite-scroll > :nth-child(1)'; + const lastBreadcrumb = '.q-breadcrumbs--last > .q-breadcrumbs__el'; + beforeEach(() => { + cy.login('developer'); + cy.visit('/'); + }); + + it('should not be breadcrumbs', () => { + cy.get('.q-breadcrumbs').should('not.exist'); + }); + + it('should get the correct breadcrumbs', () => { + cy.visit('#/customer/list'); + cy.get('.q-breadcrumbs__el').should('have.length', 2); + + cy.get(firstCard).click(); + cy.get(`${lastBreadcrumb} > .q-icon`).should('have.text', 'launch'); + }); +}); diff --git a/test/cypress/integration/vnLog.spec.js b/test/cypress/integration/vnLog.spec.js new file mode 100644 index 000000000..d120b8ad4 --- /dev/null +++ b/test/cypress/integration/vnLog.spec.js @@ -0,0 +1,25 @@ +/// +describe('VnLog', () => { + const chips = [ + ':nth-child(1) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content', + ':nth-child(2) > :nth-child(1) > .q-item__label > .q-chip > .q-chip__content', + ]; + beforeEach(() => { + cy.login('developer'); + cy.visit(`/#/claim/${1}/log`); + cy.openRightMenu('.timeline'); + }); + + it('should filter by insert actions', () => { + cy.checkOption(':nth-child(7) > .q-checkbox'); + cy.get('.q-page').click(); + cy.validateContent(chips[0], 'Document'); + cy.validateContent(chips[1], 'Beginning'); + }); + + it('should filter by entity', () => { + cy.selectOption('.q-drawer--right .q-item > .q-select', 'Claim'); + cy.get('.q-page').click(); + cy.validateContent(chips[0], 'Claim'); + }); +}); diff --git a/test/cypress/integration/wagonCreate.spec.js b/test/cypress/integration/wagon/wagonCreate.spec.js similarity index 54% rename from test/cypress/integration/wagonCreate.spec.js rename to test/cypress/integration/wagon/wagonCreate.spec.js index 62eadc5fd..9aff3a819 100644 --- a/test/cypress/integration/wagonCreate.spec.js +++ b/test/cypress/integration/wagon/wagonCreate.spec.js @@ -6,6 +6,7 @@ describe('WagonCreate', () => { }); it('should create and delete a new wagon', () => { + cy.waitForElement('.q-card'); cy.get('input').eq(0).type('1234'); cy.get('input').eq(1).type('1234ABCD'); cy.get('input').eq(2).type('100'); @@ -16,18 +17,14 @@ describe('WagonCreate', () => { cy.get('button[type="submit"]').click(); // Check data has been saved successfully - cy.get('div.text-h6').contains('1234').click(); - cy.get('input').eq(0).should('have.value', '1234'); - cy.get('input').eq(1).should('have.value', '1234ABCD'); - cy.get('input').eq(2).should('have.value', '100'); - cy.get('input').eq(3).should('have.value', 'Wagon Type #1'); + 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'); // Delete wagon type created - cy.go('back'); - cy.get('div.text-h6') - .contains('1234') - .parentsUntil('div.q-card') - .find('div.q-card__actions') - .find('button').last().click(); + cy.get('.actions > .q-btn--standard').click(); }); }); diff --git a/test/cypress/integration/wagonTypeCreate.spec.js b/test/cypress/integration/wagonType/wagonTypeCreate.spec.js similarity index 84% rename from test/cypress/integration/wagonTypeCreate.spec.js rename to test/cypress/integration/wagonType/wagonTypeCreate.spec.js index ae74dff16..bcf7fe841 100644 --- a/test/cypress/integration/wagonTypeCreate.spec.js +++ b/test/cypress/integration/wagonType/wagonTypeCreate.spec.js @@ -5,13 +5,13 @@ describe('WagonTypeCreate', () => { cy.visit('/#/wagon/type/create'); }); - function chooseColor(color){ + function chooseColor(color) { cy.get('div.shelving-down').eq(1).click(); cy.get('div.q-color-picker__cube').eq(color).click(); cy.get('div.q-card__section').find('button').click(); } - function addTray(position){ + function addTray(position) { cy.get('div.action-button').last().find('button').click(); cy.focused().type(position); cy.focused().blur(); @@ -38,7 +38,9 @@ describe('WagonTypeCreate', () => { cy.get('button[type="submit"]').click(); // Check data has been saved successfully - cy.get('div.text-h6').contains('Example for testing').click(); + cy.get(':nth-child(1) > :nth-child(1) > .justify-between > .flex > .title') + .contains('Example for testing') + .click(); cy.get('input').first().should('have.value', 'Example for testing'); cy.get('div.wagon-tray').should('have.length', 4); cy.get('div.position').eq(0).find('input').should('have.value', '150'); @@ -47,11 +49,8 @@ describe('WagonTypeCreate', () => { // Delete wagon type created cy.go('back'); - cy.get('div.text-h6') - .contains('Example for testing') - .parentsUntil('div.q-card') - .find('div.q-card__actions') - .find('button').last().click(); - + cy.get( + ':nth-child(2) > :nth-child(1) > .card-list-body > .actions > .q-btn--standard' + ).click(); }); }); diff --git a/test/cypress/integration/worker/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js new file mode 100644 index 000000000..b5c57f920 --- /dev/null +++ b/test/cypress/integration/worker/workerList.spec.js @@ -0,0 +1,22 @@ +describe('WorkerList', () => { + const workerFieldNames = + '.card-list-body > .list-items > :nth-child(1) > .value > span'; + beforeEach(() => { + cy.viewport(1280, 720); + cy.login('developer'); + cy.visit('/#/worker/list'); + }); + + it('should load workers', () => { + cy.get(workerFieldNames).eq(0).should('have.text', 'JessicaJones'); + cy.get(workerFieldNames).eq(1).should('have.text', 'BruceBanner'); + cy.get(workerFieldNames).eq(2).should('have.text', 'CharlesXavier'); + }); + + it('should open the worker summary', () => { + cy.openListSummary(0); + cy.get('.summaryHeader div').should('have.text', '1110 - Jessica Jones'); + cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data'); + cy.get('.summary .header').eq(1).should('have.text', 'User data'); + }); +}); diff --git a/test/cypress/integration/worker/workerNotificationsManager.spec.js b/test/cypress/integration/worker/workerNotificationsManager.spec.js new file mode 100644 index 000000000..175933277 --- /dev/null +++ b/test/cypress/integration/worker/workerNotificationsManager.spec.js @@ -0,0 +1,79 @@ +describe('WorkerNotificationsManager', () => { + const salesPersonId = 18; + const developerId = 9; + + const activeList = ':nth-child(1) > .q-list'; + const availableList = ':nth-child(2) > .q-list'; + const firstActiveNotification = + ':nth-child(1) > .q-list > :nth-child(1) > .q-item > .q-toggle > .q-toggle__inner'; + const firstAvailableNotification = + ':nth-child(2) > .q-list > :nth-child(1) > .q-item > .q-toggle > .q-toggle__inner'; + + beforeEach(() => { + cy.viewport(1280, 720); + }); + + it('should throw an error if you try to change a notification that is not yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${salesPersonId}/notifications`); + cy.get(firstAvailableNotification).click(); + cy.notificationHas( + '.q-notification__message', + 'The notification subscription of this worker cant be modified' + ); + }); + + it('should active a notification that is yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${developerId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); + + cy.get(activeList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstAvailableNotification).click(); + cy.get(activeList) + .children() + .should('have.length', beforeSize + 1); + }); + }); + + it('should deactivate a notification that is yours', () => { + cy.login('developer'); + cy.visit(`/#/worker/${developerId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); + + cy.get(availableList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstActiveNotification).click(); + cy.get(availableList) + .children() + .should('have.length', beforeSize + 1); + }); + }); + + it('should active a notification if you are their boss', () => { + cy.login('salesBoss'); + cy.visit(`/#/worker/${salesPersonId}/notifications`); + cy.waitForElement(activeList); + cy.waitForElement(availableList); + + cy.get(activeList) + .children() + .its('length') + .then((beforeSize) => { + cy.get(firstAvailableNotification).click(); + cy.get(activeList) + .children() + .should('have.length', beforeSize + 1); + + //Rollback + cy.get(firstActiveNotification).click(); + }); + }); +}); diff --git a/test/cypress/integration/workerSummary.spec.js b/test/cypress/integration/worker/workerSummary.spec.js similarity index 100% rename from test/cypress/integration/workerSummary.spec.js rename to test/cypress/integration/worker/workerSummary.spec.js diff --git a/test/cypress/integration/workerList.spec.js b/test/cypress/integration/workerList.spec.js deleted file mode 100644 index 219633263..000000000 --- a/test/cypress/integration/workerList.spec.js +++ /dev/null @@ -1,26 +0,0 @@ -describe('WorkerList', () => { - beforeEach(() => { - cy.viewport(1280, 720); - cy.login('developer'); - cy.visit('/#/worker/list'); - }); - - it('should load workers', () => { - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(0) - .should('have.text', 'JessicaJones'); - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(1) - .should('have.text', 'BruceBanner'); - cy.get('.card-list-body > .list-items > :nth-child(2) > .value > span') - .eq(2) - .should('have.text', 'CharlesXavier'); - }); - - it('should open the worker summary', () => { - cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(1).click(); - cy.get('.summaryHeader div').should('have.text', '1109 - Bruce Banner'); - cy.get('.summary .header').eq(0).invoke('text').should('include', 'Basic data'); - cy.get('.summary .header').eq(1).should('have.text', 'User data'); - }); -}); diff --git a/test/cypress/integration/workerNotificationsManager.spec.js b/test/cypress/integration/workerNotificationsManager.spec.js deleted file mode 100644 index 4cd54629a..000000000 --- a/test/cypress/integration/workerNotificationsManager.spec.js +++ /dev/null @@ -1,36 +0,0 @@ -xdescribe('WorkerNotificationsManager', () => { - beforeEach(() => { - const workerId = 1110; - cy.viewport(1280, 720); - cy.login('salesBoss'); - cy.visit(`/#/worker/${workerId}/notifications`); - }); - - it('should unsubscribe 2 notifications, check the unsubscription has been saved, subscribe to other one and should check the data has been saved', () => { - cy.get('.q-chip').should('have.length', 3); - cy.get('.q-toggle__thumb').eq(0).click(); - cy.get('.q-notification__message').should( - 'have.text', - 'Unsubscribed from the notification' - ); - cy.get('.q-chip > .q-icon').eq(0).click(); - - cy.reload(); - - cy.get('.q-chip').should('have.length', 1); - cy.get('.q-toggle__thumb').should('have.length', 3).eq(0).click(); - cy.get('.q-notification__message').should( - 'have.text', - 'Subscribed to the notification' - ); - cy.get('.q-toggle__thumb').should('have.length', 3).eq(1).click(); - cy.get('.q-notification__message').should( - 'have.text', - 'Subscribed to the notification' - ); - - cy.reload(); - - cy.get('.q-chip').should('have.length', 3); - }); -}); diff --git a/test/cypress/support/commands.js b/test/cypress/support/commands.js index a725837a1..4dfde6e21 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -51,15 +51,14 @@ Cypress.Commands.add('getValue', (selector) => { return cy.get(selector + '.q-checkbox__inner'); } // Si es un QSelect - else if ($el.find('.q-select__dropdown-icon').length) { + if ($el.find('.q-select__dropdown-icon').length) { return cy.get( selector + '> .q-field > .q-field__inner > .q-field__control > .q-field__control-container > .q-field__native > input' ); - } else { - // Puedes añadir un log o lanzar un error si el elemento no es reconocido - cy.log('Elemento no soportado'); } + // Puedes añadir un log o lanzar un error si el elemento no es reconocido + cy.log('Elemento no soportado'); }); }); @@ -70,7 +69,7 @@ Cypress.Commands.add('selectOption', (selector, option) => { }); Cypress.Commands.add('checkOption', (selector) => { - cy.wrap(selector).find('.q-checkbox__inner').click(); + cy.get(selector).find('.q-checkbox__inner').click(); }); // Global buttons @@ -88,7 +87,12 @@ Cypress.Commands.add('addCard', () => { cy.get('.q-page-sticky > div > .q-btn').click(); }); Cypress.Commands.add('clickConfirm', () => { - cy.get('.q-btn--unelevated > .q-btn__content > .block').click(); + cy.waitForElement('.q-dialog__inner > .q-card'); + cy.get('.q-card__actions > .q-btn--unelevated > .q-btn__content > .block').click(); +}); + +Cypress.Commands.add('notificationHas', (selector, text) => { + cy.get(selector).should('have.text', text); }); Cypress.Commands.add('fillRow', (rowSelector, data) => { @@ -104,14 +108,19 @@ Cypress.Commands.add('fillRow', (rowSelector, data) => { .then((td) => { if (td.find('.q-select__dropdown-icon').length) { cy.selectOption(td, value); - } - if (td.find('.q-checkbox__inner').length && value) { + } else if (td.find('.q-checkbox__inner').length && value) { cy.checkOption(td); - } + } else if (td.find('input[type="text"]') && value) + cy.get(td).find('input').type(value); }); }); }); +Cypress.Commands.add('addRow', () => { + cy.waitForElement('tbody'); + cy.get('.q-page-sticky > div > .q-btn > .q-btn__content').click(); +}); + Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => { cy.waitForElement('tbody'); cy.get(rowSelector).within(() => { @@ -126,4 +135,38 @@ Cypress.Commands.add('validateRow', (rowSelector, expectedValues) => { } }); }); + +Cypress.Commands.add('removeRow', (rowIndex) => { + let rowsBefore; + let rowsAfter; + + cy.get('tr') + .its('length') + .then((length) => { + rowsBefore = length; + cy.get('.q-checkbox').eq(rowIndex).click(); + cy.removeCard(); + cy.get('.q-dialog button').eq(2).click(); + }) + .then(() => { + cy.get('tr') + .its('length') + .then((length) => { + rowsAfter = length; + expect(rowsBefore).to.eq(rowsAfter + 1); + }); + }); +}); +Cypress.Commands.add('openListSummary', (row) => { + cy.get('.card-list-body .actions .q-btn:nth-child(2)').eq(row).click(); +}); + +Cypress.Commands.add('openRightMenu', (element) => { + if (element) cy.waitForElement(element); + cy.get('#actions-append').click(); +}); + +Cypress.Commands.add('validateContent', (selector, expectedValue) => { + cy.get(selector).should('have.text', expectedValue); +}); // registerCommands(); diff --git a/test/vitest/__tests__/components/common/VnLog.spec.js b/test/vitest/__tests__/components/common/VnLog.spec.js index 6787b6d51..b654bff9b 100644 --- a/test/vitest/__tests__/components/common/VnLog.spec.js +++ b/test/vitest/__tests__/components/common/VnLog.spec.js @@ -1,75 +1,134 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper } from 'app/test/vitest/helper'; +import { createWrapper, axios } from 'app/test/vitest/helper'; import VnLog from 'src/components/common/VnLog.vue'; describe('VnLog', () => { let vm; - beforeAll(() => { - vm = createWrapper(VnLog, { - global: { - stubs: ['FetchData', 'VnPaginate'], - mocks: { - fetch: vi.fn(), + const fakeLogTreeData = [ + { + id: 2, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'ClaimObservation', + oldInstance: {}, + newInstance: { + claimFk: 1, + text: 'Waiting for customer', + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, }, }, + }, + { + id: 1, + originFk: 1, + userFk: 18, + action: 'update', + changedModel: 'Claim', + oldInstance: { + hasToPickUp: false, + }, + newInstance: { + hasToPickUp: true, + }, + creationDate: '2023-09-18T12:25:34.000Z', + changedModelId: '1', + changedModelValue: null, + description: null, + user: { + id: 18, + name: 'salesPerson', + nickname: 'salesPersonNick', + image: '4fa3ada0-3ac4-11eb-9ab8-27f6fc3b85fd', + worker: { + id: 18, + userFk: 18, + }, + }, + }, + ]; + const mockValidations = { + Claim: { + locale: { + name: 'reclamación', + }, + }, + ClaimObservation: { + locale: { + name: 'observación', + }, + }, + ClaimDms: { + locale: { + name: 'documento', + }, + }, + ClaimBeginning: { + locale: { + name: 'comienzo', + }, + }, + }; + + beforeAll(async () => { + axios.get.mockImplementation(() => { + return { data: fakeLogTreeData }; + }); + + vm = createWrapper(VnLog, { + global: { + stubs: [], + mocks: {}, + }, propsData: { - model: "Claim", + model: 'Claim', }, }).vm; + vm.validations = mockValidations; }); afterEach(() => { vi.clearAllMocks(); }); - describe('formatValue()', () => { - it('should return Yes if has a true boolean', async () => { - const result = vm.formatValue(true); - - expect(result).toEqual('Yes'); - }); - it('should return No if has a true boolean', async () => { - const result = vm.formatValue(false); - - expect(result).toEqual('No'); - }); - it('should return Nothing if has no params', async () => { - const result = vm.formatValue(); - - expect(result).toEqual('Nothing'); - }); - it('should return a string from a string value', async () => { - const result = vm.formatValue('Something'); - - expect(result).toEqual(`"Something"`); - }); - it('should call to format a date', async () => { - vi.mock('src/filters', () => ({ - toDate: ()=>{ - return "Date formatted" - }, - })); - - const result = vm.formatValue('01-01-1970'); - expect(result).toEqual("Date formatted"); - }); + it('should correctly set logTree', async () => { + vm.logTree = vm.getLogTree(fakeLogTreeData); + expect(vm.logTree[0].originFk).toEqual(1); + expect(vm.logTree[0].logs[0].user.name).toEqual('salesPerson'); }); - describe('actionColor()', () => { - it('should return positive if insert', async () => { - const result = vm.actionColor('insert'); + it('should correctly set the selectedFilters when filtering', () => { + vm.searchInput = '1'; + vm.userSelect = '21'; + vm.checkboxOptions.insert.selected = true; + vm.checkboxOptions.update.selected = true; - expect(result).toEqual('positive'); - }); - it('should return positive if update', async () => { - const result = vm.actionColor('update'); + vm.selectFilter('search'); + vm.selectFilter('userSelect'); - expect(result).toEqual('positive'); - }); - it('should return negative if delete', async () => { - const result = vm.actionColor('delete'); + expect(vm.selectedFilters.changedModelId).toEqual('1'); + expect(vm.selectedFilters.userFk).toEqual('21'); + expect(vm.selectedFilters.action).toEqual({ inq: ['insert', 'update'] }); + }); - expect(result).toEqual('negative'); - }); + it('should correctly set the date from', () => { + vm.dateFrom = '18-09-2023'; + vm.selectFilter('date', 'from'); + expect(vm.selectedFilters.creationDate.between).toEqual([ + new Date('2023-09-18T00:00:00.000Z'), + new Date('2023-09-18T21:59:59.999Z'), + ]); }); }); diff --git a/test/vitest/__tests__/composables/downloadFile.spec.js b/test/vitest/__tests__/composables/downloadFile.spec.js new file mode 100644 index 000000000..f611479bf --- /dev/null +++ b/test/vitest/__tests__/composables/downloadFile.spec.js @@ -0,0 +1,25 @@ +import { vi, describe, expect, it } from 'vitest'; +import { axios } from 'app/test/vitest/helper'; +import { downloadFile } from 'src/composables/downloadFile'; +import { useSession } from 'src/composables/useSession'; + +const session = useSession(); +const token = session.getToken(); + +describe('downloadFile', () => { + it('should open a new window to download the file', async () => { + const url = 'http://localhost:9000'; + + vi.spyOn(axios, 'get').mockResolvedValueOnce({ data: url }); + + const mockWindowOpen = vi.spyOn(window, 'open'); + + await downloadFile(1); + + expect(mockWindowOpen).toHaveBeenCalledWith( + `${url}/api/dms/1/downloadFile?access_token=${token}` + ); + + mockWindowOpen.mockRestore(); + }); +}); diff --git a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInBasicData.spec.js b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInBasicData.spec.js new file mode 100644 index 000000000..a3c383f74 --- /dev/null +++ b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInBasicData.spec.js @@ -0,0 +1,34 @@ +import { vi, describe, expect, it, beforeAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import InvoiceInBasicData from 'src/pages/InvoiceIn/Card/InvoiceInBasicData.vue'; + +describe('InvoiceInBasicData', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(InvoiceInBasicData, { + global: { + stubs: [], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + describe('upsert()', () => { + it('should throw an error when data is empty', async () => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: [] }); + vi.spyOn(vm.quasar, 'notify'); + + await vm.upsert(); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: `The company can't be empty`, + type: 'negative', + }) + ); + }); + }); +}); diff --git a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js new file mode 100644 index 000000000..55ca19d71 --- /dev/null +++ b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInIntrastat.spec.js @@ -0,0 +1,34 @@ +import { vi, describe, expect, it, beforeAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import InvoiceInIntrastat from 'src/pages/InvoiceIn/Card/InvoiceInIntrastat.vue'; + +describe('InvoiceInIntrastat', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(InvoiceInIntrastat, { + global: { + stubs: ['vnPaginate'], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + vi.spyOn(axios, 'get').mockResolvedValue({ data: [{}] }); + }); + + describe('getTotal()', () => { + it('should correctly handle the sum', () => { + vm.invoceInIntrastat = [ + { amount: 10, stems: 162 }, + { amount: 20, stems: 21 }, + ]; + + const totalAmount = vm.getTotal('amount'); + const totalStems = vm.getTotal('stems'); + + expect(totalAmount).toBe(10 + 20); + expect(totalStems).toBe(162 + 21); + }); + }); +}); diff --git a/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js new file mode 100644 index 000000000..6cc082a35 --- /dev/null +++ b/test/vitest/__tests__/pages/InvoiceIn/InvoiceInVat.spec.js @@ -0,0 +1,73 @@ +import { vi, describe, expect, it, beforeAll } from 'vitest'; +import { createWrapper, axios } from 'app/test/vitest/helper'; +import InvoiceInVat from 'src/pages/InvoiceIn/Card/InvoiceInVat.vue'; + +describe('InvoiceInVat', () => { + let vm; + + beforeAll(() => { + vm = createWrapper(InvoiceInVat, { + global: { + stubs: [], + mocks: { + fetch: vi.fn(), + }, + }, + }).vm; + }); + + describe('addExpense()', () => { + beforeAll(() => { + vi.spyOn(axios, 'post').mockResolvedValue({ data: [] }); + vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); + vi.spyOn(vm.quasar, 'notify'); + }); + + it('should throw an error when the code property is undefined', async () => { + await vm.addExpense(); + + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: `The code can't be empty`, + type: 'negative', + }) + ); + }); + + it('should correctly handle expense addition', async () => { + vm.newExpense = { + code: 123, + isWithheld: false, + description: 'Descripción del gasto', + }; + + await vm.addExpense(); + expect(vm.quasar.notify).toHaveBeenCalledWith( + expect.objectContaining({ + message: 'Data saved', + type: 'positive', + }) + ); + }); + }); + + describe('taxRate()', () => { + it('should correctly compute the tax rate', () => { + const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 }; + vm.sageTaxTypes = [ + { id: 1, rate: 10 }, + { id: 2, rate: 20 }, + ]; + const result = vm.taxRate(invoiceInTax); + expect(result).toBe((10 / 100) * 100); + }); + + it('should return 0 if there is not tax rate', () => { + const invoiceInTax = { taxableBase: 100, taxTypeSageFk: 1 }; + vm.sageTaxTypes = []; + + const result = vm.taxRate(invoiceInTax); + expect(result).toBe(0); + }); + }); +}); diff --git a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js b/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js index 744346078..35ce91e61 100644 --- a/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js +++ b/test/vitest/__tests__/pages/Worker/WorkerNotificationsManager.spec.js @@ -1,15 +1,15 @@ import { vi, describe, expect, it, beforeAll, afterEach } from 'vitest'; -import { createWrapper, axios } from 'app/test/vitest/helper'; +import { createWrapper } from 'app/test/vitest/helper'; import WorkerNotificationsManager from 'src/pages/Worker/Card/WorkerNotificationsManager.vue'; +import { ref } from 'vue'; describe('WorkerNotificationsManager', () => { let vm; - const entityId = 1110; beforeAll(() => { vm = createWrapper(WorkerNotificationsManager, { - propsData: { - id: entityId, + global: { + stubs: ['CrudModel'], }, }).vm; }); @@ -18,83 +18,16 @@ describe('WorkerNotificationsManager', () => { vi.clearAllMocks(); }); - describe('fetch()', () => { - it('should fetch notification subscriptions and role mappings', async () => { - vi.spyOn(axios, 'get') - .mockResolvedValueOnce({ - data: [ - { - id: 1, - name: 'Name 1', - description: 'Description 1', - notificationFk: 1, - active: true - }, - ], - }); - await vm.fetch(); + describe('swapEntry()', () => { + it('should swap notification', async () => { + const from = ref(new Map()); + const to = ref(new Map()); + from.value.set(1, { notificationFk: 1 }); + to.value.set(2, { notificationFk: 2 }); - expect(axios.get).toHaveBeenCalledWith(`NotificationSubscriptions/${entityId}/getList`); - expect(vm.notifications).toEqual([ - { - id: 1, - notificationFk: 1, - name: 'Name 1', - description: 'Description 1', - active: true, - }, - ]); - }); - }); - - describe('disableNotification()', () => { - it('should disable the notification', async () => { - vi.spyOn(axios, 'delete').mockResolvedValue({ data: { count: 1 } }); - vi.spyOn(vm.quasar, 'notify'); - const subscriptionId = 1; - vm.notifications = [{ id: 1, active: true }]; - - await vm.disableNotification(vm.notifications[0]); - - expect(axios.delete).toHaveBeenCalledWith( - `NotificationSubscriptions/${subscriptionId}` - ); - expect(vm.notifications[0].id).toBeNull(); - expect(vm.notifications[0].id).toBeFalsy(); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'positive' }) - ); - }); - }); - - describe('toggleNotification()', () => { - it('should activate the notification', async () => { - vi.spyOn(axios, 'post').mockResolvedValue({ - data: { id: 1, notificationFk: 1 }, - }); - vm.notifications = [{ id: null, active: true, notificationFk: 1 }]; - - await vm.toggleNotification(vm.notifications[0]); - - expect(axios.post).toHaveBeenCalledWith('NotificationSubscriptions', { - notificationFk: 1, - userFk: entityId, - }); - expect(vm.notifications[0].id).toBe(1); - expect(vm.notifications[0].active).toBeTruthy(); - expect(vm.quasar.notify).toHaveBeenCalledWith( - expect.objectContaining({ type: 'positive' }) - ); - }); - - it('should disable the notification', async () => { - vi.spyOn(vm, 'disableNotification'); - vm.notifications = [{ id: 1, active: false, notificationFk: 1 }]; - - await vm.toggleNotification(vm.notifications[0]); - - expect(vm.notifications[0].id).toBe(null); - expect(vm.notifications[0].active).toBeFalsy(); + await vm.swapEntry(from.value, to.value, 1); + expect(to.value.size).toBe(2); + expect(from.value.size).toBe(0); }); }); }); diff --git a/test/vitest/helper.js b/test/vitest/helper.js index 7cc2bdfa5..89cc640fd 100644 --- a/test/vitest/helper.js +++ b/test/vitest/helper.js @@ -13,7 +13,6 @@ installQuasarPlugin({ Dialog, }, }); - const pinia = createTestingPinia({ createSpy: vi.fn, stubActions: false }); const mockPush = vi.fn(); @@ -35,8 +34,10 @@ vi.mock('vue-router', () => ({ }), })); +vi.mock('axios'); + vi.spyOn(useValidator, 'useValidator').mockImplementation(() => { - return { validate: vi.fn(), fetch: vi.fn() }; + return { validate: vi.fn() }; }); class FormDataMock {
+ {{ t('claim.summary.actions') }} +
{{ props.title }}