forked from juan/hedera-web
Refactor, bugfix, i18n
This commit is contained in:
parent
85a2b667c2
commit
65097c3f58
|
@ -1 +0,0 @@
|
||||||
/client/
|
|
|
@ -1,368 +1,368 @@
|
||||||
const warehouseIds = [1, 44];
|
const warehouseIds = [1, 44];
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethod('calcCatalog', {
|
Self.remoteMethod('calcCatalog', {
|
||||||
description: 'Get the available and prices list for an item',
|
description: 'Get the available and prices list for an item',
|
||||||
accessType: 'READ',
|
accessType: 'READ',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
arg: 'id',
|
arg: 'id',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
description: 'The item id'
|
description: 'The item id'
|
||||||
}, {
|
}, {
|
||||||
arg: 'dated',
|
arg: 'dated',
|
||||||
type: 'Date',
|
type: 'Date',
|
||||||
description: 'The date'
|
description: 'The date'
|
||||||
}, {
|
}, {
|
||||||
arg: 'addressFk',
|
arg: 'addressFk',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
description: 'The address id'
|
description: 'The address id'
|
||||||
}, {
|
}, {
|
||||||
arg: 'agencyModeFk',
|
arg: 'agencyModeFk',
|
||||||
type: 'Number',
|
type: 'Number',
|
||||||
description: 'The agency id'
|
description: 'The agency id'
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['Object'],
|
||||||
description: 'The item available and prices list',
|
description: 'The item available and prices list',
|
||||||
root: true,
|
root: true,
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
path: `/:id/calcCatalog`,
|
path: `/:id/calcCatalog`,
|
||||||
verb: 'GET'
|
verb: 'GET'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
|
Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
|
||||||
Self.dataSource.connector.query(
|
Self.dataSource.connector.query(
|
||||||
`CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
|
`CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
|
||||||
[id, dated, addressFk, agencyModeFk],
|
[id, dated, addressFk, agencyModeFk],
|
||||||
(err, res) => {
|
(err, res) => {
|
||||||
if (err) return cb(err)
|
if (err) return cb(err)
|
||||||
return cb(null, res[0])
|
return cb(null, res[0])
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.remoteMethod('catalog', {
|
||||||
|
description: 'Get the catalog',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'dated',
|
||||||
|
type: 'Date',
|
||||||
|
description: 'The available date'
|
||||||
|
}, {
|
||||||
|
arg: 'typeFk',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The item type id'
|
||||||
|
}, {
|
||||||
|
arg: 'categoryFk',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The item category id'
|
||||||
|
}, {
|
||||||
|
arg: 'search',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The search string'
|
||||||
|
}, {
|
||||||
|
arg: 'order',
|
||||||
|
type: 'String',
|
||||||
|
description: 'The order string'
|
||||||
|
}, {
|
||||||
|
arg: 'limit',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The maximum number of registers'
|
||||||
|
}, {
|
||||||
|
arg: 'tagFilter',
|
||||||
|
type: ['Object'],
|
||||||
|
description: 'The tag filter object'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['Object'],
|
||||||
|
description: 'The item list',
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/catalog`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
|
||||||
|
let $ = Self.app.models;
|
||||||
|
let itemIds;
|
||||||
|
|
||||||
|
let inboundWhere = {
|
||||||
|
warehouseFk: {inq: warehouseIds},
|
||||||
|
available: {gt: 0},
|
||||||
|
dated: {lte: dated},
|
||||||
|
and: [
|
||||||
|
{or: [
|
||||||
|
{expired: {gt: dated}},
|
||||||
|
{expired: null}
|
||||||
|
]}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.remoteMethod('catalog', {
|
// Applies base filters
|
||||||
description: 'Get the catalog',
|
|
||||||
accessType: 'READ',
|
|
||||||
accepts: [
|
|
||||||
{
|
|
||||||
arg: 'dated',
|
|
||||||
type: 'Date',
|
|
||||||
description: 'The available date'
|
|
||||||
}, {
|
|
||||||
arg: 'typeFk',
|
|
||||||
type: 'Number',
|
|
||||||
description: 'The item type id'
|
|
||||||
}, {
|
|
||||||
arg: 'categoryFk',
|
|
||||||
type: 'Number',
|
|
||||||
description: 'The item category id'
|
|
||||||
}, {
|
|
||||||
arg: 'search',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The search string'
|
|
||||||
}, {
|
|
||||||
arg: 'order',
|
|
||||||
type: 'String',
|
|
||||||
description: 'The order string'
|
|
||||||
}, {
|
|
||||||
arg: 'limit',
|
|
||||||
type: 'Number',
|
|
||||||
description: 'The maximum number of registers'
|
|
||||||
}, {
|
|
||||||
arg: 'tagFilter',
|
|
||||||
type: ['Object'],
|
|
||||||
description: 'The tag filter object'
|
|
||||||
}
|
|
||||||
],
|
|
||||||
returns: {
|
|
||||||
type: ['Object'],
|
|
||||||
description: 'The item list',
|
|
||||||
root: true,
|
|
||||||
},
|
|
||||||
http: {
|
|
||||||
path: `/catalog`,
|
|
||||||
verb: 'GET'
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
|
if (/^[0-9]+$/.test(search)) {
|
||||||
let $ = Self.app.models;
|
itemIds = [parseInt(search)];
|
||||||
let itemIds;
|
} else {
|
||||||
|
let inbounds = await $.Inbound.find({
|
||||||
|
fields: ['itemFk'],
|
||||||
|
where: inboundWhere
|
||||||
|
});
|
||||||
|
itemIds = toValues(inbounds, 'itemFk');
|
||||||
|
|
||||||
let inboundWhere = {
|
if (categoryFk || typeFk || search) {
|
||||||
warehouseFk: {inq: warehouseIds},
|
let where = {
|
||||||
available: {gt: 0},
|
id: {inq: itemIds}
|
||||||
dated: {lte: dated},
|
|
||||||
and: [
|
|
||||||
{or: [
|
|
||||||
{expired: {gt: dated}},
|
|
||||||
{expired: null}
|
|
||||||
]}
|
|
||||||
]
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Applies base filters
|
if (typeFk) {
|
||||||
|
where.typeFk = typeFk;
|
||||||
if (/^[0-9]+$/.test(search)) {
|
} else if (categoryFk) {
|
||||||
itemIds = [parseInt(search)];
|
let types = await $.ItemType.find({
|
||||||
} else {
|
fields: ['id'],
|
||||||
let inbounds = await $.Inbound.find({
|
where: {categoryFk}
|
||||||
fields: ['itemFk'],
|
});
|
||||||
where: inboundWhere
|
where.typeFk = {inq: toValues(types, 'id')};
|
||||||
});
|
|
||||||
itemIds = toValues(inbounds, 'itemFk');
|
|
||||||
|
|
||||||
if (categoryFk || typeFk || search) {
|
|
||||||
let where = {
|
|
||||||
id: {inq: itemIds}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeFk) {
|
|
||||||
where.typeFk = typeFk;
|
|
||||||
} else if (categoryFk) {
|
|
||||||
let types = await $.ItemType.find({
|
|
||||||
fields: ['id'],
|
|
||||||
where: {categoryFk}
|
|
||||||
});
|
|
||||||
where.typeFk = {inq: toValues(types, 'id')};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (search)
|
|
||||||
where.longName = {like: `%${search}%`};
|
|
||||||
|
|
||||||
let filter = {
|
|
||||||
fields: ['id'],
|
|
||||||
where
|
|
||||||
};
|
|
||||||
|
|
||||||
let items = await Self.find(filter);
|
|
||||||
itemIds = items.map(i => i.id);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Applies tag filters
|
if (search)
|
||||||
|
where.longName = {like: `%${search}%`};
|
||||||
|
|
||||||
let baseItemIds = itemIds;
|
let filter = {
|
||||||
let tagItems = [];
|
fields: ['id'],
|
||||||
let tagFilterIds = [];
|
where
|
||||||
|
};
|
||||||
|
|
||||||
if (tagFilter && tagFilter.length) {
|
let items = await Self.find(filter);
|
||||||
for (let filter of tagFilter) {
|
itemIds = items.map(i => i.id);
|
||||||
let cond;
|
}
|
||||||
let values = filter.values;
|
}
|
||||||
|
|
||||||
if (values.length)
|
// Applies tag filters
|
||||||
cond = {value: {inq: values}};
|
|
||||||
else if (values.min && values.max)
|
|
||||||
cond = {intValue: {between: [values.min, values.max]}};
|
|
||||||
else if (values.min)
|
|
||||||
cond = {intValue: {gte: values.min}};
|
|
||||||
else if (values.max)
|
|
||||||
cond = {intValue: {lte: values.max}};
|
|
||||||
|
|
||||||
let where = {
|
let baseItemIds = itemIds;
|
||||||
itemFk: {inq: itemIds},
|
let tagItems = [];
|
||||||
tagFk: filter.tagFk
|
let tagFilterIds = [];
|
||||||
};
|
|
||||||
Object.assign(where, cond);
|
|
||||||
|
|
||||||
let itemTags = await $.ItemTag.find({
|
if (tagFilter && tagFilter.length) {
|
||||||
fields: ['itemFk'],
|
for (let filter of tagFilter) {
|
||||||
where
|
let cond;
|
||||||
});
|
let values = filter.values;
|
||||||
tagItems.push(toSet(itemTags, 'itemFk'));
|
|
||||||
tagFilterIds.push(filter.tagFk);
|
|
||||||
}
|
|
||||||
|
|
||||||
itemIds = intersect(tagItems);
|
if (values.length)
|
||||||
}
|
cond = {value: {inq: values}};
|
||||||
|
else if (values.min && values.max)
|
||||||
|
cond = {intValue: {between: [values.min, values.max]}};
|
||||||
|
else if (values.min)
|
||||||
|
cond = {intValue: {gte: values.min}};
|
||||||
|
else if (values.max)
|
||||||
|
cond = {intValue: {lte: values.max}};
|
||||||
|
|
||||||
// Obtains distinct tags and it's distinct values
|
let where = {
|
||||||
|
itemFk: {inq: itemIds},
|
||||||
|
tagFk: filter.tagFk
|
||||||
|
};
|
||||||
|
Object.assign(where, cond);
|
||||||
|
|
||||||
let tags = [];
|
let itemTags = await $.ItemTag.find({
|
||||||
|
fields: ['itemFk'],
|
||||||
|
where
|
||||||
|
});
|
||||||
|
tagItems.push(toSet(itemTags, 'itemFk'));
|
||||||
|
tagFilterIds.push(filter.tagFk);
|
||||||
|
}
|
||||||
|
|
||||||
if (typeFk || search) {
|
itemIds = intersect(tagItems);
|
||||||
let tagValues = await $.ItemTag.find({
|
}
|
||||||
fields: ['tagFk', 'value', 'intValue', 'priority'],
|
|
||||||
where: {
|
|
||||||
itemFk: {inq: itemIds},
|
|
||||||
tagFk: {nin: tagFilterIds}
|
|
||||||
},
|
|
||||||
order: 'tagFk, value'
|
|
||||||
});
|
|
||||||
let tagValueMap = toMultiMap(tagValues, 'tagFk');
|
|
||||||
|
|
||||||
for (let i = 0; i < tagItems.length; i++) {
|
// Obtains distinct tags and it's distinct values
|
||||||
let tagFk = tagFilter[i].tagFk;
|
|
||||||
let itemIds;
|
|
||||||
|
|
||||||
if (tagItems.length > 1) {
|
let tags = [];
|
||||||
let siblings = tagItems.filter(v => v != tagItems[i]);
|
|
||||||
itemIds = intersect(siblings);
|
|
||||||
} else
|
|
||||||
itemIds = baseItemIds;
|
|
||||||
|
|
||||||
let tagValues = await $.ItemTag.find({
|
if (typeFk || search) {
|
||||||
fields: ['value', 'intValue', 'priority'],
|
let tagValues = await $.ItemTag.find({
|
||||||
where: {
|
fields: ['tagFk', 'value', 'intValue', 'priority'],
|
||||||
itemFk: {inq: itemIds},
|
where: {
|
||||||
tagFk: tagFk
|
itemFk: {inq: itemIds},
|
||||||
},
|
tagFk: {nin: tagFilterIds}
|
||||||
order: 'value'
|
},
|
||||||
});
|
order: 'tagFk, value'
|
||||||
|
});
|
||||||
|
let tagValueMap = toMultiMap(tagValues, 'tagFk');
|
||||||
|
|
||||||
tagValueMap.set(tagFk, tagValues);
|
for (let i = 0; i < tagItems.length; i++) {
|
||||||
}
|
let tagFk = tagFilter[i].tagFk;
|
||||||
|
let itemIds;
|
||||||
|
|
||||||
let tagIds = [...tagValueMap.keys()];
|
if (tagItems.length > 1) {
|
||||||
tags = await $.Tag.find({
|
let siblings = tagItems.filter(v => v != tagItems[i]);
|
||||||
fields: ['id', 'name', 'isQuantitative', 'unit'],
|
itemIds = intersect(siblings);
|
||||||
where: {
|
} else
|
||||||
id: {inq: tagIds}
|
itemIds = baseItemIds;
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
for (let tag of tags) {
|
let tagValues = await $.ItemTag.find({
|
||||||
let tagValues = tagValueMap.get(tag.id);
|
fields: ['value', 'intValue', 'priority'],
|
||||||
|
where: {
|
||||||
let filter = tagFilter && tagFilter.find(i => i.tagFk == tag.id);
|
itemFk: {inq: itemIds},
|
||||||
filter = filter && filter.values;
|
tagFk: tagFk
|
||||||
|
},
|
||||||
let values = toSet(tagValues, 'value');
|
order: 'value'
|
||||||
if (Array.isArray(filter))
|
|
||||||
values = new Set([...filter, ...values]);
|
|
||||||
|
|
||||||
if (tag.isQuantitative) {
|
|
||||||
let intValues = toValues(tagValues, 'intValue');
|
|
||||||
|
|
||||||
if (filter) {
|
|
||||||
if (filter.min) intValues.push(filter.min);
|
|
||||||
if (filter.max) intValues.push(filter.max);
|
|
||||||
}
|
|
||||||
|
|
||||||
let min = Math.min(...intValues);
|
|
||||||
let max = Math.max(...intValues);
|
|
||||||
let dif = max - min;
|
|
||||||
let digits = new String(dif).length;
|
|
||||||
let step = Math.pow(10, digits - 1);
|
|
||||||
if (digits > 1 && step * 5 > dif) step /= 10;
|
|
||||||
|
|
||||||
Object.assign(tag, {
|
|
||||||
step,
|
|
||||||
min: Math.floor(min / step) * step,
|
|
||||||
max: Math.ceil(max / step) * step
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Object.assign(tag, {
|
|
||||||
values: [...values],
|
|
||||||
filter
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Obtains items data
|
|
||||||
|
|
||||||
let items = await Self.find({
|
|
||||||
fields: ['id', 'longName', 'subName', 'image'],
|
|
||||||
where: {id: {inq: itemIds}},
|
|
||||||
limit: limit,
|
|
||||||
order: order,
|
|
||||||
include: [
|
|
||||||
{
|
|
||||||
relation: 'tags',
|
|
||||||
scope: {
|
|
||||||
fields: ['value', 'tagFk'],
|
|
||||||
where: {priority: {gt: 4}},
|
|
||||||
order: 'priority',
|
|
||||||
include: {
|
|
||||||
relation: 'tag',
|
|
||||||
scope: {fields: ['name']}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, {
|
|
||||||
relation: 'inbounds',
|
|
||||||
scope: {
|
|
||||||
fields: ['available', 'dated', 'tableId'],
|
|
||||||
where: inboundWhere,
|
|
||||||
order: 'dated DESC',
|
|
||||||
include: {
|
|
||||||
relation: 'buy',
|
|
||||||
scope: {fields: ['id', 'price3']}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
});
|
});
|
||||||
|
|
||||||
for (let item of items) {
|
tagValueMap.set(tagFk, tagValues);
|
||||||
item.inbound = item.inbounds()[0];
|
}
|
||||||
item.buy = item.inbound && item.inbound.buy();
|
|
||||||
item.available = sum(item.inbounds(), 'available');
|
let tagIds = [...tagValueMap.keys()];
|
||||||
|
tags = await $.Tag.find({
|
||||||
|
fields: ['id', 'name', 'isQuantitative', 'unit'],
|
||||||
|
where: {
|
||||||
|
id: {inq: tagIds}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
for (let tag of tags) {
|
||||||
|
let tagValues = tagValueMap.get(tag.id);
|
||||||
|
|
||||||
|
let filter = tagFilter && tagFilter.find(i => i.tagFk == tag.id);
|
||||||
|
filter = filter && filter.values;
|
||||||
|
|
||||||
|
let values = toSet(tagValues, 'value');
|
||||||
|
if (Array.isArray(filter))
|
||||||
|
values = new Set([...filter, ...values]);
|
||||||
|
|
||||||
|
if (tag.isQuantitative) {
|
||||||
|
let intValues = toValues(tagValues, 'intValue');
|
||||||
|
|
||||||
|
if (filter) {
|
||||||
|
if (filter.min) intValues.push(filter.min);
|
||||||
|
if (filter.max) intValues.push(filter.max);
|
||||||
|
}
|
||||||
|
|
||||||
|
let min = Math.min(...intValues);
|
||||||
|
let max = Math.max(...intValues);
|
||||||
|
let dif = max - min;
|
||||||
|
let digits = new String(dif).length;
|
||||||
|
let step = Math.pow(10, digits - 1);
|
||||||
|
if (digits > 1 && step * 5 > dif) step /= 10;
|
||||||
|
|
||||||
|
Object.assign(tag, {
|
||||||
|
step,
|
||||||
|
min: Math.floor(min / step) * step,
|
||||||
|
max: Math.ceil(max / step) * step
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return {items, tags};
|
Object.assign(tag, {
|
||||||
};
|
values: [...values],
|
||||||
|
filter
|
||||||
// Array functions
|
});
|
||||||
|
}
|
||||||
function sum(array, key) {
|
|
||||||
if (!Array.isArray(array)) return 0;
|
|
||||||
return array.reduce((a, c) => a + c[key], 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function toMap(objects, key) {
|
// Obtains items data
|
||||||
let map = new Map();
|
|
||||||
for (let object of objects)
|
|
||||||
map.set(object[key], object);
|
|
||||||
return map;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toMultiMap(objects, key) {
|
let items = await Self.find({
|
||||||
let map = new Map();
|
fields: ['id', 'longName', 'subName', 'image'],
|
||||||
for (let object of objects) {
|
where: {id: {inq: itemIds}},
|
||||||
let value = map.get(object[key]);
|
limit,
|
||||||
if (!value) map.set(object[key], value = []);
|
order,
|
||||||
value.push(object);
|
include: [
|
||||||
}
|
{
|
||||||
return map;
|
relation: 'tags',
|
||||||
}
|
scope: {
|
||||||
|
fields: ['value', 'tagFk'],
|
||||||
function toSet(objects, key) {
|
where: {priority: {gt: 4}},
|
||||||
let set = new Set();
|
order: 'priority',
|
||||||
for (let object of objects)
|
include: {
|
||||||
set.add(object[key]);
|
relation: 'tag',
|
||||||
return set;
|
scope: {fields: ['name']}
|
||||||
}
|
|
||||||
|
|
||||||
function toValues(objects, key) {
|
|
||||||
return [...toSet(objects, key)];
|
|
||||||
}
|
|
||||||
|
|
||||||
function intersect(sets) {
|
|
||||||
if (!sets.length) return [];
|
|
||||||
let array = [];
|
|
||||||
let mySets = sets.slice(0);
|
|
||||||
let firstSet = mySets.shift();
|
|
||||||
|
|
||||||
for (let value of firstSet) {
|
|
||||||
let isOnAll = true;
|
|
||||||
|
|
||||||
for (let set of mySets)
|
|
||||||
if (!set.has(value)) {
|
|
||||||
isOnAll = false;
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (isOnAll)
|
}, {
|
||||||
array.push(value);
|
relation: 'inbounds',
|
||||||
|
scope: {
|
||||||
|
fields: ['available', 'dated', 'tableId'],
|
||||||
|
where: inboundWhere,
|
||||||
|
order: 'dated DESC',
|
||||||
|
include: {
|
||||||
|
relation: 'buy',
|
||||||
|
scope: {fields: ['id', 'price3']}
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
return array;
|
for (let item of items) {
|
||||||
|
item.inbound = item.inbounds()[0];
|
||||||
|
item.buy = item.inbound && item.inbound.buy();
|
||||||
|
item.available = sum(item.inbounds(), 'available');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return {items, tags};
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Array functions
|
||||||
|
|
||||||
|
function sum(array, key) {
|
||||||
|
if (!Array.isArray(array)) return 0;
|
||||||
|
return array.reduce((a, c) => a + c[key], 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMap(objects, key) {
|
||||||
|
let map = new Map();
|
||||||
|
for (let object of objects)
|
||||||
|
map.set(object[key], object);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toMultiMap(objects, key) {
|
||||||
|
let map = new Map();
|
||||||
|
for (let object of objects) {
|
||||||
|
let value = map.get(object[key]);
|
||||||
|
if (!value) map.set(object[key], value = []);
|
||||||
|
value.push(object);
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toSet(objects, key) {
|
||||||
|
let set = new Set();
|
||||||
|
for (let object of objects)
|
||||||
|
set.add(object[key]);
|
||||||
|
return set;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toValues(objects, key) {
|
||||||
|
return [...toSet(objects, key)];
|
||||||
|
}
|
||||||
|
|
||||||
|
function intersect(sets) {
|
||||||
|
if (!sets.length) return [];
|
||||||
|
let array = [];
|
||||||
|
let mySets = sets.slice(0);
|
||||||
|
let firstSet = mySets.shift();
|
||||||
|
|
||||||
|
for (let value of firstSet) {
|
||||||
|
let isOnAll = true;
|
||||||
|
|
||||||
|
for (let set of mySets)
|
||||||
|
if (!set.has(value)) {
|
||||||
|
isOnAll = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isOnAll)
|
||||||
|
array.push(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
|
@ -2,45 +2,64 @@ const app = require('../../server/server');
|
||||||
const loopback = require('loopback');
|
const loopback = require('loopback');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const config = {
|
function getUrl() {
|
||||||
proto: 'http',
|
return app.get('rootUrl') || app.get('url');
|
||||||
host: 'localhost',
|
}
|
||||||
port: 3000,
|
|
||||||
from: 'nocontestar@verdnatura.es'
|
function getFrom() {
|
||||||
// app.dataSources.email.settings.transports[0].auth.user
|
return app.dataSources.email.settings.transports[0].auth.from;
|
||||||
};
|
}
|
||||||
|
|
||||||
|
class ValidationError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
this.name = 'ValidationError';
|
||||||
|
this.statusCode = 422;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function (Self) {
|
module.exports = function (Self) {
|
||||||
const hostBase = `${config.proto}://${config.host}`
|
|
||||||
const urlBase = `${hostBase}:8080`;
|
|
||||||
const apiBase = `${hostBase}:3000/api`;
|
|
||||||
|
|
||||||
Self.afterRemote('create', async function(ctx, instance) {
|
Self.afterRemote('create', async function(ctx, instance) {
|
||||||
|
const url = new URL(getUrl());
|
||||||
const options = {
|
const options = {
|
||||||
type: 'email',
|
type: 'email',
|
||||||
to: instance.email,
|
to: instance.email,
|
||||||
from: config.from,
|
from: getFrom(),
|
||||||
subject: 'Thanks for registering',
|
subject: 'Thanks for registering',
|
||||||
template: path.resolve(__dirname, '../../views/verify.ejs'),
|
template: path.resolve(__dirname, '../../views/verify.ejs'),
|
||||||
redirect: `${urlBase}/#/login?emailConfirmed`,
|
redirect: `${getUrl()}#/login/${instance.email}?emailConfirmed`,
|
||||||
|
host: url.hostname,
|
||||||
|
port: url.port,
|
||||||
|
protocol: url.protocol.split(':')[0],
|
||||||
user: Self
|
user: Self
|
||||||
};
|
};
|
||||||
const res = await instance.verify(options);
|
instance.verify(options)
|
||||||
console.log('> verification email sent:', res);
|
.then(res => console.log('> Verification email sent:', res));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Self.validatePassword = function(password) {
|
||||||
|
if (!password) {
|
||||||
|
throw new ValidationError('passwordEmpty');
|
||||||
|
}
|
||||||
|
|
||||||
|
const pattern = new RegExp('(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.{6,})')
|
||||||
|
if (!pattern.test(password)) {
|
||||||
|
throw new ValidationError('passwordRequeriments');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
Self.on('resetPasswordRequest', async function(info) {
|
Self.on('resetPasswordRequest', async function(info) {
|
||||||
const renderer = loopback.template(path.resolve(__dirname, '../../views/reset-password.ejs'));
|
const renderer = loopback.template(path.resolve(__dirname, '../../views/reset-password.ejs'));
|
||||||
const html = renderer({
|
const html = renderer({
|
||||||
url: `${urlBase}/#/reset-password?access_token=${info.accessToken.id}`
|
url: `${getUrl()}#/reset-password?access_token=${info.accessToken.id}`
|
||||||
});
|
});
|
||||||
|
|
||||||
await app.models.Email.send({
|
await app.models.Email.send({
|
||||||
to: info.email,
|
to: info.email,
|
||||||
from: config.from,
|
from: getFrom(),
|
||||||
subject: 'Password reset',
|
subject: 'Password reset',
|
||||||
html
|
html
|
||||||
});
|
});
|
||||||
console.log('> sending password reset email to:', info.email);
|
console.log('> Sending password reset email to:', info.email);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,15 @@
|
||||||
{
|
{
|
||||||
"name": "user",
|
"name": "user",
|
||||||
"base": "User",
|
"base": "User",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.user"
|
||||||
|
}
|
||||||
|
},
|
||||||
"idInjection": true,
|
"idInjection": true,
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"restrictResetPasswordTokenScope": true,
|
"restrictResetPasswordTokenScope": true,
|
||||||
"emailVerificationRequired": true,
|
"emailVerificationRequired": false,
|
||||||
"validations": [],
|
"validations": [],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"acls": [
|
"acls": [
|
||||||
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
|
||||||
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
|
|
||||||
|
module.exports = function(Self) {
|
||||||
|
Self.ParameterizedSQL = ParameterizedSQL;
|
||||||
|
|
||||||
|
Object.assign(Self, {
|
||||||
|
setup() {
|
||||||
|
Self.super_.setup.call(this);
|
||||||
|
},
|
||||||
|
|
||||||
|
rewriteDbError(replaceErrFunc) {
|
||||||
|
function replaceErr(err, replaceErrFunc) {
|
||||||
|
if (Array.isArray(err)) {
|
||||||
|
let errs = [];
|
||||||
|
for (let e of err)
|
||||||
|
errs.push(replaceErrFunc(e));
|
||||||
|
return errs;
|
||||||
|
}
|
||||||
|
return replaceErrFunc(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rewriteMethod(methodName) {
|
||||||
|
const realMethod = this[methodName];
|
||||||
|
return async(data, options, cb) => {
|
||||||
|
if (options instanceof Function) {
|
||||||
|
cb = options;
|
||||||
|
options = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const result = await realMethod.call(this, data, options);
|
||||||
|
|
||||||
|
if (cb) cb(null, result);
|
||||||
|
else return result;
|
||||||
|
} catch (err) {
|
||||||
|
let myErr = replaceErr(err, replaceErrFunc);
|
||||||
|
if (cb) cb(myErr);
|
||||||
|
else
|
||||||
|
throw myErr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
this.once('attached', () => {
|
||||||
|
this.remove =
|
||||||
|
this.deleteAll =
|
||||||
|
this.destroyAll = rewriteMethod.call(this, 'remove');
|
||||||
|
this.upsert = rewriteMethod.call(this, 'upsert');
|
||||||
|
this.create = rewriteMethod.call(this, 'create');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
rawSql(query, params, options, cb) {
|
||||||
|
return this.dataSource.connector.executeP(query, params, options, cb);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"name": "VnModel",
|
||||||
|
"base": "PersistedModel",
|
||||||
|
"validateUpsert": true
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
module.exports = function (Self) {
|
||||||
|
Self.remoteMethod('getEventsForAddress', {
|
||||||
|
description: 'Returns delivery days for a postcode',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'geoFk',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The geo id'
|
||||||
|
}, {
|
||||||
|
arg: 'agencyModeFk',
|
||||||
|
type: 'Number',
|
||||||
|
description: 'The agency mode id'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: 'Object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/getEvents`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getEvents = async(geoFk, agencyModeFk) => {
|
||||||
|
let [events, exclusions] = await Self.rawSql(
|
||||||
|
`CALL zone_getEvents(?, ?)`,
|
||||||
|
[geoFk, agencyModeFk]
|
||||||
|
);
|
||||||
|
return {events, exclusions};
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"name": "Zone",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "zone"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
{
|
||||||
|
"login failed": "Login failed",
|
||||||
|
"is blank": "Cannot be blank",
|
||||||
|
"is invalid": "Invalid value",
|
||||||
|
"can't be blank": "Field cannot be blank",
|
||||||
|
"invalidData": "Invalid data",
|
||||||
|
"invalidEmail": "Invalid email",
|
||||||
|
"passwordEmpty": "Password cannot be empty",
|
||||||
|
"passwordRequeriments": "Password doesn't meet requirements",
|
||||||
|
"notUniqueEmail": "User already exists"
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
{
|
||||||
|
"login failed": "Usuario o contraseña incorrectos",
|
||||||
|
"is blank": "No puede estar vacío",
|
||||||
|
"is invalid": "Valor inválido",
|
||||||
|
"can't be blank": "El campo no puede estar vacío",
|
||||||
|
"invalidData": "Los datos son inválidos",
|
||||||
|
"invalidEmail": "Correo inválido",
|
||||||
|
"passwordEmpty": "La contraseña no puede estar vacía",
|
||||||
|
"passwordRequeriments": "La contraseña no cumple los requisitos",
|
||||||
|
"notUniqueEmail": "El usuario ya existe",
|
||||||
|
"could not find a model with id checkout": "could not find a model with id checkout",
|
||||||
|
"Unknown \"Order\" id \"checkout\".": "Unknown \"Order\" id \"checkout\".",
|
||||||
|
"There is no method to handle GET /Client/1437/addresses": "There is no method to handle GET /Client/1437/addresses",
|
||||||
|
"could not find a model with id configure": "could not find a model with id configure",
|
||||||
|
"Unknown \"Order\" id \"configure\".": "Unknown \"Order\" id \"configure\".",
|
||||||
|
"could not find a model with id undefined": "could not find a model with id undefined"
|
||||||
|
}
|
|
@ -1839,6 +1839,26 @@
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz",
|
||||||
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
|
"integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw=="
|
||||||
},
|
},
|
||||||
|
"i18n": {
|
||||||
|
"version": "0.9.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/i18n/-/i18n-0.9.1.tgz",
|
||||||
|
"integrity": "sha512-ERo9WloOP2inRsJzAlzn4JDm3jvX7FW1+KB/JGXTzUVzi9Bsf4LNLXUQTMgM/aze4LNW/kvmxQX6bzg5UzqMJw==",
|
||||||
|
"requires": {
|
||||||
|
"debug": "*",
|
||||||
|
"make-plural": "^6.2.1",
|
||||||
|
"math-interval-parser": "^2.0.1",
|
||||||
|
"messageformat": "^2.3.0",
|
||||||
|
"mustache": "^4.0.1",
|
||||||
|
"sprintf-js": "^1.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"sprintf-js": {
|
||||||
|
"version": "1.1.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.2.tgz",
|
||||||
|
"integrity": "sha512-VE0SOVEHCk7Qc8ulkWw3ntAzXuqf7S2lvwQaDLRnUeIEaKNQJzV6BwmLKhOqT61aGhfUMrXeaBk+oDGCzvhcug=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"iconv-lite": {
|
"iconv-lite": {
|
||||||
"version": "0.4.24",
|
"version": "0.4.24",
|
||||||
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
|
||||||
|
@ -2715,6 +2735,11 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"make-plural": {
|
||||||
|
"version": "6.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-6.2.1.tgz",
|
||||||
|
"integrity": "sha512-AmkruwJ9EjvyTv6AM8MBMK3TAeOJvhgTv5YQXzF0EP2qawhpvMjDpHvsdOIIT0Vn+BB0+IogmYZ1z+Ulm/m0Fg=="
|
||||||
|
},
|
||||||
"map-age-cleaner": {
|
"map-age-cleaner": {
|
||||||
"version": "0.1.3",
|
"version": "0.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
|
||||||
|
@ -2723,6 +2748,11 @@
|
||||||
"p-defer": "^1.0.0"
|
"p-defer": "^1.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"math-interval-parser": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/math-interval-parser/-/math-interval-parser-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-VmlAmb0UJwlvMyx8iPhXUDnVW1F9IrGEd9CIOmv+XL8AErCUUuozoDMrgImvnYt2A+53qVX/tPW6YJurMKYsvA=="
|
||||||
|
},
|
||||||
"md5": {
|
"md5": {
|
||||||
"version": "2.2.1",
|
"version": "2.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
|
||||||
|
@ -2758,6 +2788,42 @@
|
||||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w=="
|
||||||
},
|
},
|
||||||
|
"messageformat": {
|
||||||
|
"version": "2.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/messageformat/-/messageformat-2.3.0.tgz",
|
||||||
|
"integrity": "sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==",
|
||||||
|
"requires": {
|
||||||
|
"make-plural": "^4.3.0",
|
||||||
|
"messageformat-formatters": "^2.0.1",
|
||||||
|
"messageformat-parser": "^4.1.2"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"make-plural": {
|
||||||
|
"version": "4.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/make-plural/-/make-plural-4.3.0.tgz",
|
||||||
|
"integrity": "sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==",
|
||||||
|
"requires": {
|
||||||
|
"minimist": "^1.2.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"minimist": {
|
||||||
|
"version": "1.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
|
||||||
|
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"messageformat-formatters": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/messageformat-formatters/-/messageformat-formatters-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg=="
|
||||||
|
},
|
||||||
|
"messageformat-parser": {
|
||||||
|
"version": "4.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/messageformat-parser/-/messageformat-parser-4.1.3.tgz",
|
||||||
|
"integrity": "sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg=="
|
||||||
|
},
|
||||||
"methods": {
|
"methods": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||||
|
@ -2976,6 +3042,11 @@
|
||||||
"safe-buffer": "^5.1.2"
|
"safe-buffer": "^5.1.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"mustache": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/mustache/-/mustache-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA=="
|
||||||
|
},
|
||||||
"mute-stream": {
|
"mute-stream": {
|
||||||
"version": "0.0.5",
|
"version": "0.0.5",
|
||||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "eslint .",
|
|
||||||
"start": "node .",
|
"start": "node .",
|
||||||
"posttest": "npm run lint && npm audit"
|
"posttest": "npm run lint && npm audit"
|
||||||
},
|
},
|
||||||
|
@ -15,6 +14,7 @@
|
||||||
"cors": "^2.5.2",
|
"cors": "^2.5.2",
|
||||||
"fs-extra": "^8.1.0",
|
"fs-extra": "^8.1.0",
|
||||||
"helmet": "^3.22.0",
|
"helmet": "^3.22.0",
|
||||||
|
"i18n": "^0.9.1",
|
||||||
"loopback": "^3.27.0",
|
"loopback": "^3.27.0",
|
||||||
"loopback-boot": "^2.28.0",
|
"loopback-boot": "^2.28.0",
|
||||||
"loopback-component-explorer": "^6.5.1",
|
"loopback-component-explorer": "^6.5.1",
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
|
||||||
|
module.exports = function enableAuthentication(server) {
|
||||||
|
const connector = server.dataSources.vn.connector
|
||||||
|
|
||||||
|
connector.executeAsync = function(query, params, options = {}, cb) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.execute(query, params, options, (error, response) => {
|
||||||
|
if (cb)
|
||||||
|
cb(error, response);
|
||||||
|
if (error)
|
||||||
|
reject(error);
|
||||||
|
else
|
||||||
|
resolve(response);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"rootUrl": "http://localhost:8080"
|
||||||
|
}
|
|
@ -43,6 +43,11 @@
|
||||||
"loopback#urlNotFound": {}
|
"loopback#urlNotFound": {}
|
||||||
},
|
},
|
||||||
"final:after": {
|
"final:after": {
|
||||||
"strong-error-handler": {}
|
"./middleware/error-handler": {},
|
||||||
|
"strong-error-handler": {
|
||||||
|
"params": {
|
||||||
|
"log": false
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
|
||||||
|
module.exports = function() {
|
||||||
|
return function(err, req, res, next) {
|
||||||
|
const statusCode = err.statusCode;
|
||||||
|
|
||||||
|
switch(statusCode) {
|
||||||
|
case 422: // Validation error
|
||||||
|
if (err.details) {
|
||||||
|
let messages = err.details.messages;
|
||||||
|
for (let message in messages) {
|
||||||
|
let texts = messages[message];
|
||||||
|
for (let i = 0; i < texts.length; i++) {
|
||||||
|
if (!texts[i]) continue;
|
||||||
|
texts[i] = req.__(texts[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err.message = req.__('invalidData');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
if (statusCode >= 400 && statusCode < 500) {
|
||||||
|
err.message = req.__(err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
next(err);
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,182 +1,181 @@
|
||||||
{
|
{
|
||||||
"_meta": {
|
"_meta": {
|
||||||
"sources": [
|
"sources": [
|
||||||
"loopback/common/models",
|
"loopback/common/models",
|
||||||
"loopback/server/models",
|
"loopback/server/models",
|
||||||
"../common/models",
|
"../common/models",
|
||||||
"./models"
|
"./models"
|
||||||
],
|
],
|
||||||
"mixins": [
|
"mixins": [
|
||||||
"loopback/common/mixins",
|
"loopback/common/mixins",
|
||||||
"loopback/server/mixins",
|
"loopback/server/mixins",
|
||||||
"../common/mixins",
|
"../common/mixins",
|
||||||
"./mixins"
|
"./mixins"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"AccessToken": {
|
"AccessToken": {
|
||||||
"dataSource": "vn",
|
"dataSource": "vn",
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
"table": "salix.AccessToken"
|
"table": "salix.AccessToken"
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
|
||||||
"ACL": {
|
|
||||||
"dataSource": "vn",
|
|
||||||
"options": {
|
|
||||||
"mysql": {
|
|
||||||
"table": "salix.ACL"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"Role": {
|
|
||||||
"dataSource": "vn",
|
|
||||||
"options": {
|
|
||||||
"mysql": {
|
|
||||||
"table": "salix.Role"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"RoleMapping": {
|
|
||||||
"dataSource": "vn",
|
|
||||||
"options": {
|
|
||||||
"mysql": {
|
|
||||||
"table": "salix.RoleMapping"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"User": {
|
|
||||||
"dataSource": "vn",
|
|
||||||
"public": false
|
|
||||||
},
|
|
||||||
"user": {
|
|
||||||
"dataSource": "db",
|
|
||||||
"public": true,
|
|
||||||
"options": {
|
|
||||||
"mysql": {
|
|
||||||
"table": "salix.user"
|
|
||||||
},
|
},
|
||||||
"emailVerificationRequired": true
|
"relations": {
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "user",
|
||||||
|
"foreignKey": "userId"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ACL": {
|
||||||
|
"dataSource": "vn",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.ACL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Role": {
|
||||||
|
"dataSource": "vn",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.Role"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"RoleMapping": {
|
||||||
|
"dataSource": "vn",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "salix.RoleMapping"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"user": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Email": {
|
||||||
|
"dataSource": "email"
|
||||||
|
},
|
||||||
|
"Account": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Address": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"AgencyMode": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"AlertLevel": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Buy": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Client": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Container": {
|
||||||
|
"dataSource": "storage"
|
||||||
|
},
|
||||||
|
"Country": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"DeliveryMethod": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Inbound": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Image": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ImageCollection": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ImageCollectionSize": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ItemCategory": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Item": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ItemTag": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"ItemType": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Language": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Link": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"MainAccountBank": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"New": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"NewTag": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"OrderRow": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"OrderTicket": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Order": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Province": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Sale": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"State": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Tag": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Ticket": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"TicketState": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"TicketTracking": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"UserPassword": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"UserSession": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Visit": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"VisitAccess": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"VisitAgent": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"VisitUser": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Warehouse": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
|
"Zone": {
|
||||||
|
"dataSource": "vn"
|
||||||
}
|
}
|
||||||
},
|
|
||||||
"Email": {
|
|
||||||
"dataSource": "email"
|
|
||||||
},
|
|
||||||
"Account": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Address": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"AgencyMode": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"AlertLevel": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Buy": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Client": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Container": {
|
|
||||||
"dataSource": "storage"
|
|
||||||
},
|
|
||||||
"Country": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"DeliveryMethod": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Inbound": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Image": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"ImageCollection": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"ImageCollectionSize": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"ItemCategory": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Item": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"ItemTag": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"ItemType": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Language": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Link": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"MainAccountBank": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"New": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"NewTag": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"OrderRow": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"OrderTicket": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Order": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Province": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Sale": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"State": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Tag": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Ticket": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"TicketState": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"TicketTracking": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"UserPassword": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"UserSession": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Visit": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"VisitAccess": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"VisitAgent": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"VisitUser": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
},
|
|
||||||
"Warehouse": {
|
|
||||||
"dataSource": "vn"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,19 +5,23 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
var loopback = require('loopback');
|
const loopback = require('loopback');
|
||||||
var boot = require('loopback-boot');
|
const boot = require('loopback-boot');
|
||||||
|
const i18n = require('i18n');
|
||||||
|
|
||||||
var app = module.exports = loopback();
|
const app = module.exports = loopback();
|
||||||
|
|
||||||
|
i18n.configure({directory: `${__dirname}/../locales`});
|
||||||
|
app.use(i18n.init);
|
||||||
|
|
||||||
app.start = function() {
|
app.start = function() {
|
||||||
// start the web server
|
// start the web server
|
||||||
return app.listen(function() {
|
return app.listen(function() {
|
||||||
app.emit('started');
|
app.emit('started');
|
||||||
var baseUrl = app.get('url').replace(/\/$/, '');
|
const baseUrl = app.get('url').replace(/\/$/, '');
|
||||||
console.log('Web server listening at: %s', baseUrl);
|
console.log('Web server listening at: %s', baseUrl);
|
||||||
if (app.get('loopback-component-explorer')) {
|
if (app.get('loopback-component-explorer')) {
|
||||||
var explorerPath = app.get('loopback-component-explorer').mountPath;
|
const explorerPath = app.get('loopback-component-explorer').mountPath;
|
||||||
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
|
console.log('Browse your REST API at %s%s', baseUrl, explorerPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -65,7 +65,11 @@ module.exports = function (ctx) {
|
||||||
'QSelect',
|
'QSelect',
|
||||||
'QSeparator',
|
'QSeparator',
|
||||||
'QSlideTransition',
|
'QSlideTransition',
|
||||||
|
'QSpace',
|
||||||
'QSpinner',
|
'QSpinner',
|
||||||
|
'QStepper',
|
||||||
|
'QStep',
|
||||||
|
'QStepperNavigation',
|
||||||
'QTab',
|
'QTab',
|
||||||
'QTabs',
|
'QTabs',
|
||||||
'QTabPanel',
|
'QTabPanel',
|
||||||
|
|
|
@ -7,9 +7,14 @@ export default async ({ app, Vue }) => {
|
||||||
|
|
||||||
axios.interceptors.request.use(function (config) {
|
axios.interceptors.request.use(function (config) {
|
||||||
const $state = Vue.prototype.$state
|
const $state = Vue.prototype.$state
|
||||||
|
|
||||||
if ($state.user.loggedIn) {
|
if ($state.user.loggedIn) {
|
||||||
config.headers.Authorization = $state.user.token
|
config.headers.Authorization = $state.user.token
|
||||||
}
|
}
|
||||||
|
if (config.filter) {
|
||||||
|
if (!config.params) config.params = {}
|
||||||
|
config.params.filter = config.filter
|
||||||
|
}
|
||||||
return config
|
return config
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import Toolbar from './Toolbar'
|
import Toolbar from './Toolbar'
|
||||||
|
import Portal from './Portal'
|
||||||
import LbScroll from './LbScroll'
|
import LbScroll from './LbScroll'
|
||||||
import FullImage from './FullImage'
|
import FullImage from './FullImage'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
Toolbar,
|
Toolbar,
|
||||||
|
Portal,
|
||||||
LbScroll,
|
LbScroll,
|
||||||
FullImage
|
FullImage
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<slot></slot>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: 'VnPortal',
|
||||||
|
props: {
|
||||||
|
place: String
|
||||||
|
},
|
||||||
|
mounted () {
|
||||||
|
this.placeEl = this.$state.layout.$refs[this.place]
|
||||||
|
this.placeEl.appendChild(this.$el)
|
||||||
|
},
|
||||||
|
beforeDestroy () {
|
||||||
|
this.placeEl.removeChild(this.$el)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -75,6 +75,10 @@ export default {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// errors
|
||||||
|
internalServerError: 'Internal server error',
|
||||||
|
somethingWentWrong: 'Something went wrong',
|
||||||
|
|
||||||
// Layout
|
// Layout
|
||||||
login: 'Login',
|
login: 'Login',
|
||||||
logout: 'Logout',
|
logout: 'Logout',
|
||||||
|
@ -88,12 +92,13 @@ export default {
|
||||||
notRememberPassword: 'I don\'t remember my password',
|
notRememberPassword: 'I don\'t remember my password',
|
||||||
inputEmail: 'Input email',
|
inputEmail: 'Input email',
|
||||||
inputPassword: 'Input password',
|
inputPassword: 'Input password',
|
||||||
|
emailConfirmedSuccessfully: 'E-mail confirmed succesfully',
|
||||||
|
|
||||||
// register
|
// register
|
||||||
register: 'Register',
|
register: 'Register',
|
||||||
fillData: 'Fill the data',
|
fillData: 'Fill the data',
|
||||||
notYetUser: 'You are not yet a user?',
|
notYetUser: 'You are not yet a user?',
|
||||||
userRegistered: 'User registered successfully',
|
userRegistered: 'User registered successfully, we\'ve sent you an e-mail to verify your account',
|
||||||
repeatPasswordError: 'Passwords doesn\'t match',
|
repeatPasswordError: 'Passwords doesn\'t match',
|
||||||
|
|
||||||
// recover
|
// recover
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
export default {
|
export default {
|
||||||
// global
|
// Global
|
||||||
search: 'Buscar',
|
search: 'Buscar',
|
||||||
accept: 'Aceptar',
|
accept: 'Aceptar',
|
||||||
cancel: 'Cancelar',
|
cancel: 'Cancelar',
|
||||||
|
@ -75,12 +75,15 @@ export default {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
// layout
|
// Errors
|
||||||
|
internalServerError: 'Error interno del servidor',
|
||||||
|
somethingWentWrong: 'Algo salió mal',
|
||||||
|
|
||||||
|
// MainLayout
|
||||||
login: 'Iniciar sesión',
|
login: 'Iniciar sesión',
|
||||||
logout: 'Cerrar sesión',
|
logout: 'Cerrar sesión',
|
||||||
visitor: 'Visitante',
|
|
||||||
|
|
||||||
// login
|
// Login
|
||||||
enter: 'Entrar',
|
enter: 'Entrar',
|
||||||
email: 'Correo electrónico',
|
email: 'Correo electrónico',
|
||||||
password: 'Contraseña',
|
password: 'Contraseña',
|
||||||
|
@ -88,25 +91,26 @@ export default {
|
||||||
notRememberPassword: 'No recuerdo mi contraseña',
|
notRememberPassword: 'No recuerdo mi contraseña',
|
||||||
inputEmail: 'Introduce el correo electrónico',
|
inputEmail: 'Introduce el correo electrónico',
|
||||||
inputPassword: 'Introduce la contraseña',
|
inputPassword: 'Introduce la contraseña',
|
||||||
|
emailConfirmedSuccessfully: 'Correo verificado correctamente',
|
||||||
|
|
||||||
// register
|
// Register
|
||||||
register: 'Registrarse',
|
register: 'Registrarme',
|
||||||
fillData: 'Rellena los datos',
|
fillData: 'Rellena los datos',
|
||||||
notYetUser: '¿Todavía no eres usuario?',
|
notYetUser: '¿Todavía no eres usuario?',
|
||||||
userRegistered: 'Usuario registrado correctamente',
|
userRegistered: 'Usuario registrado correctamente, te hemos enviado un correo para verificar tu dirección',
|
||||||
repeatPasswordError: 'Las contraseñas no coinciden',
|
repeatPasswordError: 'Las contraseñas no coinciden',
|
||||||
|
|
||||||
// recover
|
// Recover
|
||||||
rememberPassword: 'Recordar contraseña',
|
rememberPassword: 'Recordar contraseña',
|
||||||
dontWorry: '¡No te preocupes!',
|
dontWorry: '¡No te preocupes!',
|
||||||
weSendEmail: 'Te enviaremos un correo para restablecer tu contraseña',
|
weSendEmail: 'Te enviaremos un correo para restablecer tu contraseña',
|
||||||
weHaveSentEmailToRecover: 'Te hemos enviado un correo donde podrás recuperar tu contraseña',
|
weHaveSentEmailToRecover: 'Te hemos enviado un correo donde podrás recuperar tu contraseña',
|
||||||
|
|
||||||
// reset
|
// Reset
|
||||||
resetPassword: 'Restaurar contraseña',
|
resetPassword: 'Restaurar contraseña',
|
||||||
passwordResetSuccessfully: 'Contraseña modificada correctamente',
|
passwordResetSuccessfully: 'Contraseña modificada correctamente',
|
||||||
|
|
||||||
// menu
|
// Menu
|
||||||
home: 'Inicio',
|
home: 'Inicio',
|
||||||
catalog: 'Catálogo',
|
catalog: 'Catálogo',
|
||||||
orders: 'Pedidos',
|
orders: 'Pedidos',
|
||||||
|
@ -128,7 +132,7 @@ export default {
|
||||||
addresses: 'Direcciones',
|
addresses: 'Direcciones',
|
||||||
addressEdit: 'Editar dirección',
|
addressEdit: 'Editar dirección',
|
||||||
|
|
||||||
// home
|
// Home
|
||||||
recentNews: 'Noticias recientes',
|
recentNews: 'Noticias recientes',
|
||||||
startOrder: 'Empezar pedido',
|
startOrder: 'Empezar pedido',
|
||||||
|
|
||||||
|
@ -147,7 +151,7 @@ export default {
|
||||||
n1InPrice: 'Nº1 en precio',
|
n1InPrice: 'Nº1 en precio',
|
||||||
ourBigVolumeAllows: 'Nuestro gran volumen nos permite ofrecerte los mejores precios',
|
ourBigVolumeAllows: 'Nuestro gran volumen nos permite ofrecerte los mejores precios',
|
||||||
|
|
||||||
// catalog
|
// Catalog
|
||||||
more: 'Más',
|
more: 'Más',
|
||||||
noItemsFound: 'No se han encontrado artículos',
|
noItemsFound: 'No se han encontrado artículos',
|
||||||
pleaseSetFilter: 'Por favor, establece un filtro usando el menú de la derecha',
|
pleaseSetFilter: 'Por favor, establece un filtro usando el menú de la derecha',
|
||||||
|
@ -170,19 +174,19 @@ export default {
|
||||||
siceAsc: 'Medida ascendente',
|
siceAsc: 'Medida ascendente',
|
||||||
sizeDesc: 'Medida descendente',
|
sizeDesc: 'Medida descendente',
|
||||||
|
|
||||||
// orders
|
// Orders
|
||||||
pending: 'Pendientes',
|
pending: 'Pendientes',
|
||||||
confirmed: 'Confirmados',
|
confirmed: 'Confirmados',
|
||||||
|
|
||||||
// orders/pending
|
// Pending
|
||||||
pendingConfirmtion: 'Pendientes de confirmar',
|
pendingConfirmtion: 'Pendientes de confirmar',
|
||||||
noOrdersFound: 'No se han encontrado pedidos',
|
noOrdersFound: 'No se han encontrado pedidos',
|
||||||
|
|
||||||
// orders/confirmed
|
// Confirmed
|
||||||
ordersMadeAt: 'Pedidos realizados en',
|
ordersMadeAt: 'Pedidos realizados en',
|
||||||
packages: '{n} bultos',
|
packages: '{n} bultos',
|
||||||
|
|
||||||
// order
|
// Order
|
||||||
total: 'Total',
|
total: 'Total',
|
||||||
confirm: 'Confirmar',
|
confirm: 'Confirmar',
|
||||||
delivery: 'Fecha de entrega',
|
delivery: 'Fecha de entrega',
|
||||||
|
@ -191,7 +195,24 @@ export default {
|
||||||
warehouse: 'Almacén',
|
warehouse: 'Almacén',
|
||||||
configure: 'Configurar',
|
configure: 'Configurar',
|
||||||
|
|
||||||
// order/checkout
|
// OrderRows
|
||||||
|
|
||||||
|
rows: 'Lineas',
|
||||||
|
|
||||||
|
// OrderConfigure
|
||||||
|
|
||||||
|
deliveryMethod: 'Método de entrega',
|
||||||
|
pickupDate: 'Fecha de recogida',
|
||||||
|
deliveryAgency: 'Agencia de transporte',
|
||||||
|
pickupStore: 'Almacén de recogida',
|
||||||
|
deliveryAddress: 'Dirección de entrega',
|
||||||
|
optionalAddress: 'Dirección (Opcional)',
|
||||||
|
optionalAddressInfo: 'Para pedidos de recogida la dirección se utiliza como referencia cuando se dispone de varias direcciones y evitar que pedidos de tiendas diferentes se fusionen',
|
||||||
|
addressHint: 'Las fechas de entrega varian en función de la dirección',
|
||||||
|
storePickup: 'Recogida en almacén',
|
||||||
|
homeDelivery: 'Entrega a domicilio',
|
||||||
|
|
||||||
|
// OrderCheckout
|
||||||
checkout: 'Finalizar pedido',
|
checkout: 'Finalizar pedido',
|
||||||
orderSummary: 'Resumen del pedido',
|
orderSummary: 'Resumen del pedido',
|
||||||
accountsSummary: 'Resumen de cuentas',
|
accountsSummary: 'Resumen de cuentas',
|
||||||
|
@ -217,13 +238,13 @@ export default {
|
||||||
youExceededCredit: 'Has excedido tu crédito, por favor realiza el pago para que podamos preparar tu pedido.',
|
youExceededCredit: 'Has excedido tu crédito, por favor realiza el pago para que podamos preparar tu pedido.',
|
||||||
notes: 'Notas',
|
notes: 'Notas',
|
||||||
|
|
||||||
// conditions
|
// Conditions
|
||||||
conditionsDesc: 'Te aseguramos que el pedido llegara a tu casa/tienda en menos de 24/48 horas (Dependiendo de en que zona te encuentres).',
|
conditionsDesc: 'Te aseguramos que el pedido llegara a tu casa/tienda en menos de 24/48 horas (Dependiendo de en que zona te encuentres).',
|
||||||
|
|
||||||
// about
|
// About
|
||||||
aboutDesc: 'Verdnatura te ofrece todos los servicios que necesita tu floristería.',
|
aboutDesc: 'Verdnatura te ofrece todos los servicios que necesita tu floristería.',
|
||||||
|
|
||||||
// connections
|
// Connections
|
||||||
nConnections: '{0} connexiones',
|
nConnections: '{0} connexiones',
|
||||||
refreshRate: 'Frecuencia de actualización',
|
refreshRate: 'Frecuencia de actualización',
|
||||||
lastAction: 'Última acción',
|
lastAction: 'Última acción',
|
||||||
|
@ -231,27 +252,27 @@ export default {
|
||||||
nSeconds: '{0} segundos',
|
nSeconds: '{0} segundos',
|
||||||
dontRefresh: 'No refrescar',
|
dontRefresh: 'No refrescar',
|
||||||
|
|
||||||
// accessLog
|
// AccessLog
|
||||||
accessLog: 'Registro de accesos',
|
accessLog: 'Registro de accesos',
|
||||||
|
|
||||||
// visits
|
// Visits
|
||||||
visitsCount: '{0} visitas, {1} nuevas',
|
visitsCount: '{0} visitas, {1} nuevas',
|
||||||
|
|
||||||
// new
|
// New
|
||||||
title: 'Título',
|
title: 'Título',
|
||||||
image: 'Imagen',
|
image: 'Imagen',
|
||||||
tag: 'Etiqueta',
|
tag: 'Etiqueta',
|
||||||
priority: 'Prioridad',
|
priority: 'Prioridad',
|
||||||
text: 'Texto',
|
text: 'Texto',
|
||||||
|
|
||||||
// images
|
// Images
|
||||||
collection: 'Colección',
|
collection: 'Colección',
|
||||||
updateMatchingId: 'Actualizar ítems con id coincidente',
|
updateMatchingId: 'Actualizar ítems con id coincidente',
|
||||||
uploadAutomatically: 'Subir automáticamente',
|
uploadAutomatically: 'Subir automáticamente',
|
||||||
imagesUploadSuccess: 'Imágenes subidas correctamente',
|
imagesUploadSuccess: 'Imágenes subidas correctamente',
|
||||||
imagesUploadFailed: 'Algunas imágenes no se ha podido subir',
|
imagesUploadFailed: 'Algunas imágenes no se ha podido subir',
|
||||||
|
|
||||||
// user
|
// User
|
||||||
userName: 'Nombre de usuario',
|
userName: 'Nombre de usuario',
|
||||||
nickname: 'Nombre a mostrar',
|
nickname: 'Nombre a mostrar',
|
||||||
language: 'Idioma',
|
language: 'Idioma',
|
||||||
|
@ -270,14 +291,14 @@ export default {
|
||||||
passwordsDontMatch: 'Las contraseñas no coinciden',
|
passwordsDontMatch: 'Las contraseñas no coinciden',
|
||||||
passwordChanged: '¡Contraseña modificada correctamente!',
|
passwordChanged: '¡Contraseña modificada correctamente!',
|
||||||
|
|
||||||
// addresses
|
// Addresses
|
||||||
setAsDefault: 'Establecer como predeterminada',
|
setAsDefault: 'Establecer como predeterminada',
|
||||||
addressSetAsDefault: 'Dirección establecida como predeterminada',
|
addressSetAsDefault: 'Dirección establecida como predeterminada',
|
||||||
addressRemoved: 'Dirección eliminada',
|
addressRemoved: 'Dirección eliminada',
|
||||||
areYouSureDeleteAddress: '¿Seguro que quieres eliminar la dirección?',
|
areYouSureDeleteAddress: '¿Seguro que quieres eliminar la dirección?',
|
||||||
addressCreated: '¡Dirección creada correctamente!',
|
addressCreated: '¡Dirección creada correctamente!',
|
||||||
|
|
||||||
// address
|
// Address
|
||||||
consignatary: 'Consignatario',
|
consignatary: 'Consignatario',
|
||||||
street: 'Dirección',
|
street: 'Dirección',
|
||||||
city: 'City',
|
city: 'City',
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
<template>
|
<template>
|
||||||
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll bg-primary">
|
<q-layout id="bg" class="fullscreen row justify-center items-center layout-view scroll">
|
||||||
<q-card class="q-pa-md row items-center justify-center">
|
<q-card class="q-pa-md row items-center justify-center">
|
||||||
<transition name="slide-right">
|
<transition name="slide-right">
|
||||||
<router-view class="child-view"/>
|
<router-view class="child-view"/>
|
||||||
|
@ -9,6 +9,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="stylus" scoped>
|
<style lang="stylus" scoped>
|
||||||
|
#bg
|
||||||
|
background: repeating-linear-gradient(
|
||||||
|
45deg,
|
||||||
|
#f18d1a,
|
||||||
|
#f18d1a 20px,
|
||||||
|
$primary 20px,
|
||||||
|
$primary 40px
|
||||||
|
);
|
||||||
.q-card
|
.q-card
|
||||||
border-radius 0
|
border-radius 0
|
||||||
width 600px
|
width 600px
|
||||||
|
|
|
@ -18,13 +18,15 @@
|
||||||
{{$state.subtitle}}
|
{{$state.subtitle}}
|
||||||
</div>
|
</div>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
|
<div ref="top"/>
|
||||||
|
<q-space/>
|
||||||
|
<div ref="toolbar"/>
|
||||||
<q-btn flat
|
<q-btn flat
|
||||||
v-if="!$state.user.loggedIn"
|
v-if="!$state.user.loggedIn"
|
||||||
class="q-ml-md"
|
class="q-ml-md"
|
||||||
:label="$t('login')"
|
:label="$t('login')"
|
||||||
to="/login"
|
to="/login"
|
||||||
/>
|
/>
|
||||||
<div ref="toolbar"/>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
v-if="$state.useRightDrawer"
|
v-if="$state.useRightDrawer"
|
||||||
@click="$state.rightDrawerOpen = !$state.rightDrawerOpen"
|
@click="$state.rightDrawerOpen = !$state.rightDrawerOpen"
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
||||||
.then(res => (this.address = res.data))
|
.then(res => (this.address = res.data))
|
||||||
} else {
|
} else {
|
||||||
this.address = {
|
this.address = {
|
||||||
clientFk: this.$state.userId
|
clientFk: this.$state.user.id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
|
@ -95,7 +95,7 @@ export default {
|
||||||
'postalCode'
|
'postalCode'
|
||||||
],
|
],
|
||||||
where: {
|
where: {
|
||||||
clientFk: this.$state.userId,
|
clientFk: this.$state.user.id,
|
||||||
isActive: true
|
isActive: true
|
||||||
},
|
},
|
||||||
order: 'nickname'
|
order: 'nickname'
|
||||||
|
@ -106,14 +106,14 @@ export default {
|
||||||
filter = {
|
filter = {
|
||||||
fields: ['defaultAddressFk']
|
fields: ['defaultAddressFk']
|
||||||
}
|
}
|
||||||
this.$axios.get(`Clients/${this.$state.userId}`, { params: { filter } })
|
this.$axios.get(`Clients/${this.$state.user.id}`, { params: { filter } })
|
||||||
.then(res => (this.defaultAddress = res.data.defaultAddressFk))
|
.then(res => (this.defaultAddress = res.data.defaultAddressFk))
|
||||||
},
|
},
|
||||||
watch: {
|
watch: {
|
||||||
defaultAddress (value, oldValue) {
|
defaultAddress (value, oldValue) {
|
||||||
if (!oldValue) return
|
if (!oldValue) return
|
||||||
let data = { defaultAddressFk: value }
|
let data = { defaultAddressFk: value }
|
||||||
this.$axios.patch(`Clients/${this.$state.userId}`, data)
|
this.$axios.patch(`Clients/${this.$state.user.id}`, data)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
this.$q.notify(this.$t('addressSetAsDefault'))
|
this.$q.notify(this.$t('addressSetAsDefault'))
|
||||||
})
|
})
|
|
@ -132,7 +132,7 @@ export default {
|
||||||
'email'
|
'email'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
this.$axios.get(`Accounts/${this.$state.userId}`, { params: { filter } })
|
this.$axios.get(`Accounts/${this.$state.user.id}`, { params: { filter } })
|
||||||
.then(res => (this.user = res.data))
|
.then(res => (this.user = res.data))
|
||||||
|
|
||||||
filter = {
|
filter = {
|
||||||
|
@ -173,7 +173,7 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onSave () {
|
onSave () {
|
||||||
this.$axios.patch(`Accounts/${this.$state.userId}`, this.user)
|
this.$axios.patch(`Accounts/${this.$state.user.id}`, this.user)
|
||||||
.then(res => (this.$q.notify({
|
.then(res => (this.$q.notify({
|
||||||
message: this.$t('dataSaved'),
|
message: this.$t('dataSaved'),
|
||||||
icon: 'check',
|
icon: 'check',
|
|
@ -4,6 +4,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Admin'
|
name: 'AdminIndex'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -152,7 +152,7 @@ export default {
|
||||||
.then(res => (this.myNew = res.data))
|
.then(res => (this.myNew = res.data))
|
||||||
} else {
|
} else {
|
||||||
this.myNew = {
|
this.myNew = {
|
||||||
userFk: this.$state.userId,
|
userFk: this.$state.user.id,
|
||||||
tag: 'new',
|
tag: 'new',
|
||||||
priority: 1,
|
priority: 1,
|
||||||
text: ''
|
text: ''
|
|
@ -16,10 +16,12 @@
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:label="$t('email')"
|
:label="$t('email')"
|
||||||
:rules="[ val => !!val || $t('inputEmail')]"
|
:rules="[ val => !!val || $t('inputEmail')]"
|
||||||
|
autofocus
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<q-input
|
<q-input
|
||||||
v-model="password"
|
v-model="password"
|
||||||
|
ref="password"
|
||||||
:label="$t('password')"
|
:label="$t('password')"
|
||||||
:type="showPwd ? 'password' : 'text'"
|
:type="showPwd ? 'password' : 'text'"
|
||||||
:rules="[ val => !!val || $t('inputPassword')]"
|
:rules="[ val => !!val || $t('inputPassword')]"
|
||||||
|
@ -81,12 +83,30 @@ export default {
|
||||||
showPwd: true
|
showPwd: true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted () {
|
||||||
|
if (this.$route.query.emailConfirmed !== undefined) {
|
||||||
|
this.$q.notify({
|
||||||
|
message: this.$t('emailConfirmedSuccessfully'),
|
||||||
|
type: 'positive'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (this.$route.params.email) {
|
||||||
|
this.email = this.$route.params.email
|
||||||
|
this.$refs.password.focus()
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async onLogin () {
|
async onLogin () {
|
||||||
const params = {
|
const params = {
|
||||||
username: this.email,
|
|
||||||
password: this.password
|
password: this.password
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.email.indexOf('@') !== -1) {
|
||||||
|
params.email = this.email
|
||||||
|
} else {
|
||||||
|
params.username = this.email
|
||||||
|
}
|
||||||
|
|
||||||
const res = await this.$axios.post('users/login', params)
|
const res = await this.$axios.post('users/login', params)
|
||||||
localStorage.setItem('token', res.data.id)
|
localStorage.setItem('token', res.data.id)
|
||||||
Object.assign(this.$state.user, {
|
Object.assign(this.$state.user, {
|
|
@ -2,7 +2,7 @@
|
||||||
<div>
|
<div>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<q-icon
|
<q-icon
|
||||||
name="nature_people"
|
name="local_florist"
|
||||||
class="block q-mx-auto text-accent"
|
class="block q-mx-auto text-accent"
|
||||||
style="font-size: 120px;"
|
style="font-size: 120px;"
|
||||||
/>
|
/>
|
||||||
|
@ -16,6 +16,7 @@
|
||||||
<q-input
|
<q-input
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:label="$t('email')"
|
:label="$t('email')"
|
||||||
|
autofocus
|
||||||
hint=""
|
hint=""
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
|
@ -21,6 +21,7 @@
|
||||||
v-model="email"
|
v-model="email"
|
||||||
:label="$t('email')"
|
:label="$t('email')"
|
||||||
:rules="[ val => !!val || $t('inputEmail')]"
|
:rules="[ val => !!val || $t('inputEmail')]"
|
||||||
|
autofocus
|
||||||
filled
|
filled
|
||||||
/>
|
/>
|
||||||
<div>
|
<div>
|
|
@ -17,6 +17,7 @@
|
||||||
v-model="password"
|
v-model="password"
|
||||||
:label="$t('password')"
|
:label="$t('password')"
|
||||||
:type="showPwd ? 'password' : 'text'"
|
:type="showPwd ? 'password' : 'text'"
|
||||||
|
autofocus
|
||||||
hint=""
|
hint=""
|
||||||
filled>
|
filled>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div style="padding-bottom: 5em;">
|
<div style="padding-bottom: 5em;">
|
||||||
<toolbar>
|
<portal place="top">
|
||||||
<q-input
|
<q-input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
debounce="500"
|
debounce="500"
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</toolbar>
|
</portal>
|
||||||
<q-drawer
|
<q-drawer
|
||||||
v-model="$state.rightDrawerOpen"
|
v-model="$state.rightDrawerOpen"
|
||||||
side="right"
|
side="right"
|
||||||
|
@ -276,9 +276,9 @@
|
||||||
height 40px
|
height 40px
|
||||||
overflow hidden
|
overflow hidden
|
||||||
.category
|
.category
|
||||||
width 25%
|
width 33%
|
||||||
&.active
|
&.active
|
||||||
background: rgba(0, 0, 0, .08)
|
background rgba(0, 0, 0, .08)
|
||||||
.category-img
|
.category-img
|
||||||
height 3.5em
|
height 3.5em
|
||||||
.tags
|
.tags
|
|
@ -53,7 +53,7 @@
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Orders',
|
name: 'OrdersConfirmedIndex',
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
|
@ -79,7 +79,7 @@ export default {
|
||||||
|
|
||||||
let params = { filter: {
|
let params = { filter: {
|
||||||
where: {
|
where: {
|
||||||
clientFk: this.$state.userId,
|
clientFk: this.$state.user.id,
|
||||||
landed: { between: [start, end] }
|
landed: { between: [start, end] }
|
||||||
},
|
},
|
||||||
include: [
|
include: [
|
|
@ -9,6 +9,6 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Ticket'
|
name: 'OrdersConfirmedView'
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -12,7 +12,7 @@
|
||||||
<q-item
|
<q-item
|
||||||
v-for="order in orders"
|
v-for="order in orders"
|
||||||
:key="order.id"
|
:key="order.id"
|
||||||
:to="`/order/${order.id}/view`"
|
:to="`/order/${order.id}/`"
|
||||||
clickable
|
clickable
|
||||||
v-ripple>
|
v-ripple>
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
|
@ -40,24 +40,24 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
export default {
|
export default {
|
||||||
name: 'Orders',
|
name: 'OrdersPendingIndex',
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
||||||
orders: null
|
orders: null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted () {
|
async mounted () {
|
||||||
let filter = {
|
const filter = {
|
||||||
where: {
|
where: {
|
||||||
clientFk: this.$state.userId,
|
clientFk: this.$state.user.id,
|
||||||
isConfirmed: false
|
isConfirmed: false
|
||||||
},
|
},
|
||||||
include: 'address',
|
include: 'address',
|
||||||
order: 'created DESC',
|
order: 'created DESC',
|
||||||
limit: 20
|
limit: 20
|
||||||
}
|
}
|
||||||
this.$axios.get('Orders', { params: { filter } })
|
const res = await this.$axios.get('Orders', { params: { filter } })
|
||||||
.then(res => (this.orders = res.data))
|
this.orders = res.data
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
|
@ -150,7 +150,7 @@
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OrderCheckout',
|
name: 'OrdersPendingCheckout',
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
|
@ -0,0 +1,201 @@
|
||||||
|
<template>
|
||||||
|
<div class="vn-pp row justify-center">
|
||||||
|
<q-stepper
|
||||||
|
v-model="step"
|
||||||
|
vertical
|
||||||
|
color="primary"
|
||||||
|
animated
|
||||||
|
class="vn-w-lg">
|
||||||
|
<q-step
|
||||||
|
name="method"
|
||||||
|
:title="$t('deliveryMethod')"
|
||||||
|
icon="person_pin"
|
||||||
|
:done="step != 'method'">
|
||||||
|
<section>
|
||||||
|
<q-radio
|
||||||
|
v-model="method"
|
||||||
|
val="delivery"
|
||||||
|
:label="$t('homeDelivery')"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<section>
|
||||||
|
<q-radio
|
||||||
|
v-model="method"
|
||||||
|
val="pickup"
|
||||||
|
:label="$t('storePickup')"
|
||||||
|
/>
|
||||||
|
</section>
|
||||||
|
<q-stepper-navigation>
|
||||||
|
<q-btn
|
||||||
|
@click="step = 'when'"
|
||||||
|
color="primary"
|
||||||
|
label="Continue"
|
||||||
|
/>
|
||||||
|
</q-stepper-navigation>
|
||||||
|
</q-step>
|
||||||
|
<q-step
|
||||||
|
name="when"
|
||||||
|
:title="$t(isPickup ? 'pickupDate' : 'deliveryDate')"
|
||||||
|
icon="event"
|
||||||
|
:done="step == 'how'">
|
||||||
|
<div class="q-gutter-y-md">
|
||||||
|
<q-select
|
||||||
|
:label="$t(isPickup ? 'optionalAddress' : 'deliveryAddress')"
|
||||||
|
v-model="address"
|
||||||
|
:options="addresses"
|
||||||
|
option-label="nickname">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="info">
|
||||||
|
<q-tooltip>
|
||||||
|
<div style="max-width: 250px;">
|
||||||
|
{{ $t(isPickup ? 'optionalAddressInfo' : 'addressHint') }}
|
||||||
|
</div>
|
||||||
|
</q-tooltip>
|
||||||
|
</q-icon>
|
||||||
|
</template>
|
||||||
|
<template v-slot:option="scope">
|
||||||
|
<q-item
|
||||||
|
v-bind="scope.itemProps"
|
||||||
|
v-on="scope.itemEvents">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>{{ scope.opt.nickname }}</q-item-label>
|
||||||
|
<q-item-label caption>{{ scope.opt.street }}</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</template>
|
||||||
|
</q-select>
|
||||||
|
<q-date
|
||||||
|
v-model="date"
|
||||||
|
:options="datesFn"
|
||||||
|
minimal
|
||||||
|
flat
|
||||||
|
bordered
|
||||||
|
class="full-width"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<q-stepper-navigation>
|
||||||
|
<q-btn
|
||||||
|
label="Continue"
|
||||||
|
@click="step = 'how'"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
label="Back"
|
||||||
|
@click="step = 'method'"
|
||||||
|
color="primary"
|
||||||
|
class="q-ml-sm"
|
||||||
|
flat
|
||||||
|
/>
|
||||||
|
</q-stepper-navigation>
|
||||||
|
</q-step>
|
||||||
|
<q-step
|
||||||
|
name="how"
|
||||||
|
:title="$t(isPickup ? 'pickupStore' : 'deliveryAgency')"
|
||||||
|
:icon="isPickup ? 'store' : 'local_shipping'">
|
||||||
|
<q-select
|
||||||
|
:label="$t(isPickup ? 'warehouse' : 'agency')"
|
||||||
|
v-model="agency"
|
||||||
|
:options="agencies"
|
||||||
|
option-label="description"
|
||||||
|
option-value="id"
|
||||||
|
/>
|
||||||
|
<q-stepper-navigation>
|
||||||
|
<q-btn
|
||||||
|
label="Finish"
|
||||||
|
to="./"
|
||||||
|
color="primary"
|
||||||
|
/>
|
||||||
|
<q-btn
|
||||||
|
flat
|
||||||
|
label="Back"
|
||||||
|
@click="step = 'when'"
|
||||||
|
color="primary"
|
||||||
|
class="q-ml-sm"
|
||||||
|
/>
|
||||||
|
</q-stepper-navigation>
|
||||||
|
</q-step>
|
||||||
|
</q-stepper>
|
||||||
|
<q-page-sticky position="bottom-right" :offset="[18, 18]">
|
||||||
|
<q-btn
|
||||||
|
fab
|
||||||
|
icon="shopping_basket"
|
||||||
|
color="accent"
|
||||||
|
:title="$t('rows')"
|
||||||
|
to="./"
|
||||||
|
/>
|
||||||
|
</q-page-sticky>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style lang="stylus" scoped>
|
||||||
|
.border
|
||||||
|
border-top $layout-border
|
||||||
|
.method-desc
|
||||||
|
margin-left 3.2em
|
||||||
|
color $grey-8
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import Page from 'components/Page'
|
||||||
|
import { date } from 'quasar'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'OrderConfigure',
|
||||||
|
mixins: [Page],
|
||||||
|
data () {
|
||||||
|
return {
|
||||||
|
step: 'method',
|
||||||
|
method: 'delivery',
|
||||||
|
address: null,
|
||||||
|
addresses: null,
|
||||||
|
date: date.formatDate(new Date(), 'YYYY/MM/DD'),
|
||||||
|
agency: null,
|
||||||
|
agencies: null,
|
||||||
|
today: date.formatDate(new Date(), 'YYYY/MM/DD')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
isPickup () {
|
||||||
|
return this.method === 'pickup'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
async mounted () {
|
||||||
|
let res
|
||||||
|
let filter
|
||||||
|
let uid = this.$state.user.id
|
||||||
|
|
||||||
|
filter = {
|
||||||
|
fields: ['id', 'nickname', 'street'],
|
||||||
|
where: { isActive: true }
|
||||||
|
}
|
||||||
|
res = await this.$axios.get(`Clients/${uid}/addresses`, { filter })
|
||||||
|
this.addresses = res.data
|
||||||
|
|
||||||
|
filter = {
|
||||||
|
fields: ['id', 'defaultAddressFk'],
|
||||||
|
include: {
|
||||||
|
relation: 'defaultAddress',
|
||||||
|
scope: filter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res = await this.$axios.get(`Clients/${uid}`, { filter })
|
||||||
|
this.address = res.data.defaultAddress
|
||||||
|
|
||||||
|
res = await this.$axios.get(`AgencyModes`)
|
||||||
|
this.agencies = res.data
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
datesFn (date) {
|
||||||
|
return date >= this.today
|
||||||
|
}
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
method () {
|
||||||
|
this.step = 'when'
|
||||||
|
},
|
||||||
|
date () {
|
||||||
|
this.step = 'how'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
|
@ -77,7 +77,7 @@
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'OrderView',
|
name: 'OrdersPendingRows',
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
|
@ -26,12 +26,9 @@
|
||||||
:label="$t('agency')"
|
:label="$t('agency')"
|
||||||
:value="order.agencyMode.name"
|
:value="order.agencyMode.name"
|
||||||
readonly/>
|
readonly/>
|
||||||
<q-input
|
|
||||||
v-model="notes"
|
|
||||||
:label="$t('notes')"
|
|
||||||
type="textarea"/>
|
|
||||||
<q-btn
|
<q-btn
|
||||||
:label="$t('configure')"
|
:label="$t('configure')"
|
||||||
|
to="configure"
|
||||||
color="primary"
|
color="primary"
|
||||||
class="q-mt-md"
|
class="q-mt-md"
|
||||||
style="width: 100%;"
|
style="width: 100%;"
|
||||||
|
@ -45,7 +42,7 @@
|
||||||
import Page from 'components/Page'
|
import Page from 'components/Page'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'Order',
|
name: 'OrdersPendingView',
|
||||||
mixins: [Page],
|
mixins: [Page],
|
||||||
data () {
|
data () {
|
||||||
return {
|
return {
|
|
@ -6,20 +6,20 @@ const routes = [
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'login',
|
name: 'login',
|
||||||
path: '',
|
path: '/login/:email?',
|
||||||
component: () => import('pages/Login.vue')
|
component: () => import('pages/Login/Login.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'register',
|
name: 'register',
|
||||||
path: '/register',
|
path: '/register',
|
||||||
component: () => import('pages/Register.vue')
|
component: () => import('pages/Login/Register.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'rememberPassword',
|
name: 'rememberPassword',
|
||||||
path: '/remember-password',
|
path: '/remember-password',
|
||||||
component: () => import('pages/RememberPassword.vue')
|
component: () => import('pages/Login/RememberPassword.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'resetPassword',
|
name: 'resetPassword',
|
||||||
path: '/reset-password',
|
path: '/reset-password',
|
||||||
component: () => import('pages/ResetPassword.vue')
|
component: () => import('pages/Login/ResetPassword.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
|
@ -29,115 +29,119 @@ const routes = [
|
||||||
{
|
{
|
||||||
name: '',
|
name: '',
|
||||||
path: '',
|
path: '',
|
||||||
component: () => import('pages/Index.vue')
|
component: () => import('pages/Cms/Home.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'home',
|
name: 'home',
|
||||||
path: '/home',
|
path: '/home',
|
||||||
component: () => import('pages/Index.vue')
|
component: () => import('pages/Cms/Home.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'catalog',
|
name: 'catalog',
|
||||||
path: '/catalog/:category?/:type?',
|
path: '/catalog/:category?/:type?',
|
||||||
component: () => import('pages/Catalog.vue')
|
component: () => import('pages/Webshop/Catalog.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'orders',
|
name: 'orders',
|
||||||
path: '/orders',
|
path: '/orders',
|
||||||
component: () => import('pages/Orders.vue'),
|
component: () => import('pages/Webshop/Orders.vue'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'pending',
|
name: 'pending',
|
||||||
path: 'pending',
|
path: 'pending',
|
||||||
component: () => import('pages/Pending.vue')
|
component: () => import('pages/Webshop/Pending.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'confirmed',
|
name: 'confirmed',
|
||||||
path: 'confirmed',
|
path: 'confirmed',
|
||||||
component: () => import('pages/Confirmed.vue')
|
component: () => import('pages/Webshop/Confirmed.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
name: 'order',
|
name: 'order',
|
||||||
path: '/order/:id',
|
path: '/order/:id',
|
||||||
component: () => import('pages/Order.vue'),
|
component: () => import('pages/Webshop/Pending/View.vue'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'view',
|
name: '',
|
||||||
path: 'view',
|
path: '',
|
||||||
component: () => import('pages/OrderView.vue')
|
component: () => import('pages/Webshop/Pending/Rows.vue')
|
||||||
|
}, {
|
||||||
|
name: 'configure',
|
||||||
|
path: 'configure',
|
||||||
|
component: () => import('pages/Webshop/Pending/Configure.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'checkout',
|
name: 'checkout',
|
||||||
path: 'checkout',
|
path: 'checkout',
|
||||||
component: () => import('pages/OrderCheckout.vue')
|
component: () => import('pages/Webshop/Pending/Checkout.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
name: 'ticket',
|
name: 'ticket',
|
||||||
path: '/ticket/:id',
|
path: '/ticket/:id',
|
||||||
component: () => import('pages/Ticket.vue')
|
component: () => import('pages/Webshop/Confirmed/View.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'conditions',
|
name: 'conditions',
|
||||||
path: '/conditions',
|
path: '/conditions',
|
||||||
component: () => import('pages/Conditions.vue')
|
component: () => import('pages/Cms/Conditions.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'about',
|
name: 'about',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
component: () => import('pages/About.vue')
|
component: () => import('pages/Cms/About.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'admin',
|
name: 'admin',
|
||||||
path: '/admin',
|
path: '/admin',
|
||||||
component: () => import('pages/Admin.vue'),
|
component: () => import('pages/Admin/Index.vue'),
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
name: 'panel',
|
name: 'panel',
|
||||||
path: 'panel',
|
path: 'panel',
|
||||||
component: () => import('pages/Panel.vue')
|
component: () => import('pages/Admin/Panel.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'users',
|
name: 'users',
|
||||||
path: 'users',
|
path: 'users',
|
||||||
component: () => import('pages/Users.vue')
|
component: () => import('pages/Admin/Users.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'connections',
|
name: 'connections',
|
||||||
path: 'connections',
|
path: 'connections',
|
||||||
component: () => import('pages/Connections.vue')
|
component: () => import('pages/Admin/Connections.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'visits',
|
name: 'visits',
|
||||||
path: 'visits',
|
path: 'visits',
|
||||||
component: () => import('pages/Visits.vue')
|
component: () => import('pages/Admin/Visits.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'news',
|
name: 'news',
|
||||||
path: 'news',
|
path: 'news',
|
||||||
component: () => import('pages/News.vue')
|
component: () => import('pages/Admin/News.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'images',
|
name: 'images',
|
||||||
path: 'images',
|
path: 'images',
|
||||||
component: () => import('pages/Images.vue')
|
component: () => import('pages/Admin/Images.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'items',
|
name: 'items',
|
||||||
path: 'items',
|
path: 'items',
|
||||||
component: () => import('pages/Items.vue')
|
component: () => import('pages/Admin/Items.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}, {
|
}, {
|
||||||
name: 'accessLog',
|
name: 'accessLog',
|
||||||
path: '/access-log/:user',
|
path: '/access-log/:user',
|
||||||
component: () => import('pages/AccessLog.vue')
|
component: () => import('pages/Admin/AccessLog.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'newEdit',
|
name: 'newEdit',
|
||||||
path: '/new/:id?',
|
path: '/new/:id?',
|
||||||
component: () => import('pages/New.vue')
|
component: () => import('pages/Admin/New.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'user',
|
name: 'user',
|
||||||
path: '/user',
|
path: '/user',
|
||||||
component: () => import('pages/User.vue'),
|
component: () => import('pages/Account/User.vue'),
|
||||||
props: route => ({
|
props: route => ({
|
||||||
changePassword: String(route.query.changePassword) === 'true'
|
changePassword: String(route.query.changePassword) === 'true'
|
||||||
})
|
})
|
||||||
}, {
|
}, {
|
||||||
name: 'addresses',
|
name: 'addresses',
|
||||||
path: '/addresses',
|
path: '/addresses',
|
||||||
component: () => import('pages/Addresses.vue')
|
component: () => import('pages/Account/Addresses.vue')
|
||||||
}, {
|
}, {
|
||||||
name: 'addressEdit',
|
name: 'addressEdit',
|
||||||
path: '/address/:id?',
|
path: '/address/:id?',
|
||||||
component: () => import('pages/Address.vue')
|
component: () => import('pages/Account/Address.vue')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue