Pedidos stepper #80

Merged
jsegarra merged 16 commits from wbuezas/hedera-web-mindshore:feature/PedidosStepper into 4922-vueMigration 2024-09-03 14:06:02 +00:00
19 changed files with 966 additions and 283 deletions

View File

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

View File

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

View File

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

View File

@ -26,6 +26,9 @@ a.link {
text-decoration: underline; text-decoration: underline;
} }
} }
.default-radius {
border-radius: 0.6em;
}
.q-card { .q-card {
border-radius: 0.6em !important; border-radius: 0.6em !important;
box-shadow: 0 0 3px rgba(0, 0, 0, 0.1); 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', adminNewsDetails: 'Afegir o editar notícia',
// //
orderLoadedIntoBasket: 'Comanda carregada a la cistella!', 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', at: 'a les',
back: 'Tornar', back: 'Tornar',
next: 'Següent',
remove: 'Esborrar', 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', adminNewsDetails: 'Add or edit new',
// //
orderLoadedIntoBasket: 'Order loaded into basket!', orderLoadedIntoBasket: 'Order loaded into basket!',
loadAnOrder: 'Please load a pending order to the cart or start a new one',
at: 'at', at: 'at',
back: 'Back', back: 'Back',
next: 'Next',
remove: 'Remove', remove: 'Remove',
agency: 'Agency',
noData: 'No data', noData: 'No data',
confirm: 'Confirm',
orders: 'Orders', orders: 'Orders',
order: 'Pending order', order: 'Pending order',

View File

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

View File

@ -61,8 +61,13 @@ export default {
adminNewsDetails: 'Ajouter ou editer nouvelles', adminNewsDetails: 'Ajouter ou editer nouvelles',
// //
orderLoadedIntoBasket: 'Commande chargée dans le panier!', 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: 'à', at: 'à',
back: 'Retour', back: 'Retour',
next: 'Suivant',
remove: 'Effacer', 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', adminNewsDetails: 'Ajouter ou editer nouvelles',
// //
orderLoadedIntoBasket: 'Pedido carregado na cesta!', orderLoadedIntoBasket: 'Pedido carregado na cesta!',
loadAnOrder: 'Carregue um pedido pendente no carrinho ou inicie um novo',
at: 'às', at: 'às',
back: 'Voltar', back: 'Voltar',
next: 'Seguinte',
remove: 'Eliminar', 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 { JsonConnection } from '../vn/json-connection';
import { ResultSet } from './result-set' import { ResultSet } from './result-set';
/** /**
* Simulates a connection to a database by making asynchronous requests to a * Simulates a connection to a database by making asynchronous requests to a
@ -15,7 +15,7 @@ const Flag = {
NOT_NULL: 1, NOT_NULL: 1,
PRI_KEY: 2, PRI_KEY: 2,
AI: 512 | 2 | 1 AI: 512 | 2 | 1
} };
const Type = { const Type = {
BOOLEAN: 1, BOOLEAN: 1,
@ -24,161 +24,164 @@ const Type = {
STRING: 5, STRING: 5,
DATE: 8, DATE: 8,
DATE_TIME: 9 DATE_TIME: 9
} };
export class Connection extends JsonConnection { export class Connection extends JsonConnection {
static Flag = Flag static Flag = Flag;
static Type = Type static Type = Type;
/** /**
* Runs a SQL query on the database. * Runs a SQL query on the database.
* *
* @param {String} sql The SQL statement * @param {String} sql The SQL statement
* @return {ResultSet} The result * @return {ResultSet} The result
*/ */
async execSql (sql) { async execSql(sql) {
const json = await this.send('core/query', { sql }) const json = await this.send('core/query', { sql });
const results = [] const results = [];
let err let err;
if (json) { if (json) {
try { try {
if (json && json instanceof Array) { if (json && json instanceof Array) {
for (let i = 0; i < json.length; i++) { for (let i = 0; i < json.length; i++) {
if (json[i] !== true) { if (json[i] !== true) {
const rows = json[i].data const rows = json[i].data;
const columns = json[i].columns const columns = json[i].columns;
const data = new Array(rows.length) const data = new Array(rows.length);
results.push({ results.push({
data, data,
columns, columns,
tables: json[i].tables tables: json[i].tables
}) });
for (let j = 0; j < rows.length; j++) { for (let j = 0; j < rows.length; j++) {
const row = (data[j] = {}) const row = (data[j] = {});
for (let k = 0; k < columns.length; k++) { 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++) { for (let j = 0; j < columns.length; j++) {
let castFunc = null let castFunc = null;
const col = columns[j] const col = columns[j];
switch (col.type) { switch (col.type) {
case Type.DATE: case Type.DATE:
case Type.DATE_TIME: case Type.DATE_TIME:
case Type.TIMESTAMP: case Type.TIMESTAMP:
castFunc = this.valueToDate castFunc = this.valueToDate;
break break;
} }
if (castFunc !== null) { if (castFunc !== null) {
if (col.def != null) { if (col.def != null) {
col.def = castFunc(col.def) col.def = castFunc(col.def);
} }
for (let k = 0; k < data.length; k++) { for (let k = 0; k < data.length; k++) {
if (data[k][col.name] != null) { 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 { } else {
results.push(json[i]) results.push(json[i]);
} }
} }
} }
} catch (e) { } catch (e) {
err = e err = e;
} }
} }
return new ResultSet(results, err) return new ResultSet(results, err);
} }
/** /**
* Runs a query on the database. * Runs a query on the database.
* *
* @param {String} query The SQL statement * @param {String} query The SQL statement
* @param {Object} params The query params * @param {Object} params The query params
* @return {ResultSet} The result * @return {ResultSet} The result
*/ */
async execQuery (query, params) { async execQuery(query, params) {
const sql = query.replace(/#\w+/g, (key) => { const sql = query.replace(/#\w+/g, key => {
const value = params[key.substring(1)] const value = params[key.substring(1)];
return value ? this.renderValue(value) : key return value ? this.renderValue(value) : key;
}) });
return await this.execSql(sql) return await this.execSql(sql);
} }
async query (query, params) { async query(query, params) {
const res = await this.execQuery(query, params) const res = await this.execQuery(query, params);
return res.fetchData() return res.fetchData();
} }
async getObject (query, params) { async getObject(query, params) {
const res = await this.execQuery(query, params) const res = await this.execQuery(query, params);
return res.fetchObject() return res.fetchObject();
} }
async getValue (query, params) { async getValue(query, params) {
const res = await this.execQuery(query, params) const res = await this.execQuery(query, params);
return res.fetchValue() return res.fetchValue();
} }
renderValue (v) { renderValue(v) {
switch (typeof v) { switch (typeof v) {
case 'number': case 'number':
return v return v;
case 'boolean': case 'boolean':
return v ? 'TRUE' : 'FALSE' return v ? 'TRUE' : 'FALSE';
case 'string': case 'string':
return "'" + v.replace(this.regexp, this.replaceFunc) + "'" return "'" + v.replace(this.regexp, this.replaceFunc) + "'";
default: default:
if (v instanceof Date) { if (v instanceof Date) {
if (!isNaN(v.getTime())) { if (!isNaN(v.getTime())) {
const unixTime = parseInt(fixTz(v).getTime() / 1000) const unixTime = parseInt(fixTz(v).getTime() / 1000);
return 'DATE(FROM_UNIXTIME(' + unixTime + '))' return 'DATE(FROM_UNIXTIME(' + unixTime + '))';
} else { } else {
return '0000-00-00' return '0000-00-00';
} }
} else { } else {
return 'NULL' return 'NULL';
} }
} }
} }
/* /*
* Parses a value to date. * Parses a value to date.
*/ */
valueToDate (value) { valueToDate(value) {
return fixTz(new Date(value)) return fixTz(new Date(value));
} }
} }
// TODO: Read time zone from db configuration // TODO: Read time zone from db configuration
const tz = { timeZone: 'Europe/Madrid' } const tz = { timeZone: 'Europe/Madrid' };
const isLocal = Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone const isLocal =
Intl.DateTimeFormat().resolvedOptions().timeZone === tz.timeZone;
function fixTz (date) { function fixTz(date) {
if (isLocal) return 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 = const hasTime =
localDate.getHours() || localDate.getHours() ||
localDate.getMinutes() || localDate.getMinutes() ||
localDate.getSeconds() || localDate.getSeconds() ||
localDate.getMilliseconds() localDate.getMilliseconds();
if (!hasTime) { if (!hasTime) {
date.setHours(date.getHours() + 12) date.setHours(date.getHours() + 12);
date.setHours(0, 0, 0, 0) 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. * This class stores the database results.
*/ */
export class ResultSet { export class ResultSet {
results = null results = null;
error = null error = null;
/** /**
* Initilizes the resultset object. * Initilizes the resultset object.
*/ */
constructor (results, error) { constructor(results, error) {
this.results = results this.results = results;
this.error = error this.error = error;
} }
/** /**
* Gets the query error. * Gets the query error.
* *
* @return {Db.Err} the error or null if no errors hapened * @return {Db.Err} the error or null if no errors hapened
*/ */
getError () { getError() {
return this.error return this.error;
} }
fetch () { fetch() {
if (this.error) { if (this.error) {
throw this.error throw this.error;
} }
if (this.results !== null && this.results.length > 0) { 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. * Fetchs the next result from the resultset.
* *
* @return {Db.Result} the result or %null if error or there are no more results * @return {Db.Result} the result or %null if error or there are no more results
*/ */
fetchResult () { fetchResult() {
const result = this.fetch() const result = this.fetch();
if (result !== null) { if (result !== null) {
if (result.data instanceof Array) { if (result.data instanceof Array) {
return new Result(result) return new Result(result);
} else { } else {
return true return true;
} }
} }
return null return null;
} }
/** /**
* Fetchs the first row object from the next resultset. * Fetchs the first row object from the next resultset.
* *
* @return {Array} the row if success, %null otherwise * @return {Array} the row if success, %null otherwise
*/ */
fetchObject () { fetchObject() {
const result = this.fetch() const result = this.fetch();
if ( if (
result !== null && result !== null &&
result.data instanceof Array && result.data instanceof Array &&
result.data.length > 0 result.data.length > 0
) { ) {
return result.data[0] return result.data[0];
} }
return null return null;
} }
/** /**
* Fetchs data from the next resultset. * Fetchs data from the next resultset.
* *
* @return {Array} the data * @return {Array} the data
*/ */
fetchData () { fetchData() {
const result = this.fetch() const result = this.fetch();
if (result !== null && result.data instanceof Array) { 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. * Fetchs the first row and column value from the next resultset.
* *
* @return {Object} the value if success, %null otherwise * @return {Object} the value if success, %null otherwise
*/ */
fetchValue () { fetchValue() {
const row = this.fetchRow() const row = this.fetchRow();
if (row instanceof Array && row.length > 0) { 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. * Fetchs the first row from the next resultset.
* *
* @return {Array} the row if success, %null otherwise * @return {Array} the row if success, %null otherwise
*/ */
fetchRow () { fetchRow() {
const result = this.fetch() const result = this.fetch();
if ( if (
result !== null && result !== null &&
result.data instanceof Array && result.data instanceof Array &&
result.data.length > 0 result.data.length > 0
) { ) {
const object = result.data[0] const object = result.data[0];
const row = new Array(result.columns.length) const row = new Array(result.columns.length);
for (let i = 0; i < row.length; i++) { 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 { i18n } from 'src/boot/i18n';
import { date as qdate, format } from 'quasar'; import { date as qdate, format } from 'quasar';
const { pad } = format; const { pad } = format;
import { useAppStore } from 'stores/app';
export function currency(val) { export function currency(val) {
return typeof val === 'number' ? val.toFixed(2) + '€' : 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') => { export const formatDate = (timeStamp, format = 'YYYY-MM-DD') => {
if (!timeStamp) return ''; if (!timeStamp) return '';
const { messages, locale } = i18n.global; const appStore = useAppStore();
return qdate.formatDate(timeStamp, format, { return qdate.formatDate(timeStamp, format, appStore.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
});
}; };
/** /**

View File

@ -66,27 +66,22 @@ onMounted(() => getPackages());
<i18n lang="yaml"> <i18n lang="yaml">
en-US: en-US:
agency: Agency
bundles: Bundles bundles: Bundles
expeditions: Exps. expeditions: Exps.
prevision: Prev. prevision: Prev.
es-ES: es-ES:
agency: Agencia
bundles: Bultos bundles: Bultos
expeditions: Exps. expeditions: Exps.
prevision: Prev. prevision: Prev.
ca-ES: ca-ES:
agency: Agència
bundles: Paquets bundles: Paquets
expeditions: Exps. expeditions: Exps.
prevision: Prev. prevision: Prev.
fr-FR: fr-FR:
agency: Agence
bundles: Cartons bundles: Cartons
expeditions: Exps. expeditions: Exps.
prevision: Prev. prevision: Prev.
pt-PT: pt-PT:
agency: Agência
bundles: Bultos bundles: Bultos
expeditions: Exps. expeditions: Exps.
prevision: Prev. 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') }} {{ $t('warehouse') }}
{{ 'Algemesi' }} {{ 'Algemesi' }}
</p> </p>
<QBtn flat rounded no-caps> <QBtn
flat
rounded
no-caps
:to="{
name: 'checkout',
Review

Duda: en Lilium usamos UpperCamelCase, mientras que aquí usamos camelCase.
Ambos estándares son aceptables, pero como verias estandarizar en favor de "Lilium"

Duda: en Lilium usamos UpperCamelCase, mientras que aquí usamos camelCase. Ambos estándares son aceptables, pero como verias estandarizar en favor de "Lilium"
Review

Si, coincido en estandarizar, pero en el proyecto ya lo venían haciendo así.
Consideré en hacer el cambio pero conllevaria también cambiar todos los namings de las traducciones de las secciones ya que aparentemente el name de la ruta se usa como título de la sección.

Si, coincido en estandarizar, pero en el proyecto ya lo venían haciendo así. Consideré en hacer el cambio pero conllevaria también cambiar todos los namings de las traducciones de las secciones ya que aparentemente el `name` de la ruta se usa como título de la sección.
params: { id: appStore.basketOrderId },
query: { continue: 'catalog' }
}"
>
{{ $t('modify') }} {{ $t('modify') }}
</QBtn> </QBtn>
</div> </div>
@ -355,7 +364,7 @@ export default {
setup() { setup() {
const appStore = useAppStore(); const appStore = useAppStore();
const { isHeaderMounted } = storeToRefs(appStore); const { isHeaderMounted } = storeToRefs(appStore);
return { isHeaderMounted }; return { isHeaderMounted, appStore };
}, },
data() { data() {
return { return {
@ -417,6 +426,12 @@ export default {
created() { created() {
this.$app.useRightDrawer = true; this.$app.useRightDrawer = true;
}, },
async beforeMount() {
const isGuest = false; // TODO: Integrate isGuest logic
if (!isGuest) {
this.appStore.check('catalog');
}
},
async mounted() { async mounted() {
this.categories = await this.$jApi.query( this.categories = await this.$jApi.query(
`SELECT c.id, l.name, c.color, c.code `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(() => {
Review

Ufff...si vamos bien de tiempo, arreglamos esta funcion:

  1. El método find hace lo mismo, solo cambia el origen de datos
  2. La lógica del return hace lo mismo, solo cambia la etiqueta que quremos traducir
Ufff...si vamos bien de tiempo, arreglamos esta funcion: 1. El método find hace lo mismo, solo cambia el origen de datos 2. La lógica del return hace lo mismo, solo cambia la etiqueta que quremos traducir
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;`,
{
jsegarra marked this conversation as resolved
Review

Se que no está en dev, pero esta notificación se puede mostrar y que no cambie de paso, asi te permite escoger una dirección diferente sin hacer un click extra. Aunque lo ideal, si el mensaje dice que para esa fecha no hay, que te lleve directamente al paso de fecha.
Lo anotaria como propuesta de mejora

Se que no está en dev, pero esta notificación se puede mostrar y que no cambie de paso, asi te permite escoger una dirección diferente sin hacer un click extra. Aunque lo ideal, si el mensaje dice que para esa fecha no hay, que te lleve directamente al paso de fecha. Lo anotaria como propuesta de mejora
Review

Lo podemos poner en la lista de propuestas para mejoras

Lo podemos poner en la lista de propuestas para mejoras
Review

Anotado

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

View File

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

View File

@ -1,6 +1,8 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { jApi } from 'boot/axios'; import { jApi } from 'boot/axios';
import useNotify from 'src/composables/useNotify.js'; import useNotify from 'src/composables/useNotify.js';
import { i18n } from 'src/boot/i18n';
import { useQuasar } from 'quasar';
const { notify } = useNotify(); const { notify } = useNotify();
@ -13,7 +15,13 @@ export const useAppStore = defineStore('hedera', {
rightDrawerOpen: false, rightDrawerOpen: false,
isHeaderMounted: false, isHeaderMounted: false,
menuEssentialLinks: [], menuEssentialLinks: [],
basketOrderId: null basketOrderId: null,
localeDates: {
days: [],
months: [],
daysShort: [],
monthsShort: []
}
}), }),
actions: { actions: {
@ -48,8 +56,19 @@ export const useAppStore = defineStore('hedera', {
this.$patch({ imageUrl }); 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() { async init() {
this.getBasketOrderId(); this.getBasketOrderId();
this.getLocaleDates();
}, },
getBasketOrderId() { getBasketOrderId() {
@ -57,23 +76,70 @@ export const useAppStore = defineStore('hedera', {
}, },
async checkOrder(orderId) { 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 { try {
const resultSet = await jApi.execQuery( await this.checkOrder(this.basketOrderId);
'CALL myOrder_checkConfig(#id)', return true;
{ id: orderId }
);
resultSet.fetchValue();
} catch (err) { } 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) { loadIntoBasket(orderId) {
if (this.basketOrderId !== orderId) { if (this.basketOrderId !== orderId) {
localStorage.setItem('hederaBasket', orderId);
this.basketOrderId = orderId; this.basketOrderId = orderId;
localStorage.setItem('hederaBasket', orderId);
notify('orderLoadedIntoBasket', 'positive'); notify('orderLoadedIntoBasket', 'positive');
} }
} }
},
getters: {
isMobile() {
const $q = useQuasar();
return $q?.screen?.width <= 768;
}
} }
}); });