Merge pull request 'Pedidos stepper' (!80) from wbuezas/hedera-web-mindshore:feature/PedidosStepper into 4922-vueMigration

Reviewed-on: #80
Reviewed-by: Javier Segarra <jsegarra@verdnatura.es>
This commit is contained in:
Javier Segarra 2024-09-03 14:06:01 +00:00
commit 05568280f3
19 changed files with 966 additions and 283 deletions

View File

@ -1,123 +1,130 @@
var Result = require('./result');
const Result = require('./result');
/**
* This class stores the database results.
*/
module.exports = new Class({
results: null
,error: null
results: null,
error: null,
/**
* Initilizes the resultset object.
*/
,initialize(results, error) {
this.results = results;
this.error = error;
}
/**
* Gets the query error.
*
* @return {Db.Err} the error or null if no errors hapened
*/
,getError() {
return this.error;
}
,fetch() {
if (this.error)
throw this.error;
if (this.results !== null
&& this.results.length > 0)
return this.results.shift();
/**
* Initilizes the resultset object.
*/
initialize(results, error) {
this.results = results;
this.error = error;
},
return null;
}
/**
* Fetchs the next result from the resultset.
*
* @return {Db.Result} the result or %null if error or there are no more results
*/
,fetchResult() {
var result = this.fetch();
if (result !== null) {
if (result.data instanceof Array)
return new Result(result);
else
return true;
}
/**
* Gets the query error.
*
* @return {Db.Err} the error or null if no errors hapened
*/
getError() {
return this.error;
},
return null;
}
fetch() {
if (this.error) {
throw this.error;
}
console.log('this.results', this.results);
if (this.results !== null && this.results.length > 0) {
return this.results.shift();
}
/**
* Fetchs the first row object from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
,fetchObject() {
var result = this.fetch();
return null;
},
if (result !== null
&& result.data instanceof Array
&& result.data.length > 0)
return result.data[0];
/**
* Fetchs the next result from the resultset.
*
* @return {Db.Result} the result or %null if error or there are no more results
*/
fetchResult() {
const result = this.fetch();
console.log('test result', result);
if (result !== null) {
if (result.data instanceof Array) {
return new Result(result);
} else {
return true;
}
}
return null;
}
return null;
},
/**
* Fetchs data from the next resultset.
*
* @return {Array} the data
*/
,fetchData() {
var result = this.fetch();
/**
* Fetchs the first row object from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchObject() {
const result = this.fetch();
if (result !== null
&& result.data instanceof Array)
return result.data;
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
) {
return result.data[0];
}
return null;
}
return null;
},
/**
* Fetchs the first row and column value from the next resultset.
*
* @return {Object} the value if success, %null otherwise
*/
,fetchValue() {
var row = this.fetchRow();
/**
* Fetchs data from the next resultset.
*
* @return {Array} the data
*/
fetchData() {
const result = this.fetch();
if (row instanceof Array && row.length > 0)
return row[0];
if (result !== null && result.data instanceof Array) {
return result.data;
}
return null;
}
return null;
},
/**
* Fetchs the first row from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
,fetchRow() {
var result = this.fetch();
/**
* Fetchs the first row and column value from the next resultset.
*
* @return {Object} the value if success, %null otherwise
*/
fetchValue() {
const row = this.fetchRow();
if (result !== null
&& result.data instanceof Array
&& result.data.length > 0) {
var object = result.data[0];
var row = new Array(result.columns.length);
for(var i = 0; i < row.length; i++)
row[i] = object[result.columns[i].name];
return row;
}
return null;
}
if (row instanceof Array && row.length > 0) {
return row[0];
}
return null;
},
/**
* Fetchs the first row from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchRow() {
const result = this.fetch();
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
) {
const object = result.data[0];
const row = new Array(result.columns.length);
for (let i = 0; i < row.length; i++) {
row[i] = object[result.columns[i].name];
}
return row;
}
return null;
}
});

View File

@ -105,23 +105,18 @@ async function confirm() {
<i18n lang="yaml">
en-US:
confirm: Confirm
wantToContinue: Are you sure you want to continue?
cancel: Cancel
es-ES:
confirm: Confirmar
wantToContinue: ¿Seguro que quieres continuar?
cancel: Cancelar
ca-ES:
confirm: Confirmar
wantToContinue: Segur que vols continuar?
cancel: Cancel·lar
fr-FR:
confirm: Confirmer
wantToContinue: Êtes-vous sûr de vouloir continuer?
cancel: Annuler
pt-PT:
confirm: Confirme
wantToContinue: Tem a certeza de que deseja continuar?
cancel: Cancelar
</i18n>

View File

@ -97,6 +97,7 @@ const url = computed(() => {
spinner-color="primary"
:width="props.width"
:height="props.height"
draggable
>
<template #error>
<div
@ -115,6 +116,7 @@ const url = computed(() => {
class="img_zoom"
v-bind="$attrs"
spinner-color="primary"
draggable
/>
</QDialog>
<QDialog v-if="props.editable" v-model="showEditForm">

View File

@ -26,6 +26,9 @@ a.link {
text-decoration: underline;
}
}
.default-radius {
border-radius: 0.6em;
}
.q-card {
border-radius: 0.6em !important;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1);

View File

@ -61,8 +61,13 @@ export default {
adminNewsDetails: 'Afegir o editar notícia',
//
orderLoadedIntoBasket: 'Comanda carregada a la cistella!',
loadAnOrder:
'Si us plau carrega una comanda pendent a la cistella o en comença una de nova',
at: 'a les',
back: 'Tornar',
next: 'Següent',
remove: 'Esborrar',
noData: 'Sense dades'
agency: 'Agència',
noData: 'Sense dades',
confirm: 'Confirmar'
};

View File

@ -74,10 +74,14 @@ export default {
adminNewsDetails: 'Add or edit new',
//
orderLoadedIntoBasket: 'Order loaded into basket!',
loadAnOrder: 'Please load a pending order to the cart or start a new one',
at: 'at',
back: 'Back',
next: 'Next',
remove: 'Remove',
agency: 'Agency',
noData: 'No data',
confirm: 'Confirm',
orders: 'Orders',
order: 'Pending order',

View File

@ -80,10 +80,15 @@ export default {
adminNewsDetails: 'Añadir o editar noticia',
//
orderLoadedIntoBasket: '¡Pedido cargado en la cesta!',
loadAnOrder:
'Por favor carga un pedido pendiente en la cesta o empieza uno nuevo',
at: 'a las',
back: 'Volver',
next: 'Siguiente',
remove: 'Borrar',
agency: 'Agencia',
noData: 'Sin datos',
confirm: 'Confirmar',
orders: 'Pedidos',
order: 'Pedido pendiente',

View File

@ -61,8 +61,13 @@ export default {
adminNewsDetails: 'Ajouter ou editer nouvelles',
//
orderLoadedIntoBasket: 'Commande chargée dans le panier!',
loadAnOrder:
'Veuillez télécharger une commande en attente dans le panier ou en démarrer une nouvelle',
at: 'à',
back: 'Retour',
next: 'Suivant',
remove: 'Effacer',
noData: 'Aucune donnée'
agency: 'Agence',
noData: 'Aucune donnée',
confirm: 'Confirmer'
};

View File

@ -62,8 +62,12 @@ export default {
adminNewsDetails: 'Ajouter ou editer nouvelles',
//
orderLoadedIntoBasket: 'Pedido carregado na cesta!',
loadAnOrder: 'Carregue um pedido pendente no carrinho ou inicie um novo',
at: 'às',
back: 'Voltar',
next: 'Seguinte',
remove: 'Eliminar',
noData: 'Sem dados'
agency: 'Agência',
noData: 'Sem dados',
confirm: 'Confirme'
};

View File

@ -1,5 +1,5 @@
import { JsonConnection } from '../vn/json-connection'
import { ResultSet } from './result-set'
import { JsonConnection } from '../vn/json-connection';
import { ResultSet } from './result-set';
/**
* Simulates a connection to a database by making asynchronous requests to a
@ -15,7 +15,7 @@ const Flag = {
NOT_NULL: 1,
PRI_KEY: 2,
AI: 512 | 2 | 1
}
};
const Type = {
BOOLEAN: 1,
@ -24,161 +24,164 @@ const Type = {
STRING: 5,
DATE: 8,
DATE_TIME: 9
}
};
export class Connection extends JsonConnection {
static Flag = Flag
static Type = Type
static Flag = Flag;
static Type = Type;
/**
* Runs a SQL query on the database.
*
* @param {String} sql The SQL statement
* @return {ResultSet} The result
*/
async execSql (sql) {
const json = await this.send('core/query', { sql })
const results = []
let err
* Runs a SQL query on the database.
*
* @param {String} sql The SQL statement
* @return {ResultSet} The result
*/
async execSql(sql) {
const json = await this.send('core/query', { sql });
const results = [];
let err;
if (json) {
try {
if (json && json instanceof Array) {
for (let i = 0; i < json.length; i++) {
if (json[i] !== true) {
const rows = json[i].data
const columns = json[i].columns
const rows = json[i].data;
const columns = json[i].columns;
const data = new Array(rows.length)
const data = new Array(rows.length);
results.push({
data,
columns,
tables: json[i].tables
})
});
for (let j = 0; j < rows.length; j++) {
const row = (data[j] = {})
const row = (data[j] = {});
for (let k = 0; k < columns.length; k++) {
row[columns[k].name] = rows[j][k]
row[columns[k].name] = rows[j][k];
}
}
for (let j = 0; j < columns.length; j++) {
let castFunc = null
const col = columns[j]
let castFunc = null;
const col = columns[j];
switch (col.type) {
case Type.DATE:
case Type.DATE_TIME:
case Type.TIMESTAMP:
castFunc = this.valueToDate
break
castFunc = this.valueToDate;
break;
}
if (castFunc !== null) {
if (col.def != null) {
col.def = castFunc(col.def)
col.def = castFunc(col.def);
}
for (let k = 0; k < data.length; k++) {
if (data[k][col.name] != null) {
data[k][col.name] = castFunc(data[k][col.name])
data[k][col.name] = castFunc(
data[k][col.name]
);
}
}
}
}
} else {
results.push(json[i])
results.push(json[i]);
}
}
}
} catch (e) {
err = e
err = e;
}
}
return new ResultSet(results, err)
return new ResultSet(results, err);
}
/**
* Runs a query on the database.
*
* @param {String} query The SQL statement
* @param {Object} params The query params
* @return {ResultSet} The result
*/
async execQuery (query, params) {
const sql = query.replace(/#\w+/g, (key) => {
const value = params[key.substring(1)]
return value ? this.renderValue(value) : key
})
* Runs a query on the database.
*
* @param {String} query The SQL statement
* @param {Object} params The query params
* @return {ResultSet} The result
*/
async execQuery(query, params) {
const sql = query.replace(/#\w+/g, key => {
const value = params[key.substring(1)];
return value ? this.renderValue(value) : key;
});
return await this.execSql(sql)
return await this.execSql(sql);
}
async query (query, params) {
const res = await this.execQuery(query, params)
return res.fetchData()
async query(query, params) {
const res = await this.execQuery(query, params);
return res.fetchData();
}
async getObject (query, params) {
const res = await this.execQuery(query, params)
return res.fetchObject()
async getObject(query, params) {
const res = await this.execQuery(query, params);
return res.fetchObject();
}
async getValue (query, params) {
const res = await this.execQuery(query, params)
return res.fetchValue()
async getValue(query, params) {
const res = await this.execQuery(query, params);
return res.fetchValue();
}
renderValue (v) {
renderValue(v) {
switch (typeof v) {
case 'number':
return v
return v;
case 'boolean':
return v ? 'TRUE' : 'FALSE'
return v ? 'TRUE' : 'FALSE';
case 'string':
return "'" + v.replace(this.regexp, this.replaceFunc) + "'"
return "'" + v.replace(this.regexp, this.replaceFunc) + "'";
default:
if (v instanceof Date) {
if (!isNaN(v.getTime())) {
const unixTime = parseInt(fixTz(v).getTime() / 1000)
return 'DATE(FROM_UNIXTIME(' + unixTime + '))'
const unixTime = parseInt(fixTz(v).getTime() / 1000);
return 'DATE(FROM_UNIXTIME(' + unixTime + '))';
} else {
return '0000-00-00'
return '0000-00-00';
}
} else {
return 'NULL'
return 'NULL';
}
}
}
/*
* Parses a value to date.
*/
valueToDate (value) {
return fixTz(new Date(value))
* Parses a value to date.
*/
valueToDate(value) {
return fixTz(new Date(value));
}
}
// TODO: Read time zone from db configuration
const tz = { timeZone: 'Europe/Madrid' }
const isLocal = Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone
const tz = { timeZone: 'Europe/Madrid' };
const isLocal =
Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone;
function fixTz (date) {
if (isLocal) return date
function fixTz(date) {
if (isLocal) return date;
const localDate = new Date(date.toLocaleString('en-US', tz))
const localDate = new Date(date.toLocaleString('en-US', tz));
const hasTime =
localDate.getHours() ||
localDate.getMinutes() ||
localDate.getSeconds() ||
localDate.getMilliseconds()
localDate.getHours() ||
localDate.getMinutes() ||
localDate.getSeconds() ||
localDate.getMilliseconds();
if (!hasTime) {
date.setHours(date.getHours() + 12)
date.setHours(0, 0, 0, 0)
date.setHours(date.getHours() + 12);
date.setHours(0, 0, 0, 0);
}
return date
return date;
}

View File

@ -1,130 +1,128 @@
import { Result } from './result'
import { Result } from './result';
/**
* This class stores the database results.
*/
export class ResultSet {
results = null
error = null
results = null;
error = null;
/**
* Initilizes the resultset object.
*/
constructor (results, error) {
this.results = results
this.error = error
* Initilizes the resultset object.
*/
constructor(results, error) {
this.results = results;
this.error = error;
}
/**
* Gets the query error.
*
* @return {Db.Err} the error or null if no errors hapened
*/
getError () {
return this.error
* Gets the query error.
*
* @return {Db.Err} the error or null if no errors hapened
*/
getError() {
return this.error;
}
fetch () {
fetch() {
if (this.error) {
throw this.error
throw this.error;
}
if (this.results !== null && this.results.length > 0) {
return this.results.shift()
return this.results.shift();
}
return null
return null;
}
/**
* Fetchs the next result from the resultset.
*
* @return {Db.Result} the result or %null if error or there are no more results
*/
fetchResult () {
const result = this.fetch()
* Fetchs the next result from the resultset.
*
* @return {Db.Result} the result or %null if error or there are no more results
*/
fetchResult() {
const result = this.fetch();
if (result !== null) {
if (result.data instanceof Array) {
return new Result(result)
return new Result(result);
} else {
return true
return true;
}
}
return null
return null;
}
/**
* Fetchs the first row object from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchObject () {
const result = this.fetch()
* Fetchs the first row object from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchObject() {
const result = this.fetch();
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
result.data instanceof Array &&
result.data.length > 0
) {
return result.data[0]
return result.data[0];
}
return null
return null;
}
/**
* Fetchs data from the next resultset.
*
* @return {Array} the data
*/
fetchData () {
const result = this.fetch()
* Fetchs data from the next resultset.
*
* @return {Array} the data
*/
fetchData() {
const result = this.fetch();
if (result !== null && result.data instanceof Array) {
return result.data
return result.data;
}
return null
return null;
}
/**
* Fetchs the first row and column value from the next resultset.
*
* @return {Object} the value if success, %null otherwise
*/
fetchValue () {
const row = this.fetchRow()
* Fetchs the first row and column value from the next resultset.
*
* @return {Object} the value if success, %null otherwise
*/
fetchValue() {
const row = this.fetchRow();
if (row instanceof Array && row.length > 0) {
return row[0]
return row[0];
}
return null
return null;
}
/**
* Fetchs the first row from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchRow () {
const result = this.fetch()
* Fetchs the first row from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
fetchRow() {
const result = this.fetch();
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
result.data instanceof Array &&
result.data.length > 0
) {
const object = result.data[0]
const row = new Array(result.columns.length)
const object = result.data[0];
const row = new Array(result.columns.length);
for (let i = 0; i < row.length; i++) {
row[i] = object[result.columns[i].name]
row[i] = object[result.columns[i].name];
}
return row
return row;
}
return null
return null;
}
}

View File

@ -1,6 +1,7 @@
import { i18n } from 'src/boot/i18n';
import { date as qdate, format } from 'quasar';
const { pad } = format;
import { useAppStore } from 'stores/app';
export function currency(val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : val;
@ -16,14 +17,9 @@ export function date(val, format = 'YYYY-MM-DD') {
export const formatDate = (timeStamp, format = 'YYYY-MM-DD') => {
if (!timeStamp) return '';
const { messages, locale } = i18n.global;
const appStore = useAppStore();
return qdate.formatDate(timeStamp, format, {
days: messages.value[locale.value].date.days,
months: messages.value[locale.value].date.months,
daysShort: messages.value[locale.value].date.daysShort,
monthsShort: messages.value[locale.value].date.monthsShort
});
return qdate.formatDate(timeStamp, format, appStore.localeDates);
};
/**

View File

@ -66,27 +66,22 @@ onMounted(() => getPackages());
<i18n lang="yaml">
en-US:
agency: Agency
bundles: Bundles
expeditions: Exps.
prevision: Prev.
es-ES:
agency: Agencia
bundles: Bultos
expeditions: Exps.
prevision: Prev.
ca-ES:
agency: Agència
bundles: Paquets
expeditions: Exps.
prevision: Prev.
fr-FR:
agency: Agence
bundles: Cartons
expeditions: Exps.
prevision: Prev.
pt-PT:
agency: Agência
bundles: Bultos
expeditions: Exps.
prevision: Prev.

View File

@ -1 +1,14 @@
<template>Basket view</template>
<script setup>
import { onBeforeMount } from 'vue';
import { useAppStore } from 'stores/app';
const appStore = useAppStore();
onBeforeMount(() => {
appStore.check();
});
</script>
<template>
<div>basket view</div>
</template>

View File

@ -37,7 +37,16 @@
{{ $t('warehouse') }}
{{ 'Algemesi' }}
</p>
<QBtn flat rounded no-caps>
<QBtn
flat
rounded
no-caps
:to="{
name: 'checkout',
params: { id: appStore.basketOrderId },
query: { continue: 'catalog' }
}"
>
{{ $t('modify') }}
</QBtn>
</div>
@ -355,7 +364,7 @@ export default {
setup() {
const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted };
return { isHeaderMounted, appStore };
},
data() {
return {
@ -417,6 +426,12 @@ export default {
created() {
this.$app.useRightDrawer = true;
},
async beforeMount() {
const isGuest = false; // TODO: Integrate isGuest logic
if (!isGuest) {
this.appStore.check('catalog');
}
},
async mounted() {
this.categories = await this.$jApi.query(
`SELECT c.id, l.name, c.color, c.code

View File

@ -1 +1,572 @@
<template>Checkout</template>
<script setup>
import { ref, onMounted, inject, computed } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRoute, useRouter } from 'vue-router';
import VnSelect from 'src/components/common/VnSelect.vue';
import { formatDateTitle, formatDate } from 'src/lib/filters.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 route = useRoute();
const router = useRouter();
const { notify } = useNotify();
const appStore = useAppStore();
const { localeDates } = storeToRefs(appStore);
const stepperRef = ref(null);
const loading = ref(false);
const today = ref(null);
const addresses = ref([]);
const agencies = ref([]);
const warehouses = ref([]);
const currentStep = ref('method');
const id = route.params.id;
const orderForm = ref({
method: 'AGENCY',
date: '',
address: ''
});
const steps = {
AGENCY: [
{
name: 'method',
stepDone: false,
validateStep: () =>
validateStep('method', t('pleaseSelectAnOption'))
},
{
name: 'date',
stepDone: false,
validateStep: () => validateStep('date', t('pleaseSelectADate'))
},
{
name: 'address',
validateStep: () =>
validateStep('address', t('pleaseSelectAnAddress'))
},
{
name: 'agency',
onStepMounted: async () => {
await getAgencies();
},
validateStep: () =>
validateStep('agency', t('pleaseSelectAnAgency'))
},
{
name: 'confirm',
nextButtonLabel: t('confirm'),
onBeforeNextStep: async () => {
await submit();
}
}
],
PICKUP: [
{
name: 'method',
validateStep: () =>
validateStep('method', t('pleaseSelectAnOption'))
},
{
name: 'date',
validateStep: () => validateStep('date', t('pleaseSelectADate'))
},
{
name: 'address',
validateStep: () =>
validateStep('address', t('pleaseSelectAnAddress'))
},
{
name: 'pickup',
validateStep: () =>
validateStep('agency', t('pleaseSelectAWarehouse')),
onStepMounted: async () => {
await getWarehouses();
}
},
{
name: 'confirm',
nextButtonLabel: t('confirm'),
onBeforeNextStep: async () => {
await submit();
}
}
]
};
const confirmArrivalText = computed(() => {
const { method, agency, date } = orderForm.value;
if (!agency) {
return '';
}
const arrivalType = method === 'AGENCY' ? t('arrival') : t('pickup');
return `${arrivalType} ${formatDateTitle(date)}`;
});
const confirmAddressText = computed(() => {
if (!orderForm.value.address) return '';
const address = addresses.value.find(
address => address.id === orderForm.value.address
);
return address.street;
});
const confirmPlaceText = computed(() => {
const { agency, method } = orderForm.value;
if (!agency) return '';
if (method === 'AGENCY') {
const agencyItem = agencies.value.find(a => a.id === agency);
return agencyItem ? `${t('agency')} ${agencyItem.description}` : '';
}
if (method === 'PICKUP') {
const warehouseItem = warehouses.value.find(w => w.id === agency);
return warehouseItem
? `${t('warehouse')} ${warehouseItem.description}`
: '';
}
return '';
});
const validateStep = (formField, errorMessage) => {
const validation = !!orderForm.value[formField];
if (!validation) {
notify(errorMessage, 'negative');
}
return validation;
};
const getAddresses = async () => {
try {
addresses.value = await jApi.query(
`SELECT a.id, a.nickname, p.name province, a.city, a.street, a.isActive, c.name
FROM myAddress a
LEFT JOIN vn.province p ON p.id = a.provinceFk
JOIN vn.country c ON c.id = p.countryFk
WHERE a.isActive`
);
} catch (error) {
console.error('Error getting addresses:', error);
}
};
const getAgencies = async () => {
try {
const { results } = await jApi.execQuery(
`CALL vn.zone_getAgency(#address, #date);
SELECT DISTINCT a.agencyModeFk id, a.description
FROM tmp.zoneGetAgency a
JOIN vn.deliveryMethod d
ON d.id = a.deliveryMethodFk
WHERE d.code IN ('AGENCY', 'DELIVERY')
AND a.isVisible
ORDER BY a.description;
DROP TEMPORARY TABLE tmp.zoneGetAgency`,
{
address: orderForm.value.address,
date: new Date(orderForm.value.date)
}
);
agencies.value = results[1].data;
} catch (error) {
console.error('Error getting agencies:', error);
}
};
const getWarehouses = async () => {
try {
const { results } = await jApi.execQuery(
`CALL vn.zone_getAgency(#address, #date);
SELECT DISTINCT a.agencyModeFk id, a.description
FROM tmp.zoneGetAgency a
JOIN vn.deliveryMethod d
ON d.id = a.deliveryMethodFk
WHERE d.code IN ('PICKUP')
AND a.isVisible
ORDER BY a.description;
DROP TEMPORARY TABLE tmp.zoneGetAgency;`,
{
address: orderForm.value.address,
date: new Date(orderForm.value.date)
}
);
warehouses.value = results[1].data;
if (!warehouses.value || !warehouses.value.length) {
notify(t('noWarehousesAvailableForDate'), 'negative');
}
} catch (error) {
console.error('Error getting agencies:', error);
}
};
const onNextStep = async stepIndex => {
const currentStep = steps[orderForm.value.method][stepIndex];
if (currentStep.onBeforeNextStep) {
await currentStep.onBeforeNextStep();
}
if (currentStep.validateStep && !currentStep.validateStep()) {
return;
}
currentStep.stepDone = true;
await stepperRef.value.next();
const nextStep = steps[orderForm.value.method][stepIndex + 1];
if (nextStep && nextStep.onStepMounted) {
await nextStep.onStepMounted();
}
};
const onPreviousStep = async stepIndex => {
await stepperRef.value.previous();
const previousStep = steps[orderForm.value.method][stepIndex - 1];
if (previousStep.onStepMounted) {
await previousStep.onStepMounted();
}
};
const submit = async () => {
loading.value = true;
let query =
'CALL myOrder_create(@orderId, #date, #method, #agency, #address); SELECT @orderId;';
if (id) {
orderForm.value.id = id;
query =
'CALL myOrder_configure(#id, #date, #method, #agency, #address)';
}
let resultSet;
try {
const { date, ...restOfForm } = orderForm.value;
const _date = new Date(date);
resultSet = await jApi.execQuery(query, { ...restOfForm, date: _date });
if (id) {
notify(t('orderUpdated'), 'positive');
if (route.query.continue === 'catalog') {
router.push({ name: 'catalog' });
} else {
router.push({ name: 'basket', params: { id } });
}
} else {
const orderId = resultSet.results[1].data[0]['@orderId'];
appStore.loadIntoBasket(orderId);
router.push({ name: 'catalog' });
}
} catch (error) {
console.error('Error submitting order:', error);
} finally {
loading.value = false;
}
};
onMounted(async () => {
today.value = Date.vnNew();
today.value.setHours(0, 0, 0, 0);
if (route.params.id) {
const [order] = await jApi.query(
`SELECT m.code deliveryMethod, o.sent, o.agencyModeFk, o.addressFk
FROM myOrder o
JOIN vn.deliveryMethod m ON m.id = o.deliveryMethodFk
WHERE o.id = #id`,
{ id: route.params.id }
);
if (order) {
orderForm.value.method = order.deliveryMethod;
orderForm.value.date = formatDate(order.sent, 'YYYY/MM/DD');
orderForm.value.agency = order.agencyModeFk;
orderForm.value.address = order.addressFk;
}
}
getAddresses();
});
</script>
<template>
<QPage class="vn-w-sm">
<QStepper
v-if="steps[orderForm.method] && steps[orderForm.method].length"
v-model="currentStep"
ref="stepperRef"
animated
keep-alive
contracted
class="default-radius"
>
<QStep
v-for="(step, stepIndex) in steps[orderForm.method]"
:key="stepIndex"
:name="step.name"
:done="step.stepDone"
done-color="accent"
>
<!-- Method step -->
<div
v-if="step.name === 'method'"
class="column justify-center items-center"
>
<span class="text-h6 step-title">
{{ t('receiveOrPickOrder') }}
</span>
<div class="column" style="max-width: max-content">
<QRadio
v-model="orderForm.method"
val="AGENCY"
:label="t('receiveOrder')"
/>
<QRadio
v-model="orderForm.method"
val="PICKUP"
:label="t('pickupInStore')"
/>
</div>
</div>
<!-- Date step -->
<div
v-if="step.name === 'date'"
class="flex justify-center items-center"
>
<span class="text-h6 step-title">
{{ t('orderDateDelivery') }}
</span>
<QDate
v-model="orderForm.date"
class="margin-auto"
color="accent"
:locale="localeDates"
first-day-of-week="1"
:minimal="appStore.isMobile"
flat
/>
</div>
<!-- Address step -->
<QList
v-if="step.name === 'address'"
class="vn-w-xs q-gutter-y-sm column"
>
<span class="text-h6 step-title">
{{
t(
orderForm.method === 'PICKUP'
? 'addressStepQuestionPickup'
: 'addressStepQuestion'
)
}}
</span>
<QItem
v-for="(address, index) in addresses"
:key="index"
tag="label"
v-ripple
>
<QItemSection avatar>
<QRadio
v-model="orderForm.address"
:val="address.id"
/>
</QItemSection>
<QItemSection>
<QItemLabel>{{ address.nickname }}</QItemLabel>
<QItemLabel caption>
{{ address.street }}
</QItemLabel>
</QItemSection>
</QItem>
</QList>
<!-- Agency step (AGENCY) -->
<div
v-if="step.name === 'agency'"
class="flex justify-center items-center"
>
<span class="text-h6 step-title">
{{ t('howDoYouWantToReceive') }}
</span>
<VnSelect
v-model="orderForm.agency"
option-label="description"
option-value="id"
:options="agencies"
/>
</div>
<div
v-if="step.name === 'pickup'"
class="flex justify-center items-center"
>
<span class="text-h6 step-title">
{{ t('pickupWarehouse') }}
</span>
<VnSelect
v-model="orderForm.agency"
option-label="description"
option-value="id"
:options="warehouses"
/>
</div>
<!-- Confirm step -->
<div
v-if="step.name === 'confirm'"
class="flex column justify-center items-center"
>
<span class="text-h6 step-title">
{{ t('confirmData') }}
</span>
<div class="column vn-w-xs full-width">
<span>{{ confirmArrivalText }}</span>
<span v-if="orderForm.method === 'AGENCY'">
{{ confirmAddressText }}
</span>
<span>{{ confirmPlaceText }}</span>
</div>
</div>
<QStepperNavigation class="flex justify-between">
<QBtn
flat
color="primary"
@click="onPreviousStep(stepIndex)"
:label="step.backButtonLabel || t('back')"
class="q-ml-sm"
:class="{ invisible: currentStep === 'method' }"
/>
<QBtn
@click="onNextStep(stepIndex)"
color="primary"
:label="step.nextButtonLabel || t('next')"
/>
</QStepperNavigation>
</QStep>
</QStepper>
</QPage>
</template>
<style lang="scss" scoped>
.step-title {
min-width: 100%;
margin-bottom: 16px;
text-align: center;
font-weight: bold;
}
</style>
<i18n lang="yaml">
en-US:
receiveOrPickOrder: Do you want to receive or pickup the order?
receiveOrder: Receive in my store
pickupInStore: Store pickup
orderDateDelivery: What day you want to receive the order?
howDoYouWantToReceive: How do you want to receive the order?
confirmData: Confirm data
arrival: Arrival
orderUpdated: Order updated
pleaseSelectAnOption: Please select an option
pleaseSelectADate: Please select a date
pleaseSelectAnAddress: Please select an address
pleaseSelectAnAgency: Please select an agency
pickupWarehouse: What store you want to pickup your order?
noWarehousesAvailableForDate: There are no stores available for the selected date, check the date of order pickup and that selected address contains a correct province and zip code
noAgeciesAvailableForDate: There are no agencies available for the selected date and consignee, check the date of the order and that selected address contains a correct province and zip code
pleaseSelectAWarehouse: Please select a store
warehouse: Warehouse
pickup: Pickup
addressStepQuestion: Where do you want to receive the order?
addressStepQuestionPickup: To which address do you want to associate the order? (Optional)
es-ES:
receiveOrPickOrder: ¿Quieres recibir o recoger el pedido?
receiveOrder: Recibir en mi tienda
pickupInStore: Recoger en almacén
orderDateDelivery: ¿Qué día quieres recibir el pedido?
howDoYouWantToReceive: ¿Cómo quieres recibir el pedido?
confirmData: Confirma los datos
arrival: Llegada
orderUpdated: Pedido actualizado
pleaseSelectAnOption: Por favor, selecciona una opción
pleaseSelectADate: Por favor, selecciona una fecha
pleaseSelectAnAddress: Por favor, selecciona una dirección
pleaseSelectAnAgency: Por favor, selecciona una agencia
pickupWarehouse: ¿En qué almacén quieres recoger tu pedido?
noWarehousesAvailableForDate: No hay almacenes disponibles para la fecha seleccionada, comprueba la fecha de recogida del pedido y que consignatario seleccionado contiene una provincia y código postal correctos
noAgeciesAvailableForDate: No hay almacenes disponibles para la fecha seleccionada, comprueba la fecha de recogida del pedido y que consignatario seleccionado contiene una provincia y código postal correctos
pleaseSelectAWarehouse: Por favor elige un almacén
warehouse: Almacén
pickup: Recogida
addressStepQuestion: ¿Dónde quieres recibir el pedido?
addressStepQuestionPickup: ¿A qué dirección quieres asociar el pedido? (Opcional)
ca-ES:
receiveOrPickOrder: Vols rebre o recollir la comanda?
receiveOrder: Rebre en mi tenda
pickupInStore: Recollir en magatzem
orderDateDelivery: Quin dia vols rebre la comanda?
howDoYouWantToReceive: Com vols rebre la comanda?
confirmData: Confirma les dades
arrival: Arribada
orderUpdated: Comanda actualitzada
pleaseSelectAnOption: Si us plau tria una opció
pleaseSelectADate: Si us plau tria una data
pleaseSelectAnAddress: Si us plau tria una adreça
pleaseSelectAnAgency: Si us plau tria una agència
pickupWarehouse: En quin magatzem vols recollir la comanda?
noWarehousesAvailableForDate: No hi ha magatzems disponibles per a la data seleccionada, comprova la data de recollida de la comanda i que consignatari seleccionat conté una província i codi postal correctes
noAgeciesAvailableForDate: No hi ha agències disponibles per a la data i el consignatari seleccionats, comprova la data de la comanda i que consignatari seleccionat conté una província i codi postal correctes
pleaseSelectAWarehouse: Si us plau tria un magatzem
warehouse: Magatzem
pickup: Recollida
addressStepQuestion: On vols rebre la comanda?
addressStepQuestionPickup: A què direcció vols associar la comanda? (Opcional)
fr-FR:
receiveOrPickOrder: Voulez-vous recevoir ou récuperer l'ordre?
receiveOrder: Livraison à la boutique
pickupInStore: Récupérer en entrepôt
orderDateDelivery: Date de livraison?
howDoYouWantToReceive: Agence de livraison
confirmData: Confirmez les coordonnées
arrival: Arrivée
orderUpdated: Mise à jour commande
pleaseSelectAnOption: Veuillez choisir une option
pleaseSelectADate: Veuillez choisir une date
pleaseSelectAnAddress: Veuillez choisir une adresse
pleaseSelectAnAgency: Veuillez choisir une agence
pickupWarehouse: Dans quel magasin vuoulez-vous retirer votre commande?
noWarehousesAvailableForDate: Pas de magasins disponibles à la date sélectionnée, changer la date de retrait et vérifier quel destinataire a été sélectionné contient une province et un code postal correct
noAgeciesAvailableForDate: Aucune agence disponibles pour la date et le destinataire sélectionné, changer la date d'envoi de la commande et vérifier quel destinataire a été sélectionné contient une province et un code postal correct
pleaseSelectAWarehouse: Veuillez choisir un entrepôt
warehouse: Entrepôt
pickup: Retrait
addressStepQuestion: Adresse livraison?
addressStepQuestionPickup: À quelle adresse voulez-vous associer la commande? (Optionnel)
pt-PT:
receiveOrPickOrder: Queres receber ou levantar a encomenda?
receiveOrder: Receber na minha loja
pickupInStore: Levantar no armazém
orderDateDelivery: Como queres receber a encomenda?
howDoYouWantToReceive: Como queres receber a encomenda?
confirmData: Confirme os dados
arrival: Chegada
orderUpdated: Encomenda actualizada
pleaseSelectAnOption: Por favor, escolha uma opção
pleaseSelectADate: Por favor, escolha uma data
pleaseSelectAnAddress: Por favor, escolha um endereço
pleaseSelectAnAgency: Por favor, escolha uma agência
pickupWarehouse: Em qual armazém queres levantar a encomenda?
noWarehousesAvailableForDate: Não armazéns disponíveis para a data seleccionada, modifique a data para levantar a encomenda e verifique qual destinatário selecionou contém uma província e código postal corretos
noAgeciesAvailableForDate: Não agências disponíveis para a data e o consignatario escolhido, modifique a data de envío do pedido e verifique qual destinatário selecionou contém uma província e código postal corretos
pleaseSelectAWarehouse: Por favor, escolha um armazém
warehouse: Armazém
pickup: Recolhida
addressStepQuestion: Onde queres receber a encomenda?
addressStepQuestionPickup: Para qual endereço deseja associar o pedido? (Opcional)
</i18n>

View File

@ -126,7 +126,6 @@ en-US:
shippingInformation: Shipping Information
preparation: Preparation
delivery: Delivery
agency: Agency
warehouse: Store
deliveryAddress: Delivery address
total: Total
@ -135,7 +134,6 @@ es-ES:
shippingInformation: Datos de envío
preparation: Preparación
delivery: Entrega
agency: Agencia
warehouse: Almacén
deliveryAddress: Dirección de entrega
total: Total
@ -144,7 +142,6 @@ ca-ES:
shippingInformation: Dades d'enviament
preparation: Preparació
delivery: Lliurament
agency: Agència
warehouse: Magatzem
deliveryAddress: Adreça de lliurament
total: Total
@ -161,7 +158,6 @@ pt-PT:
shippingInformation: Dados de envio
preparation: Preparação
delivery: Entrega
agency: Agência
warehouse: Armazém
deliveryAddress: Endereço de entrega
total: Total

View File

@ -66,7 +66,7 @@ const routes = [
},
{
name: 'checkout',
path: '/ecomerce/checkout',
path: '/ecomerce/checkout/:id?',
component: () => import('pages/Ecomerce/CheckoutView.vue')
},
{

View File

@ -1,6 +1,8 @@
import { defineStore } from 'pinia';
import { jApi } from 'boot/axios';
import useNotify from 'src/composables/useNotify.js';
import { i18n } from 'src/boot/i18n';
import { useQuasar } from 'quasar';
const { notify } = useNotify();
@ -13,7 +15,13 @@ export const useAppStore = defineStore('hedera', {
rightDrawerOpen: false,
isHeaderMounted: false,
menuEssentialLinks: [],
basketOrderId: null
basketOrderId: null,
localeDates: {
days: [],
months: [],
daysShort: [],
monthsShort: []
}
}),
actions: {
@ -48,8 +56,19 @@ export const useAppStore = defineStore('hedera', {
this.$patch({ imageUrl });
},
getLocaleDates() {
const { messages, locale } = i18n.global;
this.localeDates = {
days: messages.value[locale.value].date.days,
months: messages.value[locale.value].date.months,
daysShort: messages.value[locale.value].date.daysShort,
monthsShort: messages.value[locale.value].date.monthsShort
};
},
async init() {
this.getBasketOrderId();
this.getLocaleDates();
},
getBasketOrderId() {
@ -57,23 +76,70 @@ export const useAppStore = defineStore('hedera', {
},
async checkOrder(orderId) {
const resultSet = await jApi.execQuery(
'CALL myOrder_checkConfig(#id)',
{ id: orderId }
);
resultSet.fetchValue();
},
async check(checkoutContinue) {
if (this.basketOrderId) {
return await this.checkRedirect(checkoutContinue);
} else {
this.redirect();
return false;
}
},
async checkRedirect(checkoutContinue) {
try {
const resultSet = await jApi.execQuery(
'CALL myOrder_checkConfig(#id)',
{ id: orderId }
);
resultSet.fetchValue();
await this.checkOrder(this.basketOrderId);
return true;
} catch (err) {
console.error('Error checking order', err);
if (err.exception === 'Vn.Lib.UserError') {
switch (err.code) {
case 'orderConfirmed':
case 'orderNotOwnedByUser':
await this.redirect();
break;
default:
this.router.push({
name: 'checkout',
params: { id: this.basketOrderId },
query: { continue: checkoutContinue }
});
notify(err.message, 'negative');
}
return false;
} else throw err;
}
},
async redirect() {
const resultSet = await jApi.execQuery(
'SELECT COUNT(*) > 0 FROM myOrder'
);
if (resultSet.fetchValue()) {
this.router.push({ name: 'pendingOrders' });
notify('loadAnOrder', 'warning');
} else {
this.router.push({ name: 'checkout' });
}
},
loadIntoBasket(orderId) {
if (this.basketOrderId !== orderId) {
localStorage.setItem('hederaBasket', orderId);
this.basketOrderId = orderId;
localStorage.setItem('hederaBasket', orderId);
notify('orderLoadedIntoBasket', 'positive');
}
}
},
getters: {
isMobile() {
const $q = useQuasar();
return $q?.screen?.width <= 768;
}
}
});