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/src/components/CrudModel.vue b/src/components/CrudModel.vue index a5156dc79..75353a35a 100644 --- a/src/components/CrudModel.vue +++ b/src/components/CrudModel.vue @@ -136,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() { 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/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 2a3ee6161..83107267d 100644 --- a/src/components/common/VnSelectFilter.vue +++ b/src/components/common/VnSelectFilter.vue @@ -15,6 +15,10 @@ const $props = defineProps({ type: String, default: '', }, + filterOptions: { + type: Array, + default: () => [], + }, isClearable: { type: Boolean, default: true, @@ -32,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,6 +93,7 @@ const value = computed({ map-options use-input @filter="filterHandler" + hide-selected fill-input ref="vnSelectRef" > @@ -92,7 +101,7 @@ const value = computed({ diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index 2c1518b73..b20419b5d 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -40,6 +40,10 @@ const quasar = useQuasar(); const slots = useSlots(); const { t } = useI18n(); const entity = computed(() => useArrayData($props.dataKey).store.data); + +defineExpose({ + getData, +}); onMounted(async () => { await getData(); watch( diff --git a/src/components/ui/CardSummary.vue b/src/components/ui/CardSummary.vue index b54e44398..dc749f4a7 100644 --- a/src/components/ui/CardSummary.vue +++ b/src/components/ui/CardSummary.vue @@ -82,11 +82,13 @@ watch(props, async () => { .summaryBody { display: flex; flex-direction: row; + flex-wrap: wrap; justify-content: space-evenly; gap: 15px; padding: 15px; > .q-card.vn-one { + width: 350px; flex: 1; } > .q-card.vn-two { @@ -123,7 +125,6 @@ watch(props, async () => { width: max-content; overflow: hidden; white-space: nowrap; - text-overflow: ellipsis; } } .header { diff --git a/src/components/ui/VnLv.vue b/src/components/ui/VnLv.vue index 0f9b99713..9a17fca05 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 }, }); @@ -16,7 +17,7 @@ const isBooleanValue = computed(() => typeof $props.value === 'boolean');
- {{ $props.label }} + {{ $props.label }}
diff --git a/src/components/ui/VnSearchbar.vue b/src/components/ui/VnSearchbar.vue index 693d6fce2..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(); } } 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/css/app.scss b/src/css/app.scss index 99170202d..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,6 +41,7 @@ body.body--dark { --vn-gray: #313131; --vn-label: #a8a8a8; --vn-dark: #292929; + --vn-light-gray: #424242; } .bg-vn-dark { 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/i18n/en/index.js b/src/i18n/en/index.js index f2b4c9c55..41bf03c5e 100644 --- a/src/i18n/en/index.js +++ b/src/i18n/en/index.js @@ -357,7 +357,7 @@ export default { }, invoiceOut: { pageTitles: { - invoiceOuts: 'Invoices Out', + invoiceOuts: 'Create invoice', list: 'List', createInvoiceOut: 'Create invoice out', summary: 'Summary', @@ -401,6 +401,71 @@ export default { totalWithVat: 'Amount', }, }, + 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: { workers: 'Workers', @@ -526,6 +591,7 @@ export default { openCard: 'View card', openSummary: 'Open summary', viewDescription: 'View description', + downloadFile: 'Download file', }, cardDescriptor: { mainList: 'Main list', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index 39d7d7ae0..7fcd2e9c6 100644 --- a/src/i18n/es/index.js +++ b/src/i18n/es/index.js @@ -357,7 +357,7 @@ export default { }, invoiceOut: { pageTitles: { - invoiceOuts: 'Fact. emitidas', + invoiceOuts: 'Crear factura', list: 'Listado', createInvoiceOut: 'Crear fact. emitida', summary: 'Resumen', @@ -401,6 +401,69 @@ export default { totalWithVat: 'Importe', }, }, + 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: { workers: 'Trabajadores', @@ -526,6 +589,7 @@ export default { openCard: 'Ver ficha', openSummary: 'Abrir detalles', viewDescription: 'Ver descripción', + downloadFile: 'Descargar archivo', }, cardDescriptor: { mainList: 'Listado principal', 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/InvoiceIn/Card/InvoiceInBasicData.vue b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue new file mode 100644 index 000000000..4572c4c16 --- /dev/null +++ b/src/pages/InvoiceIn/Card/InvoiceInBasicData.vue @@ -0,0 +1,710 @@ + + + + + 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 @@ + + + + + +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 @@ + + + + + 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 @@ + + + + + + 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 @@ + + + + + + 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 @@ + + + + +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 @@ + + + + + +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 @@ + + + + + + + +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/router/modules/index.js b/src/router/modules/index.js index 2916b98ef..2bb28c817 100644 --- a/src/router/modules/index.js +++ b/src/router/modules/index.js @@ -2,16 +2,9 @@ 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 Wagon from './wagon'; import Route from './route'; -export default [ - Customer, - Ticket, - Claim, - InvoiceOut, - Worker, - Wagon, - Route -] +export default [Customer, Ticket, Claim, InvoiceOut, invoiceIn, Worker, Wagon, Route]; 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/routes.js b/src/router/routes.js index e3aa22a06..6632979d3 100644 --- a/src/router/routes.js +++ b/src/router/routes.js @@ -3,6 +3,7 @@ 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 route from './modules/route'; @@ -49,6 +50,7 @@ const routes = [ claim, worker, invoiceOut, + invoiceIn, { path: '/:catchAll(.*)*', name: 'NotFound', diff --git a/src/stores/useNavigationStore.js b/src/stores/useNavigationStore.js index 168c1f9dc..5db0049cf 100644 --- a/src/stores/useNavigationStore.js +++ b/src/stores/useNavigationStore.js @@ -6,7 +6,16 @@ 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', + 'invoiceIn', + 'worker', + 'wagon', + 'route', + ]; const pinnedModules = ref([]); const role = useRole(); diff --git a/test/cypress/integration/claimAction.spec.js b/test/cypress/integration/claim/claimAction.spec.js similarity index 100% rename from test/cypress/integration/claimAction.spec.js rename to test/cypress/integration/claim/claimAction.spec.js 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 100% rename from test/cypress/integration/claimNotes.spec.js rename to test/cypress/integration/claim/claimNotes.spec.js 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 100% rename from test/cypress/integration/ticketBoxing.spec.js rename to test/cypress/integration/ticket/ticketBoxing.spec.js diff --git a/test/cypress/integration/wagonCreate.spec.js b/test/cypress/integration/wagon/wagonCreate.spec.js similarity index 100% rename from test/cypress/integration/wagonCreate.spec.js rename to test/cypress/integration/wagon/wagonCreate.spec.js diff --git a/test/cypress/integration/wagonTypeCreate.spec.js b/test/cypress/integration/wagonType/wagonTypeCreate.spec.js similarity index 100% rename from test/cypress/integration/wagonTypeCreate.spec.js rename to test/cypress/integration/wagonType/wagonTypeCreate.spec.js diff --git a/test/cypress/integration/workerList.spec.js b/test/cypress/integration/worker/workerList.spec.js similarity index 100% rename from test/cypress/integration/workerList.spec.js rename to test/cypress/integration/worker/workerList.spec.js diff --git a/test/cypress/integration/workerNotificationsManager.spec.js b/test/cypress/integration/worker/workerNotificationsManager.spec.js similarity index 100% rename from test/cypress/integration/workerNotificationsManager.spec.js rename to test/cypress/integration/worker/workerNotificationsManager.spec.js 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/support/commands.js b/test/cypress/support/commands.js index 2136584e2..4dfde6e21 100755 --- a/test/cypress/support/commands.js +++ b/test/cypress/support/commands.js @@ -108,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(() => { @@ -131,6 +136,27 @@ 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(); }); 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); + }); + }); +});