Resolve conflicts

This commit is contained in:
William Buezas 2024-08-18 20:33:17 -03:00
commit b19bf710e8
22 changed files with 829 additions and 518 deletions

View File

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

View File

@ -206,7 +206,6 @@ defineExpose({
ref="addressFormRef"
class="form"
:class="separationBetweenInputs"
@submit="submit()"
>
<span v-if="title" class="text-h6 text-bold">
{{ title }}

View File

@ -0,0 +1,152 @@
<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 =>
lineDiscountSubtotal(line) * ((100 - line.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 q-gutter-y-xs">
<div class="text-subtitle1 text-bold">
{{ t('shippingInformation') }}
</div>
<div>
{{ t('preparation') }}
{{ formatDateTitle(ticket.shipped) }}
</div>
<div>
{{ t('delivery') }}
{{ formatDateTitle(ticket.landed) }}
</div>
<div>
{{ t(ticket.method != 'PICKUP' ? 'agency' : 'warehouse') }}
{{ ticket.agency }}
</div>
</QCardSection>
<QCardSection class="no-padding q-mb-md q-gutter-y-xs">
<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

@ -0,0 +1,41 @@
<script setup>
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const props = defineProps({
noDataLabel: {
type: String,
default: ''
},
hideBottom: {
type: Boolean,
default: true
},
rowsPerPageOptions: {
type: Array,
default: () => [0]
}
});
</script>
<template>
<QTable
v-bind="$attrs"
:no-data-label="props.noDataLabel || t('noInvoicesFound')"
:hide-bottom="props.hideBottom"
:rows-per-page-options="props.rowsPerPageOptions"
table-header-class="vntable-header-default"
>
<template v-for="(_, slotName) in $slots" v-slot:[slotName]="slotProps">
<slot :name="slotName" v-bind="slotProps" />
</template>
</QTable>
</template>
<style lang="scss">
.vntable-header-default {
background-color: $accent !important;
color: white;
}
</style>

View File

@ -0,0 +1,38 @@
import { useUserStore } from 'stores/user';
import axios from 'axios';
import { useQuasar } from 'quasar';
export function usePrintService() {
const quasar = useQuasar();
const userStore = useUserStore();
const token = userStore.token;
function sendEmail(path, params) {
return axios.post(path, params).then(() =>
quasar.notify({
message: 'Notification sent',
type: 'positive',
icon: 'check'
})
);
}
function openReport(path, params) {
params = Object.assign(
{
access_token: token
},
params
);
const query = new URLSearchParams(params).toString();
window.open(`api/${path}?${query}`);
}
return {
sendEmail,
openReport
};
}

View File

@ -37,7 +37,7 @@ export default {
'November',
'December'
],
shortMonths: [
monthsShort: [
'Jan',
'Feb',
'Mar',

View File

@ -43,7 +43,7 @@ export default {
'Noviembre',
'Diciembre'
],
shortMonths: [
monthsShort: [
'Ene',
'Feb',
'Mar',

View File

@ -24,7 +24,7 @@ export default {
'Novembre',
'Décembre'
],
shortMonths: [
monthsShort: [
'Jan',
'Fév',
'Mar',

View File

@ -24,7 +24,7 @@ export default {
'Novembro',
'Dezembro'
],
shortMonths: [
monthsShort: [
'Jan',
'Fev',
'Mar',

View File

@ -3,6 +3,8 @@ import { useI18n } from 'vue-i18n';
import { ref, onMounted, inject } from 'vue';
import { useRouter } from 'vue-router';
import CardList from 'src/components/ui/CardList.vue';
import useNotify from 'src/composables/useNotify.js';
import { useVnConfirm } from 'src/composables/useVnConfirm.js';
import { useAppStore } from 'stores/app';
@ -97,42 +99,33 @@ onMounted(async () => {
no-caps
/>
</Teleport>
<QPage class="column items-center">
<QList
class="full-width rounded-borders shadow-1 shadow-transition"
style="max-width: 544px"
separator
>
<QItem
<QPage class="vn-w-sm">
<QList class="rounded-borders shadow-1 shadow-transition" separator>
<CardList
v-for="(address, index) in addresses"
:key="index"
clickable
v-ripple
:rounded="false"
tag="label"
class="full-width row items-center justify-between address-item"
style="padding: 20px"
>
<QItemSection>
<div class="row">
<QRadio
v-model="defaultAddress"
:val="address.id"
class="q-mr-sm"
@update:model-value="changeDefaultAddress"
/>
<div>
<QItemLabel class="text-bold q-mb-sm">
{{ address.nickname }}
</QItemLabel>
<QItemLabel>{{ address.street }}</QItemLabel>
<QItemLabel>
{{ address.postalCode }},
{{ address.city }}
</QItemLabel>
</div>
</div>
</QItemSection>
<QItemSection class="actions-wrapper" side>
<template #prepend>
<QRadio
v-model="defaultAddress"
:val="address.id"
class="q-mr-sm"
@update:model-value="changeDefaultAddress"
/>
</template>
<template #content>
<span class="text-bold q-mb-sm">
{{ address.nickname }}
</span>
<span>{{ address.street }}</span>
<span>
{{ address.postalCode }},
{{ address.city }}
</span>
</template>
<template #actions>
<QBtn
icon="delete"
flat
@ -151,25 +144,12 @@ onMounted(async () => {
rounded
@click.stop="goToAddressDetails(address.id)"
/>
</QItemSection>
</QItem>
</template>
</CardList>
</QList>
</QPage>
</template>
<style lang="scss" scoped>
.address-item {
.actions-wrapper {
visibility: hidden;
}
&:hover {
.actions-wrapper {
visibility: visible;
}
}
}
</style>
<i18n lang="yaml">
en-US:
addAddress: Add address

View File

@ -2,6 +2,8 @@
import { ref, inject, onMounted, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import VnTable from 'src/components/ui/VnTable.vue';
const jApi = inject('jApi');
const { t } = useI18n();
@ -53,32 +55,15 @@ onMounted(() => getPackages());
<template>
<QPage class="flex justify-center q-pa-md">
<QTable
<VnTable
:columns="columns"
:rows="packages"
:loading="loading"
class="q-mt-lg"
style="max-width: 100%; height: max-content"
table-header-class="packages-table-header"
hide-bottom
>
<template #body-cell-id="{ row }">
<QTd auto-width @click.stop>
<QBtn flat color="blue">{{ row.id }}</QBtn>
<ItemDescriptorProxy :id="row.id" />
</QTd>
</template>
</QTable>
style="height: max-content; max-width: 100%"
/>
</QPage>
</template>
<style lang="scss">
.packages-table-header {
background-color: $accent !important;
color: white;
}
</style>
<i18n lang="yaml">
en-US:
agency: Agency

View File

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

View File

@ -10,7 +10,7 @@
dense
standout
>
<template v-slot:prepend>
<template #prepend>
<QIcon v-if="search === ''" name="search" />
<QIcon
v-else
@ -220,7 +220,7 @@
</QCardActions>
</QCard>
</div>
<template v-slot:loading>
<template #loading>
<div class="row justify-center q-my-md">
<QSpinner color="primary" name="dots" size="40px" />
</div>
@ -343,8 +343,7 @@
</style>
<script>
import { date, currency } from 'src/lib/filters.js';
import { date as qdate } from 'quasar';
import { date, currency, formatDate } from 'src/lib/filters.js';
import axios from 'axios';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
@ -362,7 +361,7 @@ export default {
return {
uid: 0,
search: '',
orderDate: qdate.formatDate(new Date(), 'YYYY/MM/DD'),
orderDate: formatDate(new Date(), 'YYYY/MM/DD'),
category: null,
categories: [],
type: null,
@ -453,7 +452,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,190 +0,0 @@
<template>
<Teleport v-if="isHeaderMounted" 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';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
export default {
name: 'OrdersPendingIndex',
setup() {
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted };
},
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.fetchUser();
},
watch: {
async year() {
await this.fetchUser();
}
},
methods: {
date,
currency,
async fetchUser() {
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,162 @@
<script setup>
import { ref, onMounted, inject, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import VnTable from 'src/components/ui/VnTable.vue';
import { currency, formatDate } from 'src/lib/filters.js';
import { usePrintService } from 'src/composables/usePrintService';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
const { t } = useI18n();
const jApi = inject('jApi');
const { openReport } = usePrintService();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
const currentYear = ref(Date.vnNew().getFullYear());
const years = ref([]);
const invoices = ref([]);
const columns = computed(() => [
{ name: 'ref', label: t('invoice'), field: 'ref', align: 'left' },
{
name: 'issued',
label: t('issued'),
field: 'issued',
align: 'left',
format: val => formatDate(val, 'D MMM YYYY')
},
{
name: 'amount',
label: t('amount'),
field: 'amount',
format: val => currency(val)
},
{
name: 'hasPdf',
field: 'hasPdf',
align: 'center'
}
]);
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 v-if="isHeaderMounted" to="#actions">
<QSelect
v-model="currentYear"
:options="years"
color="white"
dark
standout
dense
rounded
@update:model-value="fetchInvoices()"
/>
</Teleport>
<div class="vn-w-sm">
<VnTable
:columns="columns"
:rows="invoices"
:hide-header="!invoices?.length"
>
<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"
target="_blank"
flat
round
@click="openReport(`InvoiceOuts/${row.id}/download`)"
>
<QTooltip>
{{ t('downloadInvoicePdf') }}
</QTooltip>
</QBtn>
<QIcon
v-else
name="warning"
:title="t('notDownloadable')"
color="warning"
size="sm"
>
<QTooltip>
{{ t('requestTheInvoiceToComercial') }}
</QTooltip>
</QIcon>
</QTd>
</template>
</VnTable>
</div>
</template>
<i18n lang="yaml">
en-US:
noInvoicesFound: No invoices found
invoice: Invoice
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
invoice: Factura
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
invoice: Factura
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
invoice: Facture
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
invoice: Fatura
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,135 @@
<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 VnInput from 'src/components/common/VnInput.vue';
import VnConfirm from 'src/components/ui/VnConfirm.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 showAmountToPayDialog = ref(null);
const amountToPay = ref(null);
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 () => {
showAmountToPayDialog.value = true;
if (debt.value <= 0) {
amountToPay.value = -debt.value;
}
};
const onConfirmPay = async () => {
if (amountToPay.value) {
const amount = amountToPay.value.toString().replace('.', ',');
amountToPay.value = parseFloat(amount);
await tpv.pay(amountToPay.value);
}
};
</script>
<template>
<Teleport v-if="isHeaderMounted" 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')"
class="info"
size="24px"
/>
<QIcon name="info" class="info" size="sm">
<QTooltip max-width="450px">
{{ t('paymentInfo') }}
</QTooltip>
</QIcon>
</div>
<QBtn
icon="payments"
:label="$t('makePayment')"
:label="t('makePayment')"
@click="onPayClick()"
rounded
no-caps
/>
>
<QTooltip>
{{ t('makePayment') }}
</QTooltip>
</QBtn>
<QBtn
to="/ecomerce/basket"
:to="{ name: 'basket' }"
icon="shopping_cart"
:label="$t('shoppingCart')"
:label="t('shoppingCart')"
rounded
no-caps
/>
>
<QTooltip>
{{ t('shoppingCart') }}
</QTooltip>
</QBtn>
</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>
<VnConfirm
v-model="showAmountToPayDialog"
:message="t('amountToPay')"
:promise="onConfirmPay"
>
<template #customHTML>
<VnInput
v-model="amountToPay"
:clearable="false"
class="full-width"
/>
</template>
</VnConfirm>
</QPage>
</template>
<style lang="scss" scoped>
@ -92,57 +157,6 @@
}
</style>
<script>
import { date, currency } from 'src/lib/filters.js';
import { tpvStore } from 'stores/tpv';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
export default {
name: 'OrdersPendingIndex',
setup() {
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted };
},
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
@ -155,6 +169,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
@ -167,6 +182,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
@ -179,6 +195,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
@ -191,6 +208,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
@ -203,4 +221,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

@ -0,0 +1,137 @@
<script setup>
import { ref, inject, onMounted } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import CardList from 'src/components/ui/CardList.vue';
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 'stores/app';
import { storeToRefs } from 'pinia';
const jApi = inject('jApi');
const { t } = useI18n();
const { openConfirmationModal } = useVnConfirm();
const { notify } = useNotify();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
const router = useRouter();
const orders = ref([]);
const getOrders = async () => {
try {
orders.value = await jApi.query(
`SELECT o.id, o.sent, o.deliveryMethodFk, o.taxableBase,
a.nickname, am.description agency
FROM myOrder o
JOIN myAddress a ON a.id = o.addressFk
JOIN vn.agencyMode am ON am.id = o.agencyModeFk
WHERE NOT o.isConfirmed
ORDER BY o.sent DESC`
);
} catch (error) {
console.error('Error getting orders:', error);
}
};
const removeOrder = async (id, index) => {
try {
await jApi.execQuery(
`START TRANSACTION;
DELETE FROM hedera.myOrder WHERE ((id = #id));
COMMIT`,
{
id
}
);
orders.value.splice(index, 1);
notify(t('dataSaved'), 'positive');
} catch (error) {
console.error('Error removing order:', error);
}
};
const loadOrder = orderId => {
// store.loadIntoBasket(orderId);
router.push({ name: 'catalog' });
};
onMounted(async () => {
getOrders();
});
</script>
<template>
<Teleport v-if="isHeaderMounted" to="#actions">
<QBtn
:to="{ name: 'checkout' }"
icon="add_shopping_cart"
:label="t('newOrder')"
rounded
no-caps
>
<QTooltip>
{{ t('newOrder') }}
</QTooltip>
</QBtn>
</Teleport>
<QPage class="vn-w-sm">
<CardList
v-for="(order, index) in orders"
:key="index"
:to="{ name: 'basket', params: { id: order.id } }"
>
<template #content>
<QItemLabel class="text-bold q-mb-sm">{{
formatDateTitle(order.sent)
}}</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.prevent="
openConfirmationModal(
null,
t('areYouSureDeleteOrder'),
() => removeOrder(order.id, index)
)
"
/>
<QBtn
icon="shopping_bag"
flat
rounded
@click.stop.prevent="loadOrder(order.id)"
/>
</template>
</CardList>
</QPage>
</template>
<style lang="scss" scoped></style>
<i18n lang="yaml">
en-US:
newOrder: New order
areYouSureDeleteOrder: Are you sure you want to delete the order?
es-ES:
newOrder: Nuevo pedido
areYouSureDeleteOrder: ¿Seguro que quieres borrar el pedido?
ca-ES:
newOrder: Nova comanda
areYouSureDeleteOrder: Segur que vols esborrar la comanda?
fr-FR:
newOrder: Nouvelle commande
areYouSureDeleteOrder: Êtes-vous sûr de vouloir supprimer la commande?
pt-PT:
newOrder: Novo pedido
areYouSureDeleteOrder: Tem certeza de que deseja excluir o pedido?
</i18n>

View File

@ -1,151 +0,0 @@
<template>
<Teleport v-if="isHeaderMounted" 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';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
export default {
name: 'OrdersConfirmedView',
setup() {
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted };
},
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,78 @@
<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 { useUserStore } from 'stores/user';
import { useAppStore } from 'stores/app';
import { storeToRefs } from 'pinia';
const { t } = useI18n();
const jApi = inject('jApi');
const route = useRoute();
const userStore = useUserStore();
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
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 v-if="isHeaderMounted" 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

@ -37,13 +37,38 @@ const routes = [
{
name: 'confirmedOrders',
path: '/ecomerce/orders',
component: () => import('src/pages/Ecomerce/Orders.vue')
component: () => import('pages/Ecomerce/OrdersView.vue')
},
{
name: 'ticket',
path: '/ecomerce/ticket/:id',
component: () => import('pages/Ecomerce/TicketView.vue')
},
{
name: 'invoices',
path: '/ecomerce/invoices',
component: () => import('pages/Ecomerce/InvoicesView.vue')
},
{
name: 'pendingOrders',
path: '/ecomerce/pending',
component: () => import('pages/Ecomerce/PendingOrders.vue')
},
{
name: 'catalog',
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,5 +1,8 @@
import { defineStore } from 'pinia';
import { jApi } from 'boot/axios';
import useNotify from 'src/composables/useNotify.js';
const { notify } = useNotify();
export const useAppStore = defineStore('hedera', {
state: () => ({
@ -9,7 +12,8 @@ export const useAppStore = defineStore('hedera', {
useRightDrawer: false,
rightDrawerOpen: false,
isHeaderMounted: false,
menuEssentialLinks: []
menuEssentialLinks: [],
basketOrderId: null
}),
actions: {
@ -38,9 +42,40 @@ export const useAppStore = defineStore('hedera', {
this.menuEssentialLinks = sectionTree;
},
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 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) {
console.log('loadIntoBasket', orderId);
console.log('this.basketOrderId', this.basketOrderId);
if (this.basketOrderId !== orderId) {
localStorage.setItem('hederaBasket', orderId);
this.basketOrderId = orderId;
notify('orderLoadedIntoBasket', 'positive');
}
}
}
});