Merge branch 'dev' of https: refs #7984//gitea.verdnatura.es/verdnatura/salix-front into 7984-addCurrency

This commit is contained in:
Alex Moreno 2025-03-26 12:09:47 +01:00
commit 9ea4d785aa
262 changed files with 14395 additions and 16782 deletions

View File

@ -1,3 +1,214 @@
# Version 25.12 - 2025-03-25
### Added 🆕
- chore: add junit-merge dependency to package.json by:alexm
- chore: downgrade version from 25.14.0 to 25.12.0 in package.json by:alexm
- chore: reduce page load timeout in Cypress configuration by:alexm
- chore: refs #6695 update Cypress to version 14.1.0 and simplify test execution in Jenkinsfile by:alexm
- chore: refs #8602 add comments for clarity in Cypress commands file by:pablone
- chore: refs #8602 enhance Cypress support files with detailed comments and organization by:pablone
- ci: refs #6695 feat jenkins parallel e2e by:alexm
- feat: integrate vue-router to enhance routing capabilities in ZoneCard component by:alexm
- feat: refs #6695 implement parallel Cypress testing and enhance timeout configurations by:alexm
- feat: refs #6695 update Cypress parallel test execution to use 3 instances by:alexm
- feat: refs #6802 add dash placeholder for empty department names in InvoiceOut list by:jgallego
- feat: refs #6802 add DepartmentDescriptorProxy to InvoiceOutList and update translations by:jgallego
- feat: refs #6802 add DepartmentDescriptorProxy to various components and update department handling by:jgallego
- feat: refs #7587 add 'ticketClaimed' translation and implement claims retrieval in TicketDescriptor by:jtubau
- feat: refs #7949 show new field in ticket sales by:Jon
- feat: refs #8045 added new logic to show the correct icon and the correct path to redirect by:Jon
- feat: refs #8045 modified icon and route to redirect from CardDescriptor by:Jon
- feat: refs #8074 modified spinner size by:Jon
- feat: refs #8600 added calendar e2e and modified basic data by:Jon
- feat: refs #8600 added deliveryDays and modified warehouse E2Es by:Jon
- feat: refs #8600 added new tests for zoneSummary & zoneLocations by:provira
- feat: refs #8602 add custom Cypress commands for improved element interaction and request handling by:pablone
- feat: refs #8602 add new Cypress command for clicking buttons with icons by:pablone
- feat: refs #8602 add remove functionality for tag filters in EntryBuys component by:pablone
- feat: refs #8602 add sorting options for select fields and update locale files with supplier name by:pablone
- feat: refs #8602 refactor EntryBuys component and enhance observation tests by:pablone
- feat: refs #8602 remove unused state property from useArrayDataStore by:pablone
- feat: refs #8602 remove unused URL property from VnTable in ClaimList component by:pablone
- feat: refs #8602 skip warehouse creation and removal test in ZoneWarehouse spec by:pablone
- feat: refs #8602 streamline beforeSaveFn execution in CrudModel component by:pablone
- feat: refs #8602 streamline beforeSaveFn execution in VnTable component by:pablone
- feat: refs #8602 streamline filter logic in EntryBuys component by:pablone
- feat: refs #8602 update entry components and tests, add data-cy attributes for Cypress integration by:pablone
- feat: refs #8602 update localization for purchased spaces and enhance Entry components with new labels by:pablone
- feat: refs #8606 adapt module to VnCatdBeta by:Jon
- feat: refs #8612 added summary button & changed e2e tests by:provira
- feat: refs #8612 changed shelving to VnTable & created e2e tests by:provira
- feat: refs #8616 add summary prop to CardDescriptor in RoadmapDescriptor and WorkerDescriptor by:jtubau
- feat: refs #8616 add VnCheckbox component to VnFilter and update prop types in VnFilterPanel and VnSearchbar by:jtubau
- feat: refs #8630 add Agency and Vehicle descriptor components with summary props by:jtubau
- feat: refs #8638 add AWB field to travel and entry forms, update translations and styles by:pablone
- feat: refs #8638 add data attributes for transfer buys functionality in EntryBuys.vue and corresponding tests by:pablone
- feat: refs #8648 enhance roadmapList tests with improved selectors and additional scenarios by:jtubau
- feat: refs #8664 add CmrFilter component and integrate it into CmrList for enhanced filtering options by:jtubau
- feat: refs #8721 add ticket navigation and update route columns by:jtubau
- feat: run.sh build neccessary images by:alexm
- feat: update Jenkinsfile to pull Docker images for back and db services by:alexm
- feat: update labels and add department selection in InvoiceOut filter and list by:jgallego
- refactor: refs #8626 update button styles and improve route redirection logic by:jtubau
- style: refs #8041 new variable by:benjaminedc
### Changed 📦
- feat: refs #8602 refactor EntryBuys component and enhance observation tests by:pablone
- refactor(cypress): refs #6695 simplify parallel test execution script by:alexm
- refactor: deleted useless (origin/Warmfix-DepartmentIcon) by:Jon
- refactor: refs #6695 enable ClaimNotes test suite by:alexm
- refactor: refs #6695 fix invoiceOutSummary by:alexm
- refactor: refs #6695 improve notification check and extend waitForElement timeout by:alexm
- refactor: refs #6695 remove mocha dependency and optimize Cypress command execution by:alexm
- refactor: refs #6695 skips by:alexm
- refactor: refs #6695 skip zoneWarehouse by:alexm
- refactor: refs #6695 streamline Cypress test execution and remove deprecated configurations by:alexm
- refactor: refs #6802 replace salesPerson references with department in claims and tickets by:jgallego
- refactor: refs #6802 replace 'salesPerson' terminology with 'team' across multiple locales and components by:jgallego
- refactor: refs #6802 update import paths for DepartmentDescriptorProxy to use Worker directory by:jgallego
- refactor: refs #6802 update InvoiceOutNegativeBases to use Department instead of Worker by:jgallego
- refactor: refs #6802 update TicketFilter and TicketSale components to use departmentFk and adjust API endpoints by:jgallego
- refactor: refs #8041 unify class link and unify titles to VnTitles by:benjaminedc
- refactor: refs #8045 modified icon and module const by:Jon
- refactor: refs #8197 rename VnCardBeta to VnCard by:alexm
- refactor: refs #8197 simplify menu retrieval logic in LeftMenu component by:alexm
- refactor: refs #8322 changed Wagon component to use VnSection/VnCardBeta by:provira
- refactor: refs #8322 remove keyBinding from Wagon router module by:alexm
- refactor: refs #8322 update WagonCard component and routing structure by:alexm
- refactor: refs #8370 modified function to get the correct date by:Jon
- refactor: refs #8472 remove added div and add class to VnInput by:jtubau
- refactor: refs #8472 unified styling for the more-create-dialog slot to ensure consistency across all scenarios by:jtubau
- refactor: refs #8472 update class names from q-span-2 to col-span-2 for consistency in layout by:jtubau
- refactor: refs #8600 changed test case description by:provira
- refactor: refs #8600 modified make invoice and send dialog e2es by:Jon
- refactor: refs #8600 modified upcomingDeliveries e2e and created deliveryDays by:Jon
- refactor: refs #8600 modified zoneSummary e2e by:Jon
- refactor: refs #8602 remove redundant date input test from entryList.spec.js by:pablone
- refactor: refs #8606 clear some warnings by:Jon
- refactor: refs #8606 deleted code and fixed translation by:Jon
- refactor: refs #8606 merged previous and e2e changes and corrected minor errors by:Jon
- refactor: refs #8606 requested changes by:Jon
- refactor: refs #8616 integrate summary dialog and update navigation in Agency and Vehicle components by:jtubau
- refactor: refs #8616 integrate VnSelectWorker component in RouteList and optimize format functions by:jtubau
- refactor: refs #8616 simplify template bindings and improve link generation in VehicleSummary by:jtubau
- refactor: refs #8616 update routing components for AgencyList and RouteRoadmap in route.js by:jtubau
- refactor: refs #8619 simplify empty data check in RouteDescriptor component by:jtubau
- refactor: refs #8626 add cardVisible property to RouteList columns by:jtubau
- refactor: refs #8626 add formatting for agency and vehicle columns in RouteList by:jtubau
- refactor: refs #8626 enhance Worker and Agency components with data attributes and improved routing by:jtubau
- refactor: refs #8626 improve test messages and selectors in route tests by:jtubau
- refactor: refs #8626 update button styles and improve route redirection logic by:jtubau
- refactor: refs #8626 update RouteList columns and enable AgencyWorkCenter tests by:jtubau
- refactor: refs #8630 add vehicle translations and enhance route list columns by:jtubau
- refactor: refs #8648 update roadmap deletion test to use current element text by:jtubau
- refactor: refs #8664 enhance CmrList component with query initialization and user parameters by:jtubau
- refactor: refs #8664 localization files by:jtubau
- refactor: refs #8664 remove CmrFilter and replace with VnSearchbar in CmrList by:jtubau
- refactor: remove unnecessary login and reload calls in ClaimDevelopment tests by:alexm
- refactor: simplify client selection in order creation test by:alexm
- refactor: update client ID input selector and remove viewport setting by:alexm
- test: refs #8197 comment out ticket list tests for refactoring by:alexm
- test: refs #8626 refactor notification check in routeList.spec.js by:jtubau
- test: refs #8626 refactor routeList.spec.js to use a constant for summary URL by:jtubau
- test: refs #8626 refactor routeList.spec.js to use selectors and improve readability by:jtubau
### Fixed 🛠️
- fix: add --init flag to Docker container for Cypress tests by:alexm
- fix: agency list filters by:jtubau
- fix: align Article label to the left in EntryBuys component by:alexm
- fix: card descriptor imports by:Jon
- fix: card descriptor merge by:Jon
- fix(ClaimAction): update shelving options to use URL instead of static data by:jgallego
- fix(ClaimSummary): clean url by:alexm
- fix(cypress.config.js): refs #6695 update reporter to junit and remove unused dependencies by:alexm
- fix(cypressParallel.sh): refs #6695 improve script readability by:alexm
- fix(cypressParallel.sh): refs #6695 improve test execution output for clarity by:alexm
- fix(cypressParallel.sh): refs #6695 simplify test execution output format by:alexm
- fix(cypress scripts): refs #6695 improve cleanup process and adjust output redirection by:alexm
- fix: fixed department descriptor icon by:Jon
- fix: fixed submodule descriptors icons by:Jon
- fix(invoiceOutSummary.spec.js): refs #6695 remove unnecessary visibility check for descriptor by:alexm
- fix(Jenkinsfile): reduce parallel Cypress test execution from 3 to 2 by:alexm
- fix(Jenkinsfile): refs #6695 add credentials for Docker login in E2E stage by:alexm
- fix(Jenkinsfile): refs #6695 change parallel test execution from 4 to 2 by:alexm
- fix(Jenkinsfile): refs #6695 increase parallel test execution from 2 to 4 by:alexm
- fix(Jenkinsfile): refs #6695 update parallel test execution to 4 by:alexm
- fix(LeftMenu): refs #8197 handle missing children in findRoute and update menu structure by:alexm
- fix: refs #6695 update Cypress configuration and test result paths by:alexm
- fix: refs #6695 update Jenkinsfile to build Docker image correctly and modify logout test visit method by:alexm
- fix: refs #6695 update Jenkinsfile to remove specific e2e XML files and adjust Cypress parallel execution by:alexm
- fix: refs #6695 update Jenkinsfile to source cypressParallel.sh correctly by:alexm
- fix: refs #6695 update visit method in TicketLackDetail.spec.js to prevent page reload by:alexm
- fix: refs #6802 update import path for DepartmentDescriptorProxy in OrderList.vue by:jgallego
- fix: refs #6802 update OrderFilter to use department relation instead of salesPerson by:jgallego
- fix: refs #8041 update redirection from preview to summary in ShelvingList tests by:benjaminedc
- fix: refs #8041 update selector for summary header in ParkingList tests by:benjaminedc
- fix: refs #8041 update summaryHeader selector in ParkingList test by:benjaminedc
- fix: refs #8322 update order property for WagonList component by:alexm
- fix: refs #8370 change param rely on month by:Jon
- fix: refs #8417 added data-cy to all files and fixed test by:provira
- fix: refs #8417 added data-cy to delete button by:provira
- fix: refs #8417 fixed claimPhoto e2e by:provira
- fix: refs #8417 fixed claimPhoto e2e test by:provira
- fix: refs #8417 fixed e2e test by:provira
- fix: refs #8417 fixed e2e test case by:provira
- fix: refs #8417 fixed failing test case by:provira
- fix: refs #8417 fixed invoiceOutSummary e2e test by:provira
- fix: refs #8417 removed .only by:provira
- fix: refs #8583 basicData, business, summary by:carlossa
- fix: refs #8583 basicData e2e by:carlossa
- fix: refs #8583 basicData timeControl by:carlossa
- fix: refs #8583 cypressconf by:carlossa
- fix: refs #8583 dataCy operator by:carlossa
- fix: refs #8583 fix AddCard by:carlossa
- fix: refs #8583 mutual create by:carlossa
- fix: refs #8583 operator by:carlossa
- fix: refs #8583 remove workerTimeControl by:carlossa
- fix: refs #8583 tMutual, tNotes, TOperator by:carlossa
- fix: refs #8583 wBusiness by:carlossa
- fix: refs #8583 wBusiness e2e by:carlossa
- fix: refs #8583 workerBasicData & workerTimeControl by:carlossa
- fix: refs #8583 workerBusiness by:carlossa
- fix: refs #8583 workerBusiness e2e by:carlossa
- fix: refs #8583 workerBusiness test by:carlossa
- fix: refs #8583 workerE2E by:carlossa
- fix: refs #8583 worker mutual e2e by:carlossa
- fix: refs #8583 workerSummary test by:carlossa
- fix: refs #8583 workerTimeControl by:carlossa
- fix: refs #8583 workerTimeControl e2e by:carlossa
- fix: refs #8600 e2e by:Jon
- fix: refs #8600 fixed calendar e2e by:Jon
- fix: refs #8600 fixed e2e and skip client ones by:Jon
- fix: refs #8600 fixed e2e by:Jon
- fix: refs #8600 fixed invoiceOut summary e2e by:Jon
- fix: refs #8600 fixed zoneList & added test case to zoneSummary by:provira
- fix: refs #8600 zone basic data e2e and skip intermitent invoice out summary it by:Jon
- fix: refs #8602 delete unused entryDms and stockBought test files (origin/8581-e2eInvoiceIn) by:pablone
- fix: refs #8606 deleted code by:Jon
- fix: refs #8612 changed QCheckbox for VnCheckbox by:provira
- fix: refs #8612 fixed shelving e2e tests by:provira
- fix: refs #8616 add conditional for SupplierDescriptorProxy and bind attributes in CardDescriptor by:jtubau
- fix: refs #8616 remove redundant v-on binding from QCheckbox in VnCheckbox.vue by:jtubau
- fix: refs #8616 update binding syntax for is-editable prop in AgencyList.vue by:jtubau
- fix: refs #8616 update FormModel prop from 'update-url' to 'url-update' in Agency and RoadMap BasicData by:jtubau
- fix: refs #8619 handle empty ticket records in RouteDescriptor component by:jtubau
- fix: refs #8619 update route descriptor to handle empty ticket records and adjust test cases by:jtubau
- fix: refs #8626 remove duplicate ref attribute from RouteList.vue by:jtubau
- fix: refs #8630 remove duplicated locations by:jtubau
- fix: refs #8638 restore invoiceInBasicData by:pablone
- fix: refs #8638 update comment formatting in VnTable.vue by:pablone
- fix: refs #8638 update null check for maxlength validation in VnInput.vue by:pablone
- fix: simplify menu structure in monitor router module (origin/fix_monitor_leftMenu) by:Javier Segarra
- refactor: refs #6695 fix invoiceOutSummary by:alexm
- refactor: refs #8606 deleted code and fixed translation by:Jon
- test: fix intermitent e2e by:alexm
- test: fix orderList e2e, unestables by:alexm
- test(OrderList): fix inconsistency by:alexm
- test(TicketList): fix inconsistency by:alexm
# Version 25.10 - 2025-03-11 # Version 25.10 - 2025-03-11
### Added 🆕 ### Added 🆕

6
Jenkinsfile vendored
View File

