diff --git a/CHANGELOG.md b/CHANGELOG.md index 243d67a34..3316aa441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ 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). +## [2352.01] - 2023-12-28 + +### 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 c541ee00e..9db93eff3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "salix-front", - "version": "23.48.01", + "version": "23.52.01", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "salix-front", - "version": "23.48.01", + "version": "23.52.01", "dependencies": { "@quasar/cli": "^2.3.0", "@quasar/extras": "^1.16.4", diff --git a/package.json b/package.json index 799401c08..bfaba6f10 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-front", - "version": "23.48.01", + "version": "23.52.01", "description": "Salix frontend", "productName": "Salix", "author": "Verdnatura", @@ -9,7 +9,7 @@ "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" @@ -53,4 +53,4 @@ "vite": "^4.3.5", "vitest": "^0.31.1" } -} \ No newline at end of file +} 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/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/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/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 b14aae7be..0949fb5cb 100644 --- a/src/components/common/VnLog.vue +++ b/src/components/common/VnLog.vue @@ -1,19 +1,25 @@ - - - {{ 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') }} @@ -144,80 +637,446 @@ 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: - actions: - insert: Creates - update: Updates - delete: Deletes - models: - Claim: Claim - ClaimDms: Document - ClaimBeginning: Claimed Sales - ClaimObservation: Observation - Shelving: Shelving - properties: - id: ID - claimFk: Claim ID - saleFk: Sale ID - quantity: Quantity - observation: Observation - ticketCreated: Created - created: Created - isChargedToMana: Charged to mana - hasToPickUp: Has to pick Up - dmsFk: Document ID - text: Description - claimStateFk: Claim State - workerFk: Worker - clientFk: Customer - rma: RMA - responsibility: Responsibility - packages: Packages + 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: + Creates: Creates + Edits: Edits + Deletes: Deletes + Accesses: Accesses + Users: + User: Usuario + All: Todo + System: Sistema + properties: + id: ID + claimFk: Claim ID + saleFk: Sale ID + quantity: Quantity + observation: Observation + ticketCreated: Created + created: Created + isChargedToMana: Charged to mana + hasToPickUp: Has to pick Up + dmsFk: Document ID + text: Description + claimStateFk: Claim State + workerFk: Worker + clientFk: Customer + rma: RMA + responsibility: Responsibility + packages: Packages es: - Audit logs: Registros de auditoría - Property: Propiedad - Before: Antes - After: Después - Yes: Si - Nothing: Nada - actions: - insert: Crea - update: Actualiza - delete: Elimina - models: - Claim: Reclamación - ClaimDms: Documento - ClaimBeginning: Línea reclamada - ClaimObservation: Observación - Shelving: Carro - properties: - id: ID - claimFk: ID reclamación - saleFk: ID linea de venta - quantity: Cantidad - observation: Observación - ticketCreated: Creado - created: Creado - isChargedToMana: Cargado a maná - hasToPickUp: Se debe recoger - dmsFk: ID documento - text: Descripción - claimStateFk: Estado de la reclamación - workerFk: Trabajador - clientFk: Cliente - rma: RMA - responsibility: Responsabilidad - packages: Bultos + 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 + After: Después + Yes: Si + Nothing: Nada + actions: + Creates: Crea + Edits: Modifica + Deletes: Elimina + Accesses: Accede + Users: + User: Usuario + All: Todo + System: Sistema + properties: + id: ID + claimFk: ID reclamación + saleFk: ID linea de venta + quantity: Cantidad + observation: Observación + ticketCreated: Creado + created: Creado + isChargedToMana: Cargado a maná + hasToPickUp: Se debe recoger + dmsFk: ID documento + text: Descripción + claimStateFk: Estado de la reclamación + workerFk: Trabajador + clientFk: Cliente + rma: RMA + responsibility: Responsabilidad + packages: Bultos diff --git a/src/components/common/VnSelectFilter.vue b/src/components/common/VnSelectFilter.vue index 04f10ace9..568da613f 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" > @@ -97,7 +106,7 @@ const value = computed({ /> - + diff --git a/src/components/ui/CardDescriptor.vue b/src/components/ui/CardDescriptor.vue index f2c07aaac..2669b53a9 100644 --- a/src/components/ui/CardDescriptor.vue +++ b/src/components/ui/CardDescriptor.vue @@ -1,6 +1,8 @@ - - - - - {{ t('components.cardDescriptor.mainList') }} - - - - + + + + {{ t('components.smartCard.openSummary') }} + + - { .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 { @@ -125,7 +127,6 @@ watch(props, async () => { width: max-content; overflow: hidden; white-space: nowrap; - text-overflow: ellipsis; } } .header { 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 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/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/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 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/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/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 fb603deca..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,11 @@ 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', @@ -37,6 +43,10 @@ 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 @@ -362,7 +372,7 @@ export default { }, invoiceOut: { pageTitles: { - invoiceOuts: 'Invoices Out', + invoiceOuts: 'Invoice out', list: 'List', negativeBases: 'Negative Bases', globalInvoicing: 'Global invoicing', @@ -457,7 +467,7 @@ export default { list: { parking: 'Parking', priority: 'Priority', - newShelving: 'New Shelving' + newShelving: 'New Shelving', }, summary: { code: 'Code', @@ -473,6 +483,71 @@ export default { 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: { workers: 'Workers', @@ -531,6 +606,7 @@ export default { typesList: 'Types List', typeCreate: 'Create type', typeEdit: 'Edit type', + wagonCounter: 'Trolley counter', }, type: { name: 'Name', @@ -669,6 +745,7 @@ export default { logOut: 'Log Out', }, smartCard: { + downloadFile: 'Download file', clone: 'Clone', openCard: 'View', openSummary: 'Summary', diff --git a/src/i18n/es/index.js b/src/i18n/es/index.js index 75ee011c3..79f1114b5 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,11 @@ 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', @@ -37,9 +43,13 @@ export default { summary: { basicData: 'Datos básicos', }, + today: 'Hoy', + yesterday: 'Ayer', + dateFormat: 'es-ES', noSelectedRows: `No tienes ninguna línea seleccionada`, - downloadCSVSuccess: 'Descarga de CSV exitosa', + microsip: 'Abrir en MicroSIP', // labels compartidos entre vistas + downloadCSVSuccess: 'Descarga de CSV exitosa', reference: 'Referencia', agency: 'Agencia', wareHouseOut: 'Alm. salida', @@ -459,7 +469,7 @@ export default { list: { parking: 'Parking', priority: 'Prioridad', - newShelving: 'Nuevo Carro' + newShelving: 'Nuevo Carro', }, summary: { code: 'Código', @@ -475,6 +485,69 @@ export default { 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: { workers: 'Trabajadores', @@ -533,6 +606,7 @@ export default { typesList: 'Listado tipos', typeCreate: 'Crear tipo', typeEdit: 'Editar tipo', + wagonCounter: 'Contador de carros', }, type: { name: 'Nombre', @@ -671,6 +745,7 @@ export default { logOut: 'Cerrar sesión', }, smartCard: { + downloadFile: 'Descargar archivo', clone: 'Clonar', openCard: 'Ficha', openSummary: 'Detalles', diff --git a/src/pages/Claim/Card/ClaimDescriptor.vue b/src/pages/Claim/Card/ClaimDescriptor.vue index 9dd8aa1f6..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'; @@ -119,7 +120,7 @@ const setData = (entity) => { {{ entity.worker.user.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/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/ClaimList.vue b/src/pages/Claim/ClaimList.vue index af45ea985..2be247645 100644 --- a/src/pages/Claim/ClaimList.vue +++ b/src/pages/Claim/ClaimList.vue @@ -5,12 +5,13 @@ import { useQuasar } from 'quasar'; import { useStateStore } from 'stores/useStateStore'; import { toDate } from 'filters/index'; import VnPaginate from 'src/components/ui/VnPaginate.vue'; -import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue'; -import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import VnSearchbar from 'components/ui/VnSearchbar.vue'; import ClaimFilter from './ClaimFilter.vue'; import VnLv from 'src/components/ui/VnLv.vue'; import CardList from 'src/components/ui/CardList.vue'; +import ClaimSummaryDialog from './Card/ClaimSummaryDialog.vue'; +import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; +import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue'; const stateStore = useStateStore(); const router = useRouter(); @@ -37,6 +38,15 @@ function viewSummary(id) { }, }); } + +function viewDescriptor(id) { + quasar.dialog({ + component: CustomerDescriptorProxy, + componentProps: { + id, + }, + }); +} @@ -86,14 +96,22 @@ function viewSummary(id) { v-for="row of rows" > - - + + + + {{ row.clientName }} + + + + + + + + {{ row.workerName }} + + + + (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') }} + + + - + + + {{ t('customer.list.phone') }} + + + +import { ref, computed } from 'vue'; +import { useRoute } from 'vue-router'; +import { useI18n } from 'vue-i18n'; +import { useQuasar } from 'quasar'; +import { useArrayData } from 'src/composables/useArrayData'; + +import { downloadFile } from 'src/composables/downloadFile'; +import FetchData from 'src/components/FetchData.vue'; +import FormModel from 'components/FormModel.vue'; +import VnSelectFilter from 'src/components/common/VnSelectFilter.vue'; +import axios from 'axios'; + +const quasar = useQuasar(); +const route = useRoute(); +const { t } = useI18n(); + +const dms = ref({}); +const editDownloadDisabled = ref(false); +const arrayData = useArrayData('InvoiceIn'); +const invoiceIn = computed(() => arrayData.store.data); +const userConfig = ref(null); + +const suppliers = ref([]); +const suppliersRef = ref(); +const currencies = ref([]); +const currenciesRef = ref(); +const companies = ref([]); +const companiesRef = ref(); +const dmsTypes = ref([]); +const dmsTypesRef = ref(); +const warehouses = ref([]); +const warehousesRef = ref(); +const allowTypesRef = ref(); +const allowedContentTypes = ref([]); +const inputFileRef = ref(); +const editDmsRef = ref(); +const createDmsRef = ref(); + +const requiredFieldRule = (val) => val || t('Required field'); +const dateMask = '####-##-##'; +const fillMask = '_'; + +async function checkFileExists(dmsId) { + if (!dmsId) return; + try { + await axios.get(`Dms/${dmsId}`, { fields: ['id'] }); + editDownloadDisabled.value = false; + } catch (e) { + editDownloadDisabled.value = true; + } +} + +async function setEditDms(dmsId) { + const { data } = await axios.get(`Dms/${dmsId}`); + dms.value = { + warehouseId: data.warehouseFk, + companyId: data.companyFk, + dmsTypeId: data.dmsTypeFk, + ...data, + }; + + if (!allowedContentTypes.value.length) await allowTypesRef.value.fetch(); + + editDmsRef.value.show(); +} + +async function setCreateDms() { + const { data } = await axios.get('DmsTypes/findOne', { + where: { code: 'invoiceIn' }, + }); + dms.value = { + reference: invoiceIn.value.supplierRef, + warehouseId: userConfig.value.warehouseFk, + companyId: userConfig.value.companyFk, + dmsTypeId: data.id, + description: invoiceIn.value.supplier.name, + hasFile: true, + hasFileAttached: true, + files: null, + }; + + createDmsRef.value.show(); +} + +async function upsert() { + try { + const isEdit = !!dms.value.id; + const errors = { + companyId: `The company can't be empty`, + warehouseId: `The warehouse can't be empty`, + dmsTypeId: `The DMS Type can't be empty`, + description: `The description can't be empty`, + }; + + Object.keys(errors).forEach((key) => { + if (!dms.value[key]) throw Error(t(errors[key])); + }); + + if (!isEdit && !dms.value.files) throw Error(t(`The files can't be empty`)); + + const formData = new FormData(); + + if (dms.value.files) { + for (let i = 0; i < dms.value.files.length; i++) + formData.append(dms.value.files[i].name, dms.value.files[i]); + dms.value.hasFileAttached = true; + } + const url = isEdit ? `dms/${dms.value.id}/updateFile` : 'Dms/uploadFile'; + const { data } = await axios.post(url, formData, { + params: dms.value, + }); + + if (data.length) invoiceIn.value.dmsFk = data[0].id; + + if (!isEdit) { + createDmsRef.value.hide(); + } else { + editDmsRef.value.hide(); + } + + quasar.notify({ + message: t('globals.dataSaved'), + type: 'positive', + }); + } catch (error) { + quasar.notify({ + message: t(`${error.message}`), + type: 'negative', + }); + } +} + + + (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/Shelving/Card/ShelvingLog.vue b/src/pages/Shelving/Card/ShelvingLog.vue index e65df3843..8f355d2ca 100644 --- a/src/pages/Shelving/Card/ShelvingLog.vue +++ b/src/pages/Shelving/Card/ShelvingLog.vue @@ -1,53 +1,6 @@ - + diff --git a/src/pages/Ticket/Card/TicketSummary.vue b/src/pages/Ticket/Card/TicketSummary.vue index 113ae84a6..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') }} + + + +import { ref, onMounted } from 'vue'; +import { useSession } from 'src/composables/useSession'; +import { useI18n } from 'vue-i18n'; +import { useQuasar } from 'quasar'; +import VnConfirm from 'components/ui/VnConfirm.vue'; + +const quasar = useQuasar(); +const { t } = useI18n(); +const session = useSession(); +const token = session.getToken(); + +const counters = ref({ + alquilerBandeja: { count: 0, id: 96001, title: 'CC Bandeja', isTray: true }, + bandejaRota: { count: 0, id: 88381, title: 'CC Bandeja Rota', isTray: true }, + carryOficial: { count: 0, id: 96000, title: 'CC Carry OFICIAL TAG5' }, + candadoRojo: { count: 0, id: 96002, title: 'CC Carry NO OFICIAL' }, + sacadores: { count: 0, id: 142260, title: 'CC Sacadores' }, + sinChapa: { count: 0, id: 2214, title: 'DC Carry Sin Placa CC' }, + carroRoto: { count: 0, id: 142251, title: 'Carro Roto' }, +}); + +const actions = { + add: (counter) => counter + 1, + subtract: (counter) => (counter ? counter - 1 : 0), + flush: () => 0, + addSpecific: (counter, amount) => counter + amount, +}; + +onMounted(() => { + const types = Object.keys(counters.value); + for (let type of types) { + const counter = localStorage.getItem(type); + counters.value[type].count = counter ? parseInt(counter) : 0; + } +}); + +function getUrl(id) { + return `/api/Images/catalog/200x200/${id}/download?access_token=${token}`; +} + +async function handleEvent(type, action, amount) { + const counter = counters.value[type].count; + let isOk = true; + + if (action == 'flush') isOk = await confirm(); + + if (isOk) { + counters.value[type].count = actions[action](counter, amount); + localStorage.setItem(type, counters.value[type].count); + } +} + +function confirm() { + return new Promise((resolve) => { + quasar + .dialog({ + component: VnConfirm, + componentProps: { + title: t('Are you sure?'), + message: t('The counter will be reset to zero'), + }, + }) + .onOk(() => resolve(true)) + .onCancel(() => resolve(false)); + }); +} + + + + + + + {{ 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/Worker/Card/WorkerDescriptor.vue b/src/pages/Worker/Card/WorkerDescriptor.vue index 5a8f4f076..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(); @@ -67,6 +73,7 @@ const setData = (entity) => { :filter="filter" :title="data.title" :subtitle="data.subtitle" + :summary="$props.summary" @on-fetch=" (data) => { worker = data; @@ -99,8 +106,18 @@ const setData = (entity) => { :label="t('worker.list.department')" :value="entity.department ? entity.department.department.name : null" /> - - + + + {{ 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 @@
{{ props.title }}