#7553 modified TicketExpedition & changes in ticket section #571

Merged
jon merged 79 commits from 7553_FixTicketExpedition into dev 2024-09-25 05:51:16 +00:00
15 changed files with 730 additions and 346 deletions
Showing only changes of commit 4a57c53c3d - Show all commits

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 }}
Review

Aqui habia un cambio con respecto al orden de los filtros. Seguro que jorge sabe donde se hace porque lo hizo para InvoiceInSummary

Aqui habia un cambio con respecto al orden de los filtros. Seguro que jorge sabe donde se hace porque lo hizo para InvoiceInSummary
<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>
jon marked this conversation as resolved Outdated

falta añadir toDate, porque sigue mostrándolo mal a pesar de dar format en la declaraciones de columnas

falta añadir toDate, porque sigue mostrándolo mal a pesar de dar format en la declaraciones de columnas
<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