@ -115,21 +115,25 @@ pipeline {
steps { steps {
script { script {
sh 'rm -f junit/e2e-*.xml' sh 'rm -f junit/e2e-*.xml'
sh 'rm -rf test/cypress/screenshots'
env.COMPOSE_TAG = PROTECTED_BRANCH.contains(env.CHANGE_TARGET) ? env.CHANGE_TARGET : 'dev' env.COMPOSE_TAG = PROTECTED_BRANCH.contains(env.CHANGE_TARGET) ? env.CHANGE_TARGET : 'dev'
def image = docker.build('lilium-dev', '-f docs/Dockerfile.dev docs') def image = docker.build('lilium-dev', '-f docs/Dockerfile.dev docs')
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY' sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
sh "docker-compose ${env.COMPOSE_PARAMS} pull back"
sh "docker-compose ${env.COMPOSE_PARAMS} pull db"
sh "docker-compose ${env.COMPOSE_PARAMS} up -d" sh "docker-compose ${env.COMPOSE_PARAMS} up -d"
image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") { image.inside("--network ${env.COMPOSE_PROJECT}_default -e CI -e TZ --init") {
sh 'sh test/cypress/cypressParallel.sh 2' sh 'sh test/cypress/cypressParallel.sh 1'
} }
} }
} }
post { post {
always { always {
sh "docker-compose ${env.COMPOSE_PARAMS} down -v" sh "docker-compose ${env.COMPOSE_PARAMS} down -v"
archiveArtifacts artifacts: 'test/cypress/screenshots/**/*', allowEmptyArchive: true
junit( junit(
testResults: 'junit/e2e-*.xml', testResults: 'junit/e2e-*.xml',
allowEmptyResults: true allowEmptyResults: true

View File

@ -32,8 +32,26 @@ pnpm run test:front
pnpm run test:e2e pnpm run test:e2e
``` ```
### Run e2e parallel
```bash
pnpm run test:e2e:parallel
```
### View e2e parallel report
```bash
pnpm run test:e2e:summary
```
### Build the app for production ### Build the app for production
```bash ```bash
quasar build quasar build
``` ```
### Serve the app for production
```bash
quasar build quasar serve dist/spa --host 0.0.0.0 --proxy=./proxy-serve.js
```

View File

@ -1,6 +1,6 @@
{ {
"name": "salix-front", "name": "salix-front",
"version": "25.12.0", "version": "25.14.0",
"description": "Salix frontend", "description": "Salix frontend",
"productName": "Salix", "productName": "Salix",
"author": "Verdnatura", "author": "Verdnatura",
@ -13,7 +13,7 @@
"format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore", "format": "prettier --write \"**/*.{js,vue,scss,html,md,json}\" --ignore-path .gitignore",
"test:e2e": "cypress open", "test:e2e": "cypress open",
"test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run", "test:e2e:ci": "npm run resetDatabase && cd ../salix-front && cypress run",
"test:e2e:parallel": "bash ./test/cypress/cypressParallel.sh", "test:e2e:parallel": "bash ./test/cypress/run.sh",
"test:e2e:summary": "bash ./test/cypress/summary.sh", "test:e2e:summary": "bash ./test/cypress/summary.sh",
"test": "echo \"See package.json => scripts for available tests.\" && exit 0", "test": "echo \"See package.json => scripts for available tests.\" && exit 0",
"test:front": "vitest", "test:front": "vitest",
@ -56,6 +56,7 @@
"eslint-plugin-cypress": "^4.1.0", "eslint-plugin-cypress": "^4.1.0",
"eslint-plugin-vue": "^9.32.0", "eslint-plugin-vue": "^9.32.0",
"husky": "^8.0.0", "husky": "^8.0.0",
"junit-merge": "^2.0.0",
"mocha": "^11.1.0", "mocha": "^11.1.0",
"postcss": "^8.4.23", "postcss": "^8.4.23",
"prettier": "^3.4.2", "prettier": "^3.4.2",

File diff suppressed because it is too large Load Diff

View File

@ -181,9 +181,8 @@ async function saveChanges(data) {
return; return;
} }
let changes = data || getChanges(); let changes = data || getChanges();
if ($props.beforeSaveFn) { if ($props.beforeSaveFn) changes = await $props.beforeSaveFn(changes, getChanges);
changes = await $props.beforeSaveFn(changes, getChanges);
}
try { try {
if (changes?.creates?.length === 0 && changes?.updates?.length === 0) { if (changes?.creates?.length === 0 && changes?.updates?.length === 0) {
return; return;
@ -194,7 +193,7 @@ async function saveChanges(data) {
isLoading.value = false; isLoading.value = false;
} }
originalData.value = JSON.parse(JSON.stringify(formData.value)); originalData.value = JSON.parse(JSON.stringify(formData.value));
if (changes.creates?.length) await vnPaginateRef.value.fetch(); if (changes?.creates?.length) await vnPaginateRef.value.fetch();
hasChanges.value = false; hasChanges.value = false;
emit('saveChanges', data); emit('saveChanges', data);

View File

@ -198,8 +198,7 @@ const setCategoryList = (data) => {
v-model="params.typeFk" v-model="params.typeFk"
:options="itemTypesOptions" :options="itemTypesOptions"
dense dense
outlined filled
rounded
use-input use-input
:disable="!selectedCategoryFk" :disable="!selectedCategoryFk"
@update:model-value=" @update:model-value="
@ -235,8 +234,7 @@ const setCategoryList = (data) => {
v-model="value.selectedTag" v-model="value.selectedTag"
:options="tagOptions" :options="tagOptions"
dense dense
outlined filled
rounded
:emit-value="false" :emit-value="false"
use-input use-input
:is-clearable="false" :is-clearable="false"
@ -252,8 +250,7 @@ const setCategoryList = (data) => {
option-value="value" option-value="value"
option-label="value" option-label="value"
dense dense
outlined filled
rounded
emit-value emit-value
use-input use-input
:disable="!value" :disable="!value"
@ -265,7 +262,6 @@ const setCategoryList = (data) => {
v-model="value.value" v-model="value.value"
:label="t('components.itemsFilterPanel.value')" :label="t('components.itemsFilterPanel.value')"
:disable="!value" :disable="!value"
is-outlined
:is-clearable="false" :is-clearable="false"
@keyup.enter="applyTags(params, searchFn)" @keyup.enter="applyTags(params, searchFn)"
/> />

View File

@ -28,6 +28,17 @@ defineProps({ row: { type: Object, required: true } });
{{ t('ticketSale.reserved') }} {{ t('ticketSale.reserved') }}
</QTooltip> </QTooltip>
</QIcon> </QIcon>
<QIcon
v-if="row?.isDeleted"
color="primary"
name="vn:deletedTicket"
size="xs"
data-cy="ticketDeletedIcon"
>
<QTooltip>
{{ t('Ticket deleted') }}
</QTooltip>
</QIcon>
<QIcon <QIcon
v-if="row?.hasRisk" v-if="row?.hasRisk"
name="vn:risk" name="vn:risk"

View File

@ -140,7 +140,7 @@ const $props = defineProps({
}, },
dataCy: { dataCy: {
type: String, type: String,
default: 'vn-table', default: 'vnTable',
}, },
}); });
@ -595,18 +595,17 @@ function cardClick(_, row) {
function removeTextValue(data, getChanges) { function removeTextValue(data, getChanges) {
let changes = data.updates; let changes = data.updates;
if (!changes) return data; if (changes) {
for (const change of changes) {
for (const change of changes) { for (const key in change.data) {
for (const key in change.data) { if (key.endsWith('VnTableTextValue')) {
if (key.endsWith('VnTableTextValue')) { delete change.data[key];
delete change.data[key]; }
} }
} }
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
} }
data.updates = changes.filter((change) => Object.keys(change.data).length > 0);
if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges); if ($attrs?.beforeSaveFn) data = $attrs.beforeSaveFn(data, getChanges);
return data; return data;
@ -634,6 +633,7 @@ const rowCtrlClickFunction = computed(() => {
:data-key="$attrs['data-key']" :data-key="$attrs['data-key']"
:columns="columns" :columns="columns"
:redirect="redirect" :redirect="redirect"
v-bind="$attrs?.['table-filter']"
> >
<template <template
v-for="(_, slotName) in $slots" v-for="(_, slotName) in $slots"
@ -685,7 +685,7 @@ const rowCtrlClickFunction = computed(() => {
@update:selected="emit('update:selected', $event)" @update:selected="emit('update:selected', $event)"
@selection="(details) => handleSelection(details, rows)" @selection="(details) => handleSelection(details, rows)"
:hide-selected-banner="true" :hide-selected-banner="true"
:data-cy="$props.dataCy ?? 'vnTable'" :data-cy
> >
<template #top-left v-if="!$props.withoutHeader"> <template #top-left v-if="!$props.withoutHeader">
<slot name="top-left"> </slot> <slot name="top-left"> </slot>
@ -776,12 +776,13 @@ const rowCtrlClickFunction = computed(() => {
:data-col-field="col?.name" :data-col-field="col?.name"
> >
<div <div
class="no-padding no-margin peter" class="no-padding no-margin"
style=" style="
overflow: hidden; overflow: hidden;
text-overflow: ellipsis; text-overflow: ellipsis;
white-space: nowrap; white-space: nowrap;
" "
:data-cy="`vnTableCell_${col.name}`"
> >
<slot <slot
:name="`column-${col.name}`" :name="`column-${col.name}`"
@ -896,7 +897,7 @@ const rowCtrlClickFunction = computed(() => {
{{ row[splittedColumns.title.name] }} {{ row[splittedColumns.title.name] }}
</span> </span>
</QCardSection> </QCardSection>
<!-- Fields --> <!-- Fields -->
<QCardSection <QCardSection
class="q-pl-sm q-py-xs" class="q-pl-sm q-py-xs"
:class="$props.cardClass" :class="$props.cardClass"
@ -920,12 +921,24 @@ const rowCtrlClickFunction = computed(() => {
:row-index="index" :row-index="index"
> >
<VnColumn <VnColumn
:column="col" :column="{
...col,
disable:
col?.component ===
'checkbox'
? true
: false,
}"
:row="row" :row="row"
:is-editable="false" :is-editable="false"
v-model="row[col.name]" v-model="row[col.name]"
component-prop="columnField" component-prop="columnField"
:show-label="true" :show-label="
col?.component ===
'checkbox'
? false
: true
"
/> />
</slot> </slot>
</span> </span>
@ -966,6 +979,8 @@ const rowCtrlClickFunction = computed(() => {
v-for="col of cols.filter((cols) => cols.visible ?? true)" v-for="col of cols.filter((cols) => cols.visible ?? true)"
:key="col?.id" :key="col?.id"
:class="getColAlign(col)" :class="getColAlign(col)"
:style="col?.width ? `max-width: ${col?.width}` : ''"
style="font-size: small"
> >
<slot <slot
:name="`column-footer-${col.name}`" :name="`column-footer-${col.name}`"
@ -1028,38 +1043,43 @@ const rowCtrlClickFunction = computed(() => {
@on-data-saved="(_, res) => createForm.onDataSaved(res)" @on-data-saved="(_, res) => createForm.onDataSaved(res)"
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<div :style="createComplement?.containerStyle"> <slot name="alter-create" :data="data">
<div <div :style="createComplement?.containerStyle">
:style="createComplement?.previousStyle" <div
v-if="!quasar.screen.xs" :style="createComplement?.previousStyle"
> v-if="!quasar.screen.xs"
<slot name="previous-create-dialog" :data="data" />
</div>
<div class="grid-create" :style="createComplement?.columnGridStyle">
<slot
v-for="column of splittedColumns.create"
:key="column.name"
:name="`column-create-${column.name}`"
:data="data"
:column-name="column.name"
:label="column.label"
> >
<VnColumn <slot name="previous-create-dialog" :data="data" />
:column="{ </div>
...column, <div
...{ disable: column?.createDisable ?? false }, class="grid-create"
}" :style="createComplement?.columnGridStyle"
:row="{}" >
default="input" <slot
v-model="data[column.name]" v-for="column of splittedColumns.create"
:show-label="true" :key="column.name"
component-prop="columnCreate" :name="`column-create-${column.name}`"
:data-cy="`${column.name}-create-popup`" :data="data"
/> :column-name="column.name"
</slot> :label="column.label"
<slot name="more-create-dialog" :data="data" /> >
<VnColumn
:column="{
...column,
...column?.createAttrs,
}"
:row="{}"
default="input"
v-model="data[column.name]"
:show-label="true"
component-prop="columnCreate"
:data-cy="`${column.name}-create-popup`"
/>
</slot>
<slot name="more-create-dialog" :data="data" />
</div>
</div> </div>
</div> </slot>
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
@ -1136,7 +1156,7 @@ es:
.grid-create { .grid-create {
display: grid; display: grid;
grid-template-columns: 1fr 1fr; grid-template-columns: repeat(auto-fit, minmax(150px, max-content));
max-width: 100%; max-width: 100%;
grid-gap: 20px; grid-gap: 20px;
margin: 0 auto; margin: 0 auto;

View File

@ -47,14 +47,6 @@ vi.mock('src/router/modules', () => ({
], ],
}, },
}, },
{
path: 'create',
name: 'CustomerCreate',
meta: {
title: 'createCustomer',
icon: 'vn:addperson',
},
},
], ],
}, },
], ],

View File

@ -1,15 +1,15 @@
<script setup> <script setup>
import {useDialogPluginComponent} from 'quasar'; import { useDialogPluginComponent } from 'quasar';
import {useI18n} from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import {computed, ref} from 'vue'; import { computed, ref } from 'vue';
import VnInput from 'components/common/VnInput.vue'; import VnInput from 'components/common/VnInput.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from "composables/useNotify"; import useNotify from 'composables/useNotify';
const MESSAGE_MAX_LENGTH = 160; const MESSAGE_MAX_LENGTH = 160;
const {t} = useI18n(); const { t } = useI18n();
const {notify} = useNotify(); const { notify } = useNotify();
const props = defineProps({ const props = defineProps({
title: { title: {
type: String, type: String,
@ -34,7 +34,7 @@ const props = defineProps({
}); });
const emit = defineEmits([...useDialogPluginComponent.emits, 'sent']); const emit = defineEmits([...useDialogPluginComponent.emits, 'sent']);
const {dialogRef, onDialogHide} = useDialogPluginComponent(); const { dialogRef, onDialogHide } = useDialogPluginComponent();
const smsRules = [ const smsRules = [
(val) => (val && val.length > 0) || t("The message can't be empty"), (val) => (val && val.length > 0) || t("The message can't be empty"),
@ -43,10 +43,10 @@ const smsRules = [
t("The message it's too long"), t("The message it's too long"),
]; ];
const message = ref(''); const message = ref(t('routeDelay'));
const charactersRemaining = computed( const charactersRemaining = computed(
() => MESSAGE_MAX_LENGTH - new Blob([message.value]).size () => MESSAGE_MAX_LENGTH - new Blob([message.value]).size,
); );
const charactersChipColor = computed(() => { const charactersChipColor = computed(() => {
@ -114,7 +114,7 @@ const onSubmit = async () => {
<QTooltip> <QTooltip>
{{ {{
t( t(
'Special characters like accents counts as a multiple' 'Special characters like accents counts as a multiple',
) )
}} }}
</QTooltip> </QTooltip>
@ -144,7 +144,10 @@ const onSubmit = async () => {
max-width: 450px; max-width: 450px;
} }
</style> </style>
<i18n> <i18n>
en:
routeDelay: "Your order has been delayed in transit.\nDelivery will take place throughout the day.\nWe apologize for the inconvenience and appreciate your patience."
es: es:
Message: Mensaje Message: Mensaje
Send: Enviar Send: Enviar
@ -153,4 +156,5 @@ es:
The destination can't be empty: El destinatario no puede estar vacio The destination can't be empty: El destinatario no puede estar vacio
The message can't be empty: El mensaje no puede estar vacio The message can't be empty: El mensaje no puede estar vacio
The message it's too long: El mensaje es demasiado largo The message it's too long: El mensaje es demasiado largo
</i18n> routeDelay: "Retraso en ruta.\nInformamos que la ruta que lleva su pedido ha sufrido un retraso y la entrega se hará a lo largo del día.\nDisculpe las molestias."
</i18n>

View File

@ -1,12 +1,15 @@
<script setup> <script setup>
import { onBeforeMount } from 'vue'; import { onBeforeMount, computed } from 'vue';
import { useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router'; import { useRoute, useRouter, onBeforeRouteUpdate, onBeforeRouteLeave } from 'vue-router';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import useCardSize from 'src/composables/useCardSize'; import useCardSize from 'src/composables/useCardSize';
import VnSubToolbar from '../ui/VnSubToolbar.vue'; import VnSubToolbar from '../ui/VnSubToolbar.vue';
const emit = defineEmits(['onFetch']);
const props = defineProps({ const props = defineProps({
id: { type: Number, required: false, default: null },
dataKey: { type: String, required: true }, dataKey: { type: String, required: true },
url: { type: String, default: undefined }, url: { type: String, default: undefined },
idInWhere: { type: Boolean, default: false }, idInWhere: { type: Boolean, default: false },
@ -16,10 +19,13 @@ const props = defineProps({
searchDataKey: { type: String, default: undefined }, searchDataKey: { type: String, default: undefined },
searchbarProps: { type: Object, default: undefined }, searchbarProps: { type: Object, default: undefined },
redirectOnError: { type: Boolean, default: false }, redirectOnError: { type: Boolean, default: false },
visual: { type: Boolean, default: true },
}); });
const route = useRoute();
const stateStore = useStateStore(); const stateStore = useStateStore();
const router = useRouter(); const router = useRouter();
const entityId = computed(() => props.id || route?.params?.id);
const arrayData = useArrayData(props.dataKey, { const arrayData = useArrayData(props.dataKey, {
url: props.url, url: props.url,
userFilter: props.filter, userFilter: props.filter,
@ -35,7 +41,7 @@ onBeforeMount(async () => {
const route = router.currentRoute.value; const route = router.currentRoute.value;
try { try {
await fetch(route.params.id); await fetch(entityId.value);
} catch { } catch {
const { matched: matches } = route; const { matched: matches } = route;
const { path } = matches.at(-1); const { path } = matches.at(-1);
@ -51,8 +57,7 @@ onBeforeRouteUpdate(async (to, from) => {
router.push({ name, params: to.params }); router.push({ name, params: to.params });
} }
} }
const id = to.params.id; if (entityId.value !== to.params.id) await fetch(to.params.id, true);
if (id !== from.params.id) await fetch(id, true);
}); });
async function fetch(id, append = false) { async function fetch(id, append = false) {
@ -61,14 +66,17 @@ async function fetch(id, append = false) {
else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`; else if (!regex.test(props.url)) arrayData.store.url = `${props.url}/${id}`;
else arrayData.store.url = props.url.replace(regex, `/${id}`); else arrayData.store.url = props.url.replace(regex, `/${id}`);
await arrayData.fetch({ append, updateRouter: false }); await arrayData.fetch({ append, updateRouter: false });
emit('onFetch', arrayData.store.data);
} }
function hasRouteParam(params, valueToCheck = ':addressId') { function hasRouteParam(params, valueToCheck = ':addressId') {
return Object.values(params).includes(valueToCheck); return Object.values(params).includes(valueToCheck);
} }
</script> </script>
<template> <template>
<VnSubToolbar /> <template v-if="visual">
<div :class="[useCardSize(), $attrs.class]"> <VnSubToolbar />
<RouterView :key="$route.path" /> <div :class="[useCardSize(), $attrs.class]">
</div> <RouterView :key="$route.path" />
</div>
</template>
</template> </template>

View File

@ -27,7 +27,11 @@ const checkboxModel = computed({
</script> </script>
<template> <template>
<div> <div>
<QCheckbox v-bind="$attrs" v-model="checkboxModel" /> <QCheckbox
v-bind="$attrs"
v-model="checkboxModel"
:data-cy="$attrs['data-cy'] ?? `vnCheckbox${$attrs['label'] ?? ''}`"
/>
<QIcon <QIcon
v-if="info" v-if="info"
v-bind="$attrs" v-bind="$attrs"

View File

@ -35,6 +35,10 @@ const $props = defineProps({
type: String, type: String,
default: null, default: null,
}, },
hasFile: {
type: Boolean,
default: false,
},
}); });
const warehouses = ref(); const warehouses = ref();
@ -90,6 +94,7 @@ function defaultData() {
if ($props.formInitialData) return (dms.value = $props.formInitialData); if ($props.formInitialData) return (dms.value = $props.formInitialData);
return addDefaultData({ return addDefaultData({
reference: route.params.id, reference: route.params.id,
hasFile: $props.hasFile,
}); });
} }
@ -177,6 +182,7 @@ function addDefaultData(data) {
name="vn:attach" name="vn:attach"
class="cursor-pointer" class="cursor-pointer"
@click="inputFileRef.pickFiles()" @click="inputFileRef.pickFiles()"
data-cy="attachFile"
> >
<QTooltip>{{ t('globals.selectFile') }}</QTooltip> <QTooltip>{{ t('globals.selectFile') }}</QTooltip>
</QIcon> </QIcon>

View File

@ -0,0 +1,166 @@
<script setup>
import VnConfirm from '../ui/VnConfirm.vue';
import VnInput from './VnInput.vue';
import VnDms from './VnDms.vue';
import axios from 'axios';
import { useQuasar } from 'quasar';
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { downloadFile } from 'src/composables/downloadFile';
const { t } = useI18n();
const quasar = useQuasar();
const documentDialogRef = ref({});
const editDownloadDisabled = ref(false);
const $props = defineProps({
defaultDmsCode: {
type: String,
default: 'invoiceIn',
},
disable: {
type: Boolean,
default: true,
},
data: {
type: Object,
default: null,
},
formRef: {
type: Object,
default: null,
},
});
function deleteFile(dmsFk) {
quasar
.dialog({
component: VnConfirm,
componentProps: {
title: t('globals.confirmDeletion'),
message: t('globals.confirmDeletionMessage'),
},
})
.onOk(async () => {
await axios.post(`dms/${dmsFk}/removeFile`);
$props.formRef.formData.dmsFk = null;
$props.formRef.formData.dms = undefined;
$props.formRef.hasChanges = true;
$props.formRef.save();
});
}
</script>
<template>
<div class="row no-wrap">
<VnInput
:label="t('Document')"
v-model="data.dmsFk"
clearable
clear-icon="close"
class="full-width"
:disable="disable"
/>
<div
v-if="data.dmsFk"
class="row no-wrap q-pa-xs q-gutter-x-xs"
data-cy="dms-buttons"
>
<QBtn
:disable="editDownloadDisabled"
@click="downloadFile(data.dmsFk)"
icon="cloud_download"
color="primary"
flat
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
round
>
<QTooltip>{{ t('Download file') }}</QTooltip>
</QBtn>
<QBtn
:disable="editDownloadDisabled"
@click="
() => {
documentDialogRef.show = true;
documentDialogRef.dms = data.dms;
}
"
icon="edit"
color="primary"
flat
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
round
>
<QTooltip>{{ t('Edit document') }}</QTooltip>
</QBtn>
<QBtn
:disable="editDownloadDisabled"
@click="deleteFile(data.dmsFk)"
icon="delete"
color="primary"
flat
round
:class="{
'no-pointer-events': editDownloadDisabled,
}"
padding="xs"
>
<QTooltip>{{ t('Delete file') }}</QTooltip>
</QBtn>
</div>
<QBtn
v-else
icon="add_circle"
color="primary"
flat
round
v-shortcut="'+'"
padding="xs"
@click="
() => {
documentDialogRef.show = true;
delete documentDialogRef.dms;
}
"
data-cy="dms-create"
>
<QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn>
</div>
<QDialog v-model="documentDialogRef.show">
<VnDms
model="dms"
:default-dms-code="defaultDmsCode"
:form-initial-data="documentDialogRef.dms"
:url="
documentDialogRef.dms
? `Dms/${documentDialogRef.dms.id}/updateFile`
: 'Dms/uploadFile'
"
:description="documentDialogRef.supplierName"
@on-data-saved="
(_, { data }) => {
let dmsData = data;
if (Array.isArray(data)) dmsData = data[0];
formRef.formData.dmsFk = dmsData.id;
formRef.formData.dms = dmsData;
formRef.hasChanges = true;
formRef.save();
}
"
/>
</QDialog>
</template>
<i18n>
es:
Document: Documento
Download file: Descargar archivo
Edit document: Editar documento
Delete file: Eliminar archivo
Create document: Crear documento
</i18n>

View File

@ -389,10 +389,7 @@ defineExpose({
</div> </div>
</template> </template>
</QTable> </QTable>
<div <div v-else class="info-row q-pa-md text-center">
v-else
class="info-row q-pa-md text-center"
>
<h5> <h5>
{{ t('No data to display') }} {{ t('No data to display') }}
</h5> </h5>
@ -416,6 +413,7 @@ defineExpose({
v-shortcut v-shortcut
@click="showFormDialog()" @click="showFormDialog()"
class="fill-icon" class="fill-icon"
data-cy="addButton"
> >
<QTooltip> <QTooltip>
{{ t('Upload file') }} {{ t('Upload file') }}

View File

@ -0,0 +1,53 @@
<script setup>
import { ref } from 'vue';
import VnSelect from './VnSelect.vue';
const stateBtnDropdownRef = ref();
const emit = defineEmits(['changeState']);
const $props = defineProps({
disable: {
type: Boolean,
default: null,
},
options: {
type: Array,
default: null,
},
optionLabel: {
type: String,
default: 'name',
},
optionValue: {
type: String,
default: 'id',
},
});
async function changeState(value) {
stateBtnDropdownRef.value?.hide();
emit('changeState', value);
}
</script>
<template>
<QBtnDropdown
ref="stateBtnDropdownRef"
color="black"
text-color="white"
:label="$t('globals.changeState')"
:disable="$props.disable"
>
<VnSelect
:options="$props.options"
:option-label="$props.optionLabel"
:option-value="$props.optionValue"
hide-selected
hide-dropdown-icon
focus-on-mount
@update:model-value="changeState"
>
</VnSelect>
</QBtnDropdown>
</template>

View File

@ -84,7 +84,7 @@ const mixinRules = [
...($attrs.rules ?? []), ...($attrs.rules ?? []),
(val) => { (val) => {
const maxlength = $props.maxlength; const maxlength = $props.maxlength;
if (maxlength && +val.length > maxlength) if (maxlength && +val?.length > maxlength)
return t(`maxLength`, { value: maxlength }); return t(`maxLength`, { value: maxlength });
const { min, max } = vnInputRef.value.$attrs; const { min, max } = vnInputRef.value.$attrs;
if (!min) return null; if (!min) return null;

View File

@ -107,7 +107,7 @@ const manageDate = (date) => {
@click="isPopupOpen = !isPopupOpen" @click="isPopupOpen = !isPopupOpen"
@keydown="isPopupOpen = false" @keydown="isPopupOpen = false"
hide-bottom-space hide-bottom-space
:data-cy="$attrs.dataCy ?? $attrs.label + '_inputDate'" :data-cy="($attrs['data-cy'] ?? $attrs.label) + '_inputDate'"
> >
<template #append> <template #append>
<QIcon <QIcon

View File

@ -10,7 +10,7 @@ import { useColor } from 'src/composables/useColor';
import { useCapitalize } from 'src/composables/useCapitalize'; import { useCapitalize } from 'src/composables/useCapitalize';
import { useValidator } from 'src/composables/useValidator'; import { useValidator } from 'src/composables/useValidator';
import VnAvatar from '../ui/VnAvatar.vue'; import VnAvatar from '../ui/VnAvatar.vue';
import VnJsonValue from '../common/VnJsonValue.vue'; import VnLogValue from './VnLogValue.vue';
import FetchData from '../FetchData.vue'; import FetchData from '../FetchData.vue';
import VnSelect from './VnSelect.vue'; import VnSelect from './VnSelect.vue';
import VnUserLink from '../ui/VnUserLink.vue'; import VnUserLink from '../ui/VnUserLink.vue';
@ -560,10 +560,9 @@ watch(
value.nameI18n value.nameI18n
}}: }}:
</span> </span>
<VnJsonValue <VnLogValue
:value=" :value="value.val"
value.val.val :name="value.name"
"
/> />
</QItem> </QItem>
</QCardSection> </QCardSection>
@ -614,7 +613,10 @@ watch(
> >
{{ prop.nameI18n }}: {{ prop.nameI18n }}:
</span> </span>
<VnJsonValue :value="prop.val.val" /> <VnLogValue
:value="prop.val"
:name="prop.name"
/>
<span <span
v-if=" v-if="
propIndex < propIndex <
@ -642,8 +644,9 @@ watch(
{{ prop.nameI18n }}: {{ prop.nameI18n }}:
</span> </span>
<span v-if="log.action == 'update'"> <span v-if="log.action == 'update'">
<VnJsonValue <VnLogValue
:value="prop.old.val" :value="prop.old"
:name="prop.name"
/> />
<span <span
v-if="prop.old.id" v-if="prop.old.id"
@ -652,8 +655,9 @@ watch(
#{{ prop.old.id }} #{{ prop.old.id }}
</span> </span>
<VnJsonValue <VnLogValue
:value="prop.val.val" :value="prop.val"
:name="prop.name"
/> />
<span <span
v-if="prop.val.id" v-if="prop.val.id"
@ -663,8 +667,9 @@ watch(
</span> </span>
</span> </span>
<span v-else="prop.old.val"> <span v-else="prop.old.val">
<VnJsonValue <VnLogValue
:value="prop.val.val" :value="prop.val"
:name="prop.name"
/> />
<span <span
v-if="prop.old.id" v-if="prop.old.id"
@ -700,6 +705,7 @@ watch(
v-model="searchInput" v-model="searchInput"
class="full-width" class="full-width"
clearable clearable
filled
clear-icon="close" clear-icon="close"
@keyup.enter="() => selectFilter('search')" @keyup.enter="() => selectFilter('search')"
@focusout="() => selectFilter('search')" @focusout="() => selectFilter('search')"
@ -719,6 +725,7 @@ watch(
v-model="selectedFilters.changedModel" v-model="selectedFilters.changedModel"
option-label="locale" option-label="locale"
option-value="value" option-value="value"
filled
:options="actions" :options="actions"
@update:model-value="selectFilter('action')" @update:model-value="selectFilter('action')"
hide-selected hide-selected
@ -744,8 +751,7 @@ watch(
class="full-width" class="full-width"
:label="t('globals.user')" :label="t('globals.user')"
v-model="userSelect" v-model="userSelect"
option-label="name" filled
option-value="id"
:url="`${model}Logs/${route.params.id}/editors`" :url="`${model}Logs/${route.params.id}/editors`"
:fields="['id', 'nickname', 'name', 'image']" :fields="['id', 'nickname', 'name', 'image']"
sort-by="nickname" sort-by="nickname"
@ -774,6 +780,7 @@ watch(
:label="t('globals.changes')" :label="t('globals.changes')"
v-model="changeInput" v-model="changeInput"
class="full-width" class="full-width"
filled
clearable clearable
clear-icon="close" clear-icon="close"
@keyup.enter="selectFilter('change')" @keyup.enter="selectFilter('change')"
@ -810,6 +817,7 @@ watch(
@clear="selectFilter('date', 'to')" @clear="selectFilter('date', 'to')"
v-model="dateFrom" v-model="dateFrom"
clearable clearable
filled
clear-icon="close" clear-icon="close"
/> />
</QItem> </QItem>
@ -822,6 +830,7 @@ watch(
@clear="selectFilter('date', 'from')" @clear="selectFilter('date', 'from')"
v-model="dateTo" v-model="dateTo"
clearable clearable
filled
clear-icon="close" clear-icon="close"
/> />
</QItem> </QItem>
@ -835,6 +844,7 @@ watch(
dense dense
flat flat
minimal minimal
filled
@update:model-value=" @update:model-value="
(value) => { (value) => {
dateFromDialog = false; dateFromDialog = false;

View File

@ -0,0 +1,28 @@
<script setup>
import { useDescriptorStore } from 'src/stores/useDescriptorStore';
import VnJsonValue from './VnJsonValue.vue';
import { computed } from 'vue';
const descriptorStore = useDescriptorStore();
const $props = defineProps({
value: { type: Object, default: () => {} },
name: { type: String, default: undefined },
});
const descriptor = computed(() => descriptorStore.has($props.name));
</script>
<template>
<VnJsonValue :value="value.val" />
<span
v-if="(value.id || typeof value.val == 'number') && descriptor"
style="margin-left: 2px"
>
<QIcon
name="launch"
class="link"
:data-cy="'iconLaunch-' + $props.name"
style="padding-bottom: 2px"
/>
<component :is="descriptor" :id="value.id ?? value.val" />
</span>
</template>

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, toRefs, computed, watch, onMounted, useAttrs } from 'vue'; import { ref, toRefs, computed, watch, onMounted, useAttrs, nextTick } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import { useRequired } from 'src/composables/useRequired'; import { useRequired } from 'src/composables/useRequired';
@ -152,6 +152,10 @@ const value = computed({
}, },
}); });
const computedSortBy = computed(() => {
return $props.sortBy || $props.optionLabel + ' ASC';
});
watch(options, (newValue) => { watch(options, (newValue) => {
setOptions(newValue); setOptions(newValue);
}); });
@ -186,7 +190,7 @@ function findKeyInOptions() {
} }
function setOptions(data) { function setOptions(data) {
data = dataByOrder(data, $props.sortBy); data = dataByOrder(data, computedSortBy.value);
myOptions.value = JSON.parse(JSON.stringify(data)); myOptions.value = JSON.parse(JSON.stringify(data));
myOptionsOriginal.value = JSON.parse(JSON.stringify(data)); myOptionsOriginal.value = JSON.parse(JSON.stringify(data));
emit('update:options', data); emit('update:options', data);
@ -216,7 +220,8 @@ function filter(val, options) {
async function fetchFilter(val) { async function fetchFilter(val) {
if (!$props.url) return; if (!$props.url) return;
const { fields, include, sortBy, limit } = $props; const { fields, include, limit } = $props;
const sortBy = computedSortBy.value;
const key = const key =
optionFilterValue.value ?? optionFilterValue.value ??
(new RegExp(/\d/g).test(val) (new RegExp(/\d/g).test(val)
@ -247,6 +252,7 @@ async function fetchFilter(val) {
} }
async function filterHandler(val, update) { async function filterHandler(val, update) {
if (isLoading.value) return update();
if (!val && lastVal.value === val) { if (!val && lastVal.value === val) {
lastVal.value = val; lastVal.value = val;
return update(); return update();
@ -294,6 +300,7 @@ async function onScroll({ to, direction, from, index }) {
await arrayData.loadMore(); await arrayData.loadMore();
setOptions(arrayData.store.data); setOptions(arrayData.store.data);
vnSelectRef.value.scrollTo(lastIndex); vnSelectRef.value.scrollTo(lastIndex);
await nextTick();
isLoading.value = false; isLoading.value = false;
} }
} }

View File

@ -4,12 +4,15 @@ import { vi, afterEach, beforeAll, describe, expect, it } from 'vitest';
describe('VnDmsList', () => { describe('VnDmsList', () => {
let vm; let vm;
const dms = { const dms = {
userFk: 1, userFk: 1,
name: 'DMS 1' name: 'DMS 1',
}; };
beforeAll(() => { beforeAll(() => {
vi.mock('src/composables/getUrl', () => ({
getUrl: vi.fn().mockResolvedValue(''),
}));
vi.spyOn(axios, 'get').mockResolvedValue({ data: [] }); vi.spyOn(axios, 'get').mockResolvedValue({ data: [] });
vm = createWrapper(VnDmsList, { vm = createWrapper(VnDmsList, {
props: { props: {
@ -18,8 +21,8 @@ describe('VnDmsList', () => {
filter: 'wd.workerFk', filter: 'wd.workerFk',
updateModel: 'Workers', updateModel: 'Workers',
deleteModel: 'WorkerDms', deleteModel: 'WorkerDms',
downloadModel: 'WorkerDms' downloadModel: 'WorkerDms',
} },
}).vm; }).vm;
}); });
@ -29,46 +32,45 @@ describe('VnDmsList', () => {
describe('setData()', () => { describe('setData()', () => {
const data = [ const data = [
{ {
userFk: 1, userFk: 1,
name: 'Jessica', name: 'Jessica',
lastName: 'Jones', lastName: 'Jones',
file: '4.jpg', file: '4.jpg',
created: '2021-07-28 21:00:00' created: '2021-07-28 21:00:00',
}, },
{ {
userFk: 2, userFk: 2,
name: 'Bruce', name: 'Bruce',
lastName: 'Banner', lastName: 'Banner',
created: '2022-07-28 21:00:00', created: '2022-07-28 21:00:00',
dms: { dms: {
userFk: 2, userFk: 2,
name: 'Bruce', name: 'Bruce',
lastName: 'BannerDMS', lastName: 'BannerDMS',
created: '2022-07-28 21:00:00', created: '2022-07-28 21:00:00',
file: '4.jpg', file: '4.jpg',
} },
}, },
{ {
userFk: 3, userFk: 3,
name: 'Natasha', name: 'Natasha',
lastName: 'Romanoff', lastName: 'Romanoff',
file: '4.jpg', file: '4.jpg',
created: '2021-10-28 21:00:00' created: '2021-10-28 21:00:00',
} },
] ];
it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => { it('Should replace objects that contain the "dms" property with the value of the same and sort by creation date', () => {
vm.setData(data); vm.setData(data);
expect([vm.rows][0][0].lastName).toEqual('BannerDMS'); expect([vm.rows][0][0].lastName).toEqual('BannerDMS');
expect([vm.rows][0][1].lastName).toEqual('Romanoff'); expect([vm.rows][0][1].lastName).toEqual('Romanoff');
}); });
}); });
describe('parseDms()', () => { describe('parseDms()', () => {
const resultDms = { ...dms, userId:1}; const resultDms = { ...dms, userId: 1 };
it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => { it('Should add properties that end with "Fk" by changing the suffix to "Id"', () => {
const parsedDms = vm.parseDms(dms); const parsedDms = vm.parseDms(dms);
expect(parsedDms).toEqual(resultDms); expect(parsedDms).toEqual(resultDms);
@ -76,12 +78,12 @@ describe('VnDmsList', () => {
}); });
describe('showFormDialog()', () => { describe('showFormDialog()', () => {
const resultDms = { ...dms, userId:1}; const resultDms = { ...dms, userId: 1 };
it('should call fn parseDms() and set show true if dms is defined', () => { it('should call fn parseDms() and set show true if dms is defined', () => {
vm.showFormDialog(dms); vm.showFormDialog(dms);
expect(vm.formDialog.show).toEqual(true); expect(vm.formDialog.show).toEqual(true);
expect(vm.formDialog.dms).toEqual(resultDms); expect(vm.formDialog.dms).toEqual(resultDms);
}); });
}); });
}); });

View File

@ -0,0 +1,26 @@
import { describe, it, expect } from 'vitest';
import VnLogValue from 'src/components/common/VnLogValue.vue';
import { createWrapper } from 'app/test/vitest/helper';
const buildComponent = (props) => {
return createWrapper(VnLogValue, {
props,
global: {},
}).wrapper;
};
describe('VnLogValue', () => {
const id = 1;
it('renders without descriptor', async () => {
expect(getIcon('inventFk').exists()).toBe(false);
});
it('renders with descriptor', async () => {
expect(getIcon('claimFk').text()).toBe('launch');
});
function getIcon(name) {
const wrapper = buildComponent({ value: { val: id }, name });
return wrapper.find('.q-icon');
}
});

View File

@ -1,357 +1,38 @@
<script setup> <script setup>
import { onBeforeMount, watch, computed, ref } from 'vue'; import { ref } from 'vue';
import { useI18n } from 'vue-i18n'; import VnDescriptor from './VnDescriptor.vue';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useArrayData } from 'composables/useArrayData';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useState } from 'src/composables/useState';
import { useRoute, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard';
import VnMoreOptions from './VnMoreOptions.vue';
const $props = defineProps({ const $props = defineProps({
url: { id: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
userFilter: {
type: Object,
default: null,
},
title: {
type: String,
default: '',
},
subtitle: {
type: Number, type: Number,
default: null, default: false,
}, },
dataKey: { card: {
type: String,
default: null,
},
summary: {
type: Object, type: Object,
default: null, default: null,
}, },
width: {
type: String,
default: 'md-width',
},
}); });
const state = useState();
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { copyText } = useClipboard();
const { viewSummary } = useSummaryDialog();
let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
const DESCRIPTOR_PROXY = 'DescriptorProxy';
const moduleName = ref();
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
defineExpose({ getData });
onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, {
url: $props.url,
userFilter: $props.filter,
skip: 0,
oneRecord: true,
});
store = arrayData.store;
entity = computed(() => {
const data = store.data ?? {};
if (data) emit('onFetch', data);
return data;
});
// It enables to load data only once if the module is the same as the dataKey
if (!isSameDataKey.value || !route.params.id) await getData();
watch(
() => [$props.url, $props.filter],
async () => {
if (!isSameDataKey.value) await getData();
},
);
});
function getName() {
let name = $props.dataKey;
if ($props.dataKey.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return name;
}
const routeName = computed(() => {
let routeName = getName();
return `${routeName}Summary`;
});
async function getData() {
store.url = $props.url;
store.filter = $props.filter ?? {};
isLoading.value = true;
try {
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
state.set($props.dataKey, data);
emit('onFetch', data);
} finally {
isLoading.value = false;
}
}
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}
function copyIdText(id) {
copyText(id, {
component: {
copyValue: id,
},
});
}
const emit = defineEmits(['onFetch']); const emit = defineEmits(['onFetch']);
const entity = ref();
const iconModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.meta?.icon;
} else {
return route.matched[1].meta.icon;
}
});
const toModule = computed(() => {
moduleName.value = getName();
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.children[0]?.redirect;
} else {
return route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect;
}
});
</script> </script>
<template> <template>
<div class="descriptor"> <component
<template v-if="entity && !isLoading"> :is="card"
<div class="header bg-primary q-pa-sm justify-between"> :id
<slot name="header-extra-action"> :visual="false"
<QBtn v-bind="$attrs"
round @on-fetch="
flat (data) => {
dense entity = data;
size="md" emit('onFetch', data);
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn>
</slot>
<QBtn
@click.stop="viewSummary(entity.id, $props.summary, $props.width)"
round
flat
dense
size="md"
icon="preview"
color="white"
class="link"
v-if="summary"
>
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn>
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
<QBtn
class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
>
<QTooltip>
{{ t('components.cardDescriptor.summary') }}
</QTooltip>
</QBtn>
</RouterLink>
<VnMoreOptions v-if="$slots.menu">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</div>
<slot name="before" />
<div class="body q-py-sm">
<QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title">
<span v-if="$props.title" :title="getValueFromPath(title)">
{{ getValueFromPath(title) ?? $props.title }}
</span>
<slot v-else name="description" :entity="entity">
<span :title="entity.name">
{{ entity.name }}
</span>
</slot>
</div>
</QItemLabel>
<QItem>
<QItemLabel class="subtitle">
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItem>
</QList>
<div class="list-box q-mt-xs">
<slot name="body" :entity="entity" />
</div>
</div>
<div class="icons">
<slot name="icons" :entity="entity" />
</div>
<div class="actions justify-center" data-cy="descriptor_actions">
<slot name="actions" :entity="entity" />
</div>
<slot name="after" />
</template>
<SkeletonDescriptor v-if="!entity || isLoading" />
</div>
<QInnerLoading
:label="t('globals.pleaseWait')"
:showing="isLoading"
color="primary"
/>
</template>
<style lang="scss">
.body {
background-color: var(--vn-section-color);
.text-h5 {
font-size: 20px;
padding-top: 5px;
padding-bottom: 0px;
}
.q-item {
min-height: 20px;
.link {
margin-left: 10px;
}
}
.vn-label-value {
display: flex;
padding: 0px 16px;
.label {
color: var(--vn-label-color);
font-size: 14px;
&:not(:has(a))::after {
content: ':';
} }
} "
.value { />
color: var(--vn-text-color); <VnDescriptor v-model="entity" v-bind="$attrs">
font-size: 14px; <template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
margin-left: 4px; <slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
overflow: hidden; </template>
text-overflow: ellipsis; </VnDescriptor>
white-space: nowrap; </template>
text-align: left;
}
.info {
margin-left: 5px;
}
}
}
</style>
<style lang="scss" scoped>
.title {
overflow: hidden;
text-overflow: ellipsis;
span {
color: var(--vn-text-color);
font-weight: bold;
}
}
.subtitle {
color: var(--vn-text-color);
font-size: 16px;
margin-bottom: 2px;
}
.list-box {
.q-item__label {
color: var(--vn-label-color);
padding-bottom: 0%;
}
}
.descriptor {
width: 256px;
.header {
display: flex;
align-items: center;
}
.icons {
margin: 0 10px;
display: flex;
justify-content: center;
.q-icon {
margin-right: 5px;
}
}
.actions {
margin: 0 5px;
justify-content: center !important;
}
}
</style>
<i18n>
en:
globals:
copyId: Copy ID
es:
globals:
copyId: Copiar ID
</i18n>

View File

@ -81,6 +81,7 @@ async function fetch() {
name: `${moduleName ?? route.meta.moduleName}Summary`, name: `${moduleName ?? route.meta.moduleName}Summary`,
params: { id: entityId || entity.id }, params: { id: entityId || entity.id },
}" }"
data-cy="goToSummaryBtn"
> >
<QIcon name="open_in_new" color="white" size="sm" /> <QIcon name="open_in_new" color="white" size="sm" />
</router-link> </router-link>
@ -200,6 +201,29 @@ async function fetch() {
} }
} }
} }
.vn-card-group {
display: flex;
flex-direction: column;
}
.vn-card-content {
display: flex;
flex-direction: column;
text-overflow: ellipsis;
> div {
max-height: 70px;
}
}
@media (min-width: 1010px) {
.vn-card-group {
flex-direction: row;
}
.vn-card-content {
flex: 1;
}
}
</style> </style>
<style lang="scss" scoped> <style lang="scss" scoped>
.summaryHeader .vn-label-value { .summaryHeader .vn-label-value {

View File

@ -0,0 +1,78 @@
<script setup>
import { onBeforeMount, watch, computed, ref } from 'vue';
import { useArrayData } from 'composables/useArrayData';
import { useState } from 'src/composables/useState';
import { useRoute } from 'vue-router';
import VnDescriptor from './VnDescriptor.vue';
const $props = defineProps({
url: {
type: String,
default: '',
},
filter: {
type: Object,
default: null,
},
dataKey: {
type: String,
default: null,
},
});
const state = useState();
const route = useRoute();
let arrayData;
let store;
let entity;
const isLoading = ref(false);
const isSameDataKey = computed(() => $props.dataKey === route.meta.moduleName);
defineExpose({ getData });
onBeforeMount(async () => {
arrayData = useArrayData($props.dataKey, {
url: $props.url,
userFilter: $props.filter,
skip: 0,
oneRecord: true,
});
store = arrayData.store;
entity = computed(() => {
const data = store.data ?? {};
if (data) emit('onFetch', data);
return data;
});
// It enables to load data only once if the module is the same as the dataKey
if (!isSameDataKey.value || !route.params.id) await getData();
watch(
() => [$props.url, $props.filter],
async () => {
if (!isSameDataKey.value) await getData();
},
);
});
async function getData() {
store.url = $props.url;
store.filter = $props.filter ?? {};
isLoading.value = true;
try {
const { data } = await arrayData.fetch({ append: false, updateRouter: false });
state.set($props.dataKey, data);
emit('onFetch', data);
} finally {
isLoading.value = false;
}
}
const emit = defineEmits(['onFetch']);
</script>
<template>
<VnDescriptor v-model="entity" v-bind="$attrs" :module="dataKey">
<template v-for="(_, slotName) in $slots" #[slotName]="slotData" :key="slotName">
<slot :name="slotName" v-bind="slotData ?? {}" :key="slotName" />
</template>
</VnDescriptor>
</template>

View File

@ -0,0 +1,318 @@
<script setup>
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import SkeletonDescriptor from 'components/ui/SkeletonDescriptor.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { useRoute, useRouter } from 'vue-router';
import { useClipboard } from 'src/composables/useClipboard';
import VnMoreOptions from './VnMoreOptions.vue';
const entity = defineModel({ type: Object, default: null });
const $props = defineProps({
title: {
type: String,
default: '',
},
subtitle: {
type: Number,
default: null,
},
summary: {
type: Object,
default: null,
},
width: {
type: String,
default: 'md-width',
},
module: {
type: String,
default: null,
},
toModule: {
type: String,
default: null,
},
});
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const { copyText } = useClipboard();
const { viewSummary } = useSummaryDialog();
const DESCRIPTOR_PROXY = 'DescriptorProxy';
const moduleName = ref();
const isSameModuleName = route.matched[1].meta.moduleName !== moduleName.value;
function getName() {
let name = $props.module;
if ($props.module.includes(DESCRIPTOR_PROXY)) {
name = name.split(DESCRIPTOR_PROXY)[0];
}
return name;
}
const routeName = computed(() => {
let routeName = getName();
return `${routeName}Summary`;
});
function getValueFromPath(path) {
if (!path) return;
const keys = path.toString().split('.');
let current = entity.value;
for (const key of keys) {
if (current[key] === undefined) return undefined;
else current = current[key];
}
return current;
}
function copyIdText(id) {
copyText(id, {
component: {
copyValue: id,
},
});
}
const emit = defineEmits(['onFetch']);
const iconModule = computed(() => {
moduleName.value = getName();
if ($props.toModule) {
return router.getRoutes().find((r) => r.name === $props.toModule.name).meta.icon;
}
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.meta?.icon;
} else {
return route.matched[1].meta.icon;
}
});
const toModule = computed(() => {
moduleName.value = getName();
if ($props.toModule) return $props.toModule;
if (isSameModuleName) {
return router.options.routes[1].children.find((r) => r.name === moduleName.value)
?.redirect;
} else {
return route.matched[1].path.split('/').length > 2
? route.matched[1].redirect
: route.matched[1].children[0].redirect;
}
});
</script>
<template>
<div class="descriptor" data-cy="vnDescriptor">
<template v-if="entity && entity?.id">
<div class="header bg-primary q-pa-sm justify-between">
<slot name="header-extra-action">
<QBtn
round
flat
dense
size="md"
:icon="iconModule"
color="white"
class="link"
:to="toModule"
>
<QTooltip>
{{ t('globals.goToModuleIndex') }}
</QTooltip>
</QBtn>
</slot>
<QBtn
@click.stop="viewSummary(entity.id, summary, width)"
round
flat
dense
size="md"
icon="preview"
color="white"
class="link"
v-if="summary"
data-cy="openSummaryBtn"
>
<QTooltip>
{{ t('components.smartCard.openSummary') }}
</QTooltip>
</QBtn>
<RouterLink :to="{ name: routeName, params: { id: entity.id } }">
<QBtn
class="link"
color="white"
dense
flat
icon="launch"
round
size="md"
data-cy="goToSummaryBtn"
>
<QTooltip>
{{ t('components.vnDescriptor.summary') }}
</QTooltip>
</QBtn>
</RouterLink>
<VnMoreOptions v-if="$slots.menu">
<template #menu="{ menuRef }">
<slot name="menu" :entity="entity" :menu-ref="menuRef" />
</template>
</VnMoreOptions>
</div>
<slot name="before" />
<div class="body q-py-sm">
<QList dense>
<QItemLabel header class="ellipsis text-h5" :lines="1">
<div class="title">
<span
v-if="title"
:title="getValueFromPath(title)"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_title`"
>
{{ getValueFromPath(title) ?? title }}
</span>
<slot v-else name="description" :entity="entity">
<span
:title="entity.name"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_description`"
v-text="entity.name"
/>
</slot>
</div>
</QItemLabel>
<QItem>
<QItemLabel
class="subtitle"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_subtitle`"
>
#{{ getValueFromPath(subtitle) ?? entity.id }}
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItem>
</QList>
<div
class="list-box q-mt-xs"
:data-cy="`${$attrs['data-cy'] ?? 'vnDescriptor'}_listbox`"
>
<slot name="body" :entity="entity" />
</div>
</div>
<div class="icons">
<slot name="icons" :entity="entity" />
</div>
<div class="actions justify-center" data-cy="descriptor_actions">
<slot name="actions" :entity="entity" />
</div>
<slot name="after" />
</template>
<SkeletonDescriptor v-if="!entity" />
</div>
<QInnerLoading :label="t('globals.pleaseWait')" :showing="!entity" color="primary" />
</template>
<style lang="scss">
.body {
background-color: var(--vn-section-color);
.text-h5 {
font-size: 20px;
padding-top: 5px;
padding-bottom: 0px;
}
.q-item {
min-height: 20px;
.link {
margin-left: 10px;
}
}
.vn-label-value {
display: flex;
padding: 0px 16px;
.label {
color: var(--vn-label-color);
font-size: 14px;
&:not(:has(a))::after {
content: ':';
}
}
.value {
color: var(--vn-text-color);
font-size: 14px;
margin-left: 4px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
text-align: left;
}
.info {
margin-left: 5px;
}
}
}
</style>
<style lang="scss" scoped>
.title {
overflow: hidden;
text-overflow: ellipsis;
span {
color: var(--vn-text-color);
font-weight: bold;
}
}
.subtitle {
color: var(--vn-text-color);
font-size: 16px;
margin-bottom: 2px;
}
.list-box {
.q-item__label {
color: var(--vn-label-color);
padding-bottom: 0%;
}
}
.descriptor {
width: 256px;
.header {
display: flex;
align-items: center;
}
.icons {
margin: 0 10px;
display: flex;
justify-content: center;
.q-icon {
margin-right: 5px;
}
}
.actions {
margin: 0 5px;
justify-content: center !important;
}
}
</style>
<i18n>
en:
globals:
copyId: Copy ID
es:
globals:
copyId: Copiar ID
</i18n>

View File

@ -249,7 +249,7 @@ const getLocale = (label) => {
:key="chip.label" :key="chip.label"
:removable="!unremovableParams?.includes(chip.label)" :removable="!unremovableParams?.includes(chip.label)"
@remove="remove(chip.label)" @remove="remove(chip.label)"
data-cy="vnFilterPanelChip" :data-cy="`vnFilterPanelChip_${chip.label}`"
> >
<slot <slot
name="tags" name="tags"

View File

@ -28,13 +28,14 @@ function copyValueText() {
const val = computed(() => $props.value); const val = computed(() => $props.value);
</script> </script>
<template> <template>
<div class="vn-label-value"> <div class="vn-label-value" :data-cy="`${$attrs['data-cy'] ?? 'vnLv'}${label ?? ''}`">
<QCheckbox <QCheckbox
v-if="typeof value === 'boolean'" v-if="typeof value === 'boolean'"
v-model="val" v-model="val"
:label="label" :label="label"
disable disable
dense dense
size="sm"
/> />
<template v-else> <template v-else>
<div v-if="label || $slots.label" class="label"> <div v-if="label || $slots.label" class="label">
@ -44,7 +45,7 @@ const val = computed(() => $props.value);
</div> </div>
<div class="value"> <div class="value">
<slot name="value"> <slot name="value">
<span :title="value"> <span :title="value" style="text-overflow: ellipsis">
{{ dash ? dashIfEmpty(value) : value }} {{ dash ? dashIfEmpty(value) : value }}
</span> </span>
</slot> </slot>

View File

@ -12,7 +12,7 @@
{{ $t('components.cardDescriptor.moreOptions') }} {{ $t('components.cardDescriptor.moreOptions') }}
</QTooltip> </QTooltip>
<QMenu ref="menuRef" data-cy="descriptor-more-opts-menu"> <QMenu ref="menuRef" data-cy="descriptor-more-opts-menu">
<QList> <QList data-cy="descriptor-more-opts_list">
<slot name="menu" :menu-ref="$refs.menuRef" /> <slot name="menu" :menu-ref="$refs.menuRef" />
</QList> </QList>
</QMenu> </QMenu>

View File

@ -40,6 +40,11 @@ const quasar = useQuasar();
const newNote = reactive({ text: null, observationTypeFk: null }); const newNote = reactive({ text: null, observationTypeFk: null });
const observationTypes = ref([]); const observationTypes = ref([]);
const vnPaginateRef = ref(); const vnPaginateRef = ref();
const defaultObservationType = computed(() =>
observationTypes.value.find(ot => ot.code === 'salesPerson')?.id
);
let originalText; let originalText;
function handleClick(e) { function handleClick(e) {
@ -111,14 +116,22 @@ function fetchData([data]) {
originalText = data?.notes; originalText = data?.notes;
emit('onFetch', data); emit('onFetch', data);
} }
const handleObservationTypes = (data) => {
observationTypes.value = data;
if(defaultObservationType.value) {
newNote.observationTypeFk = defaultObservationType.value;
}
};
</script> </script>
<template> <template>
<FetchData <FetchData
v-if="selectType" v-if="selectType"
url="ObservationTypes" url="ObservationTypes"
:filter="{ fields: ['id', 'description'] }" :filter="{ fields: ['id', 'description', 'code'] }"
auto-load auto-load
@on-fetch="(data) => (observationTypes = data)" @on-fetch="handleObservationTypes"
/> />
<FetchData <FetchData
v-if="justInput" v-if="justInput"
@ -154,6 +167,7 @@ function fetchData([data]) {
filled filled
size="lg" size="lg"
autogrow autogrow
autofocus
@keyup.enter.stop="handleClick" @keyup.enter.stop="handleClick"
:required="isRequired" :required="isRequired"
clearable clearable
@ -189,7 +203,6 @@ function fetchData([data]) {
:search-url="false" :search-url="false"
@on-fetch=" @on-fetch="
newNote.text = ''; newNote.text = '';
newNote.observationTypeFk = null;
" "
> >
<template #body="{ rows }"> <template #body="{ rows }">

View File

@ -26,6 +26,7 @@ const id = props.entityId;
:to="{ name: routeName, params: { id: id } }" :to="{ name: routeName, params: { id: id } }"
class="header link" class="header link"
:href="url" :href="url"
data-cy="goToSummaryBtn"
> >
<QIcon name="open_in_new" color="white" size="sm" /> <QIcon name="open_in_new" color="white" size="sm" />
</router-link> </router-link>

View File

@ -6,10 +6,12 @@ const session = useSession();
const token = session.getToken(); const token = session.getToken();
describe('downloadFile', () => { describe('downloadFile', () => {
const baseUrl = 'http://localhost:9000';
let defaulCreateObjectURL; let defaulCreateObjectURL;
beforeAll(() => { beforeAll(() => {
vi.mock('src/composables/getUrl', () => ({
getUrl: vi.fn().mockResolvedValue(''),
}));
defaulCreateObjectURL = window.URL.createObjectURL; defaulCreateObjectURL = window.URL.createObjectURL;
window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id'); window.URL.createObjectURL = vi.fn(() => 'blob:http://localhost:9000/blob-id');
}); });
@ -22,15 +24,14 @@ describe('downloadFile', () => {
headers: { 'content-disposition': 'attachment; filename="test-file.txt"' }, headers: { 'content-disposition': 'attachment; filename="test-file.txt"' },
}; };
vi.spyOn(axios, 'get').mockImplementation((url) => { vi.spyOn(axios, 'get').mockImplementation((url) => {
if (url == 'Urls/getUrl') return Promise.resolve({ data: baseUrl }); if (url.includes('downloadFile')) return Promise.resolve(res);
else if (url.includes('downloadFile')) return Promise.resolve(res);
}); });
await downloadFile(1); await downloadFile(1);
expect(axios.get).toHaveBeenCalledWith( expect(axios.get).toHaveBeenCalledWith(
`${baseUrl}/api/dms/1/downloadFile?access_token=${token}`, `/api/dms/1/downloadFile?access_token=${token}`,
{ responseType: 'blob' } { responseType: 'blob' },
); );
}); });
}); });

View File

@ -7,18 +7,33 @@ const { getTokenMultimedia } = useSession();
const token = getTokenMultimedia(); const token = getTokenMultimedia();
export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) { export async function downloadFile(id, model = 'dms', urlPath = '/downloadFile', url) {
const appUrl = (await getUrl('', 'lilium')).replace('/#/', ''); const appUrl = await getAppUrl();
const response = await axios.get( const response = await axios.get(
url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`, url ?? `${appUrl}/api/${model}/${id}${urlPath}?access_token=${token}`,
{ responseType: 'blob' } { responseType: 'blob' },
); );
download(response);
}
export async function downloadDocuware(url, params) {
const appUrl = await getAppUrl();
const response = await axios.get(`${appUrl}/api/` + url, {
responseType: 'blob',
params,
});
download(response);
}
function download(response) {
const contentDisposition = response.headers['content-disposition']; const contentDisposition = response.headers['content-disposition'];
const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition); const matches = /filename[^;=\n]*=((['"]).*?\2|[^;\n]*)/.exec(contentDisposition);
const filename = const filename = matches?.[1] ? matches[1].replace(/['"]/g, '') : 'downloaded-file';
matches != null && matches[1]
? matches[1].replace(/['"]/g, '')
: 'downloaded-file';
exportFile(filename, response.data); exportFile(filename, response.data);
} }
async function getAppUrl() {
return (await getUrl('', 'lilium')).replace('/#/', '');
}

View File

@ -325,7 +325,6 @@ input::-webkit-inner-spin-button {
min-height: auto !important; min-height: auto !important;
display: flex; display: flex;
align-items: flex-end; align-items: flex-end;
padding-bottom: 2px;
.q-field__native.row { .q-field__native.row {
min-height: auto !important; min-height: auto !important;
} }

View File

@ -100,7 +100,6 @@ globals:
file: File file: File
selectFile: Select a file selectFile: Select a file
copyClipboard: Copy on clipboard copyClipboard: Copy on clipboard
salesPerson: SalesPerson
send: Send send: Send
code: Code code: Code
since: Since since: Since
@ -160,6 +159,7 @@ globals:
changeState: Change state changeState: Change state
raid: 'Raid {daysInForward} days' raid: 'Raid {daysInForward} days'
isVies: Vies isVies: Vies
department: Department
noData: No data available noData: No data available
vehicle: Vehicle vehicle: Vehicle
pageTitles: pageTitles:
@ -349,7 +349,6 @@ globals:
params: params:
description: Description description: Description
clientFk: Client id clientFk: Client id
salesPersonFk: Sales person
warehouseFk: Warehouse warehouseFk: Warehouse
provinceFk: Province provinceFk: Province
stateFk: State stateFk: State
@ -607,7 +606,6 @@ worker:
balance: Balance balance: Balance
medical: Medical medical: Medical
list: list:
department: Department
schedule: Schedule schedule: Schedule
newWorker: New worker newWorker: New worker
summary: summary:
@ -650,6 +648,7 @@ worker:
model: Model model: Model
serialNumber: Serial number serialNumber: Serial number
removePDA: Deallocate PDA removePDA: Deallocate PDA
sendToTablet: Send to tablet
create: create:
lastName: Last name lastName: Last name
birth: Birth birth: Birth
@ -820,6 +819,7 @@ travel:
search: Search travel search: Search travel
searchInfo: You can search by travel id or name searchInfo: You can search by travel id or name
id: Id id: Id
awbFk: AWB
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Reference ref: Reference
@ -866,7 +866,6 @@ components:
mine: For me mine: For me
hasMinPrice: Minimum price hasMinPrice: Minimum price
# LatestBuysFilter # LatestBuysFilter
salesPersonFk: Buyer
supplierFk: Supplier supplierFk: Supplier
from: From from: From
to: To to: To
@ -887,7 +886,7 @@ components:
openCard: View openCard: View
openSummary: Summary openSummary: Summary
viewSummary: Summary viewSummary: Summary
cardDescriptor: vnDescriptor:
mainList: Main list mainList: Main list
summary: Summary summary: Summary
moreOptions: More options moreOptions: More options
@ -897,6 +896,8 @@ components:
VnLv: VnLv:
copyText: '{copyValue} has been copied to the clipboard' copyText: '{copyValue} has been copied to the clipboard'
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789'
VnNotes:
clientWithoutPhone: 'The following clients do not have a phone number and the message will not be sent to them: {clientWithoutPhone}'
weekdays: weekdays:
sun: Sunday sun: Sunday
mon: Monday mon: Monday

View File

@ -104,7 +104,6 @@ globals:
file: Fichero file: Fichero
selectFile: Seleccione un fichero selectFile: Seleccione un fichero
copyClipboard: Copiar en portapapeles copyClipboard: Copiar en portapapeles
salesPerson: Comercial
send: Enviar send: Enviar
code: Código code: Código
since: Desde since: Desde
@ -165,6 +164,7 @@ globals:
raid: 'Redada {daysInForward} días' raid: 'Redada {daysInForward} días'
isVies: Vies isVies: Vies
noData: Datos no disponibles noData: Datos no disponibles
department: Departamento
vehicle: Vehículo vehicle: Vehículo
pageTitles: pageTitles:
logIn: Inicio de sesión logIn: Inicio de sesión
@ -352,7 +352,6 @@ globals:
params: params:
description: Descripción description: Descripción
clientFk: Id cliente clientFk: Id cliente
salesPersonFk: Comercial
warehouseFk: Almacén warehouseFk: Almacén
provinceFk: Provincia provinceFk: Provincia
stateFk: Estado stateFk: Estado
@ -534,7 +533,6 @@ ticket:
state: Estado state: Estado
shipped: Enviado shipped: Enviado
landed: Entregado landed: Entregado
salesPerson: Comercial
total: Total total: Total
card: card:
customerId: ID cliente customerId: ID cliente
@ -626,8 +624,6 @@ invoiceOut:
errors: errors:
downloadCsvFailed: Error al descargar CSV downloadCsvFailed: Error al descargar CSV
order: order:
field:
salesPersonFk: Comercial
form: form:
clientFk: Cliente clientFk: Cliente
addressFk: Dirección addressFk: Dirección
@ -695,7 +691,6 @@ worker:
formation: Formación formation: Formación
medical: Mutua medical: Mutua
list: list:
department: Departamento
schedule: Horario schedule: Horario
newWorker: Nuevo trabajador newWorker: Nuevo trabajador
summary: summary:
@ -738,6 +733,7 @@ worker:
model: Modelo model: Modelo
serialNumber: Número de serie serialNumber: Número de serie
removePDA: Desasignar PDA removePDA: Desasignar PDA
sendToTablet: Enviar a la tablet
create: create:
lastName: Apellido lastName: Apellido
birth: Fecha de nacimiento birth: Fecha de nacimiento
@ -906,6 +902,7 @@ travel:
search: Buscar envío search: Buscar envío
searchInfo: Buscar envío por id o nombre searchInfo: Buscar envío por id o nombre
id: Id id: Id
awbFk: Guía aérea
travelList: travelList:
tableVisibleColumns: tableVisibleColumns:
ref: Referencia ref: Referencia
@ -953,7 +950,6 @@ components:
hasMinPrice: Precio mínimo hasMinPrice: Precio mínimo
wareHouseFk: Almacén wareHouseFk: Almacén
# LatestBuysFilter # LatestBuysFilter
salesPersonFk: Comprador
supplierFk: Proveedor supplierFk: Proveedor
visible: Visible visible: Visible
active: Activo active: Activo
@ -974,7 +970,7 @@ components:
openCard: Ficha openCard: Ficha
openSummary: Detalles openSummary: Detalles
viewSummary: Vista previa viewSummary: Vista previa
cardDescriptor: vnDescriptor:
mainList: Listado principal mainList: Listado principal
summary: Resumen summary: Resumen
moreOptions: Más opciones moreOptions: Más opciones
@ -984,6 +980,8 @@ components:
VnLv: VnLv:
copyText: '{copyValue} se ha copiado al portapepeles' copyText: '{copyValue} se ha copiado al portapepeles'
iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789' iban_tooltip: 'IBAN: ES21 1234 5678 90 0123456789'
VnNotes:
clientWithoutPhone: 'Estos clientes no tienen asociado número de télefono y el sms no les será enviado: {clientWithoutPhone}'
weekdays: weekdays:
sun: Domingo sun: Domingo
mon: Lunes mon: Lunes

View File

@ -47,7 +47,7 @@ const rolesOptions = ref([]);
:label="t('globals.name')" :label="t('globals.name')"
v-model="params.name" v-model="params.name"
lazy-rules lazy-rules
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -57,7 +57,7 @@ const rolesOptions = ref([]);
:label="t('account.card.alias')" :label="t('account.card.alias')"
v-model="params.nickname" v-model="params.nickname"
lazy-rules lazy-rules
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -75,8 +75,7 @@ const rolesOptions = ref([]);
use-input use-input
hide-selected hide-selected
dense dense
outlined filled
rounded
:input-debounce="0" :input-debounce="0"
/> />
</QItemSection> </QItemSection>

View File

@ -56,8 +56,7 @@ onBeforeMount(() => {
option-label="name" option-label="name"
use-input use-input
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -72,8 +71,7 @@ onBeforeMount(() => {
option-label="name" option-label="name"
use-input use-input
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -83,7 +81,7 @@ onBeforeMount(() => {
:label="t('acls.aclFilter.property')" :label="t('acls.aclFilter.property')"
v-model="params.property" v-model="params.property"
lazy-rules lazy-rules
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -98,8 +96,7 @@ onBeforeMount(() => {
option-label="name" option-label="name"
use-input use-input
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -114,8 +111,7 @@ onBeforeMount(() => {
option-label="name" option-label="name"
use-input use-input
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -4,7 +4,7 @@ import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import axios from 'axios'; import axios from 'axios';
@ -48,11 +48,12 @@ const removeAlias = () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`MailAliases/${entityId}`" :url="`MailAliases/${entityId}`"
data-key="Alias" data-key="Alias"
title="alias" title="alias"
:to-module="{ name: 'AccountAlias' }"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeAlias()"> <QItem v-ripple clickable @click="removeAlias()">
@ -62,7 +63,7 @@ const removeAlias = () => {
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('role.description')" :value="entity.description" /> <VnLv :label="t('role.description')" :value="entity.description" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -1,7 +1,7 @@
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue'; import { ref, computed, onMounted } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import AccountDescriptorMenu from './AccountDescriptorMenu.vue'; import AccountDescriptorMenu from './AccountDescriptorMenu.vue';
import VnImg from 'src/components/ui/VnImg.vue'; import VnImg from 'src/components/ui/VnImg.vue';
@ -20,7 +20,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`VnUsers/preview`" :url="`VnUsers/preview`"
:filter="{ ...filter, where: { id: entityId } }" :filter="{ ...filter, where: { id: entityId } }"
@ -78,7 +78,7 @@ onMounted(async () => {
</QIcon> </QIcon>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -0,0 +1,14 @@
<script setup>
import AccountDescriptor from './AccountDescriptor.vue';
import AccountSummary from './AccountSummary.vue';
</script>
<template>
<QPopupProxy style="max-width: 10px">
<AccountDescriptor
v-if="$attrs.id"
v-bind="$attrs.id"
:summary="AccountSummary"
:proxy-render="true"
/>
</QPopupProxy>
</template>

View File

@ -18,7 +18,7 @@ const entityId = computed(() => $props.id || route.params.id);
data-key="Account" data-key="Account"
ref="AccountSummary" ref="AccountSummary"
url="VnUsers/preview" url="VnUsers/preview"
:filter="filter" :filter="{ ...filter, where: { id: entityId } }"
> >
<template #header="{ entity }">{{ entity.id }} - {{ entity.nickname }}</template> <template #header="{ entity }">{{ entity.id }} - {{ entity.nickname }}</template>
<template #menu> <template #menu>

View File

@ -27,7 +27,7 @@ const props = defineProps({
:label="t('globals.name')" :label="t('globals.name')"
v-model="params.name" v-model="params.name"
lazy-rules lazy-rules
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -37,7 +37,7 @@ const props = defineProps({
:label="t('role.description')" :label="t('role.description')"
v-model="params.description" v-model="params.description"
lazy-rules lazy-rules
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>

View File

@ -2,7 +2,7 @@
import { computed } from 'vue'; import { computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import axios from 'axios'; import axios from 'axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
@ -32,11 +32,12 @@ const removeRole = async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
url="VnRoles" url="VnRoles"
:filter="{ where: { id: entityId } }" :filter="{ where: { id: entityId } }"
data-key="Role" data-key="Role"
:summary="$props.summary" :summary="$props.summary"
:to-module="{ name: 'AccountRoles' }"
> >
<template #menu> <template #menu>
<QItem v-ripple clickable @click="removeRole()"> <QItem v-ripple clickable @click="removeRole()">
@ -46,7 +47,7 @@ const removeRole = async () => {
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv :label="t('role.description')" :value="entity.description" /> <VnLv :label="t('role.description')" :value="entity.description" />
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -2,6 +2,7 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { getDifferences, getUpdatedValues } from 'src/filters';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
@ -9,12 +10,18 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnAvatar from 'src/components/ui/VnAvatar.vue'; import VnAvatar from 'src/components/ui/VnAvatar.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const workersOptions = ref([]); const workersOptions = ref([]);
function onBeforeSave(formData, originalData) {
return getUpdatedValues(
Object.keys(getDifferences(formData, originalData)),
formData,
);
}
</script> </script>
<template> <template>
<FetchData <FetchData
@ -27,6 +34,7 @@ const workersOptions = ref([]);
<FormModel <FormModel
model="Claim" model="Claim"
:url-update="`Claims/updateClaim/${route.params.id}`" :url-update="`Claims/updateClaim/${route.params.id}`"
:mapper="onBeforeSave"
auto-load auto-load
> >
<template #form="{ data, validate }"> <template #form="{ data, validate }">

View File

@ -5,7 +5,8 @@ import { useI18n } from 'vue-i18n';
import { toDateHourMinSec, toPercentage } from 'src/filters'; import { toDateHourMinSec, toPercentage } from 'src/filters';
import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue'; import TicketDescriptorProxy from 'pages/Ticket/Card/TicketDescriptorProxy.vue';
import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue'; import ClaimDescriptorMenu from 'pages/Claim/Card/ClaimDescriptorMenu.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
@ -43,7 +44,7 @@ onMounted(async () => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Claims/${entityId}`" :url="`Claims/${entityId}`"
:filter="filter" :filter="filter"
title="client.name" title="client.name"
@ -65,12 +66,12 @@ onMounted(async () => {
</template> </template>
</VnLv> </VnLv>
<VnLv :label="t('claim.created')" :value="toDateHourMinSec(entity.created)" /> <VnLv :label="t('claim.created')" :value="toDateHourMinSec(entity.created)" />
<VnLv :label="t('claim.commercial')"> <VnLv :label="t('globals.department')">
<template #value> <template #value>
<VnUserLink <span class="link">
:name="entity.client?.salesPersonUser?.name" {{ entity?.client?.department?.name || '-' }}
:worker-id="entity.client?.salesPersonFk" <DepartmentDescriptorProxy :id="entity?.client?.departmentFk" />
/> </span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
@ -146,7 +147,7 @@ onMounted(async () => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style scoped> <style scoped>
.q-item__label { .q-item__label {

View File

@ -0,0 +1,14 @@
<script setup>
import ClaimDescriptor from './ClaimDescriptor.vue';
import ClaimSummary from './ClaimSummary.vue';
</script>
<template>
<QPopupProxy style="max-width: 10px">
<ClaimDescriptor
v-if="$attrs.id"
v-bind="$attrs.id"
:summary="ClaimSummary"
:proxy-render="true"
/>
</QPopupProxy>
</template>

View File

@ -14,7 +14,7 @@ export default {
relation: 'client', relation: 'client',
scope: { scope: {
include: [ include: [
{ relation: 'salesPersonUser' }, { relation: 'department' },
{ {
relation: 'claimsRatio', relation: 'claimsRatio',
scope: { scope: {

View File

@ -117,7 +117,7 @@ const selected = ref([]);
const mana = ref(0); const mana = ref(0);
async function fetchMana() { async function fetchMana() {
const ticketId = claim.value.ticketFk; const ticketId = claim.value.ticketFk;
const response = await axios.get(`Tickets/${ticketId}/getSalesPersonMana`); const response = await axios.get(`Tickets/${ticketId}/getDepartmentMana`);
mana.value = response.data; mana.value = response.data;
} }

View File

@ -19,7 +19,9 @@ import ClaimNotes from 'src/pages/Claim/Card/ClaimNotes.vue';
import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue'; import ItemDescriptorProxy from 'src/pages/Item/Card/ItemDescriptorProxy.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import ClaimDescriptorMenu from './ClaimDescriptorMenu.vue'; import ClaimDescriptorMenu from './ClaimDescriptorMenu.vue';
import VnDropdown from 'src/components/common/VnDropdown.vue';
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@ -35,7 +37,7 @@ const $props = defineProps({
}); });
const entityId = computed(() => $props.id || route.params.id); const entityId = computed(() => $props.id || route.params.id);
const ClaimStates = ref([]); const claimStates = ref([]);
const claimDmsRef = ref(); const claimDmsRef = ref();
const claimDms = ref([]); const claimDms = ref([]);
const multimediaDialog = ref(); const multimediaDialog = ref();
@ -172,7 +174,9 @@ function openDialog(dmsId) {
} }
async function changeState(value) { async function changeState(value) {
await axios.patch(`Claims/updateClaim/${entityId.value}`, { claimStateFk: value }); await axios.patch(`Claims/updateClaim/${entityId.value}`, {
claimStateFk: value,
});
router.go(route.fullPath); router.go(route.fullPath);
} }
@ -182,13 +186,18 @@ function claimUrl(section) {
</script> </script>
<template> <template>
<FetchData
url="ClaimStates"
:filter="{ fields: ['id', 'description'] }"
@on-fetch="(data) => (claimStates = data)"
auto-load
/>
<FetchData <FetchData
url="ClaimDms" url="ClaimDms"
:filter="claimDmsFilter" :filter="claimDmsFilter"
@on-fetch="(data) => setClaimDms(data)" @on-fetch="(data) => setClaimDms(data)"
ref="claimDmsRef" ref="claimDmsRef"
/> />
<FetchData url="ClaimStates" @on-fetch="(data) => (ClaimStates = data)" auto-load />
<CardSummary <CardSummary
ref="summary" ref="summary"
:url="`Claims/${entityId}/getSummary`" :url="`Claims/${entityId}/getSummary`"
@ -200,34 +209,11 @@ function claimUrl(section) {
{{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }}) {{ claim.id }} - {{ claim.client.name }} ({{ claim.client.id }})
</template> </template>
<template #header-right> <template #header-right>
<QBtnDropdown <VnDropdown
side :options="claimStates"
top option-label="description"
color="black" @change-state="changeState"
text-color="white" />
:label="t('globals.changeState')"
>
<QList>
<QVirtualScroll
class="max-container-height"
:items="ClaimStates"
separator
v-slot="{ item, index }"
>
<QItem
:key="index"
dense
clickable
v-close-popup
@click="changeState(item.id)"
>
<QItemSection>
<QItemLabel>{{ item.description }}</QItemLabel>
</QItemSection>
</QItem>
</QVirtualScroll>
</QList>
</QBtnDropdown>
</template> </template>
<template #menu="{ entity }"> <template #menu="{ entity }">
<ClaimDescriptorMenu :claim="entity.claim" /> <ClaimDescriptorMenu :claim="entity.claim" />
@ -252,13 +238,15 @@ function claimUrl(section) {
</VnLv> </VnLv>
<VnLv <VnLv
v-if="$route.name != 'ClaimSummary'" v-if="$route.name != 'ClaimSummary'"
:label="t('globals.salesPerson')" :label="t('customer.summary.team')"
> >
<template #value> <template #value>
<VnUserLink <span class="link">
:name="claim.client?.salesPersonUser?.name" {{ claim?.client?.department?.name || '-' }}
:worker-id="claim.client?.salesPersonFk" <DepartmentDescriptorProxy
/> :id="claim?.client?.departmentFk"
/>
</span>
</template> </template>
</VnLv> </VnLv>
<VnLv v-if="$route.name != 'ClaimSummary'" :label="t('claim.attendedBy')"> <VnLv v-if="$route.name != 'ClaimSummary'" :label="t('claim.attendedBy')">

View File

@ -33,7 +33,7 @@ const props = defineProps({
:label="t('claim.customerId')" :label="t('claim.customerId')"
v-model="params.clientFk" v-model="params.clientFk"
lazy-rules lazy-rules
is-outlined filled
> >
<template #prepend> <QIcon name="badge" size="xs" /></template> <template #prepend> <QIcon name="badge" size="xs" /></template>
</VnInput> </VnInput>
@ -41,18 +41,16 @@ const props = defineProps({
:label="t('Client Name')" :label="t('Client Name')"
v-model="params.clientName" v-model="params.clientName"
lazy-rules lazy-rules
is-outlined filled
/> />
<VnSelect <VnSelect
:label="t('Salesperson')"
v-model="params.salesPersonFk"
url="Workers/activeWithInheritedRole"
:filter="{ where: { role: 'salesPerson' } }"
:use-like="false"
option-filter="firstName"
dense dense
outlined filled
rounded :label="t('globals.params.departmentFk')"
v-model="params.departmentFk"
option-value="id"
option-label="name"
url="Departments"
/> />
<VnSelect <VnSelect
:label="t('claim.attendedBy')" :label="t('claim.attendedBy')"
@ -62,8 +60,7 @@ const props = defineProps({
:use-like="false" :use-like="false"
option-filter="firstName" option-filter="firstName"
dense dense
outlined filled
rounded
/> />
<VnSelect <VnSelect
:label="t('claim.state')" :label="t('claim.state')"
@ -71,14 +68,12 @@ const props = defineProps({
:options="states" :options="states"
option-label="description" option-label="description"
dense dense
outlined filled
rounded
/> />
<VnInputDate <VnInputDate
v-model="params.created" v-model="params.created"
:label="t('claim.created')" :label="t('claim.created')"
outlined filled
rounded
dense dense
/> />
<VnSelect <VnSelect
@ -87,8 +82,7 @@ const props = defineProps({
url="Items/withName" url="Items/withName"
:use-like="false" :use-like="false"
sort-by="id DESC" sort-by="id DESC"
outlined filled
rounded
dense dense
/> />
<VnSelect <VnSelect
@ -99,15 +93,13 @@ const props = defineProps({
:use-like="false" :use-like="false"
option-filter="firstName" option-filter="firstName"
dense dense
outlined filled
rounded
/> />
<VnSelect <VnSelect
:label="t('claim.zone')" :label="t('claim.zone')"
v-model="params.zoneFk" v-model="params.zoneFk"
url="Zones" url="Zones"
outlined filled
rounded
dense dense
/> />
<QCheckbox <QCheckbox
@ -126,7 +118,6 @@ en:
search: Contains search: Contains
clientFk: Customer clientFk: Customer
clientName: Customer clientName: Customer
salesPersonFk: Salesperson
attenderFk: Attender attenderFk: Attender
claimResponsibleFk: Responsible claimResponsibleFk: Responsible
claimStateFk: State claimStateFk: State
@ -139,7 +130,6 @@ es:
search: Contiene search: Contiene
clientFk: Cliente clientFk: Cliente
clientName: Cliente clientName: Cliente
salesPersonFk: Comercial
attenderFk: Asistente attenderFk: Asistente
claimResponsibleFk: Responsable claimResponsibleFk: Responsable
claimStateFk: Estado claimStateFk: Estado
@ -148,6 +138,5 @@ es:
itemFk: Artículo itemFk: Artículo
zoneFk: Zona zoneFk: Zona
Client Name: Nombre del cliente Client Name: Nombre del cliente
Salesperson: Comercial
Item: Artículo Item: Artículo
</i18n> </i18n>

View File

@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import { toDate } from 'filters/index'; import { toDate } from 'filters/index';
import ClaimFilter from './ClaimFilter.vue'; import ClaimFilter from './ClaimFilter.vue';
import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'src/pages/Customer/Card/CustomerDescriptorProxy.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue'; import VnUserLink from 'src/components/ui/VnUserLink.vue';
import ClaimSummary from './Card/ClaimSummary.vue'; import ClaimSummary from './Card/ClaimSummary.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog'; import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -48,6 +49,20 @@ const columns = computed(() => [
}, },
columnClass: 'expand', columnClass: 'expand',
}, },
{
align: 'left',
name: 'departmentFk',
label: t('customer.summary.team'),
component: 'select',
attrs: {
url: 'Departments',
},
create: true,
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
},
{ {
align: 'left', align: 'left',
label: t('claim.attendedBy'), label: t('claim.attendedBy'),
@ -152,6 +167,12 @@ const STATE_COLOR = {
<CustomerDescriptorProxy :id="row.clientFk" /> <CustomerDescriptorProxy :id="row.clientFk" />
</span> </span>
</template> </template>
<template #column-departmentFk="{ row }">
<span class="link" @click.stop>
{{ row.departmentName || '-' }}
<DepartmentDescriptorProxy :id="row?.departmentFk" />
</span>
</template>
<template #column-attendedBy="{ row }"> <template #column-attendedBy="{ row }">
<span @click.stop> <span @click.stop>
<VnUserLink :name="row.workerName" :worker-id="row.workerFk" /> <VnUserLink :name="row.workerName" :worker-id="row.workerFk" />

View File

@ -8,7 +8,6 @@ import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import { getDifferences, getUpdatedValues } from 'src/filters'; import { getDifferences, getUpdatedValues } from 'src/filters';
import VnSelectCompany from 'src/components/common/VnSelectCompany.vue'; import VnSelectCompany from 'src/components/common/VnSelectCompany.vue';
import VnSelectCurrency from 'src/components/common/VnSelectCurrency.vue'; import VnSelectCurrency from 'src/components/common/VnSelectCurrency.vue';
@ -121,16 +120,11 @@ function onBeforeSave(formData, originalData) {
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelectWorker <VnSelect
:label="t('customer.summary.salesPerson')" :label="t('globals.department')"
v-model="data.salesPersonFk" v-model="data.departmentFk"
:params="{ url="Departments"
departmentCodes: ['VT', 'shopping'], :fields="['id', 'name']"
}"
:has-avatar="true"
:rules="validate('client.salesPersonFk')"
:expr-builder="exprBuilder"
emit-value
/> />
<VnSelect <VnSelect
v-model="data.contactChannelFk" v-model="data.contactChannelFk"

View File

@ -3,14 +3,14 @@ import { onMounted, ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { dashIfEmpty, toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import useCardDescription from 'src/composables/useCardDescription'; import useCardDescription from 'src/composables/useCardDescription';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
const state = useState(); const state = useState();
@ -54,7 +54,7 @@ const debtWarning = computed(() => {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Clients/${entityId}/getCard`" :url="`Clients/${entityId}/getCard`"
:summary="$props.summary" :summary="$props.summary"
data-key="Customer" data-key="Customer"
@ -84,14 +84,10 @@ const debtWarning = computed(() => {
:value="toCurrency(entity.debt)" :value="toCurrency(entity.debt)"
:info="t('customer.summary.riskInfo')" :info="t('customer.summary.riskInfo')"
/> />
<VnLv :label="t('customer.summary.salesPerson')"> <VnLv :label="t('globals.department')">
<template #value> <template #value>
<VnUserLink <span class="link" v-text="entity.department?.name" />
v-if="entity.salesPersonUser" <DepartmentDescriptorProxy :id="entity.department?.id" />
:name="entity.salesPersonUser.name"
:worker-id="entity.salesPersonFk"
/>
<span v-else>{{ dashIfEmpty(entity.salesPersonUser) }}</span>
</template> </template>
</VnLv> </VnLv>
<VnLv <VnLv
@ -109,15 +105,6 @@ const debtWarning = computed(() => {
> >
<QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip> <QTooltip>{{ t('customer.card.isDisabled') }}</QTooltip>
</QIcon> </QIcon>
<QIcon
v-if="entity?.substitutionAllowed"
name="help"
size="xs"
color="primary"
>
<QTooltip>{{ t('Allowed substitution') }}</QTooltip>
</QIcon>
<QIcon <QIcon
v-if="!entity.account?.active" v-if="!entity.account?.active"
color="primary" color="primary"
@ -236,7 +223,7 @@ const debtWarning = computed(() => {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>

View File

@ -61,16 +61,6 @@ const openCreateForm = (type) => {
.join('&'); .join('&');
useOpenURL(`/#/${type}/list?${params}`); useOpenURL(`/#/${type}/list?${params}`);
}; };
const updateSubstitutionAllowed = async () => {
try {
await axios.patch(`Clients/${route.params.id}`, {
substitutionAllowed: !$props.customer.substitutionAllowed,
});
notify('globals.notificationSent', 'positive');
} catch (error) {
notify(error.message, 'positive');
}
};
</script> </script>
<template> <template>
@ -79,13 +69,6 @@ const updateSubstitutionAllowed = async () => {
{{ t('globals.pageTitles.createTicket') }} {{ t('globals.pageTitles.createTicket') }}
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem v-ripple clickable>
<QItemSection @click="updateSubstitutionAllowed()">{{
$props.customer.substitutionAllowed
? t('Disable substitution')
: t('Allow substitution')
}}</QItemSection>
</QItem>
<QItem v-ripple clickable> <QItem v-ripple clickable>
<QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection> <QItemSection @click="showSmsDialog()">{{ t('Send SMS') }}</QItemSection>
</QItem> </QItem>

View File

@ -2,7 +2,6 @@
import { computed, ref } from 'vue'; import { computed, ref } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnUserLink from 'src/components/ui/VnUserLink.vue';
import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters'; import { toCurrency, toPercentage, toDate, dashOrCurrency } from 'src/filters';
import CardSummary from 'components/ui/CardSummary.vue'; import CardSummary from 'components/ui/CardSummary.vue';
@ -13,6 +12,8 @@ import CustomerSummaryTable from 'src/pages/Customer/components/CustomerSummaryT
import VnTitle from 'src/components/common/VnTitle.vue'; import VnTitle from 'src/components/common/VnTitle.vue';
import VnRow from 'src/components/ui/VnRow.vue'; import VnRow from 'src/components/ui/VnRow.vue';
import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue'; import CustomerDescriptorMenu from './CustomerDescriptorMenu.vue';
import DepartmentDescriptorProxy from 'src/pages/Worker/Department/Card/DepartmentDescriptorProxy.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const grafanaUrl = 'https://grafana.verdnatura.es'; const grafanaUrl = 'https://grafana.verdnatura.es';
@ -106,16 +107,12 @@ const sumRisk = ({ clientRisks }) => {
{{ t('globals.params.email') }} {{ t('globals.params.email') }}
<VnLinkMail email="entity.email"></VnLinkMail> </template <VnLinkMail email="entity.email"></VnLinkMail> </template
></VnLv> ></VnLv>
<VnLv <VnLv :label="t('globals.department')">
:label="t('customer.summary.salesPerson')"
:value="entity?.salesPersonUser?.name"
>
<template #value> <template #value>
<VnUserLink <span class="link" v-text="entity.department?.name" />
:name="entity.salesPersonUser?.name" <DepartmentDescriptorProxy :id="entity?.department?.id" />
:worker-id="entity.salesPersonFk" </template>
/> </template </VnLv>
></VnLv>
<VnLv <VnLv
:label="t('customer.summary.contactChannel')" :label="t('customer.summary.contactChannel')"
:value="entity?.contactChannel?.name" :value="entity?.contactChannel?.name"

View File

@ -1,146 +0,0 @@
<script setup>
import { reactive, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import FormModel from 'components/FormModel.vue';
import VnRow from 'components/ui/VnRow.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnLocation from 'src/components/common/VnLocation.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
const { t } = useI18n();
const initialData = reactive({
active: true,
isEqualizated: false,
});
const workersOptions = ref([]);
const businessTypesOptions = ref([]);
function handleLocation(data, location) {
const { town, code, provinceFk, countryFk } = location ?? {};
data.postcode = code;
data.city = town;
data.provinceFk = provinceFk;
data.countryFk = countryFk;
}
</script>
<template>
<FetchData
@on-fetch="(data) => (workersOptions = data)"
auto-load
url="Workers/search?departmentCodes"
/>
<FetchData
@on-fetch="(data) => (businessTypesOptions = data)"
auto-load
url="BusinessTypes"
/>
<QPage>
<VnSubToolbar />
<FormModel
:form-initial-data="initialData"
model="client"
url-create="Clients/createWithUser"
>
<template #form="{ data, validate }">
<VnRow>
<QInput :label="t('Comercial name')" v-model="data.name" />
<VnSelect
:label="t('Salesperson')"
:options="workersOptions"
hide-selected
option-label="name"
option-value="id"
v-model="data.salesPersonFk"
/>
</VnRow>
<VnRow>
<VnSelect
:label="t('Business type')"
:options="businessTypesOptions"
hide-selected
option-label="description"
option-value="code"
v-model="data.businessTypeFk"
/>
<QInput v-model="data.fi" :label="t('Tax number')" />
</VnRow>
<VnRow>
<QInput
:label="t('Business name')"
:rules="validate('client.socialName')"
v-model="data.socialName"
/>
</VnRow>
<VnRow>
<QInput
:label="t('Street')"
:rules="validate('client.street')"
v-model="data.street"
/>
</VnRow>
<VnRow>
<VnLocation
:rules="validate('Worker.postcode')"
:acls="[{ model: 'Town', props: '*', accessType: 'WRITE' }]"
v-model="data.location"
@update:model-value="(location) => handleLocation(data, location)"
>
</VnLocation>
</VnRow>
<VnRow>
<QInput v-model="data.userName" :label="t('Web user')" />
<QInput
:label="t('Email')"
:rules="validate('client.email')"
clearable
type="email"
v-model="data.email"
>
<template #append>
<QIcon name="info" class="cursor-info">
<QTooltip max-width="400px">{{
t('customer.basicData.youCanSaveMultipleEmails')
}}</QTooltip>
</QIcon>
</template>
</QInput>
</VnRow>
<QCheckbox
:label="t('Is equalizated')"
v-model="initialData.isEqualizated"
/>
</template>
</FormModel>
</QPage>
</template>
<style lang="scss" scoped>
.card {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
grid-gap: 20px;
}
</style>
<i18n>
es:
Comercial name: Nombre comercial
Salesperson: Comercial
Business type: Tipo de negocio
Tax number: NIF / CIF
Business name: Razón social
Street: Dirección fiscal
Postcode: Código postal
City: Población
Province: Provincia
Country: País
Web user: Usuario web
Email: Email
Is equalizated: Recargo de equivalencia
</i18n>

View File

@ -3,7 +3,6 @@ import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue'; import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnSelect from 'components/common/VnSelect.vue'; import VnSelect from 'components/common/VnSelect.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n(); const { t } = useI18n();
defineProps({ defineProps({
@ -42,7 +41,7 @@ const exprBuilder = (param, value) => {
<template #body="{ params, searchFn }"> <template #body="{ params, searchFn }">
<QItem class="q-my-sm"> <QItem class="q-my-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('FI')" v-model="params.fi" is-outlined> <VnInput :label="t('FI')" v-model="params.fi" filled>
<template #prepend> <template #prepend>
<QIcon name="badge" size="xs" /> <QIcon name="badge" size="xs" />
</template> </template>
@ -51,7 +50,7 @@ const exprBuilder = (param, value) => {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('Name')" v-model="params.name" is-outlined /> <VnInput :label="t('Name')" v-model="params.name" filled />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
@ -59,28 +58,20 @@ const exprBuilder = (param, value) => {
<VnInput <VnInput
:label="t('customer.summary.socialName')" :label="t('customer.summary.socialName')"
v-model="params.socialName" v-model="params.socialName"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnSelectWorker <VnSelect
:label="t('Salesperson')"
v-model="params.salesPersonFk"
:params="{
departmentCodes: ['VT'],
}"
:expr-builder="exprBuilder"
@update:model-value="searchFn()"
emit-value
map-options
use-input
hide-selected
dense dense
outlined filled
rounded :label="t('globals.params.departmentFk')"
:input-debounce="0" v-model="params.departmentFk"
option-value="id"
option-label="name"
url="Departments"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -97,8 +88,7 @@ const exprBuilder = (param, value) => {
map-options map-options
hide-selected hide-selected
dense dense
outlined filled
rounded
auto-load auto-load
:input-debounce="0" :input-debounce="0"
/> />
@ -106,12 +96,12 @@ const exprBuilder = (param, value) => {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('City')" v-model="params.city" is-outlined /> <VnInput :label="t('City')" v-model="params.city" filled />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('Phone')" v-model="params.phone" is-outlined> <VnInput :label="t('Phone')" v-model="params.phone" filled>
<template #prepend> <template #prepend>
<QIcon name="phone" size="xs" /> <QIcon name="phone" size="xs" />
</template> </template>
@ -120,7 +110,7 @@ const exprBuilder = (param, value) => {
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput :label="t('Email')" v-model="params.email" is-outlined> <VnInput :label="t('Email')" v-model="params.email" filled>
<template #prepend> <template #prepend>
<QIcon name="email" size="sm" /> <QIcon name="email" size="sm" />
</template> </template>
@ -140,19 +130,14 @@ const exprBuilder = (param, value) => {
map-options map-options
hide-selected hide-selected
dense dense
outlined filled
rounded
auto-load auto-load
sortBy="name ASC" sortBy="name ASC"
/></QItemSection> /></QItemSection>
</QItem> </QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection> <QItemSection>
<VnInput <VnInput :label="t('Postcode')" v-model="params.postcode" filled />
:label="t('Postcode')"
v-model="params.postcode"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>
@ -164,7 +149,6 @@ en:
params: params:
search: Contains search: Contains
fi: FI fi: FI
salesPersonFk: Salesperson
provinceFk: Province provinceFk: Province
isActive: Is active isActive: Is active
city: City city: City
@ -191,7 +175,6 @@ es:
sageTaxTypeFk: Tipo de impuesto Sage sageTaxTypeFk: Tipo de impuesto Sage
sageTransactionTypeFk: Tipo de impuesto Sage sageTransactionTypeFk: Tipo de impuesto Sage
payMethodFk: Forma de pago payMethodFk: Forma de pago
salesPersonFk: Comercial
provinceFk: Provincia provinceFk: Provincia
city: Ciudad city: Ciudad
phone: Teléfono phone: Teléfono
@ -201,7 +184,6 @@ es:
name: Nombre name: Nombre
postcode: CP postcode: CP
FI: NIF FI: NIF
Salesperson: Comercial
Province: Provincia Province: Provincia
City: Ciudad City: Ciudad
Phone: Teléfono Phone: Teléfono

View File

@ -10,7 +10,6 @@ import CustomerFilter from './CustomerFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import VnLocation from 'src/components/common/VnLocation.vue'; import VnLocation from 'src/components/common/VnLocation.vue';
import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue'; import VnLinkPhone from 'src/components/ui/VnLinkPhone.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -73,30 +72,17 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
name: 'salesPersonFk', name: 'departmentFk',
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), label: t('customer.summary.team'),
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'Departments',
fields: ['id', 'name', 'firstName'],
where: { role: 'salesPerson' },
optionFilter: 'firstName',
}, },
columnFilter: { create: true,
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name', 'firstName'],
where: { role: 'salesPerson' },
optionLabel: 'firstName',
optionValue: 'id',
},
},
create: false,
columnField: { columnField: {
component: null, component: null,
}, },
format: (row, dashIfEmpty) => dashIfEmpty(row.salesPerson), format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
}, },
{ {
align: 'left', align: 'left',
@ -155,6 +141,9 @@ const columns = computed(() => [
inWhere: true, inWhere: true,
}, },
columnClass: 'expand', columnClass: 'expand',
attrs: {
uppercase: true,
},
}, },
{ {
align: 'left', align: 'left',
@ -446,36 +435,6 @@ function handleLocation(data, location) {
redirect="customer" redirect="customer"
> >
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<VnSelectWorker
:label="t('customer.summary.salesPerson')"
v-model="data.salesPersonFk"
:params="{
departmentCodes: ['VT', 'shopping'],
}"
:has-avatar="true"
:id-value="data.salesPersonFk"
emit-value
auto-load
>
<template #prepend>
<VnAvatar
:worker-id="data.salesPersonFk"
color="primary"
:title="title"
/>
</template>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>{{ scope.opt?.name }}</QItemLabel>
<QItemLabel caption
>{{ scope.opt?.nickname }},
{{ scope.opt?.code }}</QItemLabel
>
</QItemSection>
</QItem>
</template>
</VnSelectWorker>
<VnLocation <VnLocation
:acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]" :acls="[{ model: 'Province', props: '*', accessType: 'WRITE' }]"
v-model="data.location" v-model="data.location"

View File

@ -32,28 +32,6 @@ const columns = computed(() => [
}, },
}, },
}, },
{
align: 'left',
name: 'isWorker',
label: t('Is worker'),
},
{
align: 'left',
name: 'salesPersonFk',
label: t('Salesperson'),
columnFilter: {
component: 'select',
attrs: {
url: 'Workers/activeWithInheritedRole',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
useLike: false,
optionValue: 'id',
optionLabel: 'name',
optionFilter: 'firstName',
},
},
},
{ {
align: 'left', align: 'left',
name: 'departmentFk', name: 'departmentFk',
@ -153,6 +131,11 @@ const columns = computed(() => [
label: t('Has recovery'), label: t('Has recovery'),
name: 'hasRecovery', name: 'hasRecovery',
}, },
{
align: 'left',
name: 'isWorker',
label: t('customer.params.isWorker'),
},
]); ]);
const viewAddObservation = (rowsSelected) => { const viewAddObservation = (rowsSelected) => {
@ -167,7 +150,6 @@ const viewAddObservation = (rowsSelected) => {
function exprBuilder(param, value) { function exprBuilder(param, value) {
switch (param) { switch (param) {
case 'salesPersonFk':
case 'creditInsurance': case 'creditInsurance':
case 'countryFk': case 'countryFk':
return { [`c.${param}`]: value }; return { [`c.${param}`]: value };
@ -176,7 +158,7 @@ function exprBuilder(param, value) {
case 'workerFk': case 'workerFk':
return { [`co.${param}`]: value }; return { [`co.${param}`]: value };
case 'departmentFk': case 'departmentFk':
return { [`wd.${param}`]: value }; return { [`c.${param}`]: value };
case 'amount': case 'amount':
case 'clientFk': case 'clientFk':
return { [`d.${param}`]: value }; return { [`d.${param}`]: value };
@ -241,12 +223,6 @@ function exprBuilder(param, value) {
<template #column-observation="{ row }"> <template #column-observation="{ row }">
<VnInput type="textarea" v-model="row.observation" readonly dense rows="2" /> <VnInput type="textarea" v-model="row.observation" readonly dense rows="2" />
</template> </template>
<template #column-salesPersonFk="{ row }">
<span class="link" @click.stop>
{{ row.salesPersonName }}
<WorkerDescriptorProxy :id="row.salesPersonFk" />
</span>
</template>
<template #column-departmentFk="{ row }"> <template #column-departmentFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
{{ row.departmentName }} {{ row.departmentName }}
@ -265,8 +241,6 @@ function exprBuilder(param, value) {
es: es:
Add observation: Añadir observación Add observation: Añadir observación
Client: Cliente Client: Cliente
Is worker: Es trabajador
Salesperson: Comercial
Department: Departamento Department: Departamento
Country: País Country: País
P. Method: F. Pago P. Method: F. Pago
@ -281,5 +255,5 @@ es:
Credit I.: Crédito A. Credit I.: Crédito A.
Credit insurance: Crédito asegurado Credit insurance: Crédito asegurado
From: Desde From: Desde
Has recovery: Tiene recobro Has recovery: Recobro
</i18n> </i18n>

View File

@ -15,19 +15,12 @@ const props = defineProps({
}, },
}); });
const salespersons = ref();
const countries = ref(); const countries = ref();
const authors = ref(); const authors = ref();
const departments = ref(); const departments = ref();
</script> </script>
<template> <template>
<FetchData
:filter="{ where: { role: 'salesPerson' } }"
@on-fetch="(data) => (salespersons = data)"
auto-load
url="Workers/activeWithInheritedRole"
/>
<FetchData @on-fetch="(data) => (countries = data)" auto-load url="Countries" /> <FetchData @on-fetch="(data) => (countries = data)" auto-load url="Countries" />
<FetchData <FetchData
@on-fetch="(data) => (authors = data)" @on-fetch="(data) => (authors = data)"
@ -52,8 +45,7 @@ const departments = ref();
dense dense
option-label="name" option-label="name"
option-value="id" option-value="id"
outlined filled
rounded
emit-value emit-value
hide-selected hide-selected
map-options map-options
@ -62,29 +54,6 @@ const departments = ref();
@update:model-value="searchFn()" @update:model-value="searchFn()"
/> />
</QItem> </QItem>
<QItem class="q-mb-sm">
<QItemSection v-if="salespersons">
<VnSelect
:input-debounce="0"
:label="t('Salesperson')"
:options="salespersons"
dense
emit-value
hide-selected
map-options
option-label="name"
option-value="id"
outlined
rounded
use-input
v-model="params.salesPersonFk"
@update:model-value="searchFn()"
/>
</QItemSection>
<QItemSection v-else>
<QSkeleton class="full-width" type="QInput" />
</QItemSection>
</QItem>
<QItem class="q-mb-sm"> <QItem class="q-mb-sm">
<QItemSection v-if="departments"> <QItemSection v-if="departments">
<VnSelect <VnSelect
@ -97,8 +66,7 @@ const departments = ref();
map-options map-options
option-label="name" option-label="name"
option-value="id" option-value="id"
outlined filled
rounded
use-input use-input
v-model="params.departmentFk" v-model="params.departmentFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -121,8 +89,7 @@ const departments = ref();
map-options map-options
option-label="name" option-label="name"
option-value="id" option-value="id"
outlined filled
rounded
use-input use-input
v-model="params.countryFk" v-model="params.countryFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -138,7 +105,7 @@ const departments = ref();
<VnInput <VnInput
:label="t('P. Method')" :label="t('P. Method')"
clearable clearable
is-outlined filled
v-model="params.paymentMethod" v-model="params.paymentMethod"
/> />
</QItemSection> </QItemSection>
@ -149,7 +116,7 @@ const departments = ref();
<VnInput <VnInput
:label="t('Balance D.')" :label="t('Balance D.')"
clearable clearable
is-outlined filled
v-model="params.balance" v-model="params.balance"
/> />
</QItemSection> </QItemSection>
@ -167,8 +134,7 @@ const departments = ref();
map-options map-options
option-label="name" option-label="name"
option-value="id" option-value="id"
outlined filled
rounded
use-input use-input
v-model="params.workerFk" v-model="params.workerFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"
@ -184,7 +150,7 @@ const departments = ref();
<VnInputDate <VnInputDate
:label="t('L. O. Date')" :label="t('L. O. Date')"
clearable clearable
is-outlined filled
v-model="params.date" v-model="params.date"
/> />
</QItemSection> </QItemSection>
@ -195,7 +161,7 @@ const departments = ref();
<VnInput <VnInput
:label="t('Credit I.')" :label="t('Credit I.')"
clearable clearable
is-outlined filled
v-model="params.credit" v-model="params.credit"
/> />
</QItemSection> </QItemSection>
@ -205,7 +171,7 @@ const departments = ref();
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
:label="t('From')" :label="t('From')"
is-outlined filled
v-model="params.defaulterSinced" v-model="params.defaulterSinced"
/> />
</QItemSection> </QItemSection>
@ -219,7 +185,6 @@ const departments = ref();
en: en:
params: params:
clientFk: Client clientFk: Client
salesPersonFk: Salesperson
countryFk: Country countryFk: Country
paymentMethod: P. Method paymentMethod: P. Method
balance: Balance D. balance: Balance D.
@ -230,7 +195,6 @@ en:
es: es:
params: params:
clientFk: Cliente clientFk: Cliente
salesPersonFk: Comercial
countryFk: País countryFk: País
paymentMethod: F. Pago paymentMethod: F. Pago
balance: Saldo V. balance: Saldo V.
@ -239,7 +203,6 @@ es:
credit: Crédito A. credit: Crédito A.
defaulterSinced: Desde defaulterSinced: Desde
Client: Cliente Client: Cliente
Salesperson: Comercial
Departments: Departamentos Departments: Departamentos
Country: País Country: País
P. Method: F. Pago P. Method: F. Pago

View File

@ -69,17 +69,16 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('customer.extendedList.tableVisibleColumns.salesPersonFk'), name: 'departmentFk',
name: 'salesPersonFk', label: t('customer.summary.team'),
component: 'select', component: 'select',
attrs: { attrs: {
url: 'Workers/activeWithInheritedRole', url: 'Departments',
fields: ['id', 'name'],
where: { role: 'salesPerson' },
optionFilter: 'firstName',
useLike: false,
}, },
visible: false, columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
}, },
]); ]);
</script> </script>
@ -96,7 +95,7 @@ const columns = computed(() => [
</VnSubToolbar> </VnSubToolbar>
<VnTable <VnTable
:data-key="dataKey" :data-key="dataKey"
url="Clients" url="Clients/filter"
:table="{ :table="{
'row-key': 'id', 'row-key': 'id',
selection: 'multiple', selection: 'multiple',
@ -127,7 +126,6 @@ const columns = computed(() => [
es: es:
Identifier: Identificador Identifier: Identificador
Social name: Razón social Social name: Razón social
Salesperson: Comercial
Phone: Teléfono Phone: Teléfono
City: Población City: Población
Email: Email Email: Email

View File

@ -25,7 +25,7 @@ const props = defineProps({
<template #body="{ params }"> <template #body="{ params }">
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput :label="t('Order ID')" v-model="params.orderFk" is-outlined> <VnInput :label="t('Order ID')" v-model="params.orderFk" filled>
<template #prepend> <template #prepend>
<QIcon name="vn:basket" size="xs" /> <QIcon name="vn:basket" size="xs" />
</template> </template>
@ -34,11 +34,7 @@ const props = defineProps({
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput :label="t('Customer ID')" v-model="params.clientFk" filled>
:label="t('Customer ID')"
v-model="params.clientFk"
is-outlined
>
<template #prepend> <template #prepend>
<QIcon name="vn:client" size="xs" /> <QIcon name="vn:client" size="xs" />
</template> </template>
@ -47,19 +43,15 @@ const props = defineProps({
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputNumber <VnInputNumber :label="t('Amount')" v-model="params.amount" filled />
:label="t('Amount')"
v-model="params.amount"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.from" :label="t('From')" is-outlined /> <VnInputDate v-model="params.from" :label="t('From')" filled />
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.to" :label="t('To')" is-outlined /> <VnInputDate v-model="params.to" :label="t('To')" filled />
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>

View File

@ -20,7 +20,7 @@ customer:
name: Name name: Name
contact: Contact contact: Contact
mobile: Mobile mobile: Mobile
salesPerson: Sales person team: Team
contactChannel: Contact channel contactChannel: Contact channel
socialName: Social name socialName: Social name
fiscalId: Fiscal ID fiscalId: Fiscal ID
@ -78,7 +78,6 @@ customer:
id: Identifier id: Identifier
socialName: Social name socialName: Social name
fi: Tax number fi: Tax number
salesPersonFk: Salesperson
creditInsurance: Credit insurance creditInsurance: Credit insurance
phone: Phone phone: Phone
street: Street street: Street

View File

@ -20,7 +20,7 @@ customer:
name: Nombre name: Nombre
contact: Contacto contact: Contacto
mobile: Móvil mobile: Móvil
salesPerson: Comercial team: Equipo
contactChannel: Canal de contacto contactChannel: Canal de contacto
socialName: Razón social socialName: Razón social
fiscalId: NIF/CIF fiscalId: NIF/CIF
@ -78,7 +78,6 @@ customer:
id: Identificador id: Identificador
socialName: Razón social socialName: Razón social
fi: NIF / CIF fi: NIF / CIF
salesPersonFk: Comercial
creditInsurance: Crédito asegurado creditInsurance: Crédito asegurado
phone: Teléfono phone: Teléfono
street: Dirección fiscal street: Dirección fiscal

View File

@ -13,6 +13,9 @@ import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue'; import VnSelectTravelExtended from 'src/components/common/VnSelectTravelExtended.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
import VnDmsInput from 'src/components/common/VnDmsInput.vue';
const route = useRoute(); const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
@ -23,6 +26,7 @@ const user = state.getUser().fn();
const companiesOptions = ref([]); const companiesOptions = ref([]);
const currenciesOptions = ref([]); const currenciesOptions = ref([]);
const entryRef = ref({});
onMounted(() => { onMounted(() => {
checkEntryLock(route.params.id, user.id); checkEntryLock(route.params.id, user.id);
@ -47,13 +51,14 @@ onMounted(() => {
auto-load auto-load
/> />
<FormModel <FormModel
:url-update="`Entries/${route.params.id}`" ref="entryRef"
model="Entry" model="Entry"
auto-load :url-update="`Entries/${route.params.id}`"
:clear-store-on-unmount="false" :clear-store-on-unmount="false"
auto-load
> >
<template #form="{ data }"> <template #form="{ data }">
<VnRow> <VnRow class="q-py-sm">
<VnSelectTravelExtended <VnSelectTravelExtended
:data="data" :data="data"
v-model="data.travelFk" v-model="data.travelFk"
@ -65,15 +70,22 @@ onMounted(() => {
:required="true" :required="true"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnInput v-model="data.reference" :label="t('globals.reference')" /> <VnInput
<VnInputNumber v-model="data.reference"
v-model="data.invoiceAmount" :label="t('entry.list.tableVisibleColumns.reference')"
:label="t('entry.summary.invoiceAmount')" />
:positive="false" <VnSelect
v-model="data.typeFk"
url="entryTypes"
:fields="['code', 'description']"
option-value="code"
optionLabel="description"
sortBy="description"
:label="t('entry.list.tableVisibleColumns.entryTypeDescription')"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnInput <VnInput
v-model="data.invoiceNumber" v-model="data.invoiceNumber"
:label="t('entry.summary.invoiceNumber')" :label="t('entry.summary.invoiceNumber')"
@ -84,12 +96,13 @@ onMounted(() => {
:options="companiesOptions" :options="companiesOptions"
option-value="id" option-value="id"
option-label="code" option-label="code"
sort-by="code"
map-options map-options
hide-selected hide-selected
:required="true" :required="true"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnInputNumber <VnInputNumber
:label="t('entry.summary.commission')" :label="t('entry.summary.commission')"
v-model="data.commission" v-model="data.commission"
@ -102,15 +115,15 @@ onMounted(() => {
:options="currenciesOptions" :options="currenciesOptions"
option-value="id" option-value="id"
option-label="code" option-label="code"
sort-by="code"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<VnInputNumber <VnInputNumber
v-model="data.initialTemperature" v-model="data.initialTemperature"
name="initialTemperature" name="initialTemperature"
:label="t('entry.basicData.initialTemperature')" :label="t('entry.basicData.initialTemperature')"
:step="0.5" :step="0.5"
:decimal-places="2"
:positive="false" :positive="false"
/> />
<VnInputNumber <VnInputNumber
@ -118,12 +131,21 @@ onMounted(() => {
name="finalTemperature" name="finalTemperature"
:label="t('entry.basicData.finalTemperature')" :label="t('entry.basicData.finalTemperature')"
:step="0.5" :step="0.5"
:decimal-places="2"
:positive="false" :positive="false"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<QInput <VnInputNumber
v-model="data.invoiceAmount"
:label="t('entry.list.tableVisibleColumns.invoiceAmount')"
:positive="false"
@update:model-value="data.buyerFk = user.id"
/>
<VnSelectWorker v-model="data.buyerFk" hide-selected />
<VnDmsInput :data="data" :formRef="entryRef" :disable="false" />
</VnRow>
<VnRow class="q-py-sm">
<VnInputNumber
:label="t('entry.basicData.observation')" :label="t('entry.basicData.observation')"
type="textarea" type="textarea"
v-model="data.observation" v-model="data.observation"
@ -132,14 +154,20 @@ onMounted(() => {
fill-input fill-input
/> />
</VnRow> </VnRow>
<VnRow> <VnRow class="q-py-sm">
<QCheckbox v-model="data.isOrdered" :label="t('entry.summary.ordered')" /> <VnCheckbox
<QCheckbox v-model="data.isConfirmed" :label="t('globals.confirmed')" /> v-model="data.isOrdered"
<QCheckbox :label="t('entry.list.tableVisibleColumns.isOrdered')"
v-model="data.isExcludedFromAvailable"
:label="t('entry.summary.excludedFromAvailable')"
/> />
<QCheckbox <VnCheckbox
v-model="data.isConfirmed"
:label="t('entry.list.tableVisibleColumns.isConfirmed')"
/>
<VnCheckbox
v-model="data.isExcludedFromAvailable"
:label="t('entry.list.tableVisibleColumns.isExcludedFromAvailable')"
/>
<VnCheckbox
:disable="!isAdministrative()" :disable="!isAdministrative()"
v-model="data.isBooked" v-model="data.isBooked"
:label="t('entry.basicData.booked')" :label="t('entry.basicData.booked')"

View File

@ -2,7 +2,7 @@
import { useStateStore } from 'stores/useStateStore'; import { useStateStore } from 'stores/useStateStore';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { onMounted, ref } from 'vue'; import { onMounted, ref, computed } from 'vue';
import { useState } from 'src/composables/useState'; import { useState } from 'src/composables/useState';
@ -16,6 +16,9 @@ import ItemDescriptor from 'src/pages/Item/Card/ItemDescriptor.vue';
import axios from 'axios'; import axios from 'axios';
import VnSelectEnum from 'src/components/common/VnSelectEnum.vue'; import VnSelectEnum from 'src/components/common/VnSelectEnum.vue';
import { checkEntryLock } from 'src/composables/checkEntryLock'; import { checkEntryLock } from 'src/composables/checkEntryLock';
import VnRow from 'src/components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
const $props = defineProps({ const $props = defineProps({
id: { id: {
@ -42,6 +45,8 @@ const entityId = ref($props.id ?? route.params.id);
const entryBuysRef = ref(); const entryBuysRef = ref();
const footerFetchDataRef = ref(); const footerFetchDataRef = ref();
const footer = ref({}); const footer = ref({});
const dialogRef = ref(false);
const newEntryRef = ref(null);
const columns = [ const columns = [
{ {
align: 'center', align: 'center',
@ -57,31 +62,6 @@ const columns = [
createOrder: 12, createOrder: 12,
width: '25px', width: '25px',
}, },
{
label: t('Buyer'),
name: 'workerFk',
component: 'select',
attrs: {
url: 'TicketRequests/getItemTypeWorker',
fields: ['id', 'nickname'],
optionLabel: 'nickname',
sortBy: 'nickname ASC',
optionValue: 'id',
},
visible: false,
},
{
label: t('Family'),
name: 'itemTypeFk',
component: 'select',
attrs: {
url: 'itemTypes',
fields: ['id', 'name'],
optionLabel: 'name',
optionValue: 'id',
},
visible: false,
},
{ {
name: 'id', name: 'id',
isId: true, isId: true,
@ -111,16 +91,10 @@ const columns = [
}, },
}, },
{ {
align: 'center', align: 'left',
label: t('Article'), label: t('Article'),
component: 'input',
name: 'name', name: 'name',
component: 'select',
attrs: {
url: 'Items',
fields: ['id', 'name'],
optionLabel: 'name',
optionValue: 'id',
},
width: '85px', width: '85px',
isEditable: false, isEditable: false,
}, },
@ -212,7 +186,6 @@ const columns = [
}, },
}, },
{ {
align: 'center',
labelAbbreviation: 'GM', labelAbbreviation: 'GM',
label: t('Grouping selector'), label: t('Grouping selector'),
toolTip: t('Grouping selector'), toolTip: t('Grouping selector'),
@ -240,7 +213,6 @@ const columns = [
}, },
}, },
{ {
align: 'center',
labelAbbreviation: 'G', labelAbbreviation: 'G',
label: 'Grouping', label: 'Grouping',
toolTip: 'Grouping', toolTip: 'Grouping',
@ -281,6 +253,7 @@ const columns = [
component: 'number', component: 'number',
attrs: { attrs: {
positive: false, positive: false,
decimalPlaces: 3,
}, },
cellEvent: { cellEvent: {
'update:modelValue': async (value, oldValue, row) => { 'update:modelValue': async (value, oldValue, row) => {
@ -294,7 +267,7 @@ const columns = [
align: 'center', align: 'center',
label: t('Amount'), label: t('Amount'),
name: 'amount', name: 'amount',
width: '45px', width: '75px',
component: 'number', component: 'number',
attrs: { attrs: {
positive: false, positive: false,
@ -310,7 +283,9 @@ const columns = [
toolTip: t('Package'), toolTip: t('Package'),
name: 'price2', name: 'price2',
component: 'number', component: 'number',
createDisable: true, createAttrs: {
disable: true,
},
width: '35px', width: '35px',
create: true, create: true,
format: (row) => parseFloat(row['price2']).toFixed(2), format: (row) => parseFloat(row['price2']).toFixed(2),
@ -320,7 +295,9 @@ const columns = [
label: t('Box'), label: t('Box'),
name: 'price3', name: 'price3',
component: 'number', component: 'number',
createDisable: true, createAttrs: {
disable: true,
},
cellEvent: { cellEvent: {
'update:modelValue': async (value, oldValue, row) => { 'update:modelValue': async (value, oldValue, row) => {
row['price2'] = row['price2'] * (value / oldValue); row['price2'] = row['price2'] * (value / oldValue);
@ -340,13 +317,6 @@ const columns = [
toggleIndeterminate: false, toggleIndeterminate: false,
}, },
component: 'checkbox', component: 'checkbox',
cellEvent: {
'update:modelValue': async (value, oldValue, row) => {
await axios.patch(`Items/${row['itemFk']}`, {
hasMinPrice: value,
});
},
},
width: '25px', width: '25px',
}, },
{ {
@ -356,13 +326,6 @@ const columns = [
toolTip: t('Minimum price'), toolTip: t('Minimum price'),
name: 'minPrice', name: 'minPrice',
component: 'number', component: 'number',
cellEvent: {
'update:modelValue': async (value, oldValue, row) => {
await axios.patch(`Items/${row['itemFk']}`, {
minPrice: value,
});
},
},
width: '35px', width: '35px',
style: (row) => { style: (row) => {
if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' }; if (!row?.hasMinPrice) return { color: 'var(--vn-label-color)' };
@ -425,6 +388,23 @@ const columns = [
}, },
}, },
]; ];
const buyerFk = ref(null);
const itemTypeFk = ref(null);
const inkFk = ref(null);
const tag1 = ref(null);
const tag2 = ref(null);
const tag1Filter = ref(null);
const tag2Filter = ref(null);
const filter = computed(() => {
const where = {};
where.workerFk = buyerFk.value;
where.itemTypeFk = itemTypeFk.value;
where.inkFk = inkFk.value;
where.tag1 = tag1.value;
where.tag2 = tag2.value;
return { where };
});
function getQuantityStyle(row) { function getQuantityStyle(row) {
if (row?.quantity !== row?.stickers * row?.packing) if (row?.quantity !== row?.stickers * row?.packing)
@ -521,6 +501,23 @@ async function setBuyUltimate(itemFk, data) {
}); });
} }
async function transferBuys(rows, newEntry) {
if (!newEntry) return;
const promises = rows.map((row) => {
return axios.patch('Buys', { id: row.id, entryFk: newEntry });
});
await Promise.all(promises);
await axios.post(`Entries/${newEntry}/recalcEntryPrices`);
await axios.post(`Entries/${entityId.value}/recalcEntryPrices`);
entryBuysRef.value.reload();
newEntryRef.value = null;
dialogRef.value = false;
}
onMounted(() => { onMounted(() => {
stateStore.rightDrawer = false; stateStore.rightDrawer = false;
if ($props.editableMode) checkEntryLock(entityId.value, user.id); if ($props.editableMode) checkEntryLock(entityId.value, user.id);
@ -595,6 +592,47 @@ onMounted(() => {
</QItem> </QItem>
</QList> </QList>
</QBtnDropdown> </QBtnDropdown>
<QBtn
icon="move_group"
color="primary"
:title="t('Transfer buys')"
data-cy="transferBuys"
flat
@click="dialogRef = true"
:disable="!selectedRows.length"
/>
<QDialog v-model="dialogRef">
<QCard>
<QCardSection>
<span>{{ t('Transfer buys') }}</span>
</QCardSection>
<QCardSection>
<VnInputNumber
v-model="newEntryRef"
:label="t('Entry')"
type="number"
data-cy="entryDestinyInput"
/>
</QCardSection>
<QCardSection>
<QCardActions>
<QBtn
label="Cancel"
flat
color="primary"
@click="dialogRef = false"
/>
<QBtn
label="Transfer"
data-cy="transferBuysBtn"
flat
color="primary"
@click="transferBuys(selectedRows, newEntryRef)"
/>
</QCardActions>
</QCardSection>
</QCard>
</QDialog>
</QBtnGroup> </QBtnGroup>
</Teleport> </Teleport>
<FetchData <FetchData
@ -610,6 +648,7 @@ onMounted(() => {
:url="`Entries/${entityId}/getBuyList`" :url="`Entries/${entityId}/getBuyList`"
search-url="EntryBuys" search-url="EntryBuys"
save-url="Buys/crud" save-url="Buys/crud"
:filter="editableMode ? filter : {}"
:disable-option="{ card: true }" :disable-option="{ card: true }"
v-model:selected="selectedRows" v-model:selected="selectedRows"
@on-fetch="() => footerFetchDataRef.fetch()" @on-fetch="() => footerFetchDataRef.fetch()"
@ -643,7 +682,7 @@ onMounted(() => {
}, },
columnGridStyle: { columnGridStyle: {
'max-width': '50%', 'max-width': '50%',
'margin-right': '30px', 'margin-right': '5%',
flex: 1, flex: 1,
}, },
previousStyle: { previousStyle: {
@ -655,7 +694,7 @@ onMounted(() => {
:is-editable="editableMode" :is-editable="editableMode"
:without-header="!editableMode" :without-header="!editableMode"
:with-filters="editableMode" :with-filters="editableMode"
:right-search="editableMode" :right-search="false"
:row-click="false" :row-click="false"
:columns="columns" :columns="columns"
:beforeSaveFn="beforeSave" :beforeSaveFn="beforeSave"
@ -666,6 +705,47 @@ onMounted(() => {
data-cy="entry-buys" data-cy="entry-buys"
overlay overlay
> >
<template #top-left>
<VnRow>
<VnSelect
:label="t('Buyer')"
v-model="buyerFk"
url="TicketRequests/getItemTypeWorker"
:fields="['id', 'nickname']"
option-label="nickname"
sort-by="nickname ASC"
:use-like="false"
/>
<VnSelect
:label="t('Family')"
v-model="itemTypeFk"
url="ItemTypes"
:fields="['id', 'name']"
option-label="name"
sort-by="name ASC"
/>
<VnSelect
:label="t('Color')"
v-model="inkFk"
url="Inks"
:fields="['id', 'name']"
option-label="name"
sort-by="name ASC"
/>
<VnInput
v-model="tag1Filter"
:label="t('Tag')"
@keyup.enter="tag1 = tag1Filter"
@remove="tag1 = null"
/>
<VnInput
v-model="tag2Filter"
:label="t('Tag')"
@keyup.enter="tag2 = tag2Filter"
@remove="tag2 = null"
/>
</VnRow>
</template>
<template #column-hex="{ row }"> <template #column-hex="{ row }">
<VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" /> <VnColor :colors="row?.hexJson" style="height: 100%; min-width: 2000px" />
</template> </template>
@ -696,7 +776,7 @@ onMounted(() => {
</div> </div>
</template> </template>
<template #column-footer-weight> <template #column-footer-weight>
{{ footer?.weight }} <span class="q-pr-xs">{{ footer?.weight }}</span>
</template> </template>
<template #column-footer-quantity> <template #column-footer-quantity>
<span :style="getQuantityStyle(footer)" data-cy="footer-quantity"> <span :style="getQuantityStyle(footer)" data-cy="footer-quantity">
@ -704,9 +784,8 @@ onMounted(() => {
</span> </span>
</template> </template>
<template #column-footer-amount> <template #column-footer-amount>
<span :style="getAmountStyle(footer)" data-cy="footer-amount"> <span data-cy="footer-amount">{{ footer?.amount }} / </span>
{{ footer?.amount }} <span style="color: var(--q-positive)">{{ footer?.checkedAmount }}</span>
</span>
</template> </template>
<template #column-create-itemFk="{ data }"> <template #column-create-itemFk="{ data }">
<VnSelect <VnSelect
@ -767,6 +846,8 @@ onMounted(() => {
</template> </template>
<i18n> <i18n>
es: es:
Buyer: Comprador
Family: Familia
Article: Artículo Article: Artículo
Siz.: Med. Siz.: Med.
Size: Medida Size: Medida
@ -798,6 +879,8 @@ es:
Create buy: Crear compra Create buy: Crear compra
Invert quantity value: Invertir valor de cantidad Invert quantity value: Invertir valor de cantidad
Check buy amount: Marcar como correcta la cantidad de compra Check buy amount: Marcar como correcta la cantidad de compra
Transfer buys: Transferir compras
Entry: Entrada
</i18n> </i18n>
<style lang="scss" scoped> <style lang="scss" scoped>
.centered-container { .centered-container {

View File

@ -6,7 +6,7 @@ import { toDate } from 'src/filters';
import { getUrl } from 'src/composables/getUrl'; import { getUrl } from 'src/composables/getUrl';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import { usePrintService } from 'composables/usePrintService'; import { usePrintService } from 'composables/usePrintService';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue'; import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
import axios from 'axios'; import axios from 'axios';
@ -92,7 +92,7 @@ const getEntryRedirectionFilter = (entry) => {
}; };
function showEntryReport() { function showEntryReport() {
openReport(`Entries/${entityId.value}/entry-order-pdf`); openReport(`Entries/${entityId.value}/entry-order-pdf`, {}, true);
} }
function showNotification(type, message) { function showNotification(type, message) {
@ -145,7 +145,7 @@ async function deleteEntry() {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
:url="`Entries/${entityId}`" :url="`Entries/${entityId}`"
:filter="entryFilter" :filter="entryFilter"
title="supplier.nickname" title="supplier.nickname"
@ -264,7 +264,7 @@ async function deleteEntry() {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<i18n> <i18n>
es: es:

View File

@ -2,153 +2,82 @@
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnTable from 'src/components/VnTable/VnTable.vue';
import FetchData from 'components/FetchData.vue';
import CrudModel from 'components/CrudModel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { params } = useRoute(); const { params } = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const selectedRows = ref([]);
const entryObservationsRef = ref(null); const entryObservationsRef = ref(null);
const entryObservationsOptions = ref([]); const entityId = ref(params.id);
const selected = ref([]);
const sortEntryObservationOptions = (data) => {
entryObservationsOptions.value = [...data].sort((a, b) =>
a.description.localeCompare(b.description),
);
};
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'observationType', name: 'id',
label: t('entry.notes.observationType'), isId: true,
field: (row) => row.observationTypeFk, visible: false,
sortable: true, isEditable: false,
options: entryObservationsOptions.value, columnFilter: false,
required: true,
model: 'observationTypeFk',
optionValue: 'id',
optionLabel: 'description',
tabIndex: 1,
align: 'left',
}, },
{ {
name: 'observationTypeFk',
label: t('entry.notes.observationType'),
component: 'select',
columnFilter: { inWhere: true },
attrs: {
inWhere: true,
url: 'ObservationTypes',
fields: ['id', 'description'],
optionValue: 'id',
optionLabel: 'description',
sortBy: 'description',
},
width: '30px',
create: true,
},
{
align: 'left',
name: 'description', name: 'description',
label: t('globals.description'), label: t('globals.description'),
field: (row) => row.description, component: 'input',
tabIndex: 2, columnFilter: false,
align: 'left', attrs: { autogrow: true },
create: true,
}, },
]); ]);
const filter = computed(() => ({
fields: ['id', 'entryFk', 'observationTypeFk', 'description'],
include: ['observationType'],
where: { entryFk: entityId },
}));
</script> </script>
<template> <template>
<FetchData <VnTable
url="ObservationTypes" ref="entryObservationsRef"
@on-fetch="(data) => sortEntryObservationOptions(data)" data-key="EntryObservations"
:columns="columns"
url="EntryObservations"
:user-filter="filter"
order="id ASC"
:disable-option="{ card: true }"
:is-editable="true"
:right-search="true"
v-model:selected="selectedRows"
:create="{
urlCreate: 'EntryObservations',
title: t('Create note'),
onDataSaved: () => {
entryObservationsRef.reload();
},
formInitialData: { entryFk: entityId },
}"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
auto-load auto-load
/> />
<CrudModel
data-key="EntryAccount"
url="EntryObservations"
model="EntryAccount"
:filter="{
fields: ['id', 'entryFk', 'observationTypeFk', 'description'],
where: { entryFk: params.id },
}"
ref="entryObservationsRef"
:data-required="{ entryFk: params.id }"
v-model:selected="selected"
auto-load
>
<template #body="{ rows, validate }">
<QTable
v-model:selected="selected"
:columns="columns"
:rows="rows"
:pagination="{ rowsPerPage: 0 }"
row-key="$index"
selection="multiple"
hide-pagination
:grid="$q.screen.lt.md"
table-header-class="text-left"
>
<template #body-cell-observationType="{ row, col }">
<QTd>
<VnSelect
v-model="row[col.model]"
:options="col.options"
:option-value="col.optionValue"
:option-label="col.optionLabel"
:autofocus="col.tabIndex == 1"
input-debounce="0"
hide-selected
:required="true"
/>
</QTd>
</template>
<template #body-cell-description="{ row, col }">
<QTd>
<VnInput
:label="t('globals.description')"
v-model="row[col.name]"
:rules="validate('EntryObservation.description')"
/>
</QTd>
</template>
<template #item="props">
<div class="q-pa-xs col-xs-12 col-sm-6 grid-style-transition">
<QCard bordered flat>
<QCardSection>
<QCheckbox v-model="props.selected" dense />
</QCardSection>
<QSeparator />
<QList dense>
<QItem>
<QItemSection>
<VnSelect
v-model="props.row.observationTypeFk"
:options="entryObservationsOptions"
option-value="id"
option-label="description"
input-debounce="0"
hide-selected
:required="true"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
:label="t('globals.description')"
v-model="props.row.description"
:rules="
validate('EntryObservation.description')
"
/>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
</QTable>
</template>
</CrudModel>
<QPageSticky position="bottom-right" :offset="[25, 25]">
<QBtn
fab
color="primary"
icon="add"
v-shortcut="'+'"
@click="entryObservationsRef.insert()"
/>
</QPageSticky>
</template> </template>
<i18n> <i18n>
es: es:
Add note: Añadir nota Create note: Crear nota
Remove note: Quitar nota
</i18n> </i18n>

View File

@ -84,7 +84,10 @@ onMounted(async () => {
:label="t('globals.company')" :label="t('globals.company')"
:value="entry?.company?.code" :value="entry?.company?.code"
/> />
<VnLv :label="t('globals.reference')" :value="entry?.reference" /> <VnLv
:label="t('entry.list.tableVisibleColumns.reference')"
:value="entry?.reference"
/>
<VnLv <VnLv
:label="t('entry.summary.invoiceNumber')" :label="t('entry.summary.invoiceNumber')"
:value="entry?.invoiceNumber" :value="entry?.invoiceNumber"
@ -92,13 +95,13 @@ onMounted(async () => {
</div> </div>
<div class="card-content"> <div class="card-content">
<VnCheckbox <VnCheckbox
:label="t('entry.summary.ordered')" :label="t('entry.list.tableVisibleColumns.isOrdered')"
v-model="entry.isOrdered" v-model="entry.isOrdered"
:disable="true" :disable="true"
size="xs" size="xs"
/> />
<VnCheckbox <VnCheckbox
:label="t('globals.confirmed')" :label="t('entry.list.tableVisibleColumns.isConfirmed')"
v-model="entry.isConfirmed" v-model="entry.isConfirmed"
:disable="true" :disable="true"
size="xs" size="xs"
@ -110,7 +113,11 @@ onMounted(async () => {
size="xs" size="xs"
/> />
<VnCheckbox <VnCheckbox
:label="t('entry.summary.excludedFromAvailable')" :label="
t(
'entry.list.tableVisibleColumns.isExcludedFromAvailable',
)
"
v-model="entry.isExcludedFromAvailable" v-model="entry.isExcludedFromAvailable"
:disable="true" :disable="true"
size="xs" size="xs"
@ -155,6 +162,7 @@ onMounted(async () => {
/> />
</div> </div>
<div class="card-content"> <div class="card-content">
<VnLv :label="t('travel.awbFk')" :value="entry.travel.awbFk" />
<VnCheckbox <VnCheckbox
:label="t('entry.summary.travelDelivered')" :label="t('entry.summary.travelDelivered')"
v-model="entry.travel.isDelivered" v-model="entry.travel.isDelivered"

View File

@ -85,7 +85,7 @@ const entryFilterPanel = ref();
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('entry.list.tableVisibleColumns.isConfirmed')" label="LE"
v-model="params.isConfirmed" v-model="params.isConfirmed"
toggle-indeterminate toggle-indeterminate
> >
@ -101,13 +101,14 @@ const entryFilterPanel = ref();
:label="t('params.landed')" :label="t('params.landed')"
v-model="params.landed" v-model="params.landed"
@update:model-value="searchFn()" @update:model-value="searchFn()"
is-outlined filled
data-cy="landed"
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput v-model="params.id" label="Id" is-outlined /> <VnInput v-model="params.id" label="Id" filled />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -117,15 +118,7 @@ const entryFilterPanel = ref();
@update:model-value="searchFn()" @update:model-value="searchFn()"
hide-selected hide-selected
dense dense
outlined filled
rounded
/>
</QItemSection>
<QItemSection>
<VnInput
v-model="params.invoiceNumber"
:label="t('params.invoiceNumber')"
is-outlined
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -134,7 +127,7 @@ const entryFilterPanel = ref();
<VnInput <VnInput
v-model="params.reference" v-model="params.reference"
:label="t('entry.list.tableVisibleColumns.reference')" :label="t('entry.list.tableVisibleColumns.reference')"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -149,8 +142,7 @@ const entryFilterPanel = ref();
:fields="['id', 'name']" :fields="['id', 'name']"
hide-selected hide-selected
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -159,7 +151,7 @@ const entryFilterPanel = ref();
<VnInput <VnInput
v-model="params.evaNotes" v-model="params.evaNotes"
:label="t('params.evaNotes')" :label="t('params.evaNotes')"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -171,10 +163,10 @@ const entryFilterPanel = ref();
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="Warehouses" url="Warehouses"
:fields="['id', 'name']" :fields="['id', 'name']"
sort-by="name ASC"
hide-selected hide-selected
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -186,10 +178,10 @@ const entryFilterPanel = ref();
@update:model-value="searchFn()" @update:model-value="searchFn()"
url="Warehouses" url="Warehouses"
:fields="['id', 'name']" :fields="['id', 'name']"
sort-by="name ASC"
hide-selected hide-selected
dense dense
outlined filled
rounded
> >
<template #option="scope"> <template #option="scope">
<QItem v-bind="scope.itemProps"> <QItem v-bind="scope.itemProps">
@ -211,7 +203,7 @@ const entryFilterPanel = ref();
<VnInput <VnInput
v-model="params.invoiceNumber" v-model="params.invoiceNumber"
:label="t('params.invoiceNumber')" :label="t('params.invoiceNumber')"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -228,8 +220,7 @@ const entryFilterPanel = ref();
option-label="description" option-label="description"
hide-selected hide-selected
dense dense
outlined filled
rounded
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -238,7 +229,7 @@ const entryFilterPanel = ref();
<VnInput <VnInput
v-model="params.evaNotes" v-model="params.evaNotes"
:label="t('params.evaNotes')" :label="t('params.evaNotes')"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -267,7 +258,7 @@ en:
hasToShowDeletedEntries: Show deleted entries hasToShowDeletedEntries: Show deleted entries
es: es:
params: params:
isExcludedFromAvailable: Inventario isExcludedFromAvailable: Excluida
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Confirmado isConfirmed: Confirmado
isReceived: Recibida isReceived: Recibida

View File

@ -1,264 +0,0 @@
<script setup>
import { onMounted, onUnmounted, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useStateStore } from 'stores/useStateStore';
import { toDate } from 'src/filters';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import EntryLatestBuysFilter from './EntryLatestBuysFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue';
import VnImg from 'src/components/ui/VnImg.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const tableRef = ref();
const columns = [
{
align: 'center',
label: t('entry.latestBuys.tableVisibleColumns.image'),
name: 'itemFk',
columnField: {
component: VnImg,
attrs: ({ row }) => {
return {
id: row.id,
size: '50x50',
};
},
},
columnFilter: false,
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.itemFk'),
name: 'itemFk',
isTitle: true,
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.summary.packing'),
name: 'packing',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.summary.grouping'),
name: 'grouping',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('globals.quantity'),
name: 'quantity',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('globals.description'),
name: 'description',
},
{
align: 'left',
label: t('globals.size'),
name: 'size',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('globals.tags'),
name: 'tags',
},
{
align: 'left',
label: t('globals.type'),
name: 'type',
},
{
align: 'left',
label: t('globals.intrastat'),
name: 'intrastat',
},
{
align: 'left',
label: t('globals.origin'),
name: 'origin',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.weightByPiece'),
name: 'weightByPiece',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.isActive'),
name: 'isActive',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.family'),
name: 'family',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.entryFk'),
name: 'entryFk',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.summary.buyingValue'),
name: 'buyingValue',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.freightValue'),
name: 'freightValue',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.comissionValue'),
name: 'comissionValue',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.packageValue'),
name: 'packageValue',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.isIgnored'),
name: 'isIgnored',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.price2'),
name: 'price2',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.price3'),
name: 'price3',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.minPrice'),
name: 'minPrice',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.ektFk'),
name: 'ektFk',
},
{
align: 'left',
label: t('globals.weight'),
name: 'weight',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.buys.packagingFk'),
name: 'packagingFk',
columnFilter: {
component: 'number',
inWhere: true,
},
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.packingOut'),
name: 'packingOut',
},
{
align: 'left',
label: t('entry.latestBuys.tableVisibleColumns.landing'),
name: 'landing',
component: 'date',
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(toDate(row.landing)),
},
];
onMounted(async () => {
stateStore.rightDrawer = true;
});
onUnmounted(() => (stateStore.rightDrawer = false));
</script>
<template>
<RightMenu>
<template #right-panel>
<EntryLatestBuysFilter data-key="LatestBuys" />
</template>
</RightMenu>
<VnSubToolbar />
<VnTable
ref="tableRef"
data-key="LatestBuys"
url="Buys/latestBuysFilter"
order="id DESC"
:columns="columns"
redirect="entry"
:row-click="({ entryFk }) => tableRef.redirect(entryFk)"
auto-load
:right-search="false"
/>
</template>

View File

@ -1,161 +0,0 @@
<script setup>
import { ref } from 'vue';
import { useI18n } from 'vue-i18n';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnInput from 'components/common/VnInput.vue';
import VnSelect from 'components/common/VnSelect.vue';
import ItemsFilterPanel from 'src/components/ItemsFilterPanel.vue';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
const { t } = useI18n();
defineProps({
dataKey: {
type: String,
required: true,
},
});
const tagValues = ref([]);
</script>
<template>
<ItemsFilterPanel :data-key="dataKey" :custom-tags="['tags']">
<template #body="{ params, searchFn }">
<QItem class="q-my-md">
<QItemSection>
<VnSelect
:label="t('components.itemsFilterPanel.salesPersonFk')"
v-model="params.salesPersonFk"
url="TicketRequests/getItemTypeWorker"
option-label="nickname"
:fields="['id', 'nickname']"
sort-by="nickname ASC"
dense
outlined
rounded
use-input
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnSelectSupplier
v-model="params.supplierFk"
url="Suppliers"
:fields="['id', 'name', 'nickname']"
sort-by="name ASC"
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('components.itemsFilterPanel.started')"
v-model="params.from"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem class="q-my-md">
<QItemSection>
<VnInputDate
:label="t('components.itemsFilterPanel.ended')"
v-model="params.to"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('components.itemsFilterPanel.active')"
v-model="params.active"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
<QItemSection>
<QCheckbox
:label="t('globals.visible')"
v-model="params.visible"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<QCheckbox
:label="t('components.itemsFilterPanel.floramondo')"
v-model="params.floramondo"
toggle-indeterminate
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
<QItem
v-for="(value, index) in tagValues"
:key="value"
class="q-mt-md filter-value"
>
<QItemSection class="col">
<VnSelect
:label="t('params.tag')"
v-model="value.selectedTag"
:options="tagOptions"
option-label="name"
dense
outlined
rounded
:emit-value="false"
use-input
:is-clearable="false"
@update:model-value="getSelectedTagValues(value)"
/>
</QItemSection>
<QItemSection class="col">
<VnSelect
v-if="!value?.selectedTag?.isFree && value.valueOptions"
:label="t('params.value')"
v-model="value.value"
:options="value.valueOptions || []"
option-value="value"
option-label="value"
dense
outlined
rounded
emit-value
use-input
:disable="!value"
:is-clearable="false"
class="filter-input"
@update:model-value="applyTags(params, searchFn)"
/>
<VnInput
v-else
v-model="value.value"
:label="t('params.value')"
:disable="!value"
is-outlined
class="filter-input"
:is-clearable="false"
@keyup.enter="applyTags(params, searchFn)"
/>
</QItemSection>
<QIcon
name="delete"
class="filter-icon"
@click="removeTag(index, params, searchFn)"
/>
</QItem>
</template>
</ItemsFilterPanel>
</template>

View File

@ -107,9 +107,8 @@ const columns = computed(() => [
attrs: { attrs: {
url: 'suppliers', url: 'suppliers',
fields: ['id', 'name'], fields: ['id', 'name'],
where: { order: 'name DESC' }, sortBy: 'name ASC',
}, },
format: (row, dashIfEmpty) => dashIfEmpty(row.supplierName),
width: '110px', width: '110px',
}, },
{ {
@ -145,6 +144,7 @@ const columns = computed(() => [
attrs: { attrs: {
url: 'agencyModes', url: 'agencyModes',
fields: ['id', 'name'], fields: ['id', 'name'],
sortBy: 'name ASC',
}, },
columnField: { columnField: {
component: null, component: null,
@ -158,7 +158,6 @@ const columns = computed(() => [
component: 'input', component: 'input',
}, },
{ {
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseOutFk'), label: t('entry.list.tableVisibleColumns.warehouseOutFk'),
name: 'warehouseOutFk', name: 'warehouseOutFk',
cardVisible: true, cardVisible: true,
@ -166,6 +165,7 @@ const columns = computed(() => [
attrs: { attrs: {
url: 'warehouses', url: 'warehouses',
fields: ['id', 'name'], fields: ['id', 'name'],
sortBy: 'name ASC',
}, },
columnField: { columnField: {
component: null, component: null,
@ -174,7 +174,6 @@ const columns = computed(() => [
width: '65px', width: '65px',
}, },
{ {
align: 'left',
label: t('entry.list.tableVisibleColumns.warehouseInFk'), label: t('entry.list.tableVisibleColumns.warehouseInFk'),
name: 'warehouseInFk', name: 'warehouseInFk',
cardVisible: true, cardVisible: true,
@ -182,6 +181,7 @@ const columns = computed(() => [
attrs: { attrs: {
url: 'warehouses', url: 'warehouses',
fields: ['id', 'name'], fields: ['id', 'name'],
sortBy: 'name ASC',
}, },
columnField: { columnField: {
component: null, component: null,
@ -190,7 +190,6 @@ const columns = computed(() => [
width: '65px', width: '65px',
}, },
{ {
align: 'left',
labelAbbreviation: t('Type'), labelAbbreviation: t('Type'),
label: t('entry.list.tableVisibleColumns.entryTypeDescription'), label: t('entry.list.tableVisibleColumns.entryTypeDescription'),
toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'), toolTip: t('entry.list.tableVisibleColumns.entryTypeDescription'),
@ -201,6 +200,7 @@ const columns = computed(() => [
fields: ['code', 'description'], fields: ['code', 'description'],
optionValue: 'code', optionValue: 'code',
optionLabel: 'description', optionLabel: 'description',
sortBy: 'description',
}, },
width: '65px', width: '65px',
format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription), format: (row, dashIfEmpty) => dashIfEmpty(row.entryTypeDescription),

View File

@ -1,24 +1,23 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useState } from 'src/composables/useState'; import { useQuasar, date } from 'quasar';
import { useQuasar } from 'quasar';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue'; import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import FetchData from 'components/FetchData.vue'; import FetchData from 'components/FetchData.vue';
import FormModelPopup from 'components/FormModelPopup.vue'; import FormModelPopup from 'components/FormModelPopup.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnRow from 'components/ui/VnRow.vue'; import VnRow from 'components/ui/VnRow.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
import EntryStockBoughtFilter from './EntryStockBoughtFilter.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue'; import WorkerDescriptorProxy from 'src/pages/Worker/Card/WorkerDescriptorProxy.vue';
import EntryStockBoughtDetail from 'src/pages/Entry/EntryStockBoughtDetail.vue'; import EntryStockBoughtDetail from 'src/pages/Entry/EntryStockBoughtDetail.vue';
import TravelDescriptorProxy from '../Travel/Card/TravelDescriptorProxy.vue';
import { useFilterParams } from 'src/composables/useFilterParams';
import axios from 'axios';
const { t } = useI18n(); const { t } = useI18n();
const quasar = useQuasar(); const quasar = useQuasar();
const state = useState(); const filterDate = ref(useFilterParams('StockBoughts').params);
const user = state.getUser();
const footer = ref({ bought: 0, reserve: 0 }); const footer = ref({ bought: 0, reserve: 0 });
const columns = computed(() => [ const columns = computed(() => [
{ {
@ -46,7 +45,7 @@ const columns = computed(() => [
optionValue: 'id', optionValue: 'id',
}, },
columnFilter: false, columnFilter: false,
width: '50px', width: '60%',
}, },
{ {
align: 'center', align: 'center',
@ -56,20 +55,20 @@ const columns = computed(() => [
create: true, create: true,
component: 'number', component: 'number',
summation: true, summation: true,
width: '50px',
format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)), format: ({ reserve }, dashIfEmpty) => dashIfEmpty(round(reserve)),
width: '20%',
}, },
{ {
align: 'center', align: 'right',
label: t('entryStockBought.bought'), label: t('entryStockBought.bought'),
name: 'bought', name: 'bought',
summation: true, summation: true,
cardVisible: true, cardVisible: true,
style: ({ reserve, bought }) => boughtStyle(bought, reserve), style: ({ reserve, bought }) => boughtStyle(bought, reserve),
columnFilter: false, columnFilter: false,
width: '20%',
}, },
{ {
align: 'left',
label: t('entryStockBought.date'), label: t('entryStockBought.date'),
name: 'dated', name: 'dated',
component: 'date', component: 'date',
@ -77,7 +76,7 @@ const columns = computed(() => [
create: true, create: true,
}, },
{ {
align: 'left', align: 'center',
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
@ -90,7 +89,7 @@ const columns = computed(() => [
component: EntryStockBoughtDetail, component: EntryStockBoughtDetail,
componentProps: { componentProps: {
workerFk: row.workerFk, workerFk: row.workerFk,
dated: userParams.value.dated, dated: filterDate.value.dated,
}, },
}); });
}, },
@ -98,39 +97,30 @@ const columns = computed(() => [
], ],
}, },
]); ]);
const fetchDataRef = ref(); const fetchDataRef = ref();
const travelDialogRef = ref(false); const travelDialogRef = ref(false);
const tableRef = ref(); const tableRef = ref();
const travel = ref(null); const travel = ref(null);
const userParams = ref({ const filter = computed(() => ({
dated: Date.vnNew().toJSON(), fields: ['id', 'm3', 'ref', 'warehouseInFk'],
});
const filter = ref({
fields: ['id', 'm3', 'warehouseInFk'],
include: [ include: [
{ {
relation: 'warehouseIn', relation: 'warehouseIn',
scope: { scope: {
fields: ['code'], fields: ['code', 'name'],
}, },
}, },
], ],
where: { where: {
shipped: (userParams.value.dated shipped: date.adjustDate(filterDate.value.dated, {
? new Date(userParams.value.dated) hour: 0,
: Date.vnNew() minute: 0,
).setHours(0, 0, 0, 0), second: 0,
milliseconds: 0,
}),
m3: { neq: null }, m3: { neq: null },
}, },
}); }));
const setUserParams = async ({ dated }) => {
const shipped = (dated ? new Date(dated) : Date.vnNew()).setHours(0, 0, 0, 0);
filter.value.where.shipped = shipped;
fetchDataRef.value?.fetch();
};
function openDialog() { function openDialog() {
travelDialogRef.value = true; travelDialogRef.value = true;
@ -151,6 +141,31 @@ function round(value) {
function boughtStyle(bought, reserve) { function boughtStyle(bought, reserve) {
return reserve < bought ? { color: 'var(--q-negative)' } : ''; return reserve < bought ? { color: 'var(--q-negative)' } : '';
} }
async function beforeSave(data, getChanges) {
const changes = data.creates;
if (!changes) return data;
const patchPromises = [];
for (const change of changes) {
if (change?.isReal === false && change?.reserve > 0) {
const postData = {
workerFk: change.workerFk,
reserve: change.reserve,
dated: filterDate.value.dated,
};
const promise = axios.post('StockBoughts', postData).catch((error) => {
console.error('Error processing change: ', change, error);
});
patchPromises.push(promise);
}
}
await Promise.all(patchPromises);
data.creates = [];
return data;
}
</script> </script>
<template> <template>
<VnSubToolbar> <VnSubToolbar>
@ -158,18 +173,17 @@ function boughtStyle(bought, reserve) {
<FetchData <FetchData
ref="fetchDataRef" ref="fetchDataRef"
url="Travels" url="Travels"
auto-load
:filter="filter" :filter="filter"
@on-fetch=" @on-fetch="
(data) => { (data) => {
travel = data.find( travel = data.find(
(data) => data.warehouseIn?.code.toLowerCase() === 'vnh', (data) => data.warehouseIn?.code?.toLowerCase() === 'vnh',
); );
} }
" "
/> />
<VnRow class="travel"> <VnRow class="travel">
<div v-if="travel"> <div v-show="travel">
<span style="color: var(--vn-label-color)"> <span style="color: var(--vn-label-color)">
{{ t('entryStockBought.purchaseSpaces') }}: {{ t('entryStockBought.purchaseSpaces') }}:
</span> </span>
@ -180,7 +194,7 @@ function boughtStyle(bought, reserve) {
v-if="travel?.m3" v-if="travel?.m3"
style="max-width: 20%" style="max-width: 20%"
flat flat
icon="edit" icon="search"
@click="openDialog()" @click="openDialog()"
:title="t('entryStockBought.editTravel')" :title="t('entryStockBought.editTravel')"
color="primary" color="primary"
@ -190,62 +204,47 @@ function boughtStyle(bought, reserve) {
</VnRow> </VnRow>
</template> </template>
</VnSubToolbar> </VnSubToolbar>
<QDialog v-model="travelDialogRef" :maximized="true" :class="['vn-row', 'wrap']"> <QDialog v-model="travelDialogRef" :class="['vn-row', 'wrap']">
<FormModelPopup <FormModelPopup
:url-update="`Travels/${travel?.id}`" :url-update="`Travels/${travel?.id}`"
model="travel" model="travel"
:title="t('Travel m3')" :title="t('Travel m3')"
:form-initial-data="{ id: travel?.id, m3: travel?.m3 }" :form-initial-data="travel"
@on-data-saved="fetchDataRef.fetch()" @on-data-saved="fetchDataRef.fetch()"
> >
<template #form-inputs="{ data }"> <template #form-inputs="{ data }">
<VnInput <span class="link">
v-model="data.id" {{ data.ref }}
:label="t('id')" <TravelDescriptorProxy :id="data.id" />
type="number" </span>
disable
readonly
/>
<VnInput v-model="data.m3" :label="t('m3')" type="number" /> <VnInput v-model="data.m3" :label="t('m3')" type="number" />
</template> </template>
</FormModelPopup> </FormModelPopup>
</QDialog> </QDialog>
<RightMenu>
<template #right-panel>
<EntryStockBoughtFilter
data-key="StockBoughts"
@set-user-params="setUserParams"
/>
</template>
</RightMenu>
<div class="table-container"> <div class="table-container">
<div class="column items-center"> <div class="column items-center">
<VnTable <VnTable
ref="tableRef" ref="tableRef"
data-key="StockBoughts" data-key="StockBoughts"
url="StockBoughts/getStockBought" url="StockBoughts/getStockBought"
:beforeSaveFn="beforeSave"
save-url="StockBoughts/crud" save-url="StockBoughts/crud"
search-url="StockBoughts" search-url="StockBoughts"
order="reserve DESC" order="bought DESC"
:right-search="false"
:is-editable="true" :is-editable="true"
@on-fetch="(data) => setFooter(data)" @on-fetch="
:create="{ async (data) => {
urlCreate: 'StockBoughts', setFooter(data);
title: t('entryStockBought.reserveSomeSpace'), await fetchDataRef.fetch();
onDataSaved: () => tableRef.reload(), }
formInitialData: { "
workerFk: user.id,
dated: Date.vnNow(),
},
}"
:columns="columns" :columns="columns"
:user-params="userParams"
:footer="true" :footer="true"
table-height="80vh" table-height="80vh"
auto-load
:column-search="false" :column-search="false"
:without-header="true" :without-header="true"
:user-params="{ dated: Date.vnNew() }"
auto-load
> >
<template #column-workerFk="{ row }"> <template #column-workerFk="{ row }">
<span class="link" @click.stop> <span class="link" @click.stop>
@ -254,12 +253,15 @@ function boughtStyle(bought, reserve) {
</span> </span>
</template> </template>
<template #column-footer-reserve> <template #column-footer-reserve>
<span> <span class="q-pr-xs">
{{ round(footer.reserve) }} {{ round(footer.reserve) }}
</span> </span>
</template> </template>
<template #column-footer-bought> <template #column-footer-bought>
<span :style="boughtStyle(footer?.bought, footer?.reserve)"> <span
:style="boughtStyle(footer?.bought, footer?.reserve)"
class="q-pr-xs"
>
{{ round(footer.bought) }} {{ round(footer.bought) }}
</span> </span>
</template> </template>
@ -277,10 +279,7 @@ function boughtStyle(bought, reserve) {
} }
.column { .column {
min-width: 35%; min-width: 35%;
margin-top: 5%; margin-top: 1%;
display: flex;
flex-direction: column;
align-items: center;
} }
.text-negative { .text-negative {
color: $negative !important; color: $negative !important;

View File

@ -1,70 +0,0 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { onMounted } from 'vue';
import { useStateStore } from 'stores/useStateStore';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
const { t } = useI18n();
const props = defineProps({
dataKey: {
type: String,
required: true,
},
});
const stateStore = useStateStore();
const emit = defineEmits(['set-user-params']);
const setUserParams = (params) => {
emit('set-user-params', params);
};
onMounted(async () => {
stateStore.rightDrawer = true;
});
</script>
<template>
<VnFilterPanel
:data-key="props.dataKey"
:search-button="true"
search-url="StockBoughts"
@set-user-params="setUserParams"
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params, searchFn }">
<QItem class="q-my-sm">
<QItemSection>
<VnInputDate
id="date"
v-model="params.dated"
@update:model-value="
(value) => {
params.dated = value;
setUserParams(params);
searchFn();
}
"
:label="t('Date')"
is-outlined
/>
</QItemSection>
</QItem>
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
dated: Date
workerFk: Worker
es:
Date: Fecha
params:
dated: Fecha
workerFk: Trabajador
</i18n>

View File

@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue'; import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { toDate } from 'src/filters/index'; import { toDate } from 'src/filters/index';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
import EntryBuysTableDialog from './EntryBuysTableDialog.vue'; import EntrySupplierlDetail from './EntrySupplierlDetail.vue';
import VnTable from 'components/VnTable/VnTable.vue'; import VnTable from 'components/VnTable/VnTable.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -18,18 +18,28 @@ const columns = computed(() => [
{ {
align: 'left', align: 'left',
name: 'id', name: 'id',
label: t('myEntries.id'), label: t('entrySupplier.id'),
columnFilter: false, columnFilter: false,
isId: true,
chip: {
condition: () => true,
},
},
{
align: 'left',
name: 'supplierName',
label: t('entrySupplier.supplierName'),
cardVisible: true,
isTitle: true, isTitle: true,
}, },
{ {
visible: false, visible: false,
align: 'right', align: 'right',
label: t('myEntries.shipped'), label: t('entrySupplier.shipped'),
name: 'shipped', name: 'shipped',
columnFilter: { columnFilter: {
name: 'fromShipped', name: 'fromShipped',
label: t('myEntries.fromShipped'), label: t('entrySupplier.fromShipped'),
component: 'date', component: 'date',
}, },
format: ({ shipped }) => toDate(shipped), format: ({ shipped }) => toDate(shipped),
@ -37,26 +47,26 @@ const columns = computed(() => [
{ {
visible: false, visible: false,
align: 'left', align: 'left',
label: t('myEntries.shipped'), label: t('entrySupplier.shipped'),
name: 'shipped', name: 'shipped',
columnFilter: { columnFilter: {
name: 'toShipped', name: 'toShipped',
label: t('myEntries.toShipped'), label: t('entrySupplier.toShipped'),
component: 'date', component: 'date',
}, },
format: ({ shipped }) => toDate(shipped), format: ({ shipped }) => toDate(shipped),
cardVisible: true, cardVisible: true,
}, },
{ {
align: 'right', align: 'left',
label: t('myEntries.shipped'), label: t('entrySupplier.shipped'),
name: 'shipped', name: 'shipped',
columnFilter: false, columnFilter: false,
format: ({ shipped }) => toDate(shipped), format: ({ shipped }) => toDate(shipped),
}, },
{ {
align: 'right', align: 'left',
label: t('myEntries.landed'), label: t('entrySupplier.landed'),
name: 'landed', name: 'landed',
columnFilter: false, columnFilter: false,
format: ({ landed }) => toDate(landed), format: ({ landed }) => toDate(landed),
@ -64,15 +74,13 @@ const columns = computed(() => [
{ {
align: 'right', align: 'right',
label: t('myEntries.wareHouseIn'), label: t('entrySupplier.wareHouseIn'),
name: 'warehouseInFk', name: 'warehouseInFk',
format: (row) => { format: ({ warehouseInName }) => warehouseInName,
row.warehouseInName;
},
cardVisible: true, cardVisible: true,
columnFilter: { columnFilter: {
name: 'warehouseInFk', name: 'warehouseInFk',
label: t('myEntries.warehouseInFk'), label: t('entrySupplier.warehouseInFk'),
component: 'select', component: 'select',
attrs: { attrs: {
url: 'warehouses', url: 'warehouses',
@ -86,13 +94,13 @@ const columns = computed(() => [
}, },
{ {
align: 'left', align: 'left',
label: t('myEntries.daysOnward'), label: t('entrySupplier.daysOnward'),
name: 'daysOnward', name: 'daysOnward',
visible: false, visible: false,
}, },
{ {
align: 'left', align: 'left',
label: t('myEntries.daysAgo'), label: t('entrySupplier.daysAgo'),
name: 'daysAgo', name: 'daysAgo',
visible: false, visible: false,
}, },
@ -101,8 +109,8 @@ const columns = computed(() => [
name: 'tableActions', name: 'tableActions',
actions: [ actions: [
{ {
title: t('myEntries.printLabels'), title: t('entrySupplier.printLabels'),
icon: 'move_item', icon: 'search',
isPrimary: true, isPrimary: true,
action: (row) => printBuys(row.id), action: (row) => printBuys(row.id),
}, },
@ -112,7 +120,7 @@ const columns = computed(() => [
const printBuys = (rowId) => { const printBuys = (rowId) => {
quasar.dialog({ quasar.dialog({
component: EntryBuysTableDialog, component: EntrySupplierlDetail,
componentProps: { componentProps: {
id: rowId, id: rowId,
}, },
@ -121,19 +129,18 @@ const printBuys = (rowId) => {
</script> </script>
<template> <template>
<VnSearchbar <VnSearchbar
data-key="myEntriesList" data-key="entrySupplierList"
url="Entries/filter" url="Entries/filter"
:label="t('myEntries.search')" :label="t('entrySupplier.search')"
:info="t('myEntries.searchInfo')" :info="t('entrySupplier.searchInfo')"
/> />
<VnTable <VnTable
data-key="myEntriesList" data-key="entrySupplierList"
url="Entries/filter" url="Entries/filter"
:columns="columns" :columns="columns"
:user-params="params" :user-params="params"
default-mode="card" default-mode="card"
order="shipped DESC" order="shipped DESC"
auto-load auto-load
chip-locale="myEntries"
/> />
</template> </template>

View File

@ -30,7 +30,7 @@ const entriesTableColumns = computed(() => [
align: 'left', align: 'left',
name: 'itemFk', name: 'itemFk',
field: 'itemFk', field: 'itemFk',
label: t('entry.latestBuys.tableVisibleColumns.itemFk'), label: t('entrySupplier.itemId'),
}, },
{ {
align: 'left', align: 'left',
@ -65,7 +65,15 @@ const entriesTableColumns = computed(() => [
]); ]);
function downloadCSV(rows) { function downloadCSV(rows) {
const headers = ['id', 'itemFk', 'name', 'stickers', 'packing', 'grouping', 'comment']; const headers = [
'id',
'itemFk',
'name',
'stickers',
'packing',
'grouping',
'comment',
];
const csvRows = rows.map((row) => { const csvRows = rows.map((row) => {
const buy = row; const buy = row;
@ -119,17 +127,18 @@ function downloadCSV(rows) {
> >
<template #top-left> <template #top-left>
<QBtn <QBtn
:label="t('myEntries.downloadCsv')" :label="t('entrySupplier.downloadCsv')"
color="primary" color="primary"
icon="csv" icon="csv"
@click="downloadCSV(rows)" @click="downloadCSV(rows)"
unelevated unelevated
data-cy="downloadCsvBtn"
/> />
</template> </template>
<template #top-right> <template #top-right>
<QBtn <QBtn
class="q-mr-lg" class="q-mr-lg"
:label="t('myEntries.printLabels')" :label="t('entrySupplier.printLabels')"
color="primary" color="primary"
icon="print" icon="print"
@click=" @click="
@ -148,13 +157,18 @@ function downloadCSV(rows) {
v-if="props.row.stickers > 0" v-if="props.row.stickers > 0"
@click=" @click="
openReport( openReport(
`Entries/${props.row.id}/buy-label-supplier` `Entries/${props.row.id}/buy-label-supplier`,
{},
true,
) )
" "
unelevated unelevated
color="primary"
flat
data-cy="seeLabelBtn"
> >
<QTooltip>{{ <QTooltip>{{
t('myEntries.viewLabel') t('entrySupplier.viewLabel')
}}</QTooltip> }}</QTooltip>
</QBtn> </QBtn>
</QTr> </QTr>

View File

@ -38,7 +38,7 @@ const recalc = async () => {
<template> <template>
<div class="q-pa-lg row justify-center"> <div class="q-pa-lg row justify-center">
<QCard class="bg-light" style="width: 300px"> <QCard class="bg-light" style="width: 300px" data-cy="wasteRecalc">
<QCardSection> <QCardSection>
<VnInputDate <VnInputDate
class="q-mb-lg" class="q-mb-lg"
@ -46,6 +46,7 @@ const recalc = async () => {
:label="$t('globals.from')" :label="$t('globals.from')"
rounded rounded
dense dense
data-cy="dateFrom"
/> />
<VnInputDate <VnInputDate
class="q-mb-lg" class="q-mb-lg"
@ -55,6 +56,7 @@ const recalc = async () => {
:disable="!dateFrom" :disable="!dateFrom"
rounded rounded
dense dense
data-cy="dateTo"
/> />
<QBtn <QBtn
color="primary" color="primary"
@ -63,6 +65,7 @@ const recalc = async () => {
:loading="isLoading" :loading="isLoading"
:disable="isLoading || !(dateFrom && dateTo)" :disable="isLoading || !(dateFrom && dateTo)"
@click="recalc()" @click="recalc()"
data-cy="recalc"
/> />
</QCardSection> </QCardSection>
</QCard> </QCard>

View File

@ -6,7 +6,7 @@ entry:
list: list:
newEntry: New entry newEntry: New entry
tableVisibleColumns: tableVisibleColumns:
isExcludedFromAvailable: Exclude from inventory isExcludedFromAvailable: Excluded from available
isOrdered: Ordered isOrdered: Ordered
isConfirmed: Ready to label isConfirmed: Ready to label
isReceived: Received isReceived: Received
@ -33,7 +33,7 @@ entry:
invoiceAmount: Invoice amount invoiceAmount: Invoice amount
ordered: Ordered ordered: Ordered
booked: Booked booked: Booked
excludedFromAvailable: Inventory excludedFromAvailable: Excluded
travelReference: Reference travelReference: Reference
travelAgency: Agency travelAgency: Agency
travelShipped: Shipped travelShipped: Shipped
@ -55,7 +55,7 @@ entry:
commission: Commission commission: Commission
observation: Observation observation: Observation
booked: Booked booked: Booked
excludedFromAvailable: Inventory excludedFromAvailable: Excluded
initialTemperature: Ini °C initialTemperature: Ini °C
finalTemperature: Fin °C finalTemperature: Fin °C
buys: buys:
@ -65,27 +65,10 @@ entry:
printedStickers: Printed stickers printedStickers: Printed stickers
notes: notes:
observationType: Observation type observationType: Observation type
latestBuys:
tableVisibleColumns:
image: Picture
itemFk: Item ID
weightByPiece: Weight/Piece
isActive: Active
family: Family
entryFk: Entry
freightValue: Freight value
comissionValue: Commission value
packageValue: Package value
isIgnored: Is ignored
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
packingOut: Package out
landing: Landing
isExcludedFromAvailable: Es inventory
params: params:
isExcludedFromAvailable: Exclude from inventory entryFk: Entry
observationTypeFk: Observation type
isExcludedFromAvailable: Excluded from available
isOrdered: Ordered isOrdered: Ordered
isConfirmed: Ready to label isConfirmed: Ready to label
isReceived: Received isReceived: Received
@ -127,13 +110,17 @@ entry:
company_name: Company name company_name: Company name
itemTypeFk: Item type itemTypeFk: Item type
workerFk: Worker id workerFk: Worker id
daysAgo: Days ago
toShipped: T. shipped
fromShipped: F. shipped
supplierName: Supplier
search: Search entries search: Search entries
searchInfo: You can search by entry reference searchInfo: You can search by entry reference
descriptorMenu: descriptorMenu:
showEntryReport: Show entry report showEntryReport: Show entry report
entryFilter: entryFilter:
params: params:
isExcludedFromAvailable: Exclude from inventory isExcludedFromAvailable: Excluded from available
invoiceNumber: Invoice number invoiceNumber: Invoice number
travelFk: Travel travelFk: Travel
companyFk: Company companyFk: Company
@ -155,7 +142,7 @@ entryFilter:
warehouseOutFk: Origin warehouseOutFk: Origin
warehouseInFk: Destiny warehouseInFk: Destiny
entryTypeCode: Entry type entryTypeCode: Entry type
myEntries: entrySupplier:
id: ID id: ID
landed: Landed landed: Landed
shipped: Shipped shipped: Shipped
@ -170,6 +157,8 @@ myEntries:
downloadCsv: Download CSV downloadCsv: Download CSV
search: Search entries search: Search entries
searchInfo: You can search by entry reference searchInfo: You can search by entry reference
supplierName: Supplier
itemId: Item id
entryStockBought: entryStockBought:
travel: Travel travel: Travel
editTravel: Edit travel editTravel: Edit travel

View File

@ -6,7 +6,7 @@ entry:
list: list:
newEntry: Nueva entrada newEntry: Nueva entrada
tableVisibleColumns: tableVisibleColumns:
isExcludedFromAvailable: Excluir del inventario isExcludedFromAvailable: Excluir del disponible
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Lista para etiquetar isConfirmed: Lista para etiquetar
isReceived: Recibida isReceived: Recibida
@ -25,7 +25,7 @@ entry:
entryTypeDescription: Tipo entrada entryTypeDescription: Tipo entrada
invoiceAmount: Importe invoiceAmount: Importe
dated: Fecha dated: Fecha
inventoryEntry: Es inventario inventoryEntry: Es inventario
summary: summary:
commission: Comisión commission: Comisión
currency: Moneda currency: Moneda
@ -33,7 +33,8 @@ entry:
invoiceAmount: Importe invoiceAmount: Importe
ordered: Pedida ordered: Pedida
booked: Contabilizada booked: Contabilizada
excludedFromAvailable: Inventario excludedFromAvailable: Excluir del disponible
isConfirmed: Lista para etiquetar
travelReference: Referencia travelReference: Referencia
travelAgency: Agencia travelAgency: Agencia
travelShipped: F. envio travelShipped: F. envio
@ -56,7 +57,7 @@ entry:
observation: Observación observation: Observación
commission: Comisión commission: Comisión
booked: Contabilizada booked: Contabilizada
excludedFromAvailable: Inventario excludedFromAvailable: Excluir del disponible
initialTemperature: Ini °C initialTemperature: Ini °C
finalTemperature: Fin °C finalTemperature: Fin °C
buys: buys:
@ -66,30 +67,12 @@ entry:
printedStickers: Etiquetas impresas printedStickers: Etiquetas impresas
notes: notes:
observationType: Tipo de observación observationType: Tipo de observación
latestBuys:
tableVisibleColumns:
image: Foto
itemFk: Id Artículo
weightByPiece: Peso (gramos)/tallo
isActive: Activo
family: Familia
entryFk: Entrada
freightValue: Porte
comissionValue: Comisión
packageValue: Embalaje
isIgnored: Ignorado
price2: Grouping
price3: Packing
minPrice: Min
ektFk: Ekt
packingOut: Embalaje envíos
landing: Llegada
isExcludedFromAvailable: Es inventario
search: Buscar entradas search: Buscar entradas
searchInfo: Puedes buscar por referencia de entrada searchInfo: Puedes buscar por referencia de entrada
params: params:
isExcludedFromAvailable: Excluir del inventario entryFk: Entrada
observationTypeFk: Tipo de observación
isExcludedFromAvailable: Excluir del disponible
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Lista para etiquetar isConfirmed: Lista para etiquetar
isReceived: Recibida isReceived: Recibida
@ -131,11 +114,15 @@ entry:
company_name: Nombre empresa company_name: Nombre empresa
itemTypeFk: Familia itemTypeFk: Familia
workerFk: Comprador workerFk: Comprador
daysAgo: Días atras
toShipped: F. salida(hasta)
fromShipped: F. salida(desde)
supplierName: Proveedor
entryFilter: entryFilter:
params: params:
isExcludedFromAvailable: Inventario isExcludedFromAvailable: Excluir del disponible
isOrdered: Pedida isOrdered: Pedida
isConfirmed: Confirmado isConfirmed: Lista para etiquetar
isReceived: Recibida isReceived: Recibida
isRaid: Raid isRaid: Raid
landed: Fecha landed: Fecha
@ -149,7 +136,7 @@ entryFilter:
warehouseInFk: Destino warehouseInFk: Destino
entryTypeCode: Tipo de entrada entryTypeCode: Tipo de entrada
hasToShowDeletedEntries: Mostrar entradas eliminadas hasToShowDeletedEntries: Mostrar entradas eliminadas
myEntries: entrySupplier:
id: ID id: ID
landed: F. llegada landed: F. llegada
shipped: F. salida shipped: F. salida
@ -164,10 +151,12 @@ myEntries:
downloadCsv: Descargar CSV downloadCsv: Descargar CSV
search: Buscar entradas search: Buscar entradas
searchInfo: Puedes buscar por referencia de la entrada searchInfo: Puedes buscar por referencia de la entrada
supplierName: Proveedor
itemId: Id artículo
entryStockBought: entryStockBought:
travel: Envío travel: Envío
editTravel: Editar envío editTravel: Editar envío
purchaseSpaces: Espacios de compra purchaseSpaces: Camiones reservados
buyer: Comprador buyer: Comprador
reserve: Reservado reserve: Reservado
bought: Comprado bought: Comprado

View File

@ -121,42 +121,49 @@ function deleteFile(dmsFk) {
hide-selected hide-selected
:is-clearable="false" :is-clearable="false"
:required="true" :required="true"
data-cy="invoiceInBasicDataSupplier"
/> />
<VnInput <VnInput
clearable clearable
clear-icon="close" clear-icon="close"
:label="t('invoiceIn.supplierRef')" :label="t('invoiceIn.supplierRef')"
v-model="data.supplierRef" v-model="data.supplierRef"
data-cy="invoiceInBasicDataSupplierRef"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInputDate :label="t('Expedition date')" v-model="data.issued" /> <VnInputDate
:label="t('Expedition date')"
v-model="data.issued"
data-cy="invoiceInBasicDataIssued"
/>
<VnInputDate <VnInputDate
:label="t('Operation date')" :label="t('Operation date')"
v-model="data.operated" v-model="data.operated"
autofocus autofocus
data-cy="invoiceInBasicDataOperated"
/> />
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnInputDate :label="t('Entry date')" v-model="data.bookEntried" /> <VnInputDate
<VnInputDate :label="t('Accounted date')" v-model="data.booked" /> :label="t('Entry date')"
v-model="data.bookEntried"
data-cy="invoiceInBasicDatabookEntried"
/>
<VnInputDate
:label="t('Accounted date')"
v-model="data.booked"
data-cy="invoiceInBasicDataBooked"
/>
</VnRow> </VnRow>
<VnRow> <VnRow>
<VnSelect <VnSelect
:label="t('Undeductible VAT')" :label="t('invoiceIn.summary.sage')"
v-model="data.deductibleExpenseFk" v-model="data.withholdingSageFk"
:options="expenses" :options="sageWithholdings"
option-value="id" option-value="id"
option-label="id" option-label="withholding"
:filter-options="['id', 'name']" />
data-cy="UnDeductibleVatSelect"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
{{ `${scope.opt.id}: ${scope.opt.name}` }}
</QItem>
</template>
</VnSelect>
<div class="row no-wrap"> <div class="row no-wrap">
<VnInput <VnInput
@ -182,6 +189,7 @@ function deleteFile(dmsFk) {
padding="xs" padding="xs"
round round
@click="downloadFile(data.dmsFk)" @click="downloadFile(data.dmsFk)"
data-cy="invoiceInBasicDataDmsDownload"
/> />
<QBtn <QBtn
:class="{ :class="{
@ -197,6 +205,7 @@ function deleteFile(dmsFk) {
documentDialogRef.dms = data.dms; documentDialogRef.dms = data.dms;
} }
" "
data-cy="invoiceInBasicDataDmsEdit"
> >
<QTooltip>{{ t('Edit document') }}</QTooltip> <QTooltip>{{ t('Edit document') }}</QTooltip>
</QBtn> </QBtn>
@ -210,6 +219,7 @@ function deleteFile(dmsFk) {
padding="xs" padding="xs"
round round
@click="deleteFile(data.dmsFk)" @click="deleteFile(data.dmsFk)"
data-cy="invoiceInBasicDataDmsDelete"
/> />
</div> </div>
<QBtn <QBtn
@ -224,7 +234,7 @@ function deleteFile(dmsFk) {
delete documentDialogRef.dms; delete documentDialogRef.dms;
} }
" "
data-cy="dms-create" data-cy="invoiceInBasicDataDmsAdd"
> >
<QTooltip>{{ t('Create document') }}</QTooltip> <QTooltip>{{ t('Create document') }}</QTooltip>
</QBtn> </QBtn>
@ -237,9 +247,9 @@ function deleteFile(dmsFk) {
:label="t('Currency')" :label="t('Currency')"
v-model="data.currencyFk" v-model="data.currencyFk"
:options="currencies" :options="currencies"
option-value="id"
option-label="code" option-label="code"
sort-by="id" sort-by="id"
data-cy="invoiceInBasicDataCurrencyFk"
/> />
<VnSelect <VnSelect
@ -249,17 +259,8 @@ function deleteFile(dmsFk) {
:label="t('Company')" :label="t('Company')"
v-model="data.companyFk" v-model="data.companyFk"
:options="companies" :options="companies"
option-value="id"
option-label="code" option-label="code"
/> data-cy="invoiceInBasicDataCompanyFk"
</VnRow>
<VnRow>
<VnSelect
:label="t('invoiceIn.summary.sage')"
v-model="data.withholdingSageFk"
:options="sageWithholdings"
option-value="id"
option-label="withholding"
/> />
</VnRow> </VnRow>
</template> </template>
@ -313,7 +314,6 @@ function deleteFile(dmsFk) {
supplierFk: Proveedor supplierFk: Proveedor
Expedition date: Fecha expedición Expedition date: Fecha expedición
Operation date: Fecha operación Operation date: Fecha operación
Undeductible VAT: Iva no deducible
Document: Documento Document: Documento
Download file: Descargar archivo Download file: Descargar archivo
Entry date: Fecha asiento Entry date: Fecha asiento

View File

@ -1,22 +1,16 @@
<script setup> <script setup>
import { ref, computed, capitalize } from 'vue'; import { ref, computed, capitalize } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import CrudModel from 'src/components/CrudModel.vue'; import CrudModel from 'src/components/CrudModel.vue';
import FetchData from 'src/components/FetchData.vue'; import FetchData from 'src/components/FetchData.vue';
import VnSelect from 'src/components/common/VnSelect.vue'; import VnSelect from 'src/components/common/VnSelect.vue';
const route = useRoute();
const { t } = useI18n(); const { t } = useI18n();
const arrayData = useArrayData(); const arrayData = useArrayData();
const invoiceIn = computed(() => arrayData.store.data); const invoiceIn = computed(() => arrayData.store.data);
const invoiceInCorrectionRef = ref(); const invoiceInCorrectionRef = ref();
const filter = {
include: { relation: 'invoiceIn' },
where: { correctingFk: route.params.id },
};
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'origin', name: 'origin',
@ -92,7 +86,8 @@ const requiredFieldRule = (val) => val || t('globals.requiredField');
v-if="invoiceIn" v-if="invoiceIn"
data-key="InvoiceInCorrection" data-key="InvoiceInCorrection"
url="InvoiceInCorrections" url="InvoiceInCorrections"
:filter="filter" :user-filter="{ include: { relation: 'invoiceIn' } }"
:filter="{ where: { correctingFk: $route.params.id } }"
auto-load auto-load
primary-key="correctingFk" primary-key="correctingFk"
:default-remove="false" :default-remove="false"
@ -115,6 +110,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField');
:option-label="col.optionLabel" :option-label="col.optionLabel"
:disable="row.invoiceIn.isBooked" :disable="row.invoiceIn.isBooked"
:filter-options="['description']" :filter-options="['description']"
data-cy="invoiceInCorrective_type"
> >
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
@ -137,6 +133,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField');
:rules="[requiredFieldRule]" :rules="[requiredFieldRule]"
:filter-options="['code', 'description']" :filter-options="['code', 'description']"
:disable="row.invoiceIn.isBooked" :disable="row.invoiceIn.isBooked"
data-cy="invoiceInCorrective_class"
> >
<template #option="{ opt, itemProps }"> <template #option="{ opt, itemProps }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
@ -161,6 +158,7 @@ const requiredFieldRule = (val) => val || t('globals.requiredField');
:option-label="col.optionLabel" :option-label="col.optionLabel"
:rules="[requiredFieldRule]" :rules="[requiredFieldRule]"
:disable="row.invoiceIn.isBooked" :disable="row.invoiceIn.isBooked"
data-cy="invoiceInCorrective_reason"
/> />
</QTd> </QTd>
</template> </template>

View File

@ -5,7 +5,7 @@ import { useI18n } from 'vue-i18n';
import axios from 'axios'; import axios from 'axios';
import { toCurrency, toDate } from 'src/filters'; import { toCurrency, toDate } from 'src/filters';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue'; import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import filter from './InvoiceInFilter.js'; import filter from './InvoiceInFilter.js';
import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue'; import InvoiceInDescriptorMenu from './InvoiceInDescriptorMenu.vue';
@ -17,10 +17,6 @@ const { t } = useI18n();
const cardDescriptorRef = ref(); const cardDescriptorRef = ref();
const entityId = computed(() => $props.id || +currentRoute.value.params.id); const entityId = computed(() => $props.id || +currentRoute.value.params.id);
const totalAmount = ref(); const totalAmount = ref();
const config = ref();
const cplusRectificationTypes = ref([]);
const siiTypeInvoiceIns = ref([]);
const invoiceCorrectionTypes = ref([]);
const invoiceInCorrection = reactive({ correcting: [], corrected: null }); const invoiceInCorrection = reactive({ correcting: [], corrected: null });
const routes = reactive({ const routes = reactive({
getSupplier: (id) => { getSupplier: (id) => {
@ -30,7 +26,7 @@ const routes = reactive({
return { return {
name: 'InvoiceInList', name: 'InvoiceInList',
query: { query: {
params: JSON.stringify({ supplierFk: id }), table: JSON.stringify({ supplierFk: id }),
}, },
}; };
}, },
@ -39,7 +35,7 @@ const routes = reactive({
return { return {
name: 'InvoiceInList', name: 'InvoiceInList',
query: { query: {
params: JSON.stringify({ correctedFk: entityId.value }), table: JSON.stringify({ correctedFk: entityId.value }),
}, },
}; };
} }
@ -88,7 +84,7 @@ async function setInvoiceCorrection(id) {
} }
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="cardDescriptorRef" ref="cardDescriptorRef"
data-key="InvoiceIn" data-key="InvoiceIn"
:url="`InvoiceIns/${entityId}`" :url="`InvoiceIns/${entityId}`"
@ -108,7 +104,7 @@ async function setInvoiceCorrection(id) {
<VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" /> <VnLv :label="t('invoiceIn.list.amount')" :value="toCurrency(totalAmount)" />
<VnLv :label="t('invoiceIn.list.supplier')"> <VnLv :label="t('invoiceIn.list.supplier')">
<template #value> <template #value>
<span class="link"> <span class="link" data-cy="invoiceInDescriptor_supplier">
{{ entity?.supplier?.nickname }} {{ entity?.supplier?.nickname }}
<SupplierDescriptorProxy :id="entity?.supplierFk" /> <SupplierDescriptorProxy :id="entity?.supplierFk" />
</span> </span>
@ -163,7 +159,7 @@ async function setInvoiceCorrection(id) {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.q-dialog { .q-dialog {

View File

@ -1,5 +1,5 @@
<script setup> <script setup>
import { ref, computed, toRefs, reactive } from 'vue'; import { ref, computed, toRefs, reactive, onBeforeMount } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar'; import { useQuasar } from 'quasar';
@ -111,10 +111,9 @@ async function cloneInvoice() {
} }
const isAgricultural = () => { const isAgricultural = () => {
if (!config.value) return false;
return ( return (
invoiceIn.value?.supplier?.sageFarmerWithholdingFk === invoiceIn.value?.supplier?.sageWithholdingFk ==
config?.value[0]?.sageWithholdingFk config.value?.sageFarmerWithholdingFk
); );
}; };
function showPdfInvoice() { function showPdfInvoice() {
@ -153,162 +152,183 @@ const createInvoiceInCorrection = async () => {
); );
push({ path: `/invoice-in/${correctingId}/summary` }); push({ path: `/invoice-in/${correctingId}/summary` });
}; };
onBeforeMount(async () => {
config.value = (
await axios.get('invoiceinConfigs/findOne', {
params: { fields: ['sageFarmerWithholdingFk'] },
})
).data;
});
</script> </script>
<template> <template>
<FetchData <template v-if="config">
url="InvoiceCorrectionTypes" <FetchData
@on-fetch="(data) => (invoiceCorrectionTypes = data)" url="InvoiceCorrectionTypes"
auto-load @on-fetch="(data) => (invoiceCorrectionTypes = data)"
/> auto-load
<FetchData />
url="CplusRectificationTypes" <FetchData
@on-fetch="(data) => (cplusRectificationTypes = data)" url="CplusRectificationTypes"
auto-load @on-fetch="(data) => (cplusRectificationTypes = data)"
/> auto-load
<FetchData />
url="SiiTypeInvoiceIns" <FetchData
:where="{ code: { like: 'R%' } }" url="SiiTypeInvoiceIns"
@on-fetch="(data) => (siiTypeInvoiceIns = data)" :where="{ code: { like: 'R%' } }"
auto-load @on-fetch="(data) => (siiTypeInvoiceIns = data)"
/> auto-load
<FetchData />
url="InvoiceInConfigs" <InvoiceInToBook>
:where="{ fields: ['sageWithholdingFk'] }" <template #content="{ book }">
auto-load <QItem
@on-fetch="(data) => (config = data)" v-if="!invoice?.isBooked && canEditProp('toBook')"
/> v-ripple
<InvoiceInToBook> clickable
<template #content="{ book }"> @click="book(entityId)"
<QItem >
v-if="!invoice?.isBooked && canEditProp('toBook')" <QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection>
v-ripple </QItem>
clickable </template>
@click="book(entityId)" </InvoiceInToBook>
<QItem
v-if="invoice?.isBooked && canEditProp('toUnbook')"
v-ripple
clickable
@click="triggerMenu('unbook')"
>
<QItemSection>
{{ t('invoiceIn.descriptorMenu.unbook') }}
</QItemSection>
</QItem>
<QItem
v-if="canEditProp('deleteById')"
v-ripple
clickable
@click="triggerMenu('delete')"
>
<QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection>
</QItem>
<QItem
v-if="canEditProp('clone')"
v-ripple
clickable
@click="triggerMenu('clone')"
>
<QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')">
<QItemSection>{{
t('invoiceIn.descriptorMenu.showAgriculturalPdf')
}}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')">
<QItemSection
>{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection
> >
<QItemSection>{{ t('invoiceIn.descriptorMenu.book') }}</QItemSection> </QItem>
</QItem> <QItem
</template> v-if="!invoiceInCorrection.corrected"
</InvoiceInToBook> v-ripple
<QItem clickable
v-if="invoice?.isBooked && canEditProp('toUnbook')" @click="triggerMenu('correct')"
v-ripple data-cy="createCorrectiveItem"
clickable
@click="triggerMenu('unbook')"
>
<QItemSection>
{{ t('invoiceIn.descriptorMenu.unbook') }}
</QItemSection>
</QItem>
<QItem
v-if="canEditProp('deleteById')"
v-ripple
clickable
@click="triggerMenu('delete')"
>
<QItemSection>{{ t('invoiceIn.descriptorMenu.deleteInvoice') }}</QItemSection>
</QItem>
<QItem v-if="canEditProp('clone')" v-ripple clickable @click="triggerMenu('clone')">
<QItemSection>{{ t('invoiceIn.descriptorMenu.cloneInvoice') }}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('showPdf')">
<QItemSection>{{
t('invoiceIn.descriptorMenu.showAgriculturalPdf')
}}</QItemSection>
</QItem>
<QItem v-if="isAgricultural()" v-ripple clickable @click="triggerMenu('sendPdf')">
<QItemSection
>{{ t('invoiceIn.descriptorMenu.sendAgriculturalPdf') }}...</QItemSection
> >
</QItem> <QItemSection
<QItem >{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection
v-if="!invoiceInCorrection.corrected" >
v-ripple </QItem>
clickable <QItem
@click="triggerMenu('correct')" v-if="invoice.dmsFk"
data-cy="createCorrectiveItem" v-ripple
> clickable
<QItemSection @click="downloadFile(invoice.dmsFk)"
>{{ t('invoiceIn.descriptorMenu.createCorrective') }}...</QItemSection
> >
</QItem> <QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection>
<QItem v-if="invoice.dmsFk" v-ripple clickable @click="downloadFile(invoice.dmsFk)"> </QItem>
<QItemSection>{{ t('components.smartCard.downloadFile') }}</QItemSection> <QDialog ref="correctionDialogRef">
</QItem> <QCard data-cy="correctiveInvoiceDialog">
<QDialog ref="correctionDialogRef"> <QCardSection>
<QCard> <QItem class="q-px-none">
<QCardSection> <span class="text-primary text-h6 full-width">
<QItem class="q-px-none"> {{ t('Create rectificative invoice') }}
<span class="text-primary text-h6 full-width"> </span>
{{ t('Create rectificative invoice') }} <QBtn icon="close" flat round dense v-close-popup />
</span> </QItem>
<QBtn icon="close" flat round dense v-close-popup /> </QCardSection>
</QItem> <QCardSection>
</QCardSection> <QItem>
<QCardSection> <QItemSection>
<QItem> <QInput
<QItemSection> :label="t('Original invoice')"
<QInput v-model="entityId"
:label="t('Original invoice')" readonly
v-model="entityId" />
readonly <VnSelect
/> :label="`${useCapitalize(t('globals.class'))}`"
<VnSelect v-model="correctionFormData.invoiceClass"
:label="`${useCapitalize(t('globals.class'))}`" :options="siiTypeInvoiceIns"
v-model="correctionFormData.invoiceClass" option-value="id"
:options="siiTypeInvoiceIns" option-label="code"
option-value="id" :required="true"
option-label="code" data-cy="invoiceInDescriptorMenu_class"
:required="true" />
/> </QItemSection>
</QItemSection> <QItemSection>
<QItemSection> <VnSelect
<VnSelect :label="`${useCapitalize(t('globals.type'))}`"
:label="`${useCapitalize(t('globals.type'))}`" v-model="correctionFormData.invoiceType"
v-model="correctionFormData.invoiceType" :options="cplusRectificationTypes"
:options="cplusRectificationTypes" option-value="id"
option-value="id" option-label="description"
option-label="description" :required="true"
:required="true" data-cy="invoiceInDescriptorMenu_type"
> >
<template #option="{ itemProps, opt }"> <template #option="{ itemProps, opt }">
<QItem v-bind="itemProps"> <QItem v-bind="itemProps">
<QItemSection> <QItemSection>
<QItemLabel <QItemLabel
>{{ opt.id }} - >{{ opt.id }} -
{{ opt.description }}</QItemLabel {{ opt.description }}</QItemLabel
> >
</QItemSection> </QItemSection>
</QItem> </QItem>
<div></div> <div></div>
</template> </template>
</VnSelect> </VnSelect>
<VnSelect <VnSelect
:label="`${useCapitalize(t('globals.reason'))}`" :label="`${useCapitalize(t('globals.reason'))}`"
v-model="correctionFormData.invoiceReason" v-model="correctionFormData.invoiceReason"
:options="invoiceCorrectionTypes" :options="invoiceCorrectionTypes"
option-value="id" option-value="id"
option-label="description" option-label="description"
:required="true" :required="true"
/> data-cy="invoiceInDescriptorMenu_reason"
</QItemSection> />
</QItem> </QItemSection>
</QCardSection> </QItem>
<QCardActions class="justify-end q-mr-sm"> </QCardSection>
<QBtn flat :label="t('globals.close')" color="primary" v-close-popup /> <QCardActions class="justify-end q-mr-sm">
<QBtn <QBtn
:label="t('globals.save')" flat
color="primary" :label="t('globals.close')"
v-close-popup color="primary"
@click="createInvoiceInCorrection" v-close-popup
:disable="isNotFilled" />
/> <QBtn
</QCardActions> :label="t('globals.save')"
</QCard> color="primary"
</QDialog> v-close-popup
@click="createInvoiceInCorrection"
:disable="isNotFilled"
data-cy="saveCorrectiveInvoice"
/>
</QCardActions>
</QCard>
</QDialog>
</template>
</template> </template>
<i18n> <i18n>
en: en:
isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries isNotLinked: The entry {bookEntry} has been deleted with {accountingEntries} entries

View File

@ -25,7 +25,8 @@ const invoiceInFormRef = ref();
const invoiceId = +route.params.id; const invoiceId = +route.params.id;
const filter = { where: { invoiceInFk: invoiceId } }; const filter = { where: { invoiceInFk: invoiceId } };
const areRows = ref(false); const areRows = ref(false);
const totals = ref(); const totalTaxableBase = ref();
const noMatch = computed(() => totalAmount.value != totalTaxableBase.value);
const columns = computed(() => [ const columns = computed(() => [
{ {
name: 'duedate', name: 'duedate',
@ -74,9 +75,12 @@ async function insert() {
notify(t('globals.dataSaved'), 'positive'); notify(t('globals.dataSaved'), 'positive');
} }
onBeforeMount(async () => { async function setTaxableBase() {
totals.value = (await axios.get(`InvoiceIns/${invoiceId}/getTotals`)).data; const { data } = await axios.get(`InvoiceIns/${invoiceId}/getTotals`);
}); totalTaxableBase.value = data.totalTaxableBase;
}
onBeforeMount(async () => await setTaxableBase());
</script> </script>
<template> <template>
<CrudModel <CrudModel
@ -89,13 +93,14 @@ onBeforeMount(async () => {
:data-required="{ invoiceInFk: invoiceId }" :data-required="{ invoiceInFk: invoiceId }"
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
@on-fetch="(data) => (areRows = !!data.length)" @on-fetch="(data) => (areRows = !!data.length)"
@save-changes="setTaxableBase"
> >
<template #body="{ rows }"> <template #body="{ rows }">
<QTable <QTable
v-model:selected="rowsSelected" v-model:selected="rowsSelected"
selection="multiple" selection="multiple"
:columns="columns" :columns
:rows="rows" :rows
row-key="$index" row-key="$index"
:grid="$q.screen.lt.sm" :grid="$q.screen.lt.sm"
> >
@ -151,7 +156,18 @@ onBeforeMount(async () => {
<QTd /> <QTd />
<QTd /> <QTd />
<QTd> <QTd>
{{ toCurrency(totalAmount) }} <QChip
dense
:color="noMatch ? 'negative' : 'transparent'"
class="q-pa-xs"
:title="
noMatch
? t('invoiceIn.noMatch', { totalTaxableBase })
: ''
"
>
{{ toCurrency(totalAmount) }}
</QChip>
</QTd> </QTd>
<QTd> <QTd>
<template v-if="isNotEuro(invoiceIn.currency.code)"> <template v-if="isNotEuro(invoiceIn.currency.code)">
@ -237,7 +253,7 @@ onBeforeMount(async () => {
if (!areRows) insert(); if (!areRows) insert();
else else
invoiceInFormRef.insert({ invoiceInFormRef.insert({
amount: (totals.totalTaxableBase - totalAmount).toFixed(2), amount: (totalTaxableBase - totalAmount).toFixed(2),
invoiceInFk: invoiceId, invoiceInFk: invoiceId,
}); });
} }
@ -249,6 +265,10 @@ onBeforeMount(async () => {
.bg { .bg {
background-color: var(--vn-light-gray); background-color: var(--vn-light-gray);
} }
.q-chip {
color: var(--vn-text-color);
}
</style> </style>
<i18n> <i18n>
es: es:

View File

@ -40,6 +40,13 @@ const vatColumns = ref([
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{
name: 'isDeductible',
label: 'invoiceIn.isDeductible',
field: (row) => row.isDeductible,
sortable: true,
align: 'center',
},
{ {
name: 'vat', name: 'vat',
label: 'invoiceIn.summary.sageVat', label: 'invoiceIn.summary.sageVat',
@ -198,6 +205,7 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
color="orange-11" color="orange-11"
text-color="black" text-color="black"
@click="book(entityId)" @click="book(entityId)"
data-cy="invoiceInSummary_book"
/> />
</template> </template>
</InvoiceIntoBook> </InvoiceIntoBook>
@ -206,113 +214,109 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
<InvoiceInDescriptorMenu :invoice="entity" /> <InvoiceInDescriptorMenu :invoice="entity" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<!--Basic Data--> <QCard class="max-width">
<QCard class="vn-one"> <VnTitle
<QCardSection class="q-pa-none"> :url="getLink('basic-data')"
<VnTitle :text="t('globals.pageTitles.basicData')"
:url="getLink('basic-data')"
:text="t('globals.pageTitles.basicData')"
/>
</QCardSection>
<VnLv
:label="t('invoiceIn.list.supplier')"
:value="entity.supplier?.name"
>
<template #value>
<span class="link">
{{ entity.supplier?.name }}
<SupplierDescriptorProxy :id="entity.supplierFk" />
</span>
</template>
</VnLv>
<VnLv :label="t('invoiceIn.supplierRef')" :value="entity.supplierRef" />
<VnLv
:label="t('invoiceIn.summary.currency')"
:value="entity.currency?.code"
/> />
<VnLv :label="t('invoiceIn.serial')" :value="`${entity.serial}`" /> <div class="vn-card-group">
<VnLv <div class="vn-card-content">
:label="t('globals.country')" <VnLv
:value="entity.supplier?.country?.code" :label="t('invoiceIn.list.supplier')"
/> :value="entity.supplier?.name"
</QCard> >
<QCard class="vn-one"> <template #value>
<QCardSection class="q-pa-none"> <span class="link" data-cy="invoiceInSummary_supplier">
<VnTitle {{ entity.supplier?.name }}
:url="getLink('basic-data')" <SupplierDescriptorProxy :id="entity.supplierFk" />
:text="t('globals.pageTitles.basicData')" </span>
/> </template>
</QCardSection> </VnLv>
<VnLv <VnLv
:ellipsis-value="false" :label="t('invoiceIn.supplierRef')"
:label="t('invoiceIn.summary.issued')" :value="entity.supplierRef"
:value="toDate(entity.issued)" />
/> <VnLv
<VnLv :label="t('invoiceIn.summary.currency')"
:label="t('invoiceIn.summary.operated')" :value="entity.currency?.code"
:value="toDate(entity.operated)" />
/> <VnLv
<VnLv :label="t('invoiceIn.serial')"
:label="t('invoiceIn.summary.bookEntried')" :value="`${entity.serial}`"
:value="toDate(entity.bookEntried)" />
/> <VnLv
<VnLv :label="t('globals.country')"
:label="t('invoiceIn.summary.bookedDate')" :value="entity.supplier?.country?.code"
:value="toDate(entity.booked)" />
/> </div>
<VnLv :label="t('globals.isVies')" :value="entity.supplier?.isVies" /> <div class="vn-card-content">
</QCard> <VnLv
<QCard class="vn-one"> :ellipsis-value="false"
<QCardSection class="q-pa-none"> :label="t('invoiceIn.summary.issued')"
<VnTitle :value="toDate(entity.issued)"
:url="getLink('basic-data')" />
:text="t('globals.pageTitles.basicData')" <VnLv
/> :label="t('invoiceIn.summary.operated')"
</QCardSection> :value="toDate(entity.operated)"
<VnLv />
:label="t('invoiceIn.summary.sage')" <VnLv
:value="entity.sageWithholding?.withholding" :label="t('invoiceIn.summary.bookEntried')"
/> :value="toDate(entity.bookEntried)"
<VnLv />
:label="t('invoiceIn.summary.vat')" <VnLv
:value="entity.expenseDeductible?.name" :label="t('invoiceIn.summary.bookedDate')"
/> :value="toDate(entity.booked)"
<VnLv />
:label="t('invoiceIn.card.company')" <VnLv
:value="entity.company?.code" :label="t('globals.isVies')"
/> :value="entity.supplier?.isVies"
<VnLv :label="t('invoiceIn.isBooked')" :value="invoiceIn?.isBooked" /> />
</QCard> </div>
<QCard class="vn-one"> <div class="vn-card-content">
<QCardSection class="q-pa-none"> <VnLv
<VnTitle :label="t('invoiceIn.summary.sage')"
:url="getLink('basic-data')" :value="entity.sageWithholding?.withholding"
:text="t('globals.pageTitles.basicData')" />
/> <VnLv
</QCardSection> :label="t('invoiceIn.summary.vat')"
<QCardSection class="q-pa-none"> :value="entity.expenseDeductible?.name"
<VnLv />
:label="t('invoiceIn.summary.taxableBase')" <VnLv
:value="toCurrency(entity.totals.totalTaxableBase)" :label="t('invoiceIn.card.company')"
/> :value="entity.company?.code"
<VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" /> />
<VnLv :label="t('invoiceIn.summary.dueTotal')"> <VnLv
<template #value> :label="t('invoiceIn.isBooked')"
<QChip :value="invoiceIn?.isBooked"
dense />
class="q-pa-xs" </div>
:color="amountsNotMatch ? 'negative' : 'transparent'" <div class="vn-card-content">
:title=" <VnLv
amountsNotMatch :label="t('invoiceIn.summary.taxableBase')"
? t('invoiceIn.summary.noMatch') :value="toCurrency(entity.totals.totalTaxableBase)"
: t('invoiceIn.summary.dueTotal') />
" <VnLv label="Total" :value="toCurrency(entity.totals.totalVat)" />
> <VnLv :label="t('invoiceIn.summary.dueTotal')">
{{ toCurrency(entity.totals.totalDueDay) }} <template #value>
</QChip> <QChip
</template> dense
</VnLv> class="q-pa-xs"
</QCardSection> :color="amountsNotMatch ? 'negative' : 'transparent'"
:title="
amountsNotMatch
? t('invoiceIn.noMatch', {
totalTaxableBase:
entity.totals.totalTaxableBase,
})
: t('invoiceIn.summary.dueTotal')
"
>
{{ toCurrency(entity.totals.totalDueDay) }}
</QChip>
</template>
</VnLv>
</div>
</div>
</QCard> </QCard>
<!--Vat--> <!--Vat-->
<QCard v-if="entity.invoiceInTax.length" class="vat"> <QCard v-if="entity.invoiceInTax.length" class="vat">
@ -334,6 +338,15 @@ const getLink = (param) => `#/invoice-in/${entityId.value}/${param}`;
</QTh> </QTh>
</QTr> </QTr>
</template> </template>
<template #body-cell-isDeductible="{ row }">
<QTd align="center">
<QCheckbox
v-model="row.isDeductible"
disable
data-cy="isDeductible_checkbox"
/>
</QTd>
</template>
<template #body-cell-vat="{ value: vatCell }"> <template #body-cell-vat="{ value: vatCell }">
<QTd :title="vatCell" shrink> <QTd :title="vatCell" shrink>
{{ vatCell }} {{ vatCell }}

View File

@ -53,6 +53,13 @@ const columns = computed(() => [
sortable: true, sortable: true,
align: 'left', align: 'left',
}, },
{
name: 'isDeductible',
label: t('invoiceIn.isDeductible'),
field: (row) => row.isDeductible,
model: 'isDeductible',
align: 'center',
},
{ {
name: 'sageiva', name: 'sageiva',
label: t('Sage iva'), label: t('Sage iva'),
@ -119,6 +126,7 @@ const filter = {
'foreignValue', 'foreignValue',
'taxTypeSageFk', 'taxTypeSageFk',
'transactionTypeSageFk', 'transactionTypeSageFk',
'isDeductible',
], ],
where: { where: {
invoiceInFk: route.params.id, invoiceInFk: route.params.id,
@ -202,6 +210,9 @@ function setCursor(ref) {
:option-label="col.optionLabel" :option-label="col.optionLabel"
:filter-options="['id', 'name']" :filter-options="['id', 'name']"
:tooltip="t('Create a new expense')" :tooltip="t('Create a new expense')"
:acls="[
{ model: 'Expense', props: '*', accessType: 'WRITE' },
]"
@keydown.tab.prevent=" @keydown.tab.prevent="
autocompleteExpense( autocompleteExpense(
$event, $event,
@ -227,6 +238,14 @@ function setCursor(ref) {
</VnSelectDialog> </VnSelectDialog>
</QTd> </QTd>
</template> </template>
<template #body-cell-isDeductible="{ row }">
<QTd align="center">
<QCheckbox
v-model="row.isDeductible"
data-cy="isDeductible_checkbox"
/>
</QTd>
</template>
<template #body-cell-taxablebase="{ row }"> <template #body-cell-taxablebase="{ row }">
<QTd shrink> <QTd shrink>
<VnInputNumber <VnInputNumber
@ -321,6 +340,7 @@ function setCursor(ref) {
</QTd> </QTd>
<QTd /> <QTd />
<QTd /> <QTd />
<QTd />
<QTd> <QTd>
{{ toCurrency(taxRateTotal) }} {{ toCurrency(taxRateTotal) }}
</QTd> </QTd>

View File

@ -7,6 +7,7 @@ import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import { dateRange } from 'src/filters'; import { dateRange } from 'src/filters';
import { date } from 'quasar'; import { date } from 'quasar';
import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue'; import VnSelectSupplier from 'src/components/common/VnSelectSupplier.vue';
import VnCheckbox from 'src/components/common/VnCheckbox.vue';
defineProps({ dataKey: { type: String, required: true } }); defineProps({ dataKey: { type: String, required: true } });
const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ'; const dateFormat = 'YYYY-MM-DDTHH:mm:ss.SSSZ';
@ -39,17 +40,13 @@ function handleDaysAgo(params, daysAgo) {
<VnInputDate <VnInputDate
:label="$t('globals.from')" :label="$t('globals.from')"
v-model="params.from" v-model="params.from"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate :label="$t('globals.to')" v-model="params.to" filled />
:label="$t('globals.to')"
v-model="params.to"
is-outlined
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -57,7 +54,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInputNumber <VnInputNumber
:label="$t('globals.daysAgo')" :label="$t('globals.daysAgo')"
v-model="params.daysAgo" v-model="params.daysAgo"
is-outlined filled
:step="0" :step="0"
@update:model-value="(val) => handleDaysAgo(params, val)" @update:model-value="(val) => handleDaysAgo(params, val)"
@remove="(val) => handleDaysAgo(params, val)" @remove="(val) => handleDaysAgo(params, val)"
@ -66,12 +63,7 @@ function handleDaysAgo(params, daysAgo) {
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnSelectSupplier <VnSelectSupplier v-model="params.supplierFk" dense filled />
v-model="params.supplierFk"
dense
outlined
rounded
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
@ -79,7 +71,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInput <VnInput
:label="getLocale('supplierRef')" :label="getLocale('supplierRef')"
v-model="params.supplierRef" v-model="params.supplierRef"
is-outlined filled
lazy-rules lazy-rules
/> />
</QItemSection> </QItemSection>
@ -89,7 +81,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInput <VnInput
:label="getLocale('fi')" :label="getLocale('fi')"
v-model="params.fi" v-model="params.fi"
is-outlined filled
lazy-rules lazy-rules
/> />
</QItemSection> </QItemSection>
@ -99,7 +91,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInput <VnInput
:label="getLocale('serial')" :label="getLocale('serial')"
v-model="params.serial" v-model="params.serial"
is-outlined filled
lazy-rules lazy-rules
/> />
</QItemSection> </QItemSection>
@ -109,7 +101,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInput <VnInput
:label="getLocale('account')" :label="getLocale('account')"
v-model="params.account" v-model="params.account"
is-outlined filled
lazy-rules lazy-rules
/> />
</QItemSection> </QItemSection>
@ -119,7 +111,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInput <VnInput
:label="getLocale('globals.params.awbCode')" :label="getLocale('globals.params.awbCode')"
v-model="params.awbCode" v-model="params.awbCode"
is-outlined filled
lazy-rules lazy-rules
/> />
</QItemSection> </QItemSection>
@ -129,7 +121,7 @@ function handleDaysAgo(params, daysAgo) {
<VnInputNumber <VnInputNumber
:label="$t('globals.amount')" :label="$t('globals.amount')"
v-model="params.amount" v-model="params.amount"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
@ -141,19 +133,19 @@ function handleDaysAgo(params, daysAgo) {
url="Companies" url="Companies"
option-label="code" option-label="code"
:fields="['id', 'code']" :fields="['id', 'code']"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <VnCheckbox
:label="$t('invoiceIn.isBooked')" :label="$t('invoiceIn.isBooked')"
v-model="params.isBooked" v-model="params.isBooked"
@update:model-value="searchFn()" @update:model-value="searchFn()"
toggle-indeterminate toggle-indeterminate
/> />
<QCheckbox <VnCheckbox
:label="getLocale('params.correctingFk')" :label="getLocale('params.correctingFk')"
v-model="params.correctingFk" v-model="params.correctingFk"
@update:model-value="searchFn()" @update:model-value="searchFn()"

View File

@ -119,7 +119,7 @@ const cols = computed(() => [
icon: 'preview', icon: 'preview',
type: 'submit', type: 'submit',
isPrimary: true, isPrimary: true,
action: (row) => viewSummary(row.id, InvoiceInSummary), action: (row) => viewSummary(row.id, InvoiceInSummary, 'lg-width'),
}, },
{ {
title: t('globals.download'), title: t('globals.download'),
@ -156,7 +156,7 @@ const cols = computed(() => [
:create="{ :create="{
urlCreate: 'InvoiceIns', urlCreate: 'InvoiceIns',
title: t('globals.createInvoiceIn'), title: t('globals.createInvoiceIn'),
onDataSaved: ({ id }) => tableRef.redirect(id), onDataSaved: ({ id }) => tableRef.redirect(`${id}/basic-data`),
formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() }, formInitialData: { companyFk: user.companyFk, issued: Date.vnNew() },
}" }"
redirect="invoice-in" redirect="invoice-in"

View File

@ -4,7 +4,7 @@ import { useQuasar } from 'quasar';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import VnConfirm from 'src/components/ui/VnConfirm.vue'; import VnConfirm from 'src/components/ui/VnConfirm.vue';
import { useArrayData } from 'src/composables/useArrayData'; import { useArrayData } from 'src/composables/useArrayData';
import qs from 'qs';
const { notify, dialog } = useQuasar(); const { notify, dialog } = useQuasar();
const { t } = useI18n(); const { t } = useI18n();
@ -56,22 +56,21 @@ async function checkToBook(id) {
componentProps: { componentProps: {
title: t('Are you sure you want to book this invoice?'), title: t('Are you sure you want to book this invoice?'),
message: messages.reduce((acc, msg) => `${acc}<p>${msg}</p>`, ''), message: messages.reduce((acc, msg) => `${acc}<p>${msg}</p>`, ''),
promise: () => toBook(id),
}, },
}).onOk(() => toBook(id)); });
} }
async function toBook(id) { async function toBook(id) {
let type = 'positive'; let err = false;
let message = t('globals.dataSaved');
try { try {
await axios.post(`InvoiceIns/${id}/toBook`); await axios.post(`InvoiceIns/${id}/toBook`);
store.data.isBooked = true; store.data.isBooked = true;
} catch (e) { } catch (e) {
type = 'negative'; err = true;
message = t('It was not able to book the invoice'); throw e;
} finally { } finally {
notify({ type, message }); if (!err) notify({ type: 'positive', message: t('globals.dataSaved') });
} }
} }
</script> </script>

View File

@ -25,8 +25,7 @@ const { t } = useI18n();
<VnInputNumber <VnInputNumber
v-model="params.daysAgo" v-model="params.daysAgo"
:label="t('params.daysAgo')" :label="t('params.daysAgo')"
outlined filled
rounded
dense dense
/> />
</QItemSection> </QItemSection>
@ -36,8 +35,7 @@ const { t } = useI18n();
<VnInput <VnInput
v-model="params.serial" v-model="params.serial"
:label="t('params.serial')" :label="t('params.serial')"
outlined filled
rounded
dense dense
/> />
</QItemSection> </QItemSection>

View File

@ -4,6 +4,7 @@ invoiceIn:
serial: Serial serial: Serial
isBooked: Is booked isBooked: Is booked
supplierRef: Invoice nº supplierRef: Invoice nº
isDeductible: Deductible
list: list:
ref: Reference ref: Reference
supplier: Supplier supplier: Supplier
@ -57,7 +58,6 @@ invoiceIn:
bank: Bank bank: Bank
foreignValue: Foreign value foreignValue: Foreign value
dueTotal: Due day dueTotal: Due day
noMatch: Do not match
code: Code code: Code
net: Net net: Net
stems: Stems stems: Stems
@ -68,3 +68,4 @@ invoiceIn:
isBooked: Is booked isBooked: Is booked
account: Ledger account account: Ledger account
correctingFk: Rectificative correctingFk: Rectificative
noMatch: No match with the vat({totalTaxableBase})

View File

@ -4,6 +4,7 @@ invoiceIn:
serial: Serie serial: Serie
isBooked: Contabilizada isBooked: Contabilizada
supplierRef: Nº factura supplierRef: Nº factura
isDeductible: Deducible
list: list:
ref: Referencia ref: Referencia
supplier: Proveedor supplier: Proveedor
@ -66,3 +67,4 @@ invoiceIn:
isBooked: Contabilizada isBooked: Contabilizada
account: Cuenta contable account: Cuenta contable
correctingFk: Rectificativa correctingFk: Rectificativa
noMatch: No cuadra con el iva({totalTaxableBase})

View File

@ -3,7 +3,7 @@ import { ref, computed } from 'vue';
import { useRoute } from 'vue-router'; import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import CardDescriptor from 'components/ui/CardDescriptor.vue'; import EntityDescriptor from 'components/ui/EntityDescriptor.vue';
import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from 'pages/Customer/Card/CustomerDescriptorProxy.vue';
import VnLv from 'src/components/ui/VnLv.vue'; import VnLv from 'src/components/ui/VnLv.vue';
import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue'; import InvoiceOutDescriptorMenu from './InvoiceOutDescriptorMenu.vue';
@ -34,7 +34,7 @@ function ticketFilter(invoice) {
</script> </script>
<template> <template>
<CardDescriptor <EntityDescriptor
ref="descriptor" ref="descriptor"
:url="`InvoiceOuts/${entityId}`" :url="`InvoiceOuts/${entityId}`"
:filter="filter" :filter="filter"
@ -46,6 +46,11 @@ function ticketFilter(invoice) {
<InvoiceOutDescriptorMenu :invoice-out-data="entity" :menu-ref="menuRef" /> <InvoiceOutDescriptorMenu :invoice-out-data="entity" :menu-ref="menuRef" />
</template> </template>
<template #body="{ entity }"> <template #body="{ entity }">
<VnLv
v-if="entity.externalRef"
:label="t('invoiceOut.externalRef')"
:value="entity.externalRef"
/>
<VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" /> <VnLv :label="t('invoiceOut.card.issued')" :value="toDate(entity.issued)" />
<VnLv :label="t('globals.amount')" :value="toCurrency(entity.amount)" /> <VnLv :label="t('globals.amount')" :value="toCurrency(entity.amount)" />
<VnLv v-if="entity.client" :label="t('globals.client')"> <VnLv v-if="entity.client" :label="t('globals.client')">
@ -88,5 +93,5 @@ function ticketFilter(invoice) {
</QBtn> </QBtn>
</QCardActions> </QCardActions>
</template> </template>
</CardDescriptor> </EntityDescriptor>
</template> </template>

View File

@ -7,6 +7,7 @@ import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue'; import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue'; import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps({ const props = defineProps({
@ -30,23 +31,23 @@ const states = ref();
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput <VnInput
:label="t('Customer ID')" :label="t('globals.params.clientFk')"
v-model="params.clientFk" v-model="params.clientFk"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInput v-model="params.fi" :label="t('FI')" is-outlined /> <VnInput v-model="params.fi" :label="t('globals.params.fi')" filled />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputNumber <VnInputNumber
:label="t('Amount')" :label="t('globals.amount')"
v-model="params.amount" v-model="params.amount"
is-outlined filled
data-cy="InvoiceOutFilterAmountBtn" data-cy="InvoiceOutFilterAmountBtn"
/> />
</QItemSection> </QItemSection>
@ -54,22 +55,20 @@ const states = ref();
<QItem> <QItem>
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Min')" :label="t('invoiceOut.params.min')"
dense dense
lazy-rules lazy-rules
outlined filled
rounded
type="number" type="number"
v-model.number="params.min" v-model.number="params.min"
/> />
</QItemSection> </QItemSection>
<QItemSection> <QItemSection>
<QInput <QInput
:label="t('Max')" :label="t('invoiceOut.params.max')"
dense dense
lazy-rules lazy-rules
outlined filled
rounded
type="number" type="number"
v-model.number="params.max" v-model.number="params.max"
/> />
@ -78,7 +77,7 @@ const states = ref();
<QItem> <QItem>
<QItemSection> <QItemSection>
<QCheckbox <QCheckbox
:label="t('Has PDF')" :label="t('invoiceOut.params.hasPdf')"
toggle-indeterminate toggle-indeterminate
v-model="params.hasPdf" v-model="params.hasPdf"
/> />
@ -88,14 +87,30 @@ const states = ref();
<QItemSection> <QItemSection>
<VnInputDate <VnInputDate
v-model="params.created" v-model="params.created"
:label="t('Created')" :label="t('invoiceOut.params.created')"
is-outlined filled
/> />
</QItemSection> </QItemSection>
</QItem> </QItem>
<QItem> <QItem>
<QItemSection> <QItemSection>
<VnInputDate v-model="params.dued" :label="t('Dued')" is-outlined /> <VnInputDate
v-model="params.dued"
:label="t('invoiceOut.params.dued')"
filled
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
filled
:label="t('globals.params.departmentFk')"
v-model="params.departmentFk"
option-value="id"
option-label="name"
url="Departments"
/>
</QItemSection> </QItemSection>
</QItem> </QItem>
</template> </template>

View File

@ -26,7 +26,7 @@ const serialTypesOptions = ref([]);
const handleInvoiceOutSerialsFetch = (data) => { const handleInvoiceOutSerialsFetch = (data) => {
serialTypesOptions.value = Array.from( serialTypesOptions.value = Array.from(
new Set(data.map((item) => item.type).filter((type) => type)) new Set(data.map((item) => item.type).filter((type) => type)),
); );
}; };
@ -99,8 +99,7 @@ onMounted(async () => {
option-label="name" option-label="name"
hide-selected hide-selected
dense dense
outlined filled
rounded
data-cy="InvoiceOutGlobalClientSelect" data-cy="InvoiceOutGlobalClientSelect"
> >
<template #option="scope"> <template #option="scope">
@ -124,19 +123,18 @@ onMounted(async () => {
option-label="type" option-label="type"
hide-selected hide-selected
dense dense
outlined filled
rounded
data-cy="InvoiceOutGlobalSerialSelect" data-cy="InvoiceOutGlobalSerialSelect"
/> />
<VnInputDate <VnInputDate
v-model="formData.invoiceDate" v-model="formData.invoiceDate"
:label="t('invoiceDate')" :label="t('invoiceDate')"
is-outlined filled
/> />
<VnInputDate <VnInputDate
v-model="formData.maxShipped" v-model="formData.maxShipped"
:label="t('maxShipped')" :label="t('maxShipped')"
is-outlined filled
data-cy="InvoiceOutGlobalMaxShippedDate" data-cy="InvoiceOutGlobalMaxShippedDate"
/> />
<VnSelect <VnSelect
@ -145,8 +143,7 @@ onMounted(async () => {
:options="companiesOptions" :options="companiesOptions"
option-label="code" option-label="code"
dense dense
outlined filled
rounded
data-cy="InvoiceOutGlobalCompanySelect" data-cy="InvoiceOutGlobalCompanySelect"
/> />
<VnSelect <VnSelect
@ -154,8 +151,7 @@ onMounted(async () => {
v-model="formData.printer" v-model="formData.printer"
:options="printersOptions" :options="printersOptions"
dense dense
outlined filled
rounded
data-cy="InvoiceOutGlobalPrinterSelect" data-cy="InvoiceOutGlobalPrinterSelect"
/> />
</div> </div>
@ -166,7 +162,7 @@ onMounted(async () => {
color="primary" color="primary"
class="q-mt-md full-width" class="q-mt-md full-width"
unelevated unelevated
rounded filled
dense dense
/> />
<QBtn <QBtn
@ -175,7 +171,7 @@ onMounted(async () => {
color="primary" color="primary"
class="q-mt-md full-width" class="q-mt-md full-width"
unelevated unelevated
rounded filled
dense dense
@click="getStatus = 'stopping'" @click="getStatus = 'stopping'"
/> />

View File

@ -8,7 +8,7 @@ import { useSummaryDialog } from 'src/composables/useSummaryDialog';
import { usePrintService } from 'src/composables/usePrintService'; import { usePrintService } from 'src/composables/usePrintService';
import VnTable from 'src/components/VnTable/VnTable.vue'; import VnTable from 'src/components/VnTable/VnTable.vue';
import InvoiceOutSummary from './Card/InvoiceOutSummary.vue'; import InvoiceOutSummary from './Card/InvoiceOutSummary.vue';
import { toCurrency, toDate } from 'src/filters/index'; import { toCurrency, toDate, dashIfEmpty } from 'src/filters/index';
import { QBtn } from 'quasar'; import { QBtn } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import InvoiceOutFilter from './InvoiceOutFilter.vue'; import InvoiceOutFilter from './InvoiceOutFilter.vue';
@ -16,6 +16,7 @@ import VnRow from 'src/components/ui/VnRow.vue';
import VnRadio from 'src/components/common/VnRadio.vue'; import VnRadio from 'src/components/common/VnRadio.vue';
import VnInput from 'src/components/common/VnInput.vue'; import VnInput from 'src/components/common/VnInput.vue';
import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue'; import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vue';
import DepartmentDescriptorProxy from '../Worker/Department/Card/DepartmentDescriptorProxy.vue';
import VnSection from 'src/components/common/VnSection.vue'; import VnSection from 'src/components/common/VnSection.vue';
const { t } = useI18n(); const { t } = useI18n();
@ -54,6 +55,14 @@ const columns = computed(() => [
name: 'id', name: 'id',
}, },
}, },
{
align: 'left',
name: 'issued',
label: t('invoiceOut.summary.issued'),
component: 'date',
format: (row) => toDate(row.issued),
columnField: { component: null },
},
{ {
align: 'left', align: 'left',
name: 'ref', name: 'ref',
@ -86,6 +95,20 @@ const columns = computed(() => [
component: null, component: null,
}, },
}, },
{
align: 'left',
name: 'departmentFk',
label: t('customer.summary.team'),
cardVisible: true,
component: 'select',
attrs: {
url: 'Departments',
},
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.departmentName),
},
{ {
align: 'left', align: 'left',
name: 'companyFk', name: 'companyFk',
@ -229,6 +252,12 @@ watchEffect(selectedRows);
<CustomerDescriptorProxy :id="row.clientFk" /> <CustomerDescriptorProxy :id="row.clientFk" />
</span> </span>
</template> </template>
<template #column-departmentFk="{ row }">
<span class="link" @click.stop>
{{ dashIfEmpty(row.departmentName) }}
<DepartmentDescriptorProxy :id="row?.departmentFk" />
</span>
</template>
<template #more-create-dialog="{ data }"> <template #more-create-dialog="{ data }">
<div class="row q-col-gutter-xs col-span-2"> <div class="row q-col-gutter-xs col-span-2">
<div class="col-12"> <div class="col-12">

Some files were not shown because too many files have changed in this diff Show More