fix: refs #7553 & #7356 migration
gitea/salix-front/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Jon Elias 2024-08-19 11:30:20 +02:00
parent 954a6ff6c0
commit 4a57c53c3d
15 changed files with 730 additions and 346 deletions

View File

@ -49,6 +49,10 @@ const $props = defineProps({
type: Object,
default: null,
},
bottom: {
type: Object,
default: null,
},
cardClass: {
type: String,
default: 'flex-one',
@ -388,6 +392,11 @@ defineExpose({
@click="stateStore.toggleRightDrawer()"
/>
</template>
<template #top>
<div class="full-width-slot">
<slot name="before-header" />
</div>
</template>
<template #header-cell="{ col }">
<QTh v-if="col.visible ?? true">
<div
@ -482,6 +491,9 @@ defineExpose({
/>
</QTd>
</template>
<template #bottom v-if="bottom">
<slot name="bottom-table" />
</template>
<template #item="{ row, colsMap }">
<component
:is="$props.redirect ? 'router-link' : 'span'"
@ -780,4 +792,13 @@ es:
cursor: text;
user-select: all;
}
.full-width-slot {
width: 100%;
display: flex;
text-align: center;
color: var(--vn-text-color);
margin-bottom: -1%;
background-color: var(--vn-header-color);
}
</style>

View File

@ -28,7 +28,7 @@ export function useArrayData(key = useRoute().meta.moduleName, userOptions) {
delete params.filter;
store.userParams = { ...params, ...store.userParams };
store.userFilter = { ...filter, ...store.userFilter };
if (filter.order) store.order = filter.order;
if (filter?.order) store.order = filter.order;
}
});

View File

@ -578,6 +578,8 @@ ticket:
warehouse: Warehouse
customerCard: Customer card
alias: Alias
ticketList: Ticket List
newOrder: New Order
boxing:
expedition: Expedition
item: Item
@ -628,6 +630,11 @@ ticket:
weight: Weight
goTo: Go to
summaryAmount: Summary
purchaseRequest: Purchase request
service: Service
description: Description
attender: Attender
ok: Ok
create:
client: Client
address: Address
@ -651,7 +658,6 @@ invoiceOut:
client: Client
company: Company
customerCard: Customer card
ticketList: Ticket List
summary:
issued: Issued
created: Created

View File

@ -584,6 +584,8 @@ ticket:
warehouse: Almacén
customerCard: Ficha del cliente
alias: Alias
ticketList: Listado de tickets
newOrder: Nuevo pedido
boxing:
expedition: Expedición
item: Artículo
@ -634,6 +636,10 @@ ticket:
weight: Peso
goTo: Ir a
summaryAmount: Resumen
purchaseRequest: Petición de compra
service: Servicio
description: Descripción
attender: Comprador
create:
client: Cliente
address: Dirección

View File

@ -87,6 +87,11 @@ const filter = {
};
const data = ref(useCardDescription());
function ticketFilter(ticket) {
console.log('ticket: ', ticket);
return JSON.stringify({ clientFk: ticket.clientFk });
}
</script>
<template>
@ -159,6 +164,22 @@ const data = ref(useCardDescription());
>
<QTooltip>{{ t('ticket.card.customerCard') }}</QTooltip>
</QBtn>
<QBtn
size="md"
icon="vn:ticket"
color="primary"
:to="{ name: 'TicketList', query: { table: ticketFilter(entity) } }"
>
<QTooltip>{{ t('ticket.card.ticketList') }}</QTooltip>
</QBtn>
<QBtn
size="md"
icon="vn:basketadd"
color="primary"
:to="{ name: 'OrderList' }"
>
<QTooltip>{{ t('ticket.card.newOrder') }}</QTooltip>
</QBtn>
</QCardActions>
</template>
</CardDescriptor>

View File

@ -24,6 +24,7 @@ const { openReport, sendEmail } = usePrintService();
const ticket = ref(props.ticket);
const ticketId = currentRoute.value.params.id;
const showWeightDialog = ref(false);
const actions = {
clone: async () => {
const opts = { message: t('Ticket cloned'), type: 'positive' };
@ -153,30 +154,66 @@ function openConfirmDialog(callback) {
},
});
}
function createPdfDialog() {
dialog({
component: VnConfirm,
componentProps: {
promise: createPdfInvoice,
},
});
}
async function createPdfInvoice() {
const { data } = await axios.get('invoiceOuts', {
params: {
filter: JSON.stringify({
where: { ref: ticket.value.refFk },
}),
},
});
const invoiceId = data[0].id;
const { response } = await axios.post(`InvoiceOuts/${invoiceId}/createPdf`);
if (!response) {
notify({
message: 'The invoice PDF document has been regenerated',
type: 'positive',
});
}
}
function setWeightToTicket(weight) {
console.log('weight: ', weight);
const { data } = axios.patch(`Tickets/${ticketId}`, {
weight: weight,
});
console.log('data: ', data);
if (data) window.location.reload();
}
</script>
<template>
<QItem v-ripple clickable>
<QItemSection avatar>
<QIcon name="picture_as_pdf" />
</QItemSection>
<QItemSection>{{ t('Open Delivery Note...') }}</QItemSection>
<QItemSection>{{ t('Show Delivery Note...') }}</QItemSection>
<QItemSection side>
<QIcon name="keyboard_arrow_right" />
</QItemSection>
<QMenu anchor="top end" self="top start" auto-close bordered>
<QList>
<QItem @click="openDeliveryNote('deliveryNote')" v-ripple clickable>
<QItemSection>{{ t('With prices') }}</QItemSection>
<QItemSection>{{ t('as PDF') }}</QItemSection>
</QItem>
<QItem @click="openDeliveryNote('withoutPrices')" v-ripple clickable>
<QItemSection>{{ t('Without prices') }}</QItemSection>
<QItemSection>{{ t('as PDF without prices') }}</QItemSection>
</QItem>
<QItem
@click="openDeliveryNote('deliveryNote', 'csv')"
v-ripple
clickable
>
<QItemSection>{{ t('As CSV') }}</QItemSection>
<QItemSection>{{ t('as CSV') }}</QItemSection>
</QItem>
</QList>
</QMenu>
@ -196,21 +233,21 @@ function openConfirmDialog(callback) {
v-ripple
clickable
>
<QItemSection>{{ t('With prices') }}</QItemSection>
<QItemSection>{{ t('Send PDF') }}</QItemSection>
</QItem>
<QItem
@click="sendDeliveryNoteConfirmation('withoutPrices')"
v-ripple
clickable
>
<QItemSection>{{ t('Without prices') }}</QItemSection>
<QItemSection>{{ t('Send PDF to tablet') }}</QItemSection>
</QItem>
<QItem
@click="sendDeliveryNoteConfirmation('deliveryNote', 'csv')"
v-ripple
clickable
>
<QItemSection>{{ t('As CSV') }}</QItemSection>
<QItemSection>{{ t('Send CSV') }}</QItemSection>
</QItem>
</QList>
</QMenu>
@ -219,7 +256,7 @@ function openConfirmDialog(callback) {
<QItemSection avatar>
<QIcon name="receipt" />
</QItemSection>
<QItemSection>{{ t('Open Proforma Invoice') }}</QItemSection>
<QItemSection>{{ t('Show Proforma') }}</QItemSection>
</QItem>
<QItem v-ripple clickable>
<QItemSection avatar>
@ -235,24 +272,54 @@ function openConfirmDialog(callback) {
<QItemSection>{{ t('Pending payment') }}</QItemSection>
</QItem>
<QItem @click="showSmsDialog('minAmount')" v-ripple clickable>
<QItemSection>{{ t('Minimum amount') }}</QItemSection>
<QItemSection>{{ t('Minimum import') }}</QItemSection>
</QItem>
<QItem
@click="showSmsDialogWithChanges('orderChanges')"
v-ripple
clickable
>
<QItemSection>{{ t('Order changes') }}</QItemSection>
<QItemSection>{{ t('Notify changes') }}</QItemSection>
</QItem>
</QList>
</QMenu>
</QItem>
<QItem @click="createPdfDialog()" v-ripple clickable>
<QItemSection avatar>
<QIcon name="picture_as_pdf" />
</QItemSection>
<QItemSection>{{ t('Regenerate PDF invoice') }}</QItemSection>
</QItem>
<QItem @click="openConfirmDialog('clone')" v-ripple clickable>
<QItemSection avatar>
<QIcon name="content_copy" />
</QItemSection>
<QItemSection>{{ t('To clone ticket') }}</QItemSection>
</QItem>
<QItem @click="showWeightDialog = !showWeightDialog" v-ripple clickable>
<QItemSection avatar>
<QIcon name="content_paste" />
</QItemSection>
<QItemSection>{{ t('Set ticket weight') }}</QItemSection>
</QItem>
<QDialog v-model="showWeightDialog" transition-show="scale" transition-hide="scale">
<QCard>
<QCardSection>
<QInput
v-model="weight"
type="number"
:label="t('Weight')"
@update:model-value="setWeightToTicket"
/>
<QBtn flat v-close-popup class="text-primary">
{{ t('Close') }}
</QBtn>
<QBTn flat @click="setWeightToTicket" class="text-primary">
{{ t('Save') }}
</QBTn>
</QCardSection>
</QCard>
</QDialog>
<template v-if="!ticket.isDeleted">
<QSeparator />
<QItem @click="openConfirmDialog('remove')" v-ripple clickable>
@ -266,20 +333,26 @@ function openConfirmDialog(callback) {
<i18n>
es:
Open Delivery Note...: Abrir albarán...
Show Delivery Note...: Ver albarán...
Send Delivery Note...: Enviar albarán...
With prices: Con precios
Without prices: Sin precios
As CSV: Como CSV
Open Proforma Invoice: Abrir factura proforma
as PDF: como PDF
as PDF without prices: como PDF sin precios
as CSV: Como CSV
Send PDF: Enviar PDF
Send PDF to tablet: Enviar PDF a tablet
Send CSV: Enviar CSV
Show Proforma: Ver proforma
Delete ticket: Eliminar ticket
Send SMS...: Enviar SMS
Pending payment: Pago pendiente
Minimum amount: Importe mínimo
Order changes: Cambios del pedido
Minimum import: Importe mínimo
Notify changes: Notificar cambios
Ticket deleted: Ticket eliminado
You can undo this action within the first hour: Puedes deshacer esta acción dentro de la primera hora
To clone ticket: Clonar ticket
Ticket cloned: Ticked clonado
It was not able to clone the ticket: No se pudo clonar el ticket
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
Set ticket weight: Establecer peso al ticket
</i18n>

View File

@ -22,6 +22,7 @@ import { useVnConfirm } from 'composables/useVnConfirm';
import useNotify from 'src/composables/useNotify.js';
import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
import FormModelPopup from 'src/components/FormModelPopup.vue';
const route = useRoute();
const router = useRouter();
@ -50,6 +51,9 @@ const transfer = ref({
lastActiveTickets: [],
sales: [],
});
const tableRef = ref([]);
const formModelPopupRef = ref();
const showForm = ref(false);
watch(
() => route.params.id,
@ -395,7 +399,7 @@ const changeTicketState = async (val) => {
};
const removeSelectedSales = () => {
selectedSales.value.forEach((sale) => {
selectedRows.value.forEach((sale) => {
const index = sales.value.indexOf(sale);
sales.value.splice(index, 1);
});
@ -403,17 +407,16 @@ const removeSelectedSales = () => {
const removeSales = async () => {
try {
const params = { sales: selectedValidSales.value, ticketId: store.data.id };
const params = { sales: selectedRows.value, ticketId: store.data.id };
await axios.post('Sales/deleteSales', params);
removeSelectedSales();
notify('globals.dataSaved', 'positive');
window.location.reload();
} catch (err) {
console.error('Error deleting sales', err);
}
};
const insertRow = () => sales.value.push({ ...DEFAULT_EDIT });
const setTransferParams = async () => {
try {
const checkedSales = JSON.parse(JSON.stringify(selectedSales.value));
@ -442,6 +445,15 @@ onMounted(async () => {
});
onUnmounted(() => (stateStore.rightDrawer = false));
async function save(item) {
const { data } = await axios.post(`Tickets/${route.params.id}/addSale`, {
id: route.params.id,
barcode: item.itemFk,
quantity: parseInt(item.quantity),
});
if (data) window.location.reload();
}
</script>
<template>
@ -455,12 +467,6 @@ onUnmounted(() => (stateStore.rightDrawer = false));
auto-load
@on-fetch="(data) => (isLocked = data)"
/>
<FetchData
url="Items/withName"
:filter="{ fields: ['id', 'name'], order: 'id DESC' }"
auto-load
@on-fetch="(data) => (itemsWithNameOptions = data)"
/>
<FetchData
url="States/editableStates"
:filter="{ fields: ['code', 'name', 'id', 'alertLevel'], order: 'name ASC' }"
@ -560,13 +566,13 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</QCardSection>
</div></QDrawer
>
<!-- TODO: el botón de añadir filas como está en salix-->
<VnTable
ref="tableRef"
data-key="TicketSales"
:url="`Tickets/${route.params.id}/getSales`"
:columns="columns"
v-model:selected="selectedRows"
:bottom="true"
:table="{
'row-key': 'id',
selection: 'multiple',
@ -625,7 +631,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
</div>
</template>
<template #column-visible="{ row }">
<QBadge :color="row.available < 0 ? 'alert' : 'transparent'" dense>
<QBadge :color="row.visible < 0 ? 'alert' : 'transparent'" dense>
{{ row.visible }}
</QBadge>
</template>
@ -667,7 +673,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template #column-price="{ row }">
<QTd>
<template v-if="isTicketEditable && row.id">
<QBtn flat color="primary" dense @click="onOpenEditPricePopover(row)">
<QBtn flat class="link" dense @click="onOpenEditPricePopover(row)">
{{ toCurrency(row.price) }}
</QBtn>
<TicketEditManaProxy
@ -689,12 +695,7 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template #column-discount="{ row }">
<QTd>
<template v-if="!isLocked && row.id">
<QBtn
flat
color="primary"
dense
@click="onOpenEditDiscountPopover(row)"
>
<QBtn flat class="link" dense @click="onOpenEditDiscountPopover(row)">
{{ toPercentage(row.discount / 100) }}
</QBtn>
<TicketEditManaProxy
@ -715,21 +716,54 @@ onUnmounted(() => (stateStore.rightDrawer = false));
<template #column-amount="{ row }">
{{ toCurrency(row.quantity * row.price) }}
</template>
<template #bottom-row>
<QBtn
class="cursor-pointer fill-icon q-ml-md q-my-lg"
<template #bottom-table v-if="isTicketEditable">
<QIcon
@click="showForm = !showForm"
class="fill-icon-on-hover"
color="primary"
icon="add_circle"
size="md"
round
flat
:disable="!isTicketEditable"
@click="insertRow()"
name="add_circle"
size="sm"
>
<QTooltip>
{{ t('Add item') }}
</QTooltip>
</QBtn>
</QIcon>
<QDialog v-model="showForm" transition-show="scale" transition-hide="scale">
<FormModelPopup
ref="formModelPopupRef"
:url-create="`Tickets/${route.params.id}/addSale`"
:title="t('New item')"
:form-initial-data="{
price: 0,
discount: 0,
visible: 0,
available: 0,
packaging: null,
}"
:save-fn="save"
>
<template #form-inputs="{ data }">
<VnSelect
url="Items/withName"
:option-filter="{
order: 'id DESC',
}"
:fields="['id', 'name']"
:label="t('Items')"
option-label="name"
option-value="id"
:options="itemsWithNameOptions"
hide-selected
v-model="data.itemFk"
/>
<VnInput
v-model="data.quantity"
:label="t('Quantity')"
type="number"
/>
</template>
</FormModelPopup>
</QDialog>
</template>
</VnTable>

View File

@ -50,7 +50,7 @@ const createRefund = async () => {
if (!selected.value.length) return;
const params = {
servicesIds: selected.value.map((s) => +s.ticketFk),
servicesIds: selected.value.map((s) => +s.id),
withWarehouse: false,
negative: true,
};

View File

@ -425,7 +425,7 @@ async function changeState(value) {
<VnTitle :url="ticketUrl + 'package'" :text="t('globals.packages')" />
<QTable :rows="ticket.packagings" flat>
<template #header="props">
<QTr :props="props">
<QTr class="tr-header" :props="props">
<QTh auto-width>{{ t('ticket.summary.created') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.package') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
@ -445,7 +445,7 @@ async function changeState(value) {
/>
<QTable :rows="ticket.services" flat>
<template #header="props">
<QTr :props="props">
<QTr class="tr-header" :props="props">
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('globals.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
@ -466,6 +466,49 @@ async function changeState(value) {
</template>
</QTable>
</QCard>
<QCard class="vn-max" v-if="ticket.requests.length > 0">
<VnTitle
:url="ticketUrl + 'request'"
:text="t('ticket.summary.purchaseRequest')"
/>
<QTable :rows="ticket.requests" flat>
<template #header="props">
<QTr class="tr-header" :props="props">
<QTh auto-width>{{ t('ticket.summary.description') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.created') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.requester') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.attender') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.quantity') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.price') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.item') }}</QTh>
<QTh auto-width>{{ t('ticket.summary.ok') }}</QTh>
</QTr>
</template>
<template #body="props">
<QTr :props="props">
<QTd>{{ toDate(props.row.created) }}</QTd>
<QTd>{{ props.row.description }}</QTd>
<QTd>{{ props.row.requester?.user?.username }}</QTd>
<QTd>{{ props.row.atender?.user?.username }}</QTd>
<QTd>{{ props.row.quantity }}</QTd>
<QTd>{{ toCurrency(props.row.price) }}</QTd>
<QTd>
<span class="link" v-if="props.row.isOk">
{{ props.row.itemFk }}
<ItemDescriptorProxy :id="props.row.itemFk" />
</span>
</QTd>
<QTd>
<QCheckbox
v-model="props.row.isOk"
disable
:toggle-indeterminate="false"
/>
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</template>
</CardSummary>
</template>

View File

@ -94,13 +94,11 @@ const openCreateModal = () => createTrackingDialogRef.value.show();
:no-data-label="t('globals.noResults')"
>
<template #body-cell-worker="{ row }">
<QTd @click.stop>
<span class="link">
<QBtn flat>
{{ row.user?.name }}
<WorkerDescriptorProxy :id="row.user?.worker?.id" />
</QBtn>
</span>
<QTd>
<QBtn flat class="link" @click.stop>
{{ row.user?.name }}
<WorkerDescriptorProxy :id="row.user?.worker?.id" />
</QBtn>
</QTd>
</template>
</QTable>

View File

@ -134,12 +134,10 @@ onUnmounted(() => (stateStore.rightDrawer = false));
>
<template #body-cell-item="{ row }">
<QTd>
<span class="link">
<QBtn flat>
{{ row.itemFk }}
<ItemDescriptorProxy :id="row.itemFk" />
</QBtn>
</span>
<QBtn flat class="link" @click.stop>
{{ row.itemFk }}
<ItemDescriptorProxy :id="row.itemFk" />
</QBtn>
</QTd>
</template>
<template #body-cell-description="{ row }">

View File

@ -3,8 +3,8 @@ import { onMounted, ref, computed, reactive } from 'vue';
import { useI18n } from 'vue-i18n';
import FetchData from 'components/FetchData.vue';
import VnInput from 'src/components/common/VnInput.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
// import VnInput from 'src/components/common/VnInput.vue';
// import VnSelect from 'src/components/common/VnSelect.vue';
import TicketDescriptorProxy from 'src/pages/Ticket/Card/TicketDescriptorProxy.vue';
import VnSubToolbar from 'src/components/ui/VnSubToolbar.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
@ -19,13 +19,14 @@ import useNotify from 'src/composables/useNotify.js';
import { useState } from 'src/composables/useState';
import { toDateFormat } from 'src/filters/date.js';
import axios from 'axios';
import VnTable from 'src/components/VnTable/VnTable.vue';
const state = useState();
const { t } = useI18n();
const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify();
const user = state.getUser();
const tableRef = ref();
const itemPackingTypesOptions = ref([]);
const zonesOptions = ref([]);
const selectedTickets = ref([]);
@ -44,15 +45,14 @@ const exprBuilder = (param, value) => {
case 'notMovableLines':
case 'futureZoneFk':
return { [param]: value };
case 'iptColFilter':
return { ipt: { like: `%${value}%` } };
case 'futureIptColFilter':
return { futureIpt: { like: `%${value}%` } };
case 'ipt':
return { [param]: { like: `%${value}%` } };
case 'futureIpt':
return { [param]: { like: `%${value}%` } };
}
};
const userParams = reactive({});
const arrayData = useArrayData('AdvanceTickets', {
url: 'Tickets/getTicketsAdvance',
userParams: userParams,
@ -82,220 +82,123 @@ const getInputEvents = (col) => {
};
};
const ticketColumns = computed(() => [
const columns = computed(() => [
{
label: '',
name: 'icons',
align: 'left',
columnFilter: null,
hidden: true,
},
{
label: t('advanceTickets.ticketId'),
name: 'ticketId',
align: 'center',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
filterParamKey: 'id',
event: getInputEvents,
attrs: {
dense: true,
},
},
label: t('advanceTickets.ticketId'),
name: 'id',
},
{
align: 'left',
label: t('advanceTickets.ipt'),
name: 'ipt',
field: 'ipt',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
filterParamKey: 'iptColFilter',
type: 'select',
filterValue: null,
event: getInputEvents,
component: 'select',
attrs: {
options: itemPackingTypesOptions.value,
'option-value': 'code',
'option-label': 'description',
dense: true,
url: 'itemPackingTypes',
fields: ['code', 'description'],
where: { isActive: true },
optionValue: 'code',
optionLabel: 'description',
},
},
format: (val) => dashIfEmpty(val),
format: (row, dashIfEmpty) => dashIfEmpty(row.ipt),
},
{
align: 'left',
label: t('advanceTickets.state'),
name: 'state',
align: 'left',
sortable: true,
columnFilter: null,
hidden: true,
},
{
align: 'left',
label: t('advanceTickets.liters'),
name: 'liters',
field: 'liters',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
},
{
align: 'left',
label: t('advanceTickets.lines'),
name: 'lines',
field: 'lines',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
format: (row, dashIfEmpty) => dashIfEmpty(row.lines),
},
{
align: 'left',
label: t('advanceTickets.import'),
field: 'import',
name: 'import',
align: 'left',
sortable: true,
name: 'totalWithVat',
hidden: true,
format: (row) => toCurrency(row.totalWithVat),
},
{
align: 'left',
label: t('advanceTickets.futureId'),
name: 'futureId',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
filterParamKey: 'futureId',
event: getInputEvents,
attrs: {
dense: true,
},
},
},
{
align: 'left',
label: t('advanceTickets.futureIpt'),
name: 'futureIpt',
field: 'futureIpt',
align: 'left',
sortable: true,
columnFilter: {
component: VnSelect,
filterParamKey: 'futureIptColFilter',
type: 'select',
filterValue: null,
event: getInputEvents,
component: 'select',
attrs: {
options: itemPackingTypesOptions.value,
'option-value': 'code',
'option-label': 'description',
dense: true,
url: 'itemPackingTypes',
fields: ['code', 'description'],
where: { isActive: true },
optionValue: 'code',
optionLabel: 'description',
},
},
format: (val) => dashIfEmpty(val),
format: (row, dashIfEmpty) => dashIfEmpty(row.futureIpt),
},
{
align: 'left',
label: t('advanceTickets.futureState'),
name: 'futureState',
align: 'left',
sortable: true,
columnFilter: null,
format: (val) => dashIfEmpty(val),
hidden: true,
},
{
align: 'left',
label: t('advanceTickets.futureLiters'),
name: 'futureLiters',
field: 'futureLiters',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
},
{
align: 'left',
label: t('advanceTickets.futureZone'),
name: 'futureZoneName',
field: 'futureZoneName',
align: 'left',
sortable: true,
name: 'futureZoneFk',
columnClass: 'expand',
columnFilter: {
component: VnSelect,
type: 'select',
filterValue: null,
filterParamKey: 'futureZoneFk',
event: getInputEvents,
component: 'select',
inWhere: true,
attrs: {
options: zonesOptions.value,
'option-value': 'id',
'option-label': 'name',
dense: true,
url: 'Zones',
fields: ['id', 'name'],
},
},
format: (val) => dashIfEmpty(val),
columnField: {
component: null,
},
format: (row, dashIfEmpty) => dashIfEmpty(row.futureZoneName),
},
{
align: 'left',
label: t('advanceTickets.notMovableLines'),
name: 'notMovableLines',
field: 'notMovableLines',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
},
{
align: 'left',
label: t('advanceTickets.futureLines'),
name: 'futureLines',
field: 'futureLines',
align: 'left',
sortable: true,
columnFilter: {
component: VnInput,
type: 'text',
filterValue: null,
event: getInputEvents,
attrs: {
dense: true,
},
},
format: (val) => dashIfEmpty(val),
},
{
label: t('advanceTickets.futureImport'),
name: 'futureImport',
align: 'left',
sortable: true,
columnFilter: null,
label: t('advanceTickets.futureImport'),
name: 'futureTotalWithVat',
hidden: true,
format: (row) => toCurrency(row.futureTotalWithVat),
},
]);
@ -457,6 +360,13 @@ onMounted(async () => {
const filter = { limit: 0 };
await arrayData.addFilter({ filter, userParams });
});
const getColor = (row) => {
return row?.classColor ? `bg-${row.classColor}` : 'bg-orange';
};
const getFutureColor = (row) => {
return row?.futureClassColor ? `bg-${row.futureClassColor}` : 'bg-orange';
};
</script>
<template>
@ -480,9 +390,9 @@ onMounted(async () => {
@on-fetch="(data) => (zonesOptions = data)"
/>
<VnSearchbar
data-key="WeeklyTickets"
:label="t('weeklyTickets.search')"
:info="t('weeklyTickets.searchInfo')"
data-key="AdvanceTickets"
:label="t('advanceTickets.search')"
:info="t('advanceTickets.searchInfo')"
/>
<VnSubToolbar>
<template #st-data>
@ -531,73 +441,32 @@ onMounted(async () => {
</template>
</RightMenu>
<QPage class="column items-center q-pa-md">
<QTable
:rows="tickets"
:columns="ticketColumns"
row-key="index"
selection="multiple"
<VnTable
ref="tableRef"
data-key="AdvanceTickets"
url="TicketWeeklies/filter"
:columns="columns"
auto-load
:right-search="false"
:disable-option="{ card: true }"
v-model:selected="selectedTickets"
:pagination="{ rowsPerPage: 0 }"
:no-data-label="t('globals.noResults')"
style="max-width: 99%"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
>
<template #header="props">
<QTr :props="props">
<QTh
class="horizontal-separator text-uppercase color-vn-label"
colspan="7"
translate
>
{{ t('advanceTickets.destination') }}
{{ toDateFormat(userParams.dateToAdvance) }}
</QTh>
<QTh
class="horizontal-separator text-uppercase color-vn-label"
colspan="9"
translate
>
{{ t('advanceTickets.origin') }}
{{ toDateFormat(userParams.dateFuture) }}
</QTh>
</QTr>
<QTr>
<QTh>
<QCheckbox v-model="props.selected" />
</QTh>
<QTh
v-for="(col, index) in ticketColumns"
:key="index"
:class="{ 'vertical-separator': col.name === 'futureId' }"
>
{{ col.label }}
</QTh>
</QTr>
<!--TODO: arreglar estilos-->
<template #before-header>
<span class="horizontal-separator" translate>
{{ t('advanceTickets.destination') }}
{{ toDateFormat(userParams.dateToAdvance) }}
</span>
<span class="horizontal-separator" translate>
{{ t('advanceTickets.origin') }}
{{ toDateFormat(userParams.dateFuture) }}
</span>
</template>
<template #top-row="{ cols }">
<QTr>
<QTd />
<QTd
v-for="(col, index) in cols"
:key="index"
style="max-width: 100px"
>
<component
:is="col.columnFilter.component"
v-if="col.columnFilter"
v-model="col.columnFilter.filterValue"
v-bind="col.columnFilter.attrs"
v-on="col.columnFilter.event(col)"
dense
/>
</QTd>
</QTr>
</template>
<template #header-cell-availableLines="{ col }">
<QTh class="vertical-separator">
{{ col.label }}
</QTh>
</template>
<template #body-cell-icons="{ row }">
<template #column-icons="{ row }">
<QTd class="q-gutter-x-xs">
<QIcon
v-if="row.futureAgency !== row.agency && row.agency"
@ -624,73 +493,52 @@ onMounted(async () => {
</QIcon>
</QTd>
</template>
<template #body-cell-ticketId="{ row }">
<QTd>
<QBtn flat class="link">
{{ row.id }}
<TicketDescriptorProxy :id="row.id" />
</QBtn>
</QTd>
<template #column-id="{ row }">
<span class="link" @click.stop>
{{ row.id }}
<TicketDescriptorProxy :id="row.id" />
</span>
</template>
<template #body-cell-state="{ row }">
<QTd>
<QBadge
text-color="black"
:color="row.classColor"
class="q-ma-none"
dense
>
{{ row.state }}
</QBadge>
</QTd>
<template #column-state="{ row }">
<QChip :class="getColor(row)" dense square>
{{ row.state }}
</QChip>
</template>
<template #body-cell-import="{ row }">
<QTd>
<QBadge
:text-color="isLessThan50(row.totalWithVat) ? 'black' : 'white'"
:color="totalPriceColor(row.totalWithVat)"
class="q-ma-none"
dense
>
{{ toCurrency(row.totalWithVat || 0) }}
</QBadge>
</QTd>
<template #column-totalWithVat="{ row }">
<QBadge
:text-color="isLessThan50(row.totalWithVat) ? 'black' : 'white'"
:color="totalPriceColor(row.totalWithVat)"
class="q-ma-none"
dense
>
{{ toCurrency(row.totalWithVat || 0) }}
</QBadge>
</template>
<template #body-cell-futureId="{ row }">
<QTd class="vertical-separator">
<QBtn flat class="link" dense>
<template #column-futureId="{ row }">
<!--TODO: arreglar estilos para que se muestre como antes, slot?-->
<div class="vertical-separator">
<span class="link" @click.stop>
{{ row.futureId }}
<TicketDescriptorProxy :id="row.futureId" />
</QBtn>
</QTd>
</span>
</div>
</template>
<template #body-cell-futureState="{ row }">
<QTd>
<QBadge
text-color="black"
:color="row.futureClassColor"
class="q-ma-none"
dense
>
{{ row.futureState }}
</QBadge>
</QTd>
<template #column-futureState="{ row }">
<QChip :class="getFutureColor(row)" dense square>
{{ row.futureState }}
</QChip>
</template>
<template #body-cell-futureImport="{ row }">
<QTd>
<QBadge
:text-color="
isLessThan50(row.futureTotalWithVat) ? 'black' : 'white'
"
:color="totalPriceColor(row.futureTotalWithVat)"
class="q-ma-none"
dense
>
{{ toCurrency(row.futureTotalWithVat || 0) }}
</QBadge>
</QTd>
<template #column-futureTotalWithVat="{ row }">
<QBadge
:text-color="isLessThan50(row.futureTotalWithVat) ? 'black' : 'white'"
:color="totalPriceColor(row.futureTotalWithVat)"
class="q-ma-none"
dense
>
{{ toCurrency(row.futureTotalWithVat || 0) }}
</QBadge>
</template>
</QTable>
</VnTable>
<VnProgress
:progress="progressPercentage"
:cancelled="cancelProgress"
@ -714,6 +562,8 @@ onMounted(async () => {
}
.horizontal-separator {
border-bottom: 4px solid white !important;
width: 100%;
margin-top: 1%;
border-bottom: 5px solid white !important;
}
</style>

View File

@ -2,7 +2,9 @@
import axios from 'axios';
import { computed, ref } from 'vue';
import { useI18n } from 'vue-i18n';
import { useQuasar } from 'quasar';
import { toDate, toCurrency } from 'src/filters/index';
import useNotify from 'src/composables/useNotify';
import TicketSummary from './Card/TicketSummary.vue';
import VnSearchbar from 'src/components/ui/VnSearchbar.vue';
import { useSummaryDialog } from 'src/composables/useSummaryDialog';
@ -10,14 +12,25 @@ import VnTable from 'src/components/VnTable/VnTable.vue';
import VnSelect from 'src/components/common/VnSelect.vue';
import VnInputDate from 'src/components/common/VnInputDate.vue';
import VnRow from 'src/components/ui/VnRow.vue';
import VnInput from 'src/components/common/VnInput.vue';
import FetchData from 'src/components/FetchData.vue';
const { t } = useI18n();
const { viewSummary } = useSummaryDialog();
const tableRef = ref();
const quasar = useQuasar();
const { notify } = useNotify();
const clientsOptions = ref([]);
const addressesOptions = ref([]);
const agenciesOptions = ref([]);
const selectedClient = ref();
const selectedRows = ref([]);
const hasSelectedRows = computed(() => selectedRows.value.length > 0);
const showForm = ref(false);
const dialogData = ref();
const companiesOptions = ref([]);
const accountingOptions = ref([]);
const amountToReturn = ref();
const columns = computed(() => [
{
@ -187,9 +200,158 @@ const fetchAddresses = async (formData) => {
const getColor = (row) => {
return row?.classColor ? `bg-${row.classColor}` : 'bg-orange';
};
async function makeInvoice(ticket) {
const ticketsIds = ticket.map((item) => item.id);
const { data } = await axios.post(`Tickets/invoiceTicketsAndPdf`, { ticketsIds });
const response = data;
if (response)
quasar.notify({
message: t('globals.dataSaved'),
type: 'positive',
});
}
async function sendDocuware(ticket) {
try {
let ticketIds = ticket.map((item) => item.id);
const { data } = await axios.post(`Docuwares/upload`, {
fileCabinet: 'deliveryNote',
ticketIds,
});
for (let ticket of ticketIds) {
ticket.stateFk = data.id;
ticket.state = data.name;
ticket.alertLevel = data.alertLevel;
ticket.alertLevelCode = data.code;
}
notify('globals.dataSaved', 'positive');
} catch (err) {
console.log('err: ', err);
}
}
function openBalanceDialog(ticket) {
const checkedTickets = ticket;
const amountPaid = ref(0);
const clientFk = ref(null);
const description = ref([]);
const firstTicketClientId = checkedTickets[0].clientFk;
const isSameClient = checkedTickets.every(
(ticket) => ticket.clientFk === firstTicketClientId
);
if (!isSameClient) {
throw new Error('You cannot make a payment on account from multiple clients');
}
for (let ticketData of checkedTickets) {
console.log('ticketData: ', ticketData);
amountPaid.value += ticketData.totalWithVat;
clientFk.value = ticketData.clientFk;
description.value.push(ticketData.id);
}
const balanceCreateDialog = ref({
amountPaid: amountPaid.value,
clientFk: clientFk.value,
description: `Albaran: ${description.value.join(', ')}`,
});
dialogData.value = balanceCreateDialog;
showForm.value = true;
}
async function onSubmit() {
const { data: email } = await axios.get('Clients', {
params: {
filter: JSON.stringify({ where: { id: dialogData.value.value.clientFk } }),
},
});
const { data } = await axios.post(
`Clients/${dialogData.value.value.clientFk}/createReceipt`,
{
payed: dialogData.value.payed,
companyFk: dialogData.value.companyFk,
bankFk: dialogData.value.bankFk,
amountPaid: dialogData.value.value.amountPaid,
description: dialogData.value.value.description,
clientFk: dialogData.value.value.clientFk,
email: email[0].email,
}
);
if (data) notify('globals.dataSaved', 'positive');
showForm.value = false;
}
const setAmountToReturn = (newAmountGiven) => {
const amountPaid = dialogData.value.value.amountPaid;
amountToReturn.value = newAmountGiven - amountPaid;
};
function setReference(data) {
console.log('entity: ', data);
let newDescription = '';
switch (data) {
case 1:
newDescription = `${t(
'ticketList.creditCard'
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
''
)}`;
break;
case 2:
newDescription = `${t(
'ticketList.cash'
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
''
)}`;
break;
case 3:
newDescription = `${newDescription.replace(
/^(Credit Card, |Cash, |Transfers, )/,
''
)}`;
break;
case 4:
newDescription = `${t(
'ticketList.transfers'
)}, ${dialogData.value.value.description.replace(
/^(Credit Card, |Cash, |Transfers, )/,
''
)}`;
break;
case 3317:
newDescription = '';
break;
default:
break;
}
dialogData.value.value.description = newDescription;
console.log('Updated description:', dialogData.value.value.description);
}
</script>
<template>
<FetchData
url="Companies"
@on-fetch="(data) => (companiesOptions = data)"
auto-load
/>
<FetchData
url="Accountings"
@on-fetch="(data) => (accountingOptions = data)"
auto-load
/>
<VnSearchbar
data-key="Tickets"
:label="t('Search ticket')"
@ -209,6 +371,11 @@ const getColor = (row) => {
:columns="columns"
redirect="ticket"
auto-load
v-model:selected="selectedRows"
:table="{
'row-key': 'id',
selection: 'multiple',
}"
>
<template #more-create-dialog="{ data }">
<VnRow>
@ -306,6 +473,135 @@ const getColor = (row) => {
</QChip>
</template>
</VnTable>
<QPageSticky :offset="[20, 80]" style="z-index: 2">
<QBtn
v-if="hasSelectedRows"
@click="makeInvoice(selectedRows)"
color="primary"
fab
icon="vn:invoice-in"
/>
<QTooltip>
{{ t('ticketList.createInvoice') }}
</QTooltip>
</QPageSticky>
<QPageSticky v-if="hasSelectedRows" :offset="[20, 140]" style="z-index: 2">
<QBtn
@click.stop="openBalanceDialog(selectedRows)"
color="primary"
fab
icon="vn:recovery"
/>
<QTooltip>
{{ t('ticketList.accountPayment') }}
</QTooltip>
</QPageSticky>
<QDialog ref="dialogRef" v-model="showForm">
<QCard class="q-pa-md q-mb-md">
<QForm @submit="onSubmit()" class="q-pa-sm">
{{ t('ticketList.addPayment') }}
<VnRow>
<VnInputDate
:label="t('ticketList.date')"
v-model="dialogData.payed"
/>
<VnSelect
:label="t('ticketList.company')"
v-model="dialogData.companyFk"
:options="companiesOptions"
option-value="id"
option-label="code"
hide-selected
>
</VnSelect>
</VnRow>
<VnRow>
<VnSelect
:label="t('ticketList.bank')"
v-model="dialogData.bankFk"
:options="accountingOptions"
option-value="id"
option-label="bank"
hide-selected
@update:model-value="setReference"
/>
<VnInput
:label="t('ticketList.amount')"
v-model="dialogData.value.amountPaid"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<span>
{{ t('ticketList.cash') }}
</span>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<VnInput
:label="t('ticketList.deliveredAmount')"
v-model="dialogData.value.amountGiven"
@update:model-value="setAmountToReturn"
type="number"
/>
<VnInput
:label="t('ticketList.amountToReturn')"
:model-value="amountToReturn"
type="number"
readonly
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 3 || dialogData.bankFk === 3117">
<VnInput
:label="t('ticketList.compensation')"
v-model="dialogData.value.compensation"
type="text"
/>
</VnRow>
<VnRow>
<VnInput
:label="t('ticketList.reference')"
v-model="dialogData.value.description"
type="text"
/>
</VnRow>
<VnRow v-if="dialogData.bankFk === 2">
<QCheckbox
:label="t('ticketList.viewReceipt')"
v-model="dialogData.value.viewReceipt"
:toggle-indeterminate="false"
/>
<QCheckbox
:label="t('ticketList.sendEmail')"
v-model="dialogData.value.senEmail"
:toggle-indeterminate="false"
/>
</VnRow>
<div class="q-mt-lg row justify-end">
<QBtn
:label="t('globals.save')"
color="primary"
@click="onSubmit()"
/>
<QBtn
flat
:label="t('globals.close')"
color="primary"
v-close-popup
/>
</div>
</QForm>
</QCard>
</QDialog>
<QPageSticky v-if="hasSelectedRows" :offset="[20, 200]" style="z-index: 2">
<QBtn
@click="sendDocuware(selectedRows)"
color="primary"
fab
icon="install_mobile"
/>
<QTooltip>
{{ t('ticketList.sendDocuware') }}
</QTooltip>
</QPageSticky>
</template>
<i18n>

View File

@ -66,6 +66,8 @@ advanceTickets:
advanceWithoutNegativeTitle: Advance tickets (without negatives)
advanceWithoutNegativeSubtitle: Advance {selectedTickets} tickets confirmation
errorsList: Errors list
search: Search advance tickets
searchInfo: Search advance tickets by ID or client ID
futureTickets:
problems: Problems
ticketId: ID
@ -239,3 +241,20 @@ ticketList:
summary: Summary
client: Customer
createTicket: Create ticket
createInvoice: Create invoice
accountPayment: Account payment
sendDocuware: Set delivered and send delivery note(s) to the tablet
addPayment: Add payment
date: Date
company: Company
amount: Amount
reference: Reference
bank: Bank
cash: Cash
deliveredAmount: Delivered amount
amountToReturn: Amount to return
viewReceipt: View receipt
sendEmail: Send email
compensation: Compensation
creditCard: Credit card
transfers: Transfers

View File

@ -113,6 +113,8 @@ advanceTickets:
advanceWithoutNegativeTitle: Adelantar tickets (sin negativos)
advanceWithoutNegativeSubtitle: '¿Desea adelantar {selectedTickets} tickets?'
errorsList: Lista de errores
search: Buscar por tickets adelantados
searchInfo: Buscar tickets adelantados por el identificador o el identificador del cliente
futureTickets:
problems: Problemas
ticketId: ID
@ -242,3 +244,20 @@ ticketList:
summary: Resumen
client: Cliente
createTicket: Crear ticket
createInvoice: Crear factura
accountPayment: Pago a cuenta...
sendDocuware: Marcar como servido/s y enviar albarán/es a la tablet
addPayment: Añadir pago
date: Fecha
company: Empresa
amount: Importe
reference: Referencia
bank: Caja
cash: Efectivo
deliveredAmount: Cantidad entregada
amountToReturn: Cantidad a devolver
viewReceipt: Ver recibido
sendEmail: Enviar correo
compensation: Compensación
creditCard: Tarjeta de crédito
transfers: Transferencias