Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix-front into 8599-ModifyAndCreateInvoiceOutTests

This commit is contained in:
Jon Elias 2025-02-20 07:57:11 +01:00
commit 763d7679a1
35 changed files with 418 additions and 417 deletions

View File

@ -1,6 +1,6 @@
{
"name": "salix-front",
"version": "25.08.0",
"version": "25.10.0",
"description": "Salix frontend",
"productName": "Salix",
"author": "Verdnatura",
@ -71,4 +71,4 @@
"vite": "^6.0.11",
"vitest": "^0.31.1"
}
}
}

View File

@ -30,22 +30,5 @@ export default {
} catch (error) {
console.error(error);
}
form.addEventListener('keyup', function (evt) {
if (evt.key === 'Enter' && !that.$attrs['prevent-submit']) {
const input = evt.target;
if (input.type == 'textarea' && evt.shiftKey) {
evt.preventDefault();
let { selectionStart, selectionEnd } = input;
input.value =
input.value.substring(0, selectionStart) +
'\n' +
input.value.substring(selectionEnd);
selectionStart = selectionEnd = selectionStart + 1;
return;
}
evt.preventDefault();
that.onSubmit();
}
});
},
};

View File

@ -1,6 +1,6 @@
<script setup>
import axios from 'axios';
import { onMounted, onUnmounted, computed, ref, watch, nextTick } from 'vue';
import { onMounted, onUnmounted, computed, ref, watch, nextTick, useAttrs } from 'vue';
import { onBeforeRouteLeave, useRouter, useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
@ -22,6 +22,7 @@ const { validate } = useValidator();
const { notify } = useNotify();
const route = useRoute();
const myForm = ref(null);
const attrs = useAttrs();
const $props = defineProps({
url: {
type: String,
@ -106,14 +107,14 @@ const isLoading = ref(false);
const isResetting = ref(false);
const hasChanges = ref(!$props.observeFormChanges);
const originalData = computed(() => state.get(modelValue));
const formData = ref({});
const formData = ref();
const defaultButtons = computed(() => ({
save: {
dataCy: 'saveDefaultBtn',
color: 'primary',
icon: 'save',
label: 'globals.save',
click: () => myForm.value.onSubmit(false),
click: async () => await save(),
type: 'submit',
},
reset: {
@ -208,8 +209,7 @@ async function fetch() {
}
}
async function save(prevent = false) {
if (prevent) return;
async function save() {
if ($props.observeFormChanges && !hasChanges.value)
return notify('globals.noChanges', 'negative');
@ -284,6 +284,22 @@ function trimData(data) {
return data;
}
async function onKeyup(evt) {
if (evt.key === 'Enter' && !('prevent-submit' in attrs)) {
const input = evt.target;
if (input.type == 'textarea' && evt.shiftKey) {
let { selectionStart, selectionEnd } = input;
input.value =
input.value.substring(0, selectionStart) +
'\n' +
input.value.substring(selectionEnd);
selectionStart = selectionEnd = selectionStart + 1;
return;
}
await save();
}
}
defineExpose({
save,
isLoading,
@ -298,12 +314,12 @@ defineExpose({
<QForm
ref="myForm"
v-if="formData"
@submit="save(!!$event)"
@submit.prevent
@keyup.prevent="onKeyup"
@reset="reset"
class="q-pa-md"
:style="maxWidth ? 'max-width: ' + maxWidth : ''"
id="formModel"
:prevent-submit="$attrs['prevent-submit']"
>
<QCard>
<slot

View File

@ -27,10 +27,15 @@ const formModelRef = ref(null);
const closeButton = ref(null);
const isSaveAndContinue = ref(false);
const onDataSaved = (formData, requestResponse) => {
if (closeButton.value && isSaveAndContinue) closeButton.value.click();
if (closeButton.value && !isSaveAndContinue.value) closeButton.value.click();
emit('onDataSaved', formData, requestResponse);
};
const onClick = async (saveAndContinue) => {
isSaveAndContinue.value = saveAndContinue;
await formModelRef.value.save();
};
const isLoading = computed(() => formModelRef.value?.isLoading);
const reset = computed(() => formModelRef.value?.reset);
@ -78,10 +83,7 @@ defineExpose({
:flat="showSaveAndContinueBtn"
:label="t('globals.save')"
:title="t('globals.save')"
@click="
formModelRef.save();
isSaveAndContinue = false;
"
@click="onClick(false)"
color="primary"
class="q-ml-sm"
:disabled="isLoading"
@ -99,10 +101,7 @@ defineExpose({
:loading="isLoading"
data-cy="FormModelPopup_isSaveAndContinue"
z-max
@click="
isSaveAndContinue = true;
formModelRef.save();
"
@click="onClick(true)"
/>
</div>
</template>

View File

@ -5,6 +5,18 @@ defineProps({ row: { type: Object, required: true } });
</script>
<template>
<span class="q-gutter-x-xs">
<router-link
v-if="row.claim?.claimFk"
:to="{ name: 'ClaimBasicData', params: { id: row.claim?.claimFk } }"
class="link"
>
<QIcon name="vn:claims" size="xs">
<QTooltip>
{{ t('ticketSale.claim') }}:
{{ row.claim?.claimFk }}
</QTooltip>
</QIcon>
</router-link>
<QIcon
v-if="row?.risk"
name="vn:risk"
@ -56,7 +68,7 @@ defineProps({ row: { type: Object, required: true } });
<QTooltip>{{ $t('salesTicketsTable.purchaseRequest') }}</QTooltip>
</QIcon>
<QIcon
v-if="!row?.isTaxDataChecked === 0"
v-if="row?.isTaxDataChecked !== 0"
name="vn:no036"
color="primary"
size="xs"

View File

@ -164,7 +164,6 @@ const app = inject('app');
const editingRow = ref(null);
const editingField = ref(null);
const isTableMode = computed(() => mode.value == TABLE_MODE);
const showRightIcon = computed(() => $props.rightSearch || $props.rightSearchIcon);
const selectRegex = /select/;
const emit = defineEmits(['onFetch', 'update:selected', 'saveChanges']);
const tableModes = [
@ -618,14 +617,6 @@ function cardClick(_, row) {
dense
:options="tableModes.filter((mode) => !mode.disable)"
/>
<QBtn
v-if="showRightIcon"
icon="filter_alt"
class="bg-vn-section-color q-ml-sm"
dense
@click="stateStore.toggleRightDrawer()"
/>
</template>
<template #header-cell="{ col }">
<QTh
@ -783,7 +774,7 @@ function cardClick(_, row) {
<QCardSection
vertical
class="no-margin no-padding"
:class="colsMap.tableActions ? '' : 'fit'"
:class="colsMap.tableActions ? 'w-80' : 'fit'"
>
<!-- Chips -->
<QCardSection
@ -822,11 +813,11 @@ function cardClick(_, row) {
col, index
) of splittedColumns.cardVisible"
:key="col.name"
class="fields"
>
<VnLv :label="col.label + ':'">
<template #value>
<span
class="q-pl-xs"
@click="stopEventPropagation($event)"
>
<slot

View File

@ -57,6 +57,7 @@ describe('FormModel', () => {
vm.state.set(model, formInitialData);
expect(vm.hasChanges).toBe(false);
await vm.$nextTick();
vm.formData.mockKey = 'newVal';
await vm.$nextTick();
expect(vm.hasChanges).toBe(true);
@ -94,8 +95,12 @@ describe('FormModel', () => {
it('should call axios.patch with the right data', async () => {
const spy = vi.spyOn(axios, 'patch').mockResolvedValue({ data: {} });
const { vm } = mount({ propsData: { url, model } });
vm.formData.mockKey = 'newVal';
vm.formData = {};
await vm.$nextTick();
vm.formData = { mockKey: 'newVal' };
await vm.$nextTick();
await vm.save();
expect(spy).toHaveBeenCalled();
vm.formData.mockKey = 'mockVal';

View File

@ -193,22 +193,24 @@ const toModule = computed(() =>
</div>
</QItemLabel>
<QItem>
<QItemLabel class="subtitle" caption>
<QItemLabel class="subtitle">
#{{ getValueFromPath(subtitle) ?? entity.id }}
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
</QItemLabel>
<QBtn
round
flat
dense
size="sm"
icon="content_copy"
color="primary"
@click.stop="copyIdText(entity.id)"
>
<QTooltip>
{{ t('globals.copyId') }}
</QTooltip>
</QBtn>
<!-- </QItemLabel> -->
</QItem>
</QList>
<div class="list-box q-mt-xs">

View File

@ -11,7 +11,7 @@ export async function useCau(res, message) {
const { config, headers, request, status, statusText, data } = res || {};
const { params, url, method, signal, headers: confHeaders } = config || {};
const { message: resMessage, code, name } = data?.error || {};
delete confHeaders.Authorization;
delete confHeaders?.Authorization;
const additionalData = {
path: location.hash,

View File

@ -335,3 +335,7 @@ input::-webkit-inner-spin-button {
border: 1px solid;
box-shadow: 0 4px 6px #00000000;
}
.containerShrinked {
width: 80%;
}

View File

@ -233,7 +233,7 @@ function handleLocation(data, location) {
postcode: data.postalCode,
city: data.city,
province: data.province,
country: data.province.country,
country: data.province?.country,
}"
@update:model-value="(location) => handleLocation(data, location)"
></VnLocation>

View File

@ -114,7 +114,7 @@ function onBeforeSave(data) {
if (isCash.value && shouldSendEmail.value && !data.email)
return notify(t('There is no assigned email for this client'), 'negative');
data.bankFk = data.bankFk.id;
data.bankFk = data.bankFk?.id;
return data;
}
@ -189,7 +189,7 @@ async function getAmountPaid() {
:url-create="urlCreate"
:mapper="onBeforeSave"
@on-data-saved="onDataSaved"
:prevent-submit="true"
prevent-submit
>
<template #form="{ data, validate }">
<span ref="closeButton" class="row justify-end close-icon" v-close-popup>

View File

@ -103,7 +103,7 @@ const refundInvoice = async (withWarehouse) => {
t('refundInvoiceSuccessMessage', {
refundTicket: data[0].id,
}),
'positive'
'positive',
);
};
@ -124,6 +124,13 @@ const showRefundInvoiceForm = () => {
},
});
};
const showExportationLetter = () => {
openReport(`InvoiceOuts/${$props.invoiceOutData.ref}/exportation-pdf`, {
recipientId: $props.invoiceOutData.client.id,
refFk: $props.invoiceOutData.ref,
});
};
</script>
<template>
@ -172,7 +179,7 @@ const showRefundInvoiceForm = () => {
t('Confirm deletion'),
t('Are you sure you want to delete this invoice?'),
deleteInvoice,
redirectToInvoiceOutList
redirectToInvoiceOutList,
)
"
>
@ -185,7 +192,7 @@ const showRefundInvoiceForm = () => {
openConfirmationModal(
'',
t('Are you sure you want to book this invoice?'),
bookInvoice
bookInvoice,
)
"
>
@ -198,7 +205,7 @@ const showRefundInvoiceForm = () => {
openConfirmationModal(
t('Generate PDF invoice document'),
t('Are you sure you want to generate/regenerate the PDF invoice?'),
generateInvoicePdf
generateInvoicePdf,
)
"
>
@ -226,6 +233,14 @@ const showRefundInvoiceForm = () => {
{{ t('Create a single ticket with all the content of the current invoice') }}
</QTooltip>
</QItem>
<QItem
v-if="$props.invoiceOutData.serial === 'E'"
v-ripple
clickable
@click="showExportationLetter()"
>
<QItemSection>{{ t('Show CITES letter') }}</QItemSection>
</QItem>
</template>
<i18n>
@ -255,7 +270,7 @@ es:
Create a single ticket with all the content of the current invoice: Crear un ticket único con todo el contenido de la factura actual
refundInvoiceSuccessMessage: Se ha creado el siguiente ticket de abono {refundTicket}
The email can't be empty: El email no puede estar vacío
Show CITES letter: Ver carta CITES
en:
refundInvoiceSuccessMessage: The following refund ticket have been created {refundTicket}
</i18n>

View File

@ -22,7 +22,7 @@ const states = ref();
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
@ -84,15 +84,6 @@ const states = ref();
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
v-model="params.issued"
:label="t('Issued')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInputDate
@ -110,37 +101,3 @@ const states = ref();
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
search: Contains
clientFk: Customer
fi: FI
amount: Amount
min: Min
max: Max
hasPdf: Has PDF
issued: Issued
created: Created
dued: Dued
es:
params:
search: Contiene
clientFk: Cliente
fi: CIF
amount: Importe
min: Min
max: Max
hasPdf: Tiene PDF
issued: Emitida
created: Creada
dued: Vencida
Customer ID: ID cliente
FI: CIF
Amount: Importe
Has PDF: Tiene PDF
Issued: Fecha emisión
Created: Fecha creación
Dued: Fecha vencimiento
</i18n>

View File

@ -71,14 +71,6 @@ const columns = computed(() => [
inWhere: true,
},
},
{
align: 'left',
name: 'issued',
label: t('invoiceOut.summary.issued'),
component: 'date',
format: (row) => toDate(row.issued),
columnField: { component: null },
},
{
align: 'left',
name: 'clientFk',

View File

@ -10,6 +10,8 @@ import CustomerDescriptorProxy from '../Customer/Card/CustomerDescriptorProxy.vu
import TicketDescriptorProxy from '../Ticket/Card/TicketDescriptorProxy.vue';
import WorkerDescriptorProxy from '../Worker/Card/WorkerDescriptorProxy.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import InvoiceOutNegativeBasesFilter from './InvoiceOutNegativeBasesFilter.vue';
import RightMenu from 'src/components/common/RightMenu.vue';
const { t } = useI18n();
const tableRef = ref();
@ -97,16 +99,19 @@ const columns = computed(() => [
align: 'left',
name: 'isActive',
label: t('invoiceOut.negativeBases.active'),
component: 'checkbox',
},
{
align: 'left',
name: 'hasToInvoice',
label: t('invoiceOut.negativeBases.hasToInvoice'),
component: 'checkbox',
},
{
align: 'left',
name: 'hasVerifiedData',
name: 'isTaxDataChecked',
label: t('invoiceOut.negativeBases.verifiedData'),
component: 'checkbox',
},
{
align: 'left',
@ -142,7 +147,7 @@ const downloadCSV = async () => {
await invoiceOutGlobalStore.getNegativeBasesCsv(
userParams.from,
userParams.to,
filterParams
filterParams,
);
};
</script>
@ -154,6 +159,11 @@ const downloadCSV = async () => {
</QBtn>
</template>
</VnSubToolbar>
<RightMenu>
<template #right-panel>
<InvoiceOutNegativeBasesFilter data-key="negativeFilter" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="negativeFilter"
@ -174,6 +184,7 @@ const downloadCSV = async () => {
auto-load
:is-editable="false"
:use-model="true"
:right-search="false"
>
<template #column-clientId="{ row }">
<span class="link" @click.stop>

View File

@ -2,9 +2,10 @@
import { useI18n } from 'vue-i18n';
import VnFilterPanel from 'src/components/ui/VnFilterPanel.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnInputDate from 'components/common/VnInputDate.vue';
import VnInputNumber from 'src/components/common/VnInputNumber.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnSelectWorker from 'src/components/common/VnSelectWorker.vue';
const { t } = useI18n();
const props = defineProps({
@ -24,11 +25,11 @@ const props = defineProps({
>
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<strong>{{ t(`invoiceOut.params.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
<template #body="{ params }">
<template #body="{ params, searchFn }">
<QItem>
<QItemSection>
<VnInputDate
@ -49,38 +50,70 @@ const props = defineProps({
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.company"
<VnSelect
url="Companies"
:label="t('globals.company')"
is-outlined
/>
v-model="params.company"
option-label="code"
option-value="code"
dense
outlined
rounded
@update:model-value="searchFn()"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.code }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
<VnSelect
url="Countries"
:label="t('globals.params.countryFk')"
v-model="params.country"
:label="t('globals.country')"
is-outlined
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.clientId"
:label="t('invoiceOut.negativeBases.clientId')"
is-outlined
/>
option-label="name"
option-value="name"
outlined
dense
rounded
@update:model-value="searchFn()"
>
<template #option="scope">
<QItem v-bind="scope.itemProps">
<QItemSection>
<QItemLabel>
{{ scope.opt?.name }}
</QItemLabel>
<QItemLabel caption>
{{ `#${scope.opt?.id}` }}
</QItemLabel>
</QItemSection>
</QItem>
</template>
</VnSelect>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.clientSocialName"
<VnSelect
url="Clients"
:label="t('globals.client')"
is-outlined
v-model="params.clientId"
outlined
dense
rounded
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>
@ -90,15 +123,18 @@ const props = defineProps({
v-model="params.amount"
:label="t('globals.amount')"
is-outlined
:positive="false"
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnInput
v-model="params.comercialName"
<VnSelectWorker
:label="t('invoiceOut.negativeBases.comercial')"
v-model="params.workerName"
option-value="name"
is-outlined
@update:model-value="searchFn()"
/>
</QItemSection>
</QItem>

View File

@ -4,7 +4,7 @@ invoiceOut:
params:
company: Company
country: Country
clientId: Client ID
clientId: Client
clientSocialName: Client
taxableBase: Base
ticketFk: Ticket
@ -12,6 +12,18 @@ invoiceOut:
hasToInvoice: Has to invoice
hasVerifiedData: Verified data
workerName: Worker
isTaxDataChecked: Verified data
amount: Amount
clientFk: Client
companyFk: Company
created: Created
dued: Dued
customsAgentFk: Custom Agent
ref: Reference
fi: FI
min: Min
max: Max
hasPdf: Has PDF
card:
issued: Issued
customerCard: Customer card
@ -53,7 +65,7 @@ invoiceOut:
active: Active
hasToInvoice: Has to Invoice
verifiedData: Verified Data
comercial: Commercial
comercial: Sales person
errors:
downloadCsvFailed: CSV download failed
invoiceOutModule:

View File

@ -4,7 +4,7 @@ invoiceOut:
params:
company: Empresa
country: País
clientId: ID del cliente
clientId: Cliente
clientSocialName: Cliente
taxableBase: Base
ticketFk: Ticket
@ -12,6 +12,18 @@ invoiceOut:
hasToInvoice: Debe facturar
hasVerifiedData: Datos verificados
workerName: Comercial
isTaxDataChecked: Datos comprobados
amount: Importe
clientFk: Cliente
companyFk: Empresa
created: Creada
dued: Vencida
customsAgentFk: Agente aduanas
ref: Referencia
fi: CIF
min: Min
max: Max
hasPdf: Tiene PDF
card:
issued: Fecha emisión
customerCard: Ficha del cliente

View File

@ -6,6 +6,8 @@ import VnLv from 'components/ui/VnLv.vue';
import { dashIfEmpty, toDate } from 'src/filters';
import RouteDescriptorMenu from 'pages/Route/Card/RouteDescriptorMenu.vue';
import filter from './RouteFilter.js';
import useCardDescription from 'src/composables/useCardDescription';
import axios from 'axios';
const $props = defineProps({
id: {
@ -16,7 +18,6 @@ const $props = defineProps({
});
const route = useRoute();
const { t } = useI18n();
const zone = ref();
const zoneId = ref();
const entityId = computed(() => {
@ -50,9 +51,9 @@ onMounted(async () => {
width="lg-width"
>
<template #body="{ entity }">
<VnLv :label="t('Date')" :value="toDate(entity?.dated)" />
<VnLv :label="t('Agency')" :value="entity?.agencyMode?.name" />
<VnLv :label="t('Zone')" :value="zone" />
<VnLv :label="$t('Date')" :value="toDate(entity?.dated)" />
<VnLv :label="$t('Agency')" :value="entity?.agencyMode?.name" />
<VnLv :label="$t('Zone')" :value="zone" />
<VnLv
:label="$t('Volume')"
:value="`${dashIfEmpty(entity?.m3)} / ${dashIfEmpty(

View File

@ -14,7 +14,6 @@ export default {
'started',
'finished',
'cost',
'zoneFk',
'isOk',
],
include: [
@ -23,7 +22,6 @@ export default {
relation: 'vehicle',
scope: { fields: ['id', 'm3'] },
},
{ relation: 'zone', scope: { fields: ['id', 'name'] } },
{
relation: 'worker',
scope: {

View File

@ -28,52 +28,6 @@ const defaultInitialData = {
isOk: false,
};
const maxDistance = ref();
const routeFilter = {
fields: [
'id',
'workerFk',
'agencyModeFk',
'dated',
'm3',
'warehouseFk',
'description',
'vehicleFk',
'kmStart',
'kmEnd',
'started',
'finished',
'cost',
'isOk',
],
include: [
{ relation: 'agencyMode', scope: { fields: ['id', 'name'] } },
{
relation: 'vehicle',
scope: { fields: ['id', 'm3'] },
},
{
relation: 'ticket',
scope: {
fields: ['id', 'name', 'zoneFk'],
include: { relation: 'zone', scope: { fields: ['id', 'name'] } },
},
},
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['id'],
include: { relation: 'emailUser', scope: { fields: ['email'] } },
},
},
},
},
],
};
const onSave = (data, response) => {
if (isNew) {
axios.post(`Routes/${response?.id}/updateWorkCenter`);

View File

@ -16,6 +16,7 @@ import useNotify from 'src/composables/useNotify.js';
import { useState } from 'src/composables/useState';
import { toDateTimeFormat } from 'src/filters/date.js';
import axios from 'axios';
import TicketProblems from 'src/components/TicketProblems.vue';
const state = useState();
const { t } = useI18n();
@ -286,71 +287,7 @@ watch(
</span>
</QTooltip>
</QIcon>
<QIcon
v-if="row.isTaxDataChecked === 0"
color="primary"
name="vn:no036"
size="xs"
>
<QTooltip>
{{ t('futureTickets.noVerified') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasTicketRequest"
color="primary"
name="vn:buyrequest"
size="xs"
>
<QTooltip>
{{ t('futureTickets.purchaseRequest') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.itemShortage"
color="primary"
name="vn:unavailable"
size="xs"
>
<QTooltip>
{{ t('ticketSale.noVisible') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.isFreezed"
color="primary"
name="vn:frozen"
size="xs"
>
<QTooltip>
{{ t('futureTickets.clientFrozen') }}
</QTooltip>
</QIcon>
<QIcon v-if="row.risk" color="primary" name="vn:risk" size="xs">
<QTooltip>
{{ t('futureTickets.risk') }}: {{ row.risk }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasComponentLack"
color="primary"
name="vn:components"
size="xs"
>
<QTooltip>
{{ t('futureTickets.componentLack') }}
</QTooltip>
</QIcon>
<QIcon
v-if="row.hasRounding"
color="primary"
name="sync_problem"
size="xs"
>
<QTooltip>
{{ t('futureTickets.rounding') }}
</QTooltip>
</QIcon>
<TicketProblems :row />
</span>
</template>
<template #column-id="{ row }">

View File

@ -2,6 +2,7 @@
import { onMounted, ref, computed, watch } from 'vue';
import { QBtn } from 'quasar';
import { useI18n } from 'vue-i18n';
import { useRoute } from 'vue-router';
import SupplierDescriptorProxy from 'src/pages/Supplier/Card/SupplierDescriptorProxy.vue';
import TravelDescriptorProxy from 'src/pages/Travel/Card/TravelDescriptorProxy.vue';
@ -22,6 +23,8 @@ import VnPopup from 'src/components/common/VnPopup.vue';
const stateStore = useStateStore();
const { t } = useI18n();
const { openReport } = usePrintService();
const route = useRoute();
const tableParams = ref();
const shippedFrom = ref(Date.vnNew());
const landedTo = ref(Date.vnNew());
@ -143,7 +146,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('globals.pageTitles.supplier'),
label: t('extraCommunity.cargoShip'),
field: 'cargoSupplierNickname',
name: 'cargoSupplierNickname',
align: 'left',
@ -171,7 +174,7 @@ const columns = computed(() => [
? value.reduce((sum, entry) => {
return sum + (entry.invoiceAmount || 0);
}, 0)
: 0
: 0,
),
},
{
@ -200,7 +203,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('kg'),
label: t('extraCommunity.kg'),
field: 'kg',
name: 'kg',
align: 'left',
@ -208,7 +211,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('physicKg'),
label: t('extraCommunity.physicKg'),
field: 'loadedKg',
name: 'loadedKg',
align: 'left',
@ -232,7 +235,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('shipped'),
label: t('extraCommunity.shipped'),
field: 'shipped',
name: 'shipped',
align: 'left',
@ -249,7 +252,7 @@ const columns = computed(() => [
sortable: true,
},
{
label: t('landed'),
label: t('extraCommunity.landed'),
field: 'landed',
name: 'landed',
align: 'left',
@ -258,7 +261,7 @@ const columns = computed(() => [
format: (value) => toDate(value),
},
{
label: t('notes'),
label: t('extraCommunity.notes'),
field: '',
name: 'notes',
align: 'center',
@ -284,7 +287,7 @@ watch(
if (!arrayData.store.data) return;
onStoreDataChange();
},
{ deep: true, immediate: true }
{ deep: true, immediate: true },
);
const openReportPdf = () => {
@ -451,13 +454,24 @@ const getColor = (percentage) => {
for (const { value, className } of travelKgPercentages.value)
if (percentage > value) return className;
};
const filteredEntries = (entries) => {
if (!tableParams?.value?.entrySupplierFk) return entries;
return entries?.filter(
(entry) => entry.supplierFk === tableParams?.value?.entrySupplierFk,
);
};
watch(route, () => {
tableParams.value = JSON.parse(route.query.table);
});
</script>
<template>
<VnSearchbar
data-key="ExtraCommunity"
:limit="20"
:label="t('searchExtraCommunity')"
:label="t('extraCommunity.searchExtraCommunity')"
/>
<RightMenu>
<template #right-panel>
@ -521,7 +535,7 @@ const getColor = (percentage) => {
? tableColumnComponents[col.name].event(
rows[props.rowIndex][col.field],
col.field,
props.rowIndex
props.rowIndex,
)
: {}
"
@ -546,7 +560,7 @@ const getColor = (percentage) => {
},
{
link: ['id', 'cargoSupplierNickname'].includes(
col.name
col.name,
),
},
]"
@ -564,9 +578,8 @@ const getColor = (percentage) => {
</component>
</QTd>
</QTr>
<QTr
v-for="(entry, index) in props.row.entries"
v-for="(entry, index) in filteredEntries(props.row.entries)"
:key="index"
:props="props"
class="bg-vn-secondary-row cursor-pointer"
@ -598,7 +611,7 @@ const getColor = (percentage) => {
name="warning"
color="negative"
size="md"
:title="t('requiresInspection')"
:title="t('extraCommunity.requiresInspection')"
>
</QIcon>
</QTd>
@ -709,24 +722,3 @@ const getColor = (percentage) => {
width: max-content;
}
</style>
<i18n>
en:
searchExtraCommunity: Search for extra community shipping
kg: BI. KG
physicKg: Phy. KG
shipped: W. shipped
landed: W. landed
requiresInspection: Requires inspection
BIP: Boder Inspection Point
notes: Notes
es:
searchExtraCommunity: Buscar por envío extra comunitario
kg: KG Bloq.
physicKg: KG físico
shipped: F. envío
landed: F. llegada
notes: Notas
Open as PDF: Abrir como PDF
requiresInspection: Requiere inspección
BIP: Punto de Inspección Fronteriza
</i18n>

View File

@ -79,7 +79,7 @@ warehouses();
<VnFilterPanel :data-key="props.dataKey" :search-button="true">
<template #tags="{ tag, formatFn }">
<div class="q-gutter-x-xs">
<strong>{{ t(`params.${tag.label}`) }}: </strong>
<strong>{{ t(`extraCommunity.filter.${tag.label}`) }}: </strong>
<span>{{ formatFn(tag.value) }}</span>
</div>
</template>
@ -92,7 +92,7 @@ warehouses();
<QItem>
<QItemSection>
<VnInput
:label="t('params.reference')"
:label="t('extraCommunity.filter.reference')"
v-model="params.reference"
is-outlined
/>
@ -103,7 +103,7 @@ warehouses();
<QInput
v-model="params.totalEntries"
type="number"
:label="t('params.totalEntries')"
:label="t('extraCommunity.filter.totalEntries')"
dense
outlined
rounded
@ -133,10 +133,10 @@ warehouses();
<QItem>
<QItemSection>
<VnSelect
:label="t('params.agencyModeFk')"
:label="t('extraCommunity.filter.agencyModeFk')"
v-model="params.agencyModeFk"
:options="agenciesOptions"
option-value="agencyFk"
option-value="id"
option-label="name"
hide-selected
dense
@ -148,7 +148,7 @@ warehouses();
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.shippedFrom')"
:label="t('extraCommunity.filter.shippedFrom')"
v-model="params.shippedFrom"
@update:model-value="searchFn()"
is-outlined
@ -158,7 +158,7 @@ warehouses();
<QItem>
<QItemSection>
<VnInputDate
:label="t('params.landedTo')"
:label="t('extraCommunity.filter.landedTo')"
v-model="params.landedTo"
@update:model-value="searchFn()"
is-outlined
@ -168,7 +168,7 @@ warehouses();
<QItem v-if="warehousesByContinent[params.continent]">
<QItemSection>
<VnSelect
:label="t('params.warehouseOutFk')"
:label="t('extraCommunity.filter.warehouseOutFk')"
v-model="params.warehouseOutFk"
:options="warehousesByContinent[params.continent]"
option-value="id"
@ -183,7 +183,7 @@ warehouses();
<QItem v-else>
<QItemSection>
<VnSelect
:label="t('params.warehouseOutFk')"
:label="t('extraCommunity.filter.warehouseOutFk')"
v-model="params.warehouseOutFk"
:options="warehousesOptions"
option-value="id"
@ -198,7 +198,7 @@ warehouses();
<QItem>
<QItemSection>
<VnSelect
:label="t('params.warehouseInFk')"
:label="t('extraCommunity.filter.warehouseInFk')"
v-model="params.warehouseInFk"
:options="warehousesOptions"
option-value="id"
@ -213,6 +213,7 @@ warehouses();
<QItem>
<QItemSection>
<VnSelectSupplier
:label="t('extraCommunity.cargoShip')"
v-model="params.cargoSupplierFk"
hide-selected
dense
@ -221,10 +222,21 @@ warehouses();
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelectSupplier
v-model="params.entrySupplierFk"
hide-selected
dense
outlined
rounded
/>
</QItemSection>
</QItem>
<QItem>
<QItemSection>
<VnSelect
:label="t('params.continent')"
:label="t('extraCommunity.filter.continent')"
v-model="params.continent"
:options="continentsOptions"
option-value="code"
@ -240,30 +252,3 @@ warehouses();
</template>
</VnFilterPanel>
</template>
<i18n>
en:
params:
id: Id
reference: Reference
totalEntries: Total entries
agencyModeFk: Agency
warehouseInFk: Warehouse In
warehouseOutFk: Warehouse Out
shippedFrom: Shipped from
landedTo: Landed to
cargoSupplierFk: Supplier
continent: Continent out
es:
params:
id: Id
reference: Referencia
totalEntries: Ent. totales
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: Alm. salida
shippedFrom: Llegada desde
landedTo: Llegada hasta
cargoSupplierFk: Proveedor
continent: Cont. Salida
</i18n>

View File

@ -0,0 +1,22 @@
extraCommunity:
cargoShip: Cargo ship
searchExtraCommunity: Search for extra community shipping
kg: BI. KG
physicKg: Phy. KG
shipped: W. shipped
landed: W. landed
requiresInspection: Requires inspection
BIP: Boder Inspection Point
notes: Notes
filter:
id: Id
reference: Reference
totalEntries: Total entries
agencyModeFk: Agency
warehouseInFk: Warehouse In
warehouseOutFk: Warehouse Out
shippedFrom: Shipped from
landedTo: Landed to
cargoSupplierFk: Cargo supplier
continent: Continent out
entrySupplierFk: Supplier

View File

@ -0,0 +1,23 @@
extraCommunity:
cargoShip: Carguera
searchExtraCommunity: Buscar por envío extra comunitario
kg: KG Bloq.
physicKg: KG físico
shipped: F. envío
landed: F. llegada
notes: Notas
Open as PDF: Abrir como PDF
requiresInspection: Requiere inspección
BIP: Punto de Inspección Fronteriza
filter:
id: Id
reference: Referencia
totalEntries: Ent. totales
agencyModeFk: Agencia
warehouseInFk: Alm. entrada
warehouseOutFk: Alm. salida
shippedFrom: Llegada desde
landedTo: Llegada hasta
cargoSupplierFk: Carguera
continent: Cont. Salida
entrySupplierFk: Proveedor

View File

@ -25,7 +25,7 @@ const setFilteredAddresses = (data) => {
@on-fetch="(data) => (validAddresses = data)"
/>
<FetchData url="Addresses" auto-load @on-fetch="setFilteredAddresses" />
<FormModel auto-load model="zone">
<FormModel auto-load model="Zone">
<template #form="{ data, validate }">
<VnRow>
<VnInput
@ -33,6 +33,7 @@ const setFilteredAddresses = (data) => {
:label="t('Name')"
clearable
v-model="data.name"
:required="true"
/>
</VnRow>
<VnRow>
@ -83,7 +84,7 @@ const setFilteredAddresses = (data) => {
type="number"
min="0"
/>
<VnInputTime v-model="data.hour" :label="t('Closing')" />
<VnInputTime v-model="data.hour" :label="t('Closing')" :required="true" />
</VnRow>
<VnRow>
@ -92,7 +93,7 @@ const setFilteredAddresses = (data) => {
:label="t('Price')"
type="number"
min="0"
required="true"
:required="true"
clearable
/>
<VnInput
@ -100,7 +101,7 @@ const setFilteredAddresses = (data) => {
:label="t('Price optimum')"
type="number"
min="0"
required="true"
:required="true"
clearable
/>
</VnRow>

View File

@ -38,7 +38,12 @@ const agencies = ref([]);
<template #body="{ params, searchFn }">
<QItem>
<QItemSection>
<VnInput :label="t('list.name')" v-model="params.name" is-outlined />
<VnInput
:label="t('list.name')"
v-model="params.name"
is-outlined
data-cy="zoneFilterPanelNameInput"
/>
</QItemSection>
</QItem>
<QItem>
@ -53,6 +58,7 @@ const agencies = ref([]);
dense
outlined
rounded
data-cy="zoneFilterPanelAgencySelect"
>
</VnSelect>
</QItemSection>

View File

@ -65,7 +65,6 @@ const tableFilter = {
const columns = computed(() => [
{
align: 'left',
name: 'id',
label: t('list.id'),
chip: {
@ -75,6 +74,8 @@ const columns = computed(() => [
columnFilter: {
inWhere: true,
},
columnClass: 'shrink-column',
component: 'number',
},
{
align: 'left',
@ -106,7 +107,6 @@ const columns = computed(() => [
format: (row, dashIfEmpty) => dashIfEmpty(row?.agencyMode?.name),
},
{
align: 'left',
name: 'price',
label: t('list.price'),
cardVisible: true,
@ -114,9 +114,11 @@ const columns = computed(() => [
columnFilter: {
inWhere: true,
},
columnClass: 'shrink-column',
component: 'number',
},
{
align: 'left',
align: 'center',
name: 'hour',
label: t('list.close'),
cardVisible: true,
@ -129,6 +131,7 @@ const columns = computed(() => [
label: t('list.addressFk'),
cardVisible: true,
columnFilter: false,
columnClass: 'expand',
},
{
align: 'right',
@ -177,67 +180,73 @@ function formatRow(row) {
<ZoneFilterPanel data-key="ZonesList" />
</template>
</RightMenu>
<VnTable
ref="tableRef"
data-key="ZonesList"
url="Zones"
:create="{
urlCreate: 'Zones',
title: t('list.createZone'),
onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`),
formInitialData: {},
}"
:user-filter="tableFilter"
:columns="columns"
redirect="zone"
:right-search="false"
>
<template #column-addressFk="{ row }">
{{ dashIfEmpty(formatRow(row)) }}
</template>
<template #more-create-dialog="{ data }">
<VnSelect
url="AgencyModes"
v-model="data.agencyModeFk"
option-value="id"
option-label="name"
:label="t('list.agency')"
/>
<VnInput
v-model="data.price"
:label="t('list.price')"
min="0"
type="number"
required="true"
/>
<VnInput
v-model="data.bonus"
:label="t('zone.bonus')"
min="0"
type="number"
/>
<VnInput
v-model="data.travelingDays"
:label="t('zone.travelingDays')"
type="number"
min="0"
/>
<VnInputTime v-model="data.hour" :label="t('list.close')" />
<VnSelect
url="Warehouses"
v-model="data.warehouseFK"
option-value="id"
option-label="name"
:label="t('list.warehouse')"
:options="warehouseOptions"
/>
<QCheckbox
v-model="data.isVolumetric"
:label="t('list.isVolumetric')"
:toggle-indeterminate="false"
/>
</template>
</VnTable>
<div class="table-container">
<div class="column items-center">
<VnTable
ref="tableRef"
data-key="ZonesList"
url="Zones"
:create="{
urlCreate: 'Zones',
title: t('list.createZone'),
onDataSaved: ({ id }) => tableRef.redirect(`${id}/location`),
formInitialData: {},
}"
:user-filter="tableFilter"
:columns="columns"
redirect="zone"
:right-search="false"
table-height="85vh"
order="id ASC"
>
<template #column-addressFk="{ row }">
{{ dashIfEmpty(formatRow(row)) }}
</template>
<template #more-create-dialog="{ data }">
<VnSelect
url="AgencyModes"
v-model="data.agencyModeFk"
option-value="id"
option-label="name"
:label="t('list.agency')"
/>
<VnInput
v-model="data.price"
:label="t('list.price')"
min="0"
type="number"
required="true"
/>
<VnInput
v-model="data.bonus"
:label="t('zone.bonus')"
min="0"
type="number"
/>
<VnInput
v-model="data.travelingDays"
:label="t('zone.travelingDays')"
type="number"
min="0"
/>
<VnInputTime v-model="data.hour" :label="t('list.close')" />
<VnSelect
url="Warehouses"
v-model="data.warehouseFK"
option-value="id"
option-label="name"
:label="t('list.warehouse')"
:options="warehouseOptions"
/>
<QCheckbox
v-model="data.isVolumetric"
:label="t('list.isVolumetric')"
:toggle-indeterminate="false"
/>
</template>
</VnTable>
</div>
</div>
</template>
<i18n>
@ -245,3 +254,20 @@ es:
Search zone: Buscar zona
You can search zones by id or name: Puedes buscar zonas por id o nombre
</i18n>
<style lang="scss" scoped>
.table-container {
display: flex;
justify-content: center;
}
.column {
display: flex;
flex-direction: column;
align-items: center;
min-width: 70%;
}
:deep(.shrink-column) {
width: 8%;
}
</style>

View File

@ -56,7 +56,7 @@ onMounted(() => weekdayStore.initStore());
<ZoneSearchbar />
<VnSubToolbar />
<QPage class="column items-center q-pa-md">
<QCard class="full-width q-pa-md">
<QCard class="containerShrinked q-pa-md">
<div
v-for="(detail, index) in details"
:key="index"

View File

@ -44,6 +44,8 @@ summary:
filterPanel:
name: Name
agencyModeFk: Agency
id: ID
price: Price
deliveryPanel:
pickup: Pick up
delivery: Delivery

View File

@ -45,6 +45,8 @@ summary:
filterPanel:
name: Nombre
agencyModeFk: Agencia
id: ID
price: Precio
deliveryPanel:
pickup: Recogida
delivery: Entrega

View File

@ -1,4 +1,5 @@
describe('ZoneList', () => {
const agency = 'inhouse pickup';
beforeEach(() => {
cy.viewport(1280, 720);
cy.login('developer');
@ -6,11 +7,15 @@ describe('ZoneList', () => {
});
it('should filter by agency', () => {
cy.get('input[aria-label="Agency"]').type('{downArrow}{enter}');
cy.dataCy('zoneFilterPanelNameInput').type('{downArrow}{enter}');
});
it('should open the zone summary', () => {
cy.get('input[aria-label="Name"]').type('zone refund');
cy.get('.q-scrollarea__content > .q-btn--standard > .q-btn__content').click();
cy.dataCy('zoneFilterPanelAgencySelect').type(agency);
cy.get('.q-menu .q-item').contains(agency).click();
cy.get(':nth-child(1) > [data-col-field="agencyModeFk"]').should(
'include.text',
agency,
);
});
});