0
0
Fork 0

refs #4801 working on fetched tags

This commit is contained in:
Alexandre Riera 2022-11-25 15:54:35 +01:00
parent d33da3cf3c
commit 04693529d7
8 changed files with 622 additions and 0 deletions

View File

@ -22,3 +22,8 @@ $positive: #21ba45;
$negative: #c10015; $negative: #c10015;
$info: #31ccec; $info: #31ccec;
$warning: #f2c037; $warning: #f2c037;
$color-spacer-light: rgba(255, 255, 255, .12);
$border-thin-light: 1px solid $color-spacer-light;
$spacing-md: 16px;

View File

@ -0,0 +1,4 @@
export default function (value) {
if (value == null || value === '') return '-';
return value;
}

View File

@ -2,10 +2,14 @@ import toLowerCase from './toLowerCase';
import toDate from './toDate'; import toDate from './toDate';
import toCurrency from './toCurrency'; import toCurrency from './toCurrency';
import toPercentage from './toPercentage'; import toPercentage from './toPercentage';
import dashIfEmpty from './dashIfEmpty';
import zeroFill from './zeroFill';
export { export {
toLowerCase, toLowerCase,
toDate, toDate,
toCurrency, toCurrency,
toPercentage, toPercentage,
dashIfEmpty,
zeroFill
}; };

4
src/filters/zeroFill.js Normal file
View File

@ -0,0 +1,4 @@
export default function (value, pad) {
const valueStr = String(value);
return valueStr.padStart(pad, '0');
}

View File

