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 @@
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) {
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() {
getError() {
return this.error;
}
},
,fetch() {
if (this.error)
fetch() {
if (this.error) {
throw this.error;
if (this.results !== null
&& this.results.length > 0)
}
console.log('this.results', this.results);
if (this.results !== 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() {
var result = this.fetch();
fetchResult() {
const result = this.fetch();
console.log('test result', result);
if (result !== null) {
if (result.data instanceof Array)
if (result.data instanceof Array) {
return new Result(result);
else
} else {
return true;
}
}
return null;
}
},
/**
* Fetchs the first row object from the next resultset.
*
* @return {Array} the row if success, %null otherwise
*/
,fetchObject() {
var result = this.fetch();
fetchObject() {
const result = this.fetch();
if (result !== null
&& result.data instanceof Array
&& result.data.length > 0)
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
) {
return result.data[0];
}
return null;
}
},
/**
* Fetchs data from the next resultset.
*
* @return {Array} the data
*/
,fetchData() {
var result = this.fetch();
fetchData() {
const result = this.fetch();
if (result !== null
&& result.data instanceof Array)
if (result !== null && result.data instanceof Array) {
return result.data;
}
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();
fetchValue() {
const row = this.fetchRow();
if (row instanceof Array && row.length > 0)
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() {
var result = this.fetch();
fetchRow() {
const 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++)
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,11 +24,11 @@ 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.
@ -37,67 +37,69 @@ export class Connection extends JsonConnection {
* @return {ResultSet} The result
*/
async execSql(sql) {
const json = await this.send('core/query', { sql })
const results = []
let err
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);
}
/**
@ -108,47 +110,47 @@ export class Connection extends JsonConnection {
* @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
})
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()
const res = await this.execQuery(query, params);
return res.fetchData();
}
async getObject(query, params) {
const res = await this.execQuery(query, params)
return res.fetchObject()
const res = await this.execQuery(query, params);
return res.fetchObject();
}
async getValue(query, params) {
const res = await this.execQuery(query, params)
return res.fetchValue()
const res = await this.execQuery(query, params);
return res.fetchValue();
}
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';
}
}
}
@ -157,28 +159,29 @@ export class Connection extends JsonConnection {
* Parses a value to date.
*/
valueToDate(value) {
return fixTz(new Date(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
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.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,18 +1,18 @@
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
this.results = results;
this.error = error;
}
/**
@ -21,19 +21,19 @@ export class ResultSet {
* @return {Db.Err} the error or null if no errors hapened
*/
getError() {
return this.error
return this.error;
}
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;
}
/**
@ -42,17 +42,17 @@ export class ResultSet {
* @return {Db.Result} the result or %null if error or there are no more results
*/
fetchResult() {
const result = this.fetch()
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;
}
/**
@ -61,17 +61,17 @@ export class ResultSet {
* @return {Array} the row if success, %null otherwise
*/
fetchObject() {
const result = this.fetch()
const result = this.fetch();
if (
result !== null &&
result.data instanceof Array &&
result.data.length > 0
) {
return result.data[0]
return result.data[0];
}
return null
return null;
}
/**
@ -80,13 +80,13 @@ export class ResultSet {
* @return {Array} the data
*/
fetchData() {
const result = this.fetch()
const result = this.fetch();
if (result !== null && result.data instanceof Array) {
return result.data
return result.data;
}
return null
return null;
}
/**
@ -95,13 +95,12 @@ export class ResultSet {
* @return {Object} the value if success, %null otherwise
*/
fetchValue() {
const row = this.fetchRow()
const row = this.fetchRow();
if (row instanceof Array && row.length > 0) {
return row[0]
return row[0];
}
return null
return null;
}
/**
@ -110,21 +109,20 @@ export class ResultSet {
* @return {Array} the row if success, %null otherwise
*/
fetchRow() {
const result = this.fetch()
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)
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',
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') }}
</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(() => {
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"
jsegarra marked this conversation as resolved Outdated

La fecha está en inglés

La fecha está en inglés

Corregido.

Commit: 020e0afc96

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/020e0afc96427ca1bf6db03dba58e4b1f38b8b27
:locale="localeDates"
jsegarra marked this conversation as resolved Outdated

Empieza en domingo, a nivel de empresa podría servir, pero decara a un usuario final, puede llevar a confusión

Empieza en domingo, a nivel de empresa podría servir, pero decara a un usuario final, puede llevar a confusión

Agregado.

Commit: 975495113d

Agregado. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/975495113d2d7b2c5257d3f9b3d9f7e17f1cb7a8
first-day-of-week="1"
:minimal="appStore.isMobile"
flat
/>
</div>
<!-- Address step -->
jsegarra marked this conversation as resolved Outdated

Este step no tiene titulo?

Este step no tiene titulo?

Corregido.

Commit: f2bd3c2fa6

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/f2bd3c2fa6f6db469205c772612d1b1b28cde459
<QList
jsegarra marked this conversation as resolved Outdated

Con el segundo metodo, he podido avvanzar sin seleccionar una dirección. Esto en los otros pasos no ocurre.

Con el segundo metodo, he podido avvanzar sin seleccionar una dirección. Esto en los otros pasos no ocurre.

Validación agregada.

Commit: 3a21292030

Validación agregada. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/3a21292030bc7b091de789e8771cd69114e09602
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>
jsegarra marked this conversation as resolved Outdated

Si selecciono "Recibir en mi tienda", me aparece el mismo titulo que seleccionar fecha

Si selecciono "Recibir en mi tienda", me aparece el mismo titulo que seleccionar fecha

Corregido.

Commit: 6423ecfb05

Corregido. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/6423ecfb05a63b0cd6b637e378e75da733591d5b
<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 }}
jsegarra marked this conversation as resolved Outdated

traducir

traducir

Traducido.

Commit: 95e23c05fa

Traducido. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/95e23c05fa8dbdb329fa72b7edd034faf0656ff5
</span>
<span>{{ confirmPlaceText }}</span>
</div>
</div>
<QStepperNavigation class="flex justify-between">
<QBtn
flat
jsegarra marked this conversation as resolved Outdated

traducir

traducir

Traducido.

Commit: 95e23c05fa

Traducido. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/95e23c05fa8dbdb329fa72b7edd034faf0656ff5
color="primary"
jsegarra marked this conversation as resolved Outdated

La label del botón en el ultimo paso debería ser confirmar, finalizar, guardar, confirmar con Javi

La label del botón en el ultimo paso debería ser confirmar, finalizar, guardar, confirmar con Javi

Agregado.

Commit: 8e0f09cc0f

Agregado. Commit: https://gitea.verdnatura.es/verdnatura/hedera-web/commit/8e0f09cc0f73afda885f28991fb40689a1bf9e2e
@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) {
try {
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 {
await this.checkOrder(this.basketOrderId);
return true;
} catch (err) {

What?

What?

Literal lo copie y pegue del viejo hedera, no se ni lo que es jajaja

Literal lo copie y pegue del viejo hedera, no se ni lo que es jajaja
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;

Porque no usamos pinia?

Porque no usamos pinia?
localStorage.setItem('hederaBasket', orderId);
notify('orderLoadedIntoBasket', 'positive');
}
}
},
getters: {
isMobile() {
const $q = useQuasar();
return $q?.screen?.width <= 768;
}
}
});