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 />
</template>
<script>
import { defineComponent } from 'vue';
<script setup>
import { useAppStore } from 'stores/app';
import { onBeforeMount } from 'vue';
const appStore = useAppStore();
export default defineComponent({
name: 'App'
});
onBeforeMount(() => appStore.init());
</script>

View File

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

View File

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

View File

@ -23,9 +23,9 @@ const handleClick = () => {
@click="handleClick()"
>
<QItemSection class="no-padding">
<div class="row">
<div class="row no-wrap">
<slot name="prepend" />
<div class="column">
<div class="column full-width">
<slot name="content" />
</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>
import { ref, computed } from 'vue';
import { appStore } from 'stores/app';
import { useAppStore } from 'stores/app';
const $props = defineProps({
const props = defineProps({
baseURL: {
type: String,
default: null
@ -23,23 +23,27 @@ const $props = defineProps({
id: {
type: Number,
required: true
},
rounded: {
type: Boolean,
default: false
}
});
const app = appStore();
const app = useAppStore();
const show = ref(false);
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>
<template>
<QImg
:class="{ zoomIn: $props.zoomSize }"
:class="{ zoomIn: props.zoomSize, rounded: props.rounded }"
:src="url"
v-bind="$attrs"
@click="show = !show"
spinner-color="primary"
/>
<QDialog v-model="show" v-if="$props.zoomSize">
<QDialog v-model="show" v-if="props.zoomSize">
<QImg
:src="url"
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',
accountConfig: 'Configuració',
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',
addressesList: 'Addresses',
addressDetails: 'Configuration',
checkout: 'Configure order',
//
orderLoadedIntoBasket: 'Order loaded into basket!',
orders: 'Orders',
order: 'Pending order',

View File

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

View File

@ -49,5 +49,8 @@ export default {
agencyPackages: 'Liste par agence',
accountConfig: 'Configuration',
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',
accountConfig: 'Configuração',
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'
const { pad } = format
import { i18n } from 'src/boot/i18n';
import { date as qdate, format } from 'quasar';
const { pad } = format;
export function currency (val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : val
export function currency(val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : val;
}
export function date (val, format) {
if (val == null) return val
export function date(val, format) {
if (val == null) return val;
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) {
if (val == null) return val
export const formatDateTitle = timeStamp => {
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)) {
val = new Date(val)
val = new Date(val);
}
const dif = qdate.getDateDiff(new Date(), val, 'days')
let day
const dif = qdate.getDateDiff(new Date(), val, 'days');
let day;
switch (dif) {
case 0:
day = 'today'
break
day = 'today';
break;
case 1:
day = 'yesterday'
break
day = 'yesterday';
break;
case -1:
day = 'tomorrow'
break
day = 'tomorrow';
break;
}
if (day) {
day = window.i18n.t(day)
day = i18n.global.t(day);
} else {
if (dif > 0 && dif <= 7) {
day = qdate.formatDate(val, 'ddd', window.i18n.tm('date'))
day = qdate.formatDate(val, 'ddd', i18n.global.tm('date'));
} 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) {
if (val == null) return val
export function relTime(val) {
if (val == null) return val;
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) {
if (val == null) return val
export function elapsedTime(val) {
if (val == null) return val;
if (!(val instanceof Date)) {
val = new Date(val)
val = new Date(val);
}
const now = new Date().getTime()
val = Math.floor((now - val.getTime()) / 1000)
const now = new Date().getTime();
val = Math.floor((now - val.getTime()) / 1000);
const hours = Math.floor(val / 3600)
val -= hours * 3600
const minutes = Math.floor(val / 60)
val -= minutes * 60
const seconds = val
const hours = Math.floor(val / 3600);
val -= hours * 3600;
const minutes = Math.floor(val / 60);
val -= minutes * 60;
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 as qdate } from 'quasar';
import axios from 'axios';
import { useAppStore } from 'stores/app';
const CancelToken = axios.CancelToken;
export default {
name: 'HederaCatalog',
setup() {
const appStore = useAppStore();
return { appStore };
},
data() {
return {
uid: 0,
@ -446,7 +451,7 @@ export default {
if (!value) return;
const res = await this.$jApi.execQuery(
`CALL myBasket_getAvailable;
`CALL myOrder_getAvailable(${this.appStore.basketOrderId});
SELECT DISTINCT t.id, l.name
FROM vn.item i
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>
<Teleport :to="$actions">
<div class="balance">
<span class="label">{{ $t('balance') }}</span>
<span class="label">{{ t('balance') }}</span>
<span class="amount" :class="{ negative: debt < 0 }">
{{ currency(debt || 0) }}
</span>
<QIcon
name="info"
:title="$t('paymentInfo')"
:title="t('paymentInfo')"
class="info"
size="24px"
size="sm"
/>
</div>
<QBtn
icon="payments"
:label="$t('makePayment')"
:label="t('makePayment')"
@click="onPayClick()"
rounded
no-caps
/>
<QBtn
to="/ecomerce/basket"
:to="{ name: 'basket' }"
icon="shopping_cart"
:label="$t('shoppingCart')"
:label="t('shoppingCart')"
rounded
no-caps
/>
</Teleport>
<div class="vn-w-sm">
<QPage class="vn-w-sm">
<div
v-if="!orders?.length"
class="text-subtitle1 text-center text-grey-7 q-pa-md"
>
{{ $t('noOrdersFound') }}
{{ t('noOrdersFound') }}
</div>
<QCard v-if="orders?.length">
<QList bordered separator padding>
<QItem
v-for="order in orders"
:key="order.id"
:to="`ticket/${order.id}`"
clickable
v-ripple
>
<QItemSection>
<QItemLabel>
{{ date(order.landed, 'ddd, MMMM Do') }}
</QItemLabel>
<QItemLabel caption>#{{ order.id }}</QItemLabel>
<QItemLabel caption>{{ order.nickname }}</QItemLabel>
<QItemLabel caption>{{ order.agency }}</QItemLabel>
</QItemSection>
<QItemSection side top> {{ order.total }} </QItemSection>
</QItem>
</QList>
</QCard>
<QList v-if="orders?.length">
<CardList
v-for="order in orders"
:key="order.id"
:to="`ticket/${order.id}`"
tag="label"
>
<template #content>
<QItemLabel
class="full-width text-bold q-mb-sm flex row justify-between"
>
<span>{{ formatDateTitle(order.landed) }}</span>
<span>{{ currency(order.total) }}</span>
</QItemLabel>
<QItemLabel>#{{ order.id }}</QItemLabel>
<QItemLabel>{{ order.nickname }}</QItemLabel>
<QItemLabel>{{ order.agency }}</QItemLabel>
</template>
</CardList>
</QList>
<QPageSticky>
<QBtn
fab
icon="add_shopping_cart"
color="accent"
to="/ecomerce/catalog"
:title="$t('startOrder')"
:to="{ name: 'catalog' }"
:title="t('startOrder')"
/>
</QPageSticky>
</div>
</QPage>
</template>
<style lang="scss" scoped>
@ -92,50 +134,6 @@
}
</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">
en-US:
startOrder: Start order
@ -148,6 +146,7 @@ en-US:
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
payment button, delete the suggested amount and enter the amount you want.
amountToPay: 'Amount to pay (€):'
es-ES:
startOrder: Empezar pedido
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
cuenta, pulsa el botón de pago, borra la cantidad sugerida e introduce la
cantidad que desees.
amountToPay: 'Cantidad a pagar (€):'
ca-ES:
startOrder: Començar encàrrec
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
lliurament a compte, prem el botó de pagament, esborra la quantitat suggerida
e introdueix la quantitat que vulguis.
amountToPay: 'Quantitat a pagar (€):'
fr-FR:
startOrder: Acheter
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
voulez faire un versement, le montant suggéré effacé et entrez le montant que
vous souhaitez.
amountToPay: 'Montant à payer (€):'
pt-PT:
startOrder: Iniciar encomenda
noOrdersFound: Nenhum pedido encontrado
@ -196,4 +198,5 @@ pt-PT:
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
a quantidade que deseje.
amountToPay: 'Valor a pagar (€):'
</i18n>

View File

@ -1,18 +1,20 @@
<script setup>
import { ref, inject, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { date } from 'quasar';
import { useRouter } from 'vue-router';
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 useNotify from 'src/composables/useNotify.js';
import { useAppStore } from 'src/stores/app.js';
const jApi = inject('jApi');
const { t, locale } = useI18n();
const globalI18n = useI18n({ useScope: 'global' });
const { t } = useI18n();
const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify();
const store = useAppStore();
const router = useRouter();
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) => {
try {
await jApi.execQuery(
@ -61,6 +51,11 @@ const removeOrder = async (id, index) => {
}
};
const loadOrder = orderId => {
store.loadIntoBasket(orderId);
router.push({ name: 'catalog' });
};
onMounted(async () => {
getOrders();
});
@ -69,30 +64,34 @@ onMounted(async () => {
<template>
<Teleport :to="$actions">
<QBtn
to="/ecomerce/basket"
icon="shopping_cart"
:label="$t('shoppingCart')"
:to="{ name: 'checkout' }"
icon="add_shopping_cart"
:label="t('newOrder')"
rounded
no-caps
/>
</Teleport>
<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>
<span class="text-bold q-mb-sm">{{
<QItemLabel class="text-bold q-mb-sm">{{
formatDateTitle(order.sent)
}}</span>
<span> #{{ order.id }} </span>
<span>{{ order.nickname }}</span>
<span>{{ order.agency }}</span>
<span>{{ toCurrency(order.taxableBase) }}</span>
}}</QItemLabel>
<QItemLabel> #{{ order.id }} </QItemLabel>
<QItemLabel>{{ order.nickname }}</QItemLabel>
<QItemLabel>{{ order.agency }}</QItemLabel>
<QItemLabel>{{ currency(order.taxableBase) }}</QItemLabel>
</template>
<template #actions>
<QBtn
icon="delete"
flat
rounded
@click.stop="
@click.stop.prevent="
openConfirmationModal(
null,
t('areYouSureDeleteOrder'),
@ -104,8 +103,7 @@ onMounted(async () => {
icon="shopping_bag"
flat
rounded
:to="{ name: 'basket', params: { id: order.id } }"
@click.stop="goToAddressDetails(address.id)"
@click.stop.prevent="loadOrder(order.id)"
/>
</template>
</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 { appStore } from 'stores/app';
import { useAppStore } from 'stores/app';
import {
createRouter,
createMemoryHistory,
@ -39,7 +39,7 @@ export default route(function (/* { store, ssrContext } */) {
Router.afterEach((to, from) => {
if (from.name === to.name) return;
const app = appStore();
const app = useAppStore();
app.$patch({
title: i18n.global.t(to.name || 'home'),
subtitle: null,

View File

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

View File

@ -1,19 +1,51 @@
import { defineStore } from 'pinia'
import { jApi } from 'boot/axios'
import { defineStore } from 'pinia';
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: () => ({
title: null,
subtitle: null,
imageUrl: '',
useRightDrawer: false,
rightDrawerOpen: false
rightDrawerOpen: false,
basketOrderId: null
}),
actions: {
async loadConfig () {
const imageUrl = await jApi.getValue('SELECT url FROM imageConfig')
this.$patch({ imageUrl })
async init() {
this.getBasketOrderId();
},
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 { createPinia } from 'pinia'
import { store } from 'quasar/wrappers';
import { createPinia } from 'pinia';
/*
* If not building with SSR mode, you can
@ -11,10 +11,10 @@ import { createPinia } from 'pinia'
*/
export default store((/* { ssrContext } */) => {
const pinia = createPinia()
const pinia = createPinia();
// You can add Pinia plugins here
// pinia.use(SomePiniaPlugin)
return pinia
})
return pinia;
});