@ -187,6 +187,48 @@ export default {
selectVideo: 'Select video:', selectVideo: 'Select video:',
notFound: 'No videos available', notFound: 'No videos available',
}, },
summary: {
state: 'State',
salesPerson: 'Sales person',
agency: 'Agency',
zone: 'Zone',
warehouse: 'Warehouse',
route: 'Route',
invoice: 'Invoice',
shipped: 'Shipped',
landed: 'Landed',
packages: 'Packages',
consigneePhone: 'Consignee phone',
consigneeMobile: 'Consignee mobile',
clientPhone: 'Client phone',
clientMobile: 'Client mobile',
consignee: 'Consignee',
subtotal: 'Subtotal',
vat: 'VAT',
total: 'Total',
saleLines: 'Line items',
item: 'Item',
visible: 'Visible',
available: 'Available',
quantity: 'Quantity',
description: 'Description',
price: 'Price',
discount: 'Discount',
amount: 'Amount',
packing: 'Packing',
hasComponentLack: 'Component lack',
itemShortage: 'Not visible',
claim: 'Claim',
reserved: 'Reserved',
created: 'Created',
package: 'Package',
taxClass: 'Tax class',
services: 'Services',
changeState: 'Change state',
requester: 'Requester',
atender: 'Atender',
request: 'Request'
}
}, },
claim: { claim: {
pageTitles: { pageTitles: {

View File

@ -186,6 +186,48 @@ export default {
selectVideo: 'Seleccionar vídeo:', selectVideo: 'Seleccionar vídeo:',
notFound: 'No hay vídeos disponibles', notFound: 'No hay vídeos disponibles',
}, },
summary: {
state: 'Estado',
salesPerson: 'Comercial',
agency: 'Agencia',
zone: 'Zona',
warehouse: 'Almacén',
route: 'Ruta',
invoice: 'Factura',
shipped: 'Enviado',
landed: 'Entregado',
packages: 'Bultos',
consigneePhone: 'Tel. consignatario',
consigneeMobile: 'Móv. consignatario',
clientPhone: 'Tel. cliente',
clientMobile: 'Móv. cliente',
consignee: 'Consignatario',
subtotal: 'Subtotal',
vat: 'IVA',
total: 'Total',
saleLines: 'Líneas del pedido',
item: 'Artículo',
visible: 'Visible',
available: 'Disponible',
quantity: 'Cantidad',
description: 'Descripción',
price: 'Precio',
discount: 'Descuento',
amount: 'Importe',
packing: 'Encajado',
hasComponentLack: 'Faltan componentes',
itemShortage: 'No visible',
claim: 'Reclamación',
reserved: 'Reservado',
created: 'Fecha creación',
package: 'Embalaje',
taxClass: 'Tipo IVA',
services: 'Servicios',
changeState: 'Cambiar estado',
requester: 'Solicitante',
atender: 'Comprador',
request: 'Petición de compra'
}
}, },
claim: { claim: {
pageTitles: { pageTitles: {

View File

@ -0,0 +1,17 @@
<script setup>
defineProps({
maxLength: {
type: Number,
required: true,
},
item: {
type: Object,
required: true,
},
});
</script>
<template>
<div>{{ $props.item.value5 }}</div>
</template>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,504 @@
<script setup>
import { onMounted, ref, computed, onUpdated } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
import axios from 'axios';
import { dashIfEmpty, toDate, toCurrency, zeroFill } from 'src/filters';
import SkeletonSummary from 'components/ui/SkeletonSummary.vue';
import FetchData from 'components/FetchData.vue';
import FetchedTags from './FetchedTags.vue';
onMounted(() => fetch());
onUpdated(() => fetch());
const route = useRoute();
const router = useRouter();
const { t } = useI18n();
const $props = defineProps({
id: {
type: Number,
required: false,
default: null,
},
});
const entityId = computed(() => $props.id || route.params.id);
const ticket = ref();
const salesLines = ref(null);
const editableStates = ref([]);
async function fetch() {
const { data } = await axios.get(`Tickets/${entityId.value}/summary`);
if (data) {
ticket.value = data;
console.log(ticket.value);
salesLines.value = data.sales;
}
}
function stateColor(state) {
if (state.code === 'OK') return 'text-green';
if (state.code === 'FREE') return 'text-blue-3';
if (state.alertLevel === 1) return 'text-primary';
if (state.alertLevel === 0) return 'text-red';
}
function formattedAddress() {
if (!ticket.value) return '';
const address = this.ticket.address;
const postcode = address.postalCode;
const province = address.province ? `(${address.province.name})` : '';
return `${address.street} - ${postcode} - ${address.city} ${province}`;
}
function isEditable() {
try {
return !this.ticket.ticketState.state.alertLevel;
} catch (e) {
console.error(e);
}
return true;
}
async function changeState(value) {
if (!this.ticket.id) return;
const formData = {
ticketFk: this.ticket.id,
code: value,
};
await axios.post(`TicketTrackings/changeState`, formData);
await router.go(route.fullPath);
}
</script>
<template>
<fetch-data url="States/editableStates" @on-fetch="(data) => (editableStates = data)" auto-load />
<div class="summary container">
<q-card>
<skeleton-summary v-if="!ticket" />
<template v-if="ticket">
<div class="header bg-primary q-pa-sm q-mb-md">
<span>
Ticket #{{ ticket.id }} - {{ ticket.client.name }} ({{ ticket.client.id }}) -
{{ ticket.nickname }}
</span>
<q-btn-dropdown
side
top
color="dark"
:label="t('ticket.summary.changeState')"
:disable="!isEditable()"
>
<q-list>
<q-virtual-scroll
style="max-height: 300px"
:items="editableStates"
separator
v-slot="{ item, index }"
>
<q-item :key="index" dense clickable v-close-popup @click="changeState(item.code)">
<q-item-section>
<q-item-label>{{ item.name }}</q-item-label>
</q-item-section>
</q-item>
</q-virtual-scroll>
</q-list>
</q-btn-dropdown>
</div>
<div class="row q-pa-md q-col-gutter-md q-mb-md">
<div class="col">
<q-list>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.state') }}</q-item-label>
<q-item-label :class="stateColor(ticket.ticketState.state)">
{{ ticket.ticketState.state.name }}
</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.salesPerson') }}</q-item-label>
<q-item-label>{{ ticket.client.salesPersonUser.name }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.agency') }}</q-item-label>
<q-item-label>{{ ticket.agencyMode.name }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.zone') }}</q-item-label>
<q-item-label class="link">{{ ticket.routeFk }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.warehouse') }}</q-item-label>
<q-item-label>{{ ticket.warehouse.name }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.invoice') }}</q-item-label>
<q-item-label>{{ dashIfEmpty(ticket.refFk) }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
<div class="col">
<q-list>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.shipped') }}</q-item-label>
<q-item-label>{{ toDate(ticket.shipped) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.landed') }}</q-item-label>
<q-item-label>{{ toDate(ticket.landed) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.packages') }}</q-item-label>
<q-item-label>{{ ticket.packages }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.consigneePhone') }}</q-item-label>
<q-item-label>{{ ticket.address.phone }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.consigneeMobile') }}</q-item-label>
<q-item-label>{{ ticket.address.mobile }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.clientPhone') }}</q-item-label>
<q-item-label>{{ ticket.client.phone }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.clientMobile') }}</q-item-label>
<q-item-label>{{ ticket.client.mobile }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.consignee') }}</q-item-label>
<q-item-label>{{ formattedAddress() }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
<div class="col">
<q-list>
<q-item v-for="note in ticket.notes" :key="note.id">
<q-item-section>
<q-item-label caption>
{{ note.observationType.description }}
</q-item-label>
<q-item-label>
{{ note.description }}
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
<div class="col">
<q-list class="taxes">
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.subtotal') }}</q-item-label>
<q-item-label>{{ toCurrency(ticket.totalWithoutVat) }}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.vat') }}</q-item-label>
<q-item-label>{{
toCurrency(ticket.totalWithVat - ticket.totalWithoutVat)
}}</q-item-label>
</q-item-section>
</q-item>
<q-item>
<q-item-section>
<q-item-label caption>{{ t('ticket.summary.total') }}</q-item-label>
<q-item-label>{{ toCurrency(ticket.totalWithVat) }}</q-item-label>
</q-item-section>
</q-item>
</q-list>
</div>
</div>
<div class="row q-pa-md" v-if="salesLines.length > 0">
<div class="col">
<q-item-label header class="text-h6">
{{ t('ticket.summary.saleLines') }}
</q-item-label>
<q-table :rows="ticket.sales">
<template #header="props">
<q-tr :props="props">
<q-th auto-width></q-th>
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.visible') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.available') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.discount') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.packing') }}</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td>
<q-btn
flat
round
size="xs"
icon="vn:claims"
v-if="props.row.claim"
color="primary"
:to="{ name: 'ClaimCard', params: { id: props.row.claim.claimFk } }"
>
<q-tooltip
>{{ t('ticket.summary.claim') }}:
{{ props.row.claim.claimFk }}</q-tooltip
>
</q-btn>
<q-btn
flat
round
size="xs"
icon="vn:claims"
v-if="props.row.claimBeginning"
color="primary"
:to="{
name: 'ClaimCard',
params: { id: props.row.claimBeginning.claimFk },
}"
>
<q-tooltip
>{{ t('ticket.summary.claim') }}:
{{ props.row.claimBeginning.claimFk }}</q-tooltip
>
</q-btn>
<q-icon name="warning" v-show="props.row.visible < 0" size="xs" color="primary">
<q-tooltip
>{{ t('ticket.summary.visible') }}: {{ props.row.visible }}</q-tooltip
>
</q-icon>
<q-icon name="vn:reserva" v-show="props.row.reserved" size="xs" color="primary">
<q-tooltip>{{ t('ticket.summary.reserved') }}</q-tooltip>
</q-icon>
<q-icon
name="vn:unavailable"
v-show="props.row.itemShortage"
size="xs"
color="primary"
>
<q-tooltip>{{ t('ticket.summary.itemShortage') }}</q-tooltip>
</q-icon>
<q-icon
name="vn:components"
v-show="props.row.hasComponentLack"
size="xs"
color="primary"
>
<q-tooltip>{{ t('ticket.summary.hasComponentLack') }}</q-tooltip>
</q-icon>
</q-td>
<q-td class="link">{{ zeroFill(props.row.itemFk, 6) }}</q-td>
<q-td>{{ props.row.visible }}</q-td>
<q-td>{{ props.row.available }}</q-td>
<q-td>{{ props.row.quantity }}</q-td>
<q-td>
{{ props.row.concept }}
<fetched-tags :item="props.row.item" :max-length="5"></fetched-tags>
</q-td>
<q-td>{{ props.row.price }}</q-td>
<q-td>{{ props.row.discount }} %</q-td>
<q-td
>{{
toCurrency(
props.row.quantity *
props.row.price *
((100 - props.row.discount) / 100)
)
}}
</q-td>
<q-td>{{ dashIfEmpty(props.row.item.itemPackingTypeFk) }}</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
<div class="row q-pa-md" v-if="ticket.packagings.length > 0 || ticket.services.length > 0">
<div class="col" v-if="ticket.packagings.length > 0">
<q-item-label header class="text-h6">
{{ t('ticket.summary.packages') }}
</q-item-label>
<q-table :rows="ticket.packagings">
<template #header="props">
<q-tr :props="props">
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.package') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td>{{ toDate(props.row.created) }}</q-td>
<q-td>{{ props.row.packaging.item.name }}</q-td>
<q-td>{{ props.row.quantity }}</q-td>
</q-tr>
</template>
</q-table>
</div>
<div class="col" v-if="ticket.services.length > 0">
<q-item-label header class="text-h6">
{{ t('ticket.summary.services') }}
</q-item-label>
<q-table :rows="ticket.services">
<template #header="props">
<q-tr :props="props">
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.taxClass') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.amount') }}</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td>{{ props.row.quantity }}</q-td>
<q-td>{{ props.row.description }}</q-td>
<q-td>{{ toCurrency(props.row.price) }}</q-td>
<q-td>{{ props.row.taxClass.description }}</q-td>
<q-td>{{ toCurrency(props.row.quantity * props.row.price) }}</q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
<div class="row q-pa-md" v-if="ticket.requests.length > 0">
<div class="col">
<q-item-label header class="text-h6">
{{ t('ticket.summary.request') }}
</q-item-label>
<q-table :rows="ticket.requests">
<template #header="props">
<q-tr :props="props">
<q-th auto-width>{{ t('ticket.summary.description') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.created') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.requester') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.atender') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.quantity') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.price') }}</q-th>
<q-th auto-width>{{ t('ticket.summary.item') }}</q-th>
<q-th auto-width>Ok</q-th>
</q-tr>
</template>
<template #body="props">
<q-tr :props="props">
<q-td>{{ props.row.description }}</q-td>
<q-td>{{ toDate(props.row.created) }}</q-td>
<q-td>{{ props.row.requester.user.name }}</q-td>
<q-td>{{ props.row.atender.user.name }}</q-td>
<q-td>{{ props.row.quantity }}</q-td>
<q-td>{{ toCurrency(props.row.price) }}</q-td>
<q-td v-if="!props.row.sale">-</q-td>
<q-td v-if="props.row.sale" class="link">{{
zeroFill(props.row.sale.itemFk, 6)
}}</q-td>
<q-td><q-checkbox v-model="props.row.isOk" :disable="true" /></q-td>
</q-tr>
</template>
</q-table>
</div>
</div>
</template>
</q-card>
</div>
</template>
<style lang="scss" scoped>
.container {
display: flex;
justify-content: center;
}
.q-card {
width: 100%;
max-width: 1200px;
}
.summary {
.q-table__container {
text-align: left;
.q-icon {
padding: 2%;
}
}
.taxes {
border: $border-thin-light;
text-align: right;
padding: 8px;
}
.row {
flex-wrap: wrap;
.col {
min-width: 250px;
padding-left: 1.5%;
padding-right: 1.5%;
}
}
.text-h6 {
margin-bottom: $spacing-md;
text-transform: uppercase;
line-height: 1;
padding: 7px 0;
padding-bottom: 5px;
border-bottom: 2px solid $primary;
overflow: hidden;
position: relative;
}
.header {
font-size: 18px;
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
margin: 0;
text-align: center;
span {
flex: 1;
}
.q-btn {
flex: none;
}
}
}
</style>