0
1
Fork 0

Several changes

This commit is contained in:
William Buezas 2024-08-02 21:56:20 -03:00
parent 745e9a569c
commit 67c6f84de3
27 changed files with 671 additions and 541 deletions

View File

@ -2,10 +2,10 @@
<router-view /> <router-view />
</template> </template>
<script> <script setup>
import { defineComponent } from 'vue'; import { useAppStore } from 'stores/app';
import { onBeforeMount } from 'vue';
const appStore = useAppStore();
export default defineComponent({ onBeforeMount(() => appStore.init());
name: 'App'
});
</script> </script>

View File

@ -1,10 +1,10 @@
import { boot } from 'quasar/wrappers' import { boot } from 'quasar/wrappers';
import { appStore } from 'stores/app' import { useAppStore } from 'stores/app';
import { userStore } from 'stores/user' import { userStore } from 'stores/user';
export default boot(({ app }) => { export default boot(({ app }) => {
const props = app.config.globalProperties const props = app.config.globalProperties;
props.$app = appStore() props.$app = useAppStore();
props.$user = userStore() props.$user = userStore();
props.$actions = document.createElement('div') props.$actions = document.createElement('div');
}) });

View File

@ -9,7 +9,7 @@ const emit = defineEmits([
'remove' 'remove'
]); ]);
const $props = defineProps({ const props = defineProps({
modelValue: { modelValue: {
type: [String, Number], type: [String, Number],
default: null default: null
@ -33,7 +33,7 @@ const requiredFieldRule = val => !!val || t('globals.fieldRequired');
const vnInputRef = ref(null); const vnInputRef = ref(null);
const value = computed({ const value = computed({
get() { get() {
return $props.modelValue; return props.modelValue;
}, },
set(value) { set(value) {
emit('update:modelValue', value); emit('update:modelValue', value);
@ -41,7 +41,7 @@ const value = computed({
}); });
const hover = ref(false); const hover = ref(false);
const styleAttrs = computed(() => { const styleAttrs = computed(() => {
return $props.isOutlined return props.isOutlined
? { dense: true, outlined: true, rounded: true } ? { dense: true, outlined: true, rounded: true }
: {}; : {};
}); });
@ -88,9 +88,7 @@ const inputRules = [
<template #append> <template #append>
<slot v-if="$slots.append && !$attrs.disabled" name="append" /> <slot v-if="$slots.append && !$attrs.disabled" name="append" />
<QIcon <QIcon
v-if=" v-if="hover && value && !$attrs.disabled && props.clearable"
hover && value && !$attrs.disabled && $props.clearable
"
name="close" name="close"
size="xs" size="xs"
@click=" @click="

View File

@ -23,9 +23,9 @@ const handleClick = () => {
@click="handleClick()" @click="handleClick()"
> >
<QItemSection class="no-padding"> <QItemSection class="no-padding">
<div class="row"> <div class="row no-wrap">
<slot name="prepend" /> <slot name="prepend" />
<div class="column"> <div class="column full-width">
<slot name="content" /> <slot name="content" />
</div> </div>
</div> </div>

View File

@ -0,0 +1,154 @@
<script setup>
import { useI18n } from 'vue-i18n';
import { currency, formatDateTitle } from 'src/lib/filters.js';
import VnImg from 'src/components/ui/VnImg.vue';
defineProps({
ticket: {
type: Object,
default: () => ({})
},
rows: {
type: Array,
default: () => []
}
});
const { t } = useI18n();
const lineDiscountSubtotal = line => {
return line.quantity * line.price;
};
const lineSubtotal = line => {
const discount = line.discount;
return lineDiscountSubtotal(line) * ((100 - discount) / 100);
};
</script>
<template>
<QCard class="vn-w-sm" style="padding: 32px">
<QCardSection class="no-padding q-mb-md">
<div class="text-h6">#{{ ticket.id }}</div>
</QCardSection>
<QCardSection class="no-padding q-mb-md">
<div class="text-subtitle1 text-bold">
{{ t('shippingInformation') }}
</div>
<div>
{{ t('preparation') }}
{{ formatDateTitle(ticket.shipped) }}
</div>
<div>
{{ t('delivery') }}
{{ formatDateTitle(ticket.shipped) }}
</div>
<div>
{{ t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
{{ ticket.agency }}
</div>
</QCardSection>
<QCardSection class="no-padding q-mb-md">
<div class="text-subtitle1 text-bold">
{{ t('deliveryAddress') }}
</div>
<div>{{ ticket.nickname }}</div>
<div>{{ ticket.street }}</div>
<div>
{{ ticket.postalCode }} {{ ticket.city }} ({{
ticket.province
}})
</div>
</QCardSection>
<QCardSection
class="no-padding q-mb-md text-subtitle1 text-bold column"
>
<span class="text-right">
{{ t('total') }} {{ currency(ticket.taxBase) }}
</span>
<span class="text-right">
{{ t('totalTax') }} {{ currency(ticket.total) }}
</span>
</QCardSection>
<QSeparator inset />
<QList v-for="row in rows" :key="row.itemFk">
<QItem>
<QItemSection avatar>
<VnImg
storage="catalog"
size="200x200"
:id="row.image"
rounded
/>
</QItemSection>
<QItemSection>
<QItemLabel lines="1">
{{ row.concept }}
</QItemLabel>
<QItemLabel lines="1" caption>
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</QItemLabel>
<QItemLabel lines="1">
{{ row.quantity }} x {{ currency(row.price) }}
</QItemLabel>
</QItemSection>
<QItemSection side class="total">
<QItemLabel>
<span class="discount" v-if="row.discount">
{{ currency(lineDiscountSubtotal(row)) }} -
{{ currency(row.discount) }} =
</span>
{{ currency(lineSubtotal(row)) }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QCard>
</template>
<i18n lang="yaml">
en-US:
shippingInformation: Shipping Information
preparation: Preparation
delivery: Delivery
agency: Agency
warehouse: Store
deliveryAddress: Delivery address
total: Total
totalTax: Total + IVA
es-ES:
shippingInformation: Datos de envío
preparation: Preparación
delivery: Entrega
agency: Agencia
warehouse: Almacén
deliveryAddress: Dirección de entrega
total: Total
totalTax: Total + IVA
ca-ES:
shippingInformation: Dades d'enviament
preparation: Preparació
delivery: Lliurament
agency: Agència
warehouse: Magatzem
deliveryAddress: Adreça de lliurament
total: Total
totalTax: Total + IVA
fr-FR:
shippingInformation: Informations sur la livraison
preparation: Préparation
delivery: Livraison
warehouse: Entrepôt
deliveryAddress: Adresse de livraison
total: Total
totalTax: Total + IVA
pt-PT:
shippingInformation: Dados de envio
preparation: Preparação
delivery: Entrega
agency: Agência
warehouse: Armazém
deliveryAddress: Endereço de entrega
total: Total
totalTax: Total + IVA
</i18n>

View File

@ -1,8 +1,8 @@
<script setup> <script setup>
import { ref, computed } from 'vue'; import { ref, computed } from 'vue';
import { appStore } from 'stores/app'; import { useAppStore } from 'stores/app';
const $props = defineProps({ const props = defineProps({
baseURL: { baseURL: {
type: String, type: String,
default: null default: null
@ -23,23 +23,27 @@ const $props = defineProps({
id: { id: {
type: Number, type: Number,
required: true required: true
},
rounded: {
type: Boolean,
default: false
} }
}); });
const app = appStore(); const app = useAppStore();
const show = ref(false); const show = ref(false);
const url = computed(() => { const url = computed(() => {
return `${$props.baseURL ?? app.imageUrl}/${$props.storage}/${$props.size}/${$props.id}`; return `${props.baseURL ?? app.imageUrl}/${props.storage}/${props.size}/${props.id}`;
}); });
</script> </script>
<template> <template>
<QImg <QImg
:class="{ zoomIn: $props.zoomSize }" :class="{ zoomIn: props.zoomSize, rounded: props.rounded }"
:src="url" :src="url"
v-bind="$attrs" v-bind="$attrs"
@click="show = !show" @click="show = !show"
spinner-color="primary" spinner-color="primary"
/> />
<QDialog v-model="show" v-if="$props.zoomSize"> <QDialog v-model="show" v-if="props.zoomSize">
<QImg <QImg
:src="url" :src="url"
size="full" size="full"

View File

@ -1,3 +0,0 @@
import toCurrency from './toCurrency';
export { toCurrency };

View File

@ -1,18 +0,0 @@
import { useI18n } from 'vue-i18n';
export default function (value, symbol = 'EUR', fractionSize = 2) {
if (value == null || value === '') value = 0;
const { locale } = useI18n();
const options = {
style: 'currency',
currency: symbol,
minimumFractionDigits: fractionSize,
maximumFractionDigits: fractionSize
};
const lang = locale.value === 'es' ? 'de' : locale.value;
return new Intl.NumberFormat(lang, options).format(value);
}

View File

@ -49,5 +49,8 @@ export default {
agencyPackages: 'Paquets per agència', agencyPackages: 'Paquets per agència',
accountConfig: 'Configuració', accountConfig: 'Configuració',
addressesList: 'Adreces', addressesList: 'Adreces',
addressDetails: 'Configuració' addressDetails: 'Configuració',
checkout: 'Configurar encàrrec',
//
orderLoadedIntoBasket: 'Comanda carregada a la cistella!'
}; };

View File

@ -63,6 +63,9 @@ export default {
accountConfig: 'Configuration', accountConfig: 'Configuration',
addressesList: 'Addresses', addressesList: 'Addresses',
addressDetails: 'Configuration', addressDetails: 'Configuration',
checkout: 'Configure order',
//
orderLoadedIntoBasket: 'Order loaded into basket!',
orders: 'Orders', orders: 'Orders',
order: 'Pending order', order: 'Pending order',

View File

@ -72,6 +72,9 @@ export default {
accountConfig: 'Configuración', accountConfig: 'Configuración',
addressesList: 'Direcciones', addressesList: 'Direcciones',
addressDetails: 'Configuración', addressDetails: 'Configuración',
checkout: 'Configurar pedido',
//
orderLoadedIntoBasket: '¡Pedido cargado en la cesta!',
orders: 'Pedidos', orders: 'Pedidos',
order: 'Pedido pendiente', order: 'Pedido pendiente',

View File

@ -49,5 +49,8 @@ export default {
agencyPackages: 'Liste par agence', agencyPackages: 'Liste par agence',
accountConfig: 'Configuration', accountConfig: 'Configuration',
addressesList: 'Adresses', addressesList: 'Adresses',
addressDetails: 'Configuration' addressDetails: 'Configuration',
checkout: "Définissez l'ordre",
//
orderLoadedIntoBasket: 'Commande chargée dans le panier!'
}; };

View File

@ -50,5 +50,8 @@ export default {
agencyPackages: 'Bultos por agencia', agencyPackages: 'Bultos por agencia',
accountConfig: 'Configuração', accountConfig: 'Configuração',
addressesList: 'Moradas', addressesList: 'Moradas',
addressDetails: 'Configuração' addressDetails: 'Configuração',
checkout: 'Configurar encomenda',
//
orderLoadedIntoBasket: 'Pedido carregado na cesta!'
}; };

View File

@ -1,73 +1,87 @@
import { date as qdate, format } from 'quasar' import { i18n } from 'src/boot/i18n';
const { pad } = format import { date as qdate, format } from 'quasar';
const { pad } = format;
export function currency (val) { export function currency(val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : val return typeof val === 'number' ? val.toFixed(2) + '€' : val;
} }
export function date (val, format) { export function date(val, format) {
if (val == null) return val if (val == null) return val;
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val);
} }
return qdate.formatDate(val, format, window.i18n.tm('date')) return qdate.formatDate(val, format, i18n.global.tm('date'));
} }
export function relDate (val) { export const formatDateTitle = timeStamp => {
if (val == null) return val const { t, messages, locale } = i18n.global;
const formattedString = qdate.formatDate(
timeStamp,
`dddd, D [${t('of')}] MMMM [${t('of')}] YYYY`,
{
days: messages.value[locale.value].date.days,
months: messages.value[locale.value].date.months
}
);
return formattedString;
};
export function relDate(val) {
if (val == null) return val;
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val);
} }
const dif = qdate.getDateDiff(new Date(), val, 'days') const dif = qdate.getDateDiff(new Date(), val, 'days');
let day let day;
switch (dif) { switch (dif) {
case 0: case 0:
day = 'today' day = 'today';
break break;
case 1: case 1:
day = 'yesterday' day = 'yesterday';
break break;
case -1: case -1:
day = 'tomorrow' day = 'tomorrow';
break break;
} }
if (day) { if (day) {
day = window.i18n.t(day) day = i18n.global.t(day);
} else { } else {
if (dif > 0 && dif <= 7) { if (dif > 0 && dif <= 7) {
day = qdate.formatDate(val, 'ddd', window.i18n.tm('date')) day = qdate.formatDate(val, 'ddd', i18n.global.tm('date'));
} else { } else {
day = qdate.formatDate(val, 'ddd, MMMM Do', window.i18n.tm('date')) day = qdate.formatDate(val, 'ddd, MMMM Do', i18n.global.tm('date'));
} }
} }
return day return day;
} }
export function relTime (val) { export function relTime(val) {
if (val == null) return val if (val == null) return val;
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val);
} }
return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss') return relDate(val) + ' ' + qdate.formatDate(val, 'H:mm:ss');
} }
export function elapsedTime (val) { export function elapsedTime(val) {
if (val == null) return val if (val == null) return val;
if (!(val instanceof Date)) { if (!(val instanceof Date)) {
val = new Date(val) val = new Date(val);
} }
const now = new Date().getTime() const now = new Date().getTime();
val = Math.floor((now - val.getTime()) / 1000) val = Math.floor((now - val.getTime()) / 1000);
const hours = Math.floor(val / 3600) const hours = Math.floor(val / 3600);
val -= hours * 3600 val -= hours * 3600;
const minutes = Math.floor(val / 60) const minutes = Math.floor(val / 60);
val -= minutes * 60 val -= minutes * 60;
const seconds = val const seconds = val;
return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}` return `${pad(hours, 2)}:${pad(minutes, 2)}:${pad(seconds, 2)}`;
} }

View File

@ -0,0 +1 @@
<template>Basket view</template>

View File

@ -346,11 +346,16 @@
import { date, currency } from 'src/lib/filters.js'; import { date, currency } from 'src/lib/filters.js';
import { date as qdate } from 'quasar'; import { date as qdate } from 'quasar';
import axios from 'axios'; import axios from 'axios';
import { useAppStore } from 'stores/app';
const CancelToken = axios.CancelToken; const CancelToken = axios.CancelToken;
export default { export default {
name: 'HederaCatalog', name: 'HederaCatalog',
setup() {
const appStore = useAppStore();
return { appStore };
},
data() { data() {
return { return {
uid: 0, uid: 0,
@ -446,7 +451,7 @@ export default {
if (!value) return; if (!value) return;
const res = await this.$jApi.execQuery( const res = await this.$jApi.execQuery(
`CALL myBasket_getAvailable; `CALL myOrder_getAvailable(${this.appStore.basketOrderId});
SELECT DISTINCT t.id, l.name SELECT DISTINCT t.id, l.name
FROM vn.item i FROM vn.item i
JOIN vn.itemType t ON t.id = i.typeFk JOIN vn.itemType t ON t.id = i.typeFk

View File

@ -0,0 +1 @@
<template>Checkout</template>

View File

@ -1,183 +0,0 @@
<template>
<Teleport :to="$actions">
<QSelect
v-model="year"
:options="years"
color="white"
dark
standout
dense
rounded
/>
</Teleport>
<div class="vn-w-sm">
<div
v-if="!invoices?.length"
class="text-subtitle1 text-center text-grey-7 q-pa-md"
>
{{ $t('noInvoicesFound') }}
</div>
<QCard v-if="invoices?.length">
<QTable
:columns="columns"
:pagination="pagination"
:rows="invoices"
row-key="id"
hide-header
hide-bottom
>
<template v-slot:body="props">
<QTr :props="props">
<QTd key="ref" :props="props">
{{ props.row.ref }}
</QTd>
<QTd key="issued" :props="props">
{{ date(props.row.issued, 'ddd, MMMM Do') }}
</QTd>
<QTd key="amount" :props="props">
{{ currency(props.row.amount) }}
</QTd>
<QTd key="hasPdf" :props="props">
<QBtn
v-if="props.row.hasPdf"
icon="download"
:title="$t('downloadInvoicePdf')"
:href="invoiceUrl(props.row.id)"
target="_blank"
flat
round
/>
<QIcon
v-else
name="warning"
:title="$t('notDownloadable')"
color="warning"
size="24px"
/>
</QTd>
</QTr>
</template>
</QTable>
</QCard>
</div>
</template>
<script>
import { date, currency } from 'src/lib/filters.js';
export default {
name: 'OrdersPendingIndex',
data() {
const curYear = new Date().getFullYear();
const years = [];
for (let year = curYear - 5; year <= curYear; year++) {
years.push(year);
}
return {
columns: [
{ name: 'ref', label: 'serial', field: 'ref', align: 'left' },
{
name: 'issued',
label: 'issued',
field: 'issued',
align: 'left'
},
{ name: 'amount', label: 'amount', field: 'amount' },
{
name: 'hasPdf',
label: 'download',
field: 'hasPdf',
align: 'center'
}
],
pagination: {
rowsPerPage: 0
},
year: curYear,
years,
invoices: null
};
},
async mounted() {
await this.loadData();
},
watch: {
async year() {
await this.loadData();
}
},
methods: {
date,
currency,
async loadData() {
const params = {
from: new Date(this.year, 0),
to: new Date(this.year, 11, 31, 23, 59, 59)
};
this._invoices = await this.$jApi.query(
`SELECT id, ref, issued, amount, hasPdf
FROM myInvoice
WHERE issued BETWEEN #from AND #to
ORDER BY issued DESC
LIMIT 500`,
params
);
},
invoiceUrl(id) {
return (
'?' +
new URLSearchParams({
srv: 'rest:dms/invoice',
invoice: id,
access_token: this.$user.token
}).toString()
);
}
}
};
</script>
<i18n lang="yaml">
en-US:
noInvoicesFound: No invoices found
serial: Serial
issued: Date
amount: Import
downloadInvoicePdf: Download invoice PDF
notDownloadable: Not available for download, request the invoice to your salesperson
es-ES:
noInvoicesFound: No se han encontrado facturas
serial: Serie
issued: Fecha
amount: Importe
downloadInvoicePdf: Descargar factura en PDF
notDownloadable: No disponible para descarga, solicita la factura a tu comercial
ca-ES:
noInvoicesFound: No s'han trobat factures
serial: Sèrie
issued: Data
amount: Import
downloadInvoicePdf: Descarregar PDF
notDownloadable: No disponible per cescarrega, sol·licita la factura al teu comercial
fr-FR:
noInvoicesFound: Aucune facture trouvée
serial: Série
issued: Date
amount: Montant
downloadInvoicePdf: Télécharger le PDF
notDownloadable: Non disponible en téléchargement, demander la facture à votre commercial
pt-PT:
noInvoicesFound: Nenhuma fatura encontrada
serial: Serie
issued: Data
amount: Importe
downloadInvoicePdf: Baixar PDF
notDownloadable: Não disponível para download, solicite a fatura ao seu comercial
</i18n>

View File

@ -0,0 +1,170 @@
<script setup>
import { ref, onMounted, inject, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { currency } from 'src/lib/filters.js';
import { date as qdate } from 'quasar';
import { userStore as useUserStore } from 'stores/user';
const { t } = useI18n();
const jApi = inject('jApi');
const userStore = useUserStore();
const currentYear = ref(new Date('2001-01-01').getFullYear());
const years = ref([]);
const invoices = ref([]);
const columns = computed(() => [
{ name: 'ref', label: t('serial'), field: 'ref', align: 'left' },
{
name: 'issued',
label: t('issued'),
field: 'issued',
align: 'left',
format: val => qdate.formatDate(val, 'D MMM YYYY')
},
{
name: 'amount',
label: t('amount'),
field: 'amount',
format: val => currency(val)
},
{
name: 'hasPdf',
field: 'hasPdf',
align: 'center'
}
]);
const getInvoiceUrl = id => {
const params = new URLSearchParams({
access_token: userStore.token
});
return `/api/InvoiceOuts/${id}/download?${params}`;
};
const fetchInvoices = async () => {
const params = {
from: new Date(currentYear.value, 0),
to: new Date(currentYear.value, 11, 31, 23, 59, 59)
};
invoices.value = await jApi.query(
`SELECT id, ref, issued, amount, hasPdf
FROM myInvoice
WHERE issued BETWEEN #from AND #to
ORDER BY issued DESC
LIMIT 100`,
params
);
};
onMounted(async () => {
await fetchInvoices();
for (let year = currentYear.value - 5; year <= currentYear.value; year++) {
years.value.push(year);
}
});
</script>
<template>
<Teleport :to="$actions">
<QSelect
v-model="currentYear"
:options="years"
color="white"
dark
standout
dense
rounded
@update:model-value="fetchInvoices()"
/>
</Teleport>
<div class="vn-w-sm">
<QCard>
<!-- -->
<QTable
:columns="columns"
:rows="invoices"
:no-data-label="t('noInvoicesFound')"
row-key="id"
:hide-header="!invoices.length"
:rows-per-page-options="[0]"
>
<template #body-cell-hasPdf="{ row }">
<QTd
auto-width
@click.stop
class="flex full-width justify-center items-center"
>
<QBtn
v-if="row.hasPdf"
icon="download"
:href="getInvoiceUrl(row.id)"
target="_blank"
flat
round
>
<QTooltip>
{{ t('downloadInvoicePdf') }}
</QTooltip>
</QBtn>
<QIcon
v-else
name="warning"
:title="t('notDownloadable')"
color="warning"
size="sm"
>
<QTooltip>
{{ t('requestTheInvoiceToComercial') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</QTable>
</QCard>
</div>
</template>
<i18n lang="yaml">
en-US:
noInvoicesFound: No invoices found
serial: Serial
issued: Date
amount: Import
downloadInvoicePdf: Download invoice PDF
notDownloadable: Not available for download, request the invoice to your salesperson
requestTheInvoiceToComercial: Request the invoice to your salesperson
es-ES:
noInvoicesFound: No se han encontrado facturas
serial: Serie
issued: Fecha
amount: Importe
downloadInvoicePdf: Descargar factura en PDF
notDownloadable: No disponible para descarga, solicita la factura a tu comercial
requestTheInvoiceToComercial: Solicita la factura a tu comercial
ca-ES:
noInvoicesFound: No s'han trobat factures
serial: Sèrie
issued: Data
amount: Import
downloadInvoicePdf: Descarregar PDF
notDownloadable: No disponible per cescarrega, sol·licita la factura al teu comercial
requestTheInvoiceToComercial: Sol·licita la factura al teu comercial
fr-FR:
noInvoicesFound: Aucune facture trouvée
serial: Série
issued: Date
amount: Montant
downloadInvoicePdf: Télécharger le PDF
notDownloadable: Non disponible en téléchargement, demander la facture à votre commercial
requestTheInvoiceToComercial: Demander la facture à votre commercial
pt-PT:
noInvoicesFound: Nenhuma fatura encontrada
serial: Serie
issued: Data
amount: Importe
downloadInvoicePdf: Baixar PDF
notDownloadable: Não disponível para download, solicite a fatura ao seu comercial
requestTheInvoiceToComercial: Solicite a fatura ao seu comercial
</i18n>

View File

@ -1,70 +1,112 @@
<script setup>
import { ref, onMounted, inject } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import CardList from 'src/components/ui/CardList.vue';
import { currency, formatDateTitle } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv';
const { t } = useI18n();
const route = useRoute();
const jApi = inject('jApi');
const orders = ref(null);
const debt = ref(0);
const tpv = tpvStore();
onMounted(async () => {
await tpv.check(route);
orders.value = await jApi.query('CALL myTicket_list(NULL, NULL)');
debt.value = await jApi.getValue('SELECT -myClient_getDebt(NULL)');
});
const onPayClick = async () => {
let amount = -debt.value;
amount = amount <= 0 ? null : amount;
let defaultAmountStr = '';
if (amount !== null) {
defaultAmountStr = amount;
}
amount = prompt(t('amountToPay'), defaultAmountStr);
if (amount != null) {
amount = parseFloat(amount.replace(',', '.'));
await tpv.pay(amount);
}
};
</script>
<template> <template>
<Teleport :to="$actions"> <Teleport :to="$actions">
<div class="balance"> <div class="balance">
<span class="label">{{ $t('balance') }}</span> <span class="label">{{ t('balance') }}</span>
<span class="amount" :class="{ negative: debt < 0 }"> <span class="amount" :class="{ negative: debt < 0 }">
{{ currency(debt || 0) }} {{ currency(debt || 0) }}
</span> </span>
<QIcon <QIcon
name="info" name="info"
:title="$t('paymentInfo')" :title="t('paymentInfo')"
class="info" class="info"
size="24px" size="sm"
/> />
</div> </div>
<QBtn <QBtn
icon="payments" icon="payments"
:label="$t('makePayment')" :label="t('makePayment')"
@click="onPayClick()" @click="onPayClick()"
rounded rounded
no-caps no-caps
/> />
<QBtn <QBtn
to="/ecomerce/basket" :to="{ name: 'basket' }"
icon="shopping_cart" icon="shopping_cart"
:label="$t('shoppingCart')" :label="t('shoppingCart')"
rounded rounded
no-caps no-caps
/> />
</Teleport> </Teleport>
<div class="vn-w-sm"> <QPage class="vn-w-sm">
<div <div
v-if="!orders?.length" v-if="!orders?.length"
class="text-subtitle1 text-center text-grey-7 q-pa-md" class="text-subtitle1 text-center text-grey-7 q-pa-md"
> >
{{ $t('noOrdersFound') }} {{ t('noOrdersFound') }}
</div> </div>
<QCard v-if="orders?.length"> <QList v-if="orders?.length">
<QList bordered separator padding> <CardList
<QItem v-for="order in orders"
v-for="order in orders" :key="order.id"
:key="order.id" :to="`ticket/${order.id}`"
:to="`ticket/${order.id}`" tag="label"
clickable >
v-ripple <template #content>
> <QItemLabel
<QItemSection> class="full-width text-bold q-mb-sm flex row justify-between"
<QItemLabel> >
{{ date(order.landed, 'ddd, MMMM Do') }} <span>{{ formatDateTitle(order.landed) }}</span>
</QItemLabel> <span>{{ currency(order.total) }}</span>
<QItemLabel caption>#{{ order.id }}</QItemLabel> </QItemLabel>
<QItemLabel caption>{{ order.nickname }}</QItemLabel> <QItemLabel>#{{ order.id }}</QItemLabel>
<QItemLabel caption>{{ order.agency }}</QItemLabel> <QItemLabel>{{ order.nickname }}</QItemLabel>
</QItemSection> <QItemLabel>{{ order.agency }}</QItemLabel>
<QItemSection side top> {{ order.total }} </QItemSection> </template>
</QItem> </CardList>
</QList> </QList>
</QCard>
<QPageSticky> <QPageSticky>
<QBtn <QBtn
fab fab
icon="add_shopping_cart" icon="add_shopping_cart"
color="accent" color="accent"
to="/ecomerce/catalog" :to="{ name: 'catalog' }"
:title="$t('startOrder')" :title="t('startOrder')"
/> />
</QPageSticky> </QPageSticky>
</div> </QPage>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
@ -92,50 +134,6 @@
} }
</style> </style>
<script>
import { date, currency } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv';
export default {
name: 'OrdersPendingIndex',
data() {
return {
orders: null,
debt: 0,
tpv: tpvStore()
};
},
async mounted() {
await this.tpv.check(this.$route);
this.orders = await this.$jApi.query('CALL myTicket_list(NULL, NULL)');
this.debt = await this.$jApi.getValue('SELECT -myClient_getDebt(NULL)');
},
methods: {
date,
currency,
async onPayClick() {
let amount = -this.debt;
amount = amount <= 0 ? null : amount;
let defaultAmountStr = '';
if (amount !== null) {
defaultAmountStr = amount;
}
amount = prompt(this.$t('amountToPay'), defaultAmountStr);
if (amount != null) {
amount = parseFloat(amount.replace(',', '.'));
await this.tpv.pay(amount);
}
}
}
};
</script>
<i18n lang="yaml"> <i18n lang="yaml">
en-US: en-US:
startOrder: Start order startOrder: Start order
@ -148,6 +146,7 @@ en-US:
disregards future orders. For get your order shipped, this amount must be disregards future orders. For get your order shipped, this amount must be
equal to or greater than 0. If you want to make a down payment, click the equal to or greater than 0. If you want to make a down payment, click the
payment button, delete the suggested amount and enter the amount you want. payment button, delete the suggested amount and enter the amount you want.
amountToPay: 'Amount to pay (€):'
es-ES: es-ES:
startOrder: Empezar pedido startOrder: Empezar pedido
noOrdersFound: No se encontrado pedidos noOrdersFound: No se encontrado pedidos
@ -160,6 +159,7 @@ es-ES:
esta cantidad debe ser igual o mayor que 0. Si quieres realizar una entrega a esta cantidad debe ser igual o mayor que 0. Si quieres realizar una entrega a
cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la
cantidad que desees. cantidad que desees.
amountToPay: 'Cantidad a pagar (€):'
ca-ES: ca-ES:
startOrder: Començar encàrrec startOrder: Començar encàrrec
noOrdersFound: No s'han trobat comandes noOrdersFound: No s'han trobat comandes
@ -172,6 +172,7 @@ ca-ES:
enviat, aquesta quantitat ha de ser igual o més gran que 0. Si vols fer un enviat, aquesta quantitat ha de ser igual o més gran que 0. Si vols fer un
lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida
e introdueix la quantitat que vulguis. e introdueix la quantitat que vulguis.
amountToPay: 'Quantitat a pagar (€):'
fr-FR: fr-FR:
startOrder: Acheter startOrder: Acheter
noOrdersFound: Aucune commande trouvée noOrdersFound: Aucune commande trouvée
@ -184,6 +185,7 @@ fr-FR:
commande est expédiée, ce montant doit être égal ou supérieur à 0. Si vous commande est expédiée, ce montant doit être égal ou supérieur à 0. Si vous
voulez faire un versement, le montant suggéré effacé et entrez le montant que voulez faire un versement, le montant suggéré effacé et entrez le montant que
vous souhaitez. vous souhaitez.
amountToPay: 'Montant à payer (€):'
pt-PT: pt-PT:
startOrder: Iniciar encomenda startOrder: Iniciar encomenda
noOrdersFound: Nenhum pedido encontrado noOrdersFound: Nenhum pedido encontrado
@ -196,4 +198,5 @@ pt-PT:
quantidade deve ser igual ou superior a 0. Se queres realizar um depósito à quantidade deve ser igual ou superior a 0. Se queres realizar um depósito à
conta, clique no botão de pagamento, apague a quantidade sugerida e introduza conta, clique no botão de pagamento, apague a quantidade sugerida e introduza
a quantidade que deseje. a quantidade que deseje.
amountToPay: 'Valor a pagar (€):'
</i18n> </i18n>

View File

@ -1,18 +1,20 @@
<script setup> <script setup>
import { ref, inject, onMounted } from 'vue'; import { ref, inject, onMounted } from 'vue';
import { useI18n } from 'vue-i18n'; import { useI18n } from 'vue-i18n';
import { date } from 'quasar'; import { useRouter } from 'vue-router';
import CardList from 'src/components/ui/CardList.vue'; import CardList from 'src/components/ui/CardList.vue';
import { toCurrency } from 'src/filters/index'; import { currency, formatDateTitle } from 'src/lib/filters.js';
import { useVnConfirm } from 'src/composables/useVnConfirm.js'; import { useVnConfirm } from 'src/composables/useVnConfirm.js';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { useAppStore } from 'src/stores/app.js';
const jApi = inject('jApi'); const jApi = inject('jApi');
const { t, locale } = useI18n(); const { t } = useI18n();
const globalI18n = useI18n({ useScope: 'global' });
const { openConfirmationModal } = useVnConfirm(); const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify(); const { notify } = useNotify();
const store = useAppStore();
const router = useRouter();
const orders = ref([]); const orders = ref([]);
@ -32,18 +34,6 @@ const getOrders = async () => {
} }
}; };
const formatDateTitle = timeStamp => {
const formattedString = date.formatDate(
timeStamp,
`dddd, D [${t('of')}] MMMM [${t('of')}] YYYY`,
{
days: globalI18n.messages.value[locale.value].date.days,
months: globalI18n.messages.value[locale.value].date.months
}
);
return formattedString;
};
const removeOrder = async (id, index) => { const removeOrder = async (id, index) => {
try { try {
await jApi.execQuery( await jApi.execQuery(
@ -61,6 +51,11 @@ const removeOrder = async (id, index) => {
} }
}; };
const loadOrder = orderId => {
store.loadIntoBasket(orderId);
router.push({ name: 'catalog' });
};
onMounted(async () => { onMounted(async () => {
getOrders(); getOrders();
}); });
@ -69,30 +64,34 @@ onMounted(async () => {
<template> <template>
<Teleport :to="$actions"> <Teleport :to="$actions">
<QBtn <QBtn
to="/ecomerce/basket" :to="{ name: 'checkout' }"
icon="shopping_cart" icon="add_shopping_cart"
:label="$t('shoppingCart')" :label="t('newOrder')"
rounded rounded
no-caps no-caps
/> />
</Teleport> </Teleport>
<QPage class="vn-w-sm"> <QPage class="vn-w-sm">
<CardList v-for="(order, index) in orders" :key="index"> <CardList
v-for="(order, index) in orders"
:key="index"
:to="{ name: 'basket', params: { id: order.id } }"
>
<template #content> <template #content>
<span class="text-bold q-mb-sm">{{ <QItemLabel class="text-bold q-mb-sm">{{
formatDateTitle(order.sent) formatDateTitle(order.sent)
}}</span> }}</QItemLabel>
<span> #{{ order.id }} </span> <QItemLabel> #{{ order.id }} </QItemLabel>
<span>{{ order.nickname }}</span> <QItemLabel>{{ order.nickname }}</QItemLabel>
<span>{{ order.agency }}</span> <QItemLabel>{{ order.agency }}</QItemLabel>
<span>{{ toCurrency(order.taxableBase) }}</span> <QItemLabel>{{ currency(order.taxableBase) }}</QItemLabel>
</template> </template>
<template #actions> <template #actions>
<QBtn <QBtn
icon="delete" icon="delete"
flat flat
rounded rounded
@click.stop=" @click.stop.prevent="
openConfirmationModal( openConfirmationModal(
null, null,
t('areYouSureDeleteOrder'), t('areYouSureDeleteOrder'),
@ -104,8 +103,7 @@ onMounted(async () => {
icon="shopping_bag" icon="shopping_bag"
flat flat
rounded rounded
:to="{ name: 'basket', params: { id: order.id } }" @click.stop.prevent="loadOrder(order.id)"
@click.stop="goToAddressDetails(address.id)"
/> />
</template> </template>
</CardList> </CardList>

View File

@ -1,145 +0,0 @@
<template>
<Teleport :to="$actions">
<QBtn
icon="print"
:label="$t('printDeliveryNote')"
@click="onPrintClick()"
rounded
no-caps
/>
</Teleport>
<div>
<QCard class="vn-w-sm">
<QCardSection>
<div class="text-h6">#{{ ticket.id }}</div>
</QCardSection>
<QCardSection>
<div class="text-h6">{{ $t('shippingInformation') }}</div>
<div>
{{ $t('preparation') }}
{{ date(ticket.shipped, 'ddd, MMMM Do') }}
</div>
<div>
{{ $t('delivery') }}
{{ date(ticket.shipped, 'ddd, MMMM Do') }}
</div>
<div>
{{ $t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
{{ ticket.agency }}
</div>
</QCardSection>
<QCardSection>
<div class="text-h6">{{ $t('deliveryAddress') }}</div>
<div>{{ ticket.nickname }}</div>
<div>{{ ticket.street }}</div>
<div>
{{ ticket.postalCode }} {{ ticket.city }} ({{
ticket.province
}})
</div>
</QCardSection>
<QSeparator inset />
<QList v-for="row in rows" :key="row.itemFk">
<QItem>
<QItemSection avatar>
<QAvatar size="68px">
<img
:src="`${$app.imageUrl}/catalog/200x200/${row.image}`"
/>
</QAvatar>
</QItemSection>
<QItemSection>
<QItemLabel lines="1">
{{ row.concept }}
</QItemLabel>
<QItemLabel lines="1" caption>
{{ row.value5 }} {{ row.value6 }} {{ row.value7 }}
</QItemLabel>
<QItemLabel lines="1">
{{ row.quantity }} x {{ currency(row.price) }}
</QItemLabel>
</QItemSection>
<QItemSection side class="total">
<QItemLabel>
<span class="discount" v-if="row.discount">
{{ currency(discountSubtotal(row)) }} -
{{ currency(row.discount) }} =
</span>
{{ currency(subtotal(row)) }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
</QCard>
</div>
</template>
<style lang="scss" scoped>
.total {
justify-content: flex-end;
}
</style>
<script>
import { date, currency } from 'src/lib/filters.js';
export default {
name: 'OrdersConfirmedView',
data() {
return {
ticket: {},
rows: null,
services: null,
packages: null
};
},
async mounted() {
const params = {
ticket: parseInt(this.$route.params.id)
};
this.ticket = await this.$jApi.getObject(
'CALL myTicket_get(#ticket)',
params
);
this.rows = await this.$jApi.query(
'CALL myTicket_getRows(#ticket)',
params
);
this.services = await this.$jApi.query(
'CALL myTicket_getServices(#ticket)',
params
);
this.packages = await this.$jApi.query(
'CALL myTicket_getPackages(#ticket)',
params
);
},
methods: {
date,
currency,
discountSubtotal(line) {
return line.quantity * line.price;
},
subtotal(line) {
const discount = line.discount;
return this.discountSubtotal(line) * ((100 - discount) / 100);
},
onPrintClick() {
const params = new URLSearchParams({
access_token: this.$user.token,
recipientId: this.$user.id,
type: 'deliveryNote'
});
window.open(
`/api/Tickets/${this.ticket.id}/delivery-note-pdf?${params.toString()}`
);
}
}
};
</script>

View File

@ -0,0 +1,74 @@
<script setup>
import { onMounted, inject, ref } from 'vue';
import { useRoute } from 'vue-router';
import { useI18n } from 'vue-i18n';
import TicketDetails from 'src/components/ui/TicketDetails.vue';
import { userStore as useUserStore } from 'stores/user';
const { t } = useI18n();
const jApi = inject('jApi');
const route = useRoute();
const userStore = useUserStore();
const ticket = ref({});
const rows = ref([]);
const services = ref(null);
const packages = ref(null);
onMounted(async () => {
const params = {
ticket: parseInt(route.params.id)
};
ticket.value = await jApi.getObject('CALL myTicket_get(#ticket)', params);
rows.value = await jApi.query('CALL myTicket_getRows(#ticket)', params);
services.value = await jApi.query(
'CALL myTicket_getServices(#ticket)',
params
);
packages.value = await jApi.query(
'CALL myTicket_getPackages(#ticket)',
params
);
});
const onPrintClick = () => {
const params = new URLSearchParams({
access_token: userStore.token,
recipientId: userStore.id,
type: 'deliveryNote'
});
window.open(
`/api/Tickets/${ticket.value.id}/delivery-note-pdf?${params.toString()}`
);
};
</script>
<template>
<Teleport :to="$actions">
<QBtn
icon="print"
:label="t('printDeliveryNote')"
@click="onPrintClick()"
rounded
no-caps
/>
</Teleport>
<QPage>
<TicketDetails :rows="rows" :ticket="ticket" />
</QPage>
</template>
<i18n lang="yaml">
en-US:
printDeliveryNote: Print delivery note
es-ES:
printDeliveryNote: Imprimir albarán
ca-ES:
printDeliveryNote: Imprimir albarà
fr-FR:
printDeliveryNote: Imprimer bulletin de livraison
pt-PT:
printDeliveryNote: Imprimir nota de entrega
</i18n>

View File

@ -1,5 +1,5 @@
import { route } from 'quasar/wrappers'; import { route } from 'quasar/wrappers';
import { appStore } from 'stores/app'; import { useAppStore } from 'stores/app';
import { import {
createRouter, createRouter,
createMemoryHistory, createMemoryHistory,
@ -39,7 +39,7 @@ export default route(function (/* { store, ssrContext } */) {
Router.afterEach((to, from) => { Router.afterEach((to, from) => {
if (from.name === to.name) return; if (from.name === to.name) return;
const app = appStore(); const app = useAppStore();
app.$patch({ app.$patch({
title: i18n.global.t(to.name || 'home'), title: i18n.global.t(to.name || 'home'),
subtitle: null, subtitle: null,

View File

@ -37,17 +37,17 @@ const routes = [
{ {
name: 'confirmedOrders', name: 'confirmedOrders',
path: '/ecomerce/orders', path: '/ecomerce/orders',
component: () => import('pages/Ecomerce/Orders.vue') component: () => import('pages/Ecomerce/OrdersView.vue')
}, },
{ {
name: 'ticket', name: 'ticket',
path: '/ecomerce/ticket/:id', path: '/ecomerce/ticket/:id',
component: () => import('pages/Ecomerce/Ticket.vue') component: () => import('pages/Ecomerce/TicketView.vue')
}, },
{ {
name: 'invoices', name: 'invoices',
path: '/ecomerce/invoices', path: '/ecomerce/invoices',
component: () => import('pages/Ecomerce/Invoices.vue') component: () => import('pages/Ecomerce/InvoicesView.vue')
}, },
{ {
name: 'pendingOrders', name: 'pendingOrders',
@ -59,6 +59,16 @@ const routes = [
path: '/ecomerce/catalog/:category?/:type?', path: '/ecomerce/catalog/:category?/:type?',
component: () => import('pages/Ecomerce/Catalog.vue') component: () => import('pages/Ecomerce/Catalog.vue')
}, },
{
name: 'basket',
path: '/ecomerce/basket/:id?',
component: () => import('pages/Ecomerce/BasketView.vue')
},
{
name: 'checkout',
path: '/ecomerce/checkout',
component: () => import('pages/Ecomerce/CheckoutView.vue')
},
{ {
name: 'agencyPackages', name: 'agencyPackages',
path: '/agencies/packages', path: '/agencies/packages',

View File

@ -1,19 +1,51 @@
import { defineStore } from 'pinia' import { defineStore } from 'pinia';
import { jApi } from 'boot/axios' import { jApi } from 'boot/axios';
import useNotify from 'src/composables/useNotify.js';
export const appStore = defineStore('hedera', { const { notify } = useNotify();
export const useAppStore = defineStore('hedera', {
state: () => ({ state: () => ({
title: null, title: null,
subtitle: null, subtitle: null,
imageUrl: '', imageUrl: '',
useRightDrawer: false, useRightDrawer: false,
rightDrawerOpen: false rightDrawerOpen: false,
basketOrderId: null
}), }),
actions: { actions: {
async loadConfig () { async init() {
const imageUrl = await jApi.getValue('SELECT url FROM imageConfig') this.getBasketOrderId();
this.$patch({ imageUrl }) },
getBasketOrderId() {
this.basketOrderId = localStorage.getItem('hederaBasket');
},
async loadConfig() {
const imageUrl = await jApi.getValue('SELECT url FROM imageConfig');
this.$patch({ imageUrl });
},
async checkOrder(orderId) {
try {
const resultSet = await jApi.execQuery(
'CALL myOrder_checkConfig(#id)',
{ id: orderId }
);
resultSet.fetchValue();
} catch (err) {
console.error('Error checking order', err);
}
},
loadIntoBasket(orderId) {
if (this.basketOrderId !== orderId) {
localStorage.setItem('hederaBasket', orderId);
this.basketOrderId = orderId;
notify('orderLoadedIntoBasket', 'positive');
}
} }
} }
}) });

View File

@ -1,5 +1,5 @@
import { store } from 'quasar/wrappers' import { store } from 'quasar/wrappers';
import { createPinia } from 'pinia' import { createPinia } from 'pinia';
/* /*
* If not building with SSR mode, you can * If not building with SSR mode, you can
@ -11,10 +11,10 @@ import { createPinia } from 'pinia'
*/ */
export default store((/* { ssrContext } */) => { export default store((/* { ssrContext } */) => {
const pinia = createPinia() const pinia = createPinia();
// You can add Pinia plugins here // You can add Pinia plugins here
// pinia.use(SomePiniaPlugin) // pinia.use(SomePiniaPlugin)
return pinia return pinia;
}) });