Refactor, bugfix, i18n
This commit is contained in:
parent
85a2b667c2
commit
65097c3f58
|
@ -1 +0,0 @@
|
|||
/client/
|
|
@ -1,368 +1,368 @@
|
|||
const warehouseIds = [1, 44];
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('calcCatalog', {
|
||||
description: 'Get the available and prices list for an item',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The item id'
|
||||
}, {
|
||||
arg: 'dated',
|
||||
type: 'Date',
|
||||
description: 'The date'
|
||||
}, {
|
||||
arg: 'addressFk',
|
||||
type: 'Number',
|
||||
description: 'The address id'
|
||||
}, {
|
||||
arg: 'agencyModeFk',
|
||||
type: 'Number',
|
||||
description: 'The agency id'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
description: 'The item available and prices list',
|
||||
root: true,
|
||||
},
|
||||
http: {
|
||||
path: `/:id/calcCatalog`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
Self.remoteMethod('calcCatalog', {
|
||||
description: 'Get the available and prices list for an item',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The item id'
|
||||
}, {
|
||||
arg: 'dated',
|
||||
type: 'Date',
|
||||
description: 'The date'
|
||||
}, {
|
||||
arg: 'addressFk',
|
||||
type: 'Number',
|
||||
description: 'The address id'
|
||||
}, {
|
||||
arg: 'agencyModeFk',
|
||||
type: 'Number',
|
||||
description: 'The agency id'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
description: 'The item available and prices list',
|
||||
root: true,
|
||||
},
|
||||
http: {
|
||||
path: `/:id/calcCatalog`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
|
||||
Self.dataSource.connector.query(
|
||||
`CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
|
||||
[id, dated, addressFk, agencyModeFk],
|
||||
(err, res) => {
|
||||
if (err) return cb(err)
|
||||
return cb(null, res[0])
|
||||
}
|
||||
);
|
||||
Self.calcCatalog = (id, dated, addressFk, agencyModeFk, cb) => {
|
||||
Self.dataSource.connector.query(
|
||||
`CALL hedera.item_calcCatalog(?, ?, ?, ?)`,
|
||||
[id, dated, addressFk, agencyModeFk],
|
||||
(err, res) => {
|
||||
if (err) return cb(err)
|
||||
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', {
|
||||
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'
|
||||
}
|
||||
});
|
||||
// Applies base filters
|
||||
|
||||
Self.catalog = async (dated, typeFk, categoryFk, search, order, limit, tagFilter) => {
|
||||
let $ = Self.app.models;
|
||||
let itemIds;
|
||||
if (/^[0-9]+$/.test(search)) {
|
||||
itemIds = [parseInt(search)];
|
||||
} else {
|
||||
let inbounds = await $.Inbound.find({
|
||||
fields: ['itemFk'],
|
||||
where: inboundWhere
|
||||
});
|
||||
itemIds = toValues(inbounds, 'itemFk');
|
||||
|
||||
let inboundWhere = {
|
||||
warehouseFk: {inq: warehouseIds},
|
||||
available: {gt: 0},
|
||||
dated: {lte: dated},
|
||||
and: [
|
||||
{or: [
|
||||
{expired: {gt: dated}},
|
||||
{expired: null}
|
||||
]}
|
||||
]
|
||||
if (categoryFk || typeFk || search) {
|
||||
let where = {
|
||||
id: {inq: itemIds}
|
||||
};
|
||||
|
||||
// Applies base filters
|
||||
|
||||
if (/^[0-9]+$/.test(search)) {
|
||||
itemIds = [parseInt(search)];
|
||||
} else {
|
||||
let inbounds = await $.Inbound.find({
|
||||
fields: ['itemFk'],
|
||||
where: inboundWhere
|
||||
});
|
||||
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);
|
||||
}
|
||||
if (typeFk) {
|
||||
where.typeFk = typeFk;
|
||||
} else if (categoryFk) {
|
||||
let types = await $.ItemType.find({
|
||||
fields: ['id'],
|
||||
where: {categoryFk}
|
||||
});
|
||||
where.typeFk = {inq: toValues(types, 'id')};
|
||||
}
|
||||
|
||||
// Applies tag filters
|
||||
if (search)
|
||||
where.longName = {like: `%${search}%`};
|
||||
|
||||
let baseItemIds = itemIds;
|
||||
let tagItems = [];
|
||||
let tagFilterIds = [];
|
||||
let filter = {
|
||||
fields: ['id'],
|
||||
where
|
||||
};
|
||||
|
||||
if (tagFilter && tagFilter.length) {
|
||||
for (let filter of tagFilter) {
|
||||
let cond;
|
||||
let values = filter.values;
|
||||
let items = await Self.find(filter);
|
||||
itemIds = items.map(i => i.id);
|
||||
}
|
||||
}
|
||||
|
||||
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}};
|
||||
// Applies tag filters
|
||||
|
||||
let where = {
|
||||
itemFk: {inq: itemIds},
|
||||
tagFk: filter.tagFk
|
||||
};
|
||||
Object.assign(where, cond);
|
||||
let baseItemIds = itemIds;
|
||||
let tagItems = [];
|
||||
let tagFilterIds = [];
|
||||
|
||||
let itemTags = await $.ItemTag.find({
|
||||
fields: ['itemFk'],
|
||||
where
|
||||
});
|
||||
tagItems.push(toSet(itemTags, 'itemFk'));
|
||||
tagFilterIds.push(filter.tagFk);
|
||||
}
|
||||
if (tagFilter && tagFilter.length) {
|
||||
for (let filter of tagFilter) {
|
||||
let cond;
|
||||
let values = filter.values;
|
||||
|
||||
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) {
|
||||
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');
|
||||
itemIds = intersect(tagItems);
|
||||
}
|
||||
|
||||
for (let i = 0; i < tagItems.length; i++) {
|
||||
let tagFk = tagFilter[i].tagFk;
|
||||
let itemIds;
|
||||
// Obtains distinct tags and it's distinct values
|
||||
|
||||
if (tagItems.length > 1) {
|
||||
let siblings = tagItems.filter(v => v != tagItems[i]);
|
||||
itemIds = intersect(siblings);
|
||||
} else
|
||||
itemIds = baseItemIds;
|
||||
let tags = [];
|
||||
|
||||
let tagValues = await $.ItemTag.find({
|
||||
fields: ['value', 'intValue', 'priority'],
|
||||
where: {
|
||||
itemFk: {inq: itemIds},
|
||||
tagFk: tagFk
|
||||
},
|
||||
order: 'value'
|
||||
});
|
||||
if (typeFk || search) {
|
||||
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');
|
||||
|
||||
tagValueMap.set(tagFk, tagValues);
|
||||
}
|
||||
for (let i = 0; i < tagItems.length; i++) {
|
||||
let tagFk = tagFilter[i].tagFk;
|
||||
let itemIds;
|
||||
|
||||
let tagIds = [...tagValueMap.keys()];
|
||||
tags = await $.Tag.find({
|
||||
fields: ['id', 'name', 'isQuantitative', 'unit'],
|
||||
where: {
|
||||
id: {inq: tagIds}
|
||||
}
|
||||
});
|
||||
if (tagItems.length > 1) {
|
||||
let siblings = tagItems.filter(v => v != tagItems[i]);
|
||||
itemIds = intersect(siblings);
|
||||
} else
|
||||
itemIds = baseItemIds;
|
||||
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
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']}
|
||||
},
|
||||
}
|
||||
}
|
||||
]
|
||||
let tagValues = await $.ItemTag.find({
|
||||
fields: ['value', 'intValue', 'priority'],
|
||||
where: {
|
||||
itemFk: {inq: itemIds},
|
||||
tagFk: tagFk
|
||||
},
|
||||
order: 'value'
|
||||
});
|
||||
|
||||
for (let item of items) {
|
||||
item.inbound = item.inbounds()[0];
|
||||
item.buy = item.inbound && item.inbound.buy();
|
||||
item.available = sum(item.inbounds(), 'available');
|
||||
tagValueMap.set(tagFk, tagValues);
|
||||
}
|
||||
|
||||
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};
|
||||
};
|
||||
|
||||
// Array functions
|
||||
|
||||
function sum(array, key) {
|
||||
if (!Array.isArray(array)) return 0;
|
||||
return array.reduce((a, c) => a + c[key], 0);
|
||||
Object.assign(tag, {
|
||||
values: [...values],
|
||||
filter
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function toMap(objects, key) {
|
||||
let map = new Map();
|
||||
for (let object of objects)
|
||||
map.set(object[key], object);
|
||||
return map;
|
||||
}
|
||||
// Obtains items data
|
||||
|
||||
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;
|
||||
let items = await Self.find({
|
||||
fields: ['id', 'longName', 'subName', 'image'],
|
||||
where: {id: {inq: itemIds}},
|
||||
limit,
|
||||
order,
|
||||
include: [
|
||||
{
|
||||
relation: 'tags',
|
||||
scope: {
|
||||
fields: ['value', 'tagFk'],
|
||||
where: {priority: {gt: 4}},
|
||||
order: 'priority',
|
||||
include: {
|
||||
relation: 'tag',
|
||||
scope: {fields: ['name']}
|
||||
}
|
||||
|
||||
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 path = require('path');
|
||||
|
||||
const config = {
|
||||
proto: 'http',
|
||||
host: 'localhost',
|
||||
port: 3000,
|
||||
from: 'nocontestar@verdnatura.es'
|
||||
// app.dataSources.email.settings.transports[0].auth.user
|
||||
};
|
||||
function getUrl() {
|
||||
return app.get('rootUrl') || app.get('url');
|
||||
}
|
||||
|
||||
function getFrom() {
|
||||
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) {
|
||||
const hostBase = `${config.proto}://${config.host}`
|
||||
const urlBase = `${hostBase}:8080`;
|
||||
const apiBase = `${hostBase}:3000/api`;
|
||||
|
||||
Self.afterRemote('create', async function(ctx, instance) {
|
||||
const url = new URL(getUrl());
|
||||
const options = {
|
||||
type: 'email',
|
||||
to: instance.email,
|
||||
from: config.from,
|
||||
from: getFrom(),
|
||||
subject: 'Thanks for registering',
|
||||
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
|
||||
};
|
||||
const res = await instance.verify(options);
|
||||
console.log('> verification email sent:', res);
|
||||
instance.verify(options)
|
||||
.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) {
|
||||
const renderer = loopback.template(path.resolve(__dirname, '../../views/reset-password.ejs'));
|
||||
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({
|
||||
to: info.email,
|
||||
from: config.from,
|
||||
from: getFrom(),
|
||||
subject: 'Password reset',
|
||||
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",
|
||||
"base": "User",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.user"
|
||||
}
|
||||
},
|
||||
"idInjection": true,
|
||||
"properties": {},
|
||||
"restrictResetPasswordTokenScope": true,
|
||||
"emailVerificationRequired": true,
|
||||
"emailVerificationRequired": false,
|
||||
"validations": [],
|
||||
"relations": {},
|
||||
"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",
|
||||
"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": {
|
||||
"version": "0.4.24",
|
||||
"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": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
|
||||
|
@ -2723,6 +2748,11 @@
|
|||
"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": {
|
||||
"version": "2.2.1",
|
||||
"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",
|
||||
"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": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
|
||||
|
@ -2976,6 +3042,11 @@
|
|||
"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": {
|
||||
"version": "0.0.5",
|
||||
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz",
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
"node": ">=6"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .",
|
||||
"start": "node .",
|
||||
"posttest": "npm run lint && npm audit"
|
||||
},
|
||||
|
@ -15,6 +14,7 @@
|
|||
"cors": "^2.5.2",
|
||||
"fs-extra": "^8.1.0",
|
||||
"helmet": "^3.22.0",
|
||||
"i18n": "^0.9.1",
|
||||
"loopback": "^3.27.0",
|
||||
"loopback-boot": "^2.28.0",
|
||||
"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": {}
|
||||
},
|
||||
"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": {
|
||||
"sources": [
|
||||
"loopback/common/models",
|
||||
"loopback/server/models",
|
||||
"../common/models",
|
||||
"./models"
|
||||
],
|
||||
"mixins": [
|
||||
"loopback/common/mixins",
|
||||
"loopback/server/mixins",
|
||||
"../common/mixins",
|
||||
"./mixins"
|
||||
]
|
||||
},
|
||||
"AccessToken": {
|
||||
"dataSource": "vn",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"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"
|
||||
"_meta": {
|
||||
"sources": [
|
||||
"loopback/common/models",
|
||||
"loopback/server/models",
|
||||
"../common/models",
|
||||
"./models"
|
||||
],
|
||||
"mixins": [
|
||||
"loopback/common/mixins",
|
||||
"loopback/server/mixins",
|
||||
"../common/mixins",
|
||||
"./mixins"
|
||||
]
|
||||
},
|
||||
"AccessToken": {
|
||||
"dataSource": "vn",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.AccessToken"
|
||||
}
|
||||
},
|
||||
"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';
|
||||
|
||||
var loopback = require('loopback');
|
||||
var boot = require('loopback-boot');
|
||||
const loopback = require('loopback');
|
||||
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() {
|
||||
// start the web server
|
||||
return app.listen(function() {
|
||||
app.emit('started');
|
||||
var baseUrl = app.get('url').replace(/\/$/, '');
|
||||
const baseUrl = app.get('url').replace(/\/$/, '');
|
||||
console.log('Web server listening at: %s', baseUrl);
|
||||
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);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -65,7 +65,11 @@ module.exports = function (ctx) {
|
|||
'QSelect',
|
||||
'QSeparator',
|
||||
'QSlideTransition',
|
||||
'QSpace',
|
||||
'QSpinner',
|
||||
'QStepper',
|
||||
'QStep',
|
||||
'QStepperNavigation',
|
||||
'QTab',
|
||||
'QTabs',
|
||||
'QTabPanel',
|
||||
|
|
|
@ -7,9 +7,14 @@ export default async ({ app, Vue }) => {
|
|||
|
||||
axios.interceptors.request.use(function (config) {
|
||||
const $state = Vue.prototype.$state
|
||||
|
||||
if ($state.user.loggedIn) {
|
||||
config.headers.Authorization = $state.user.token
|
||||
}
|
||||
if (config.filter) {
|
||||
if (!config.params) config.params = {}
|
||||
config.params.filter = config.filter
|
||||
}
|
||||
return config
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
import Toolbar from './Toolbar'
|
||||
import Portal from './Portal'
|
||||
import LbScroll from './LbScroll'
|
||||
import FullImage from './FullImage'
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Toolbar,
|
||||
Portal,
|
||||
LbScroll,
|
||||
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
|
||||
login: 'Login',
|
||||
logout: 'Logout',
|
||||
|
@ -88,12 +92,13 @@ export default {
|
|||
notRememberPassword: 'I don\'t remember my password',
|
||||
inputEmail: 'Input email',
|
||||
inputPassword: 'Input password',
|
||||
emailConfirmedSuccessfully: 'E-mail confirmed succesfully',
|
||||
|
||||
// register
|
||||
register: 'Register',
|
||||
fillData: 'Fill the data',
|
||||
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',
|
||||
|
||||
// recover
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
export default {
|
||||
// global
|
||||
// Global
|
||||
search: 'Buscar',
|
||||
accept: 'Aceptar',
|
||||
cancel: 'Cancelar',
|
||||
|
@ -75,12 +75,15 @@ export default {
|
|||
]
|
||||
},
|
||||
|
||||
// layout
|
||||
// Errors
|
||||
internalServerError: 'Error interno del servidor',
|
||||
somethingWentWrong: 'Algo salió mal',
|
||||
|
||||
// MainLayout
|
||||
login: 'Iniciar sesión',
|
||||
logout: 'Cerrar sesión',
|
||||
visitor: 'Visitante',
|
||||
|
||||
// login
|
||||
// Login
|
||||
enter: 'Entrar',
|
||||
email: 'Correo electrónico',
|
||||
password: 'Contraseña',
|
||||
|
@ -88,25 +91,26 @@ export default {
|
|||
notRememberPassword: 'No recuerdo mi contraseña',
|
||||
inputEmail: 'Introduce el correo electrónico',
|
||||
inputPassword: 'Introduce la contraseña',
|
||||
emailConfirmedSuccessfully: 'Correo verificado correctamente',
|
||||
|
||||
// register
|
||||
register: 'Registrarse',
|
||||
// Register
|
||||
register: 'Registrarme',
|
||||
fillData: 'Rellena los datos',
|
||||
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',
|
||||
|
||||
// recover
|
||||
// Recover
|
||||
rememberPassword: 'Recordar contraseña',
|
||||
dontWorry: '¡No te preocupes!',
|
||||
weSendEmail: 'Te enviaremos un correo para restablecer tu contraseña',
|
||||
weHaveSentEmailToRecover: 'Te hemos enviado un correo donde podrás recuperar tu contraseña',
|
||||
|
||||
// reset
|
||||
// Reset
|
||||
resetPassword: 'Restaurar contraseña',
|
||||
passwordResetSuccessfully: 'Contraseña modificada correctamente',
|
||||
|
||||
// menu
|
||||
// Menu
|
||||
home: 'Inicio',
|
||||
catalog: 'Catálogo',
|
||||
orders: 'Pedidos',
|
||||
|
@ -128,7 +132,7 @@ export default {
|
|||
addresses: 'Direcciones',
|
||||
addressEdit: 'Editar dirección',
|
||||
|
||||
// home
|
||||
// Home
|
||||
recentNews: 'Noticias recientes',
|
||||
startOrder: 'Empezar pedido',
|
||||
|
||||
|
@ -147,7 +151,7 @@ export default {
|
|||
n1InPrice: 'Nº1 en precio',
|
||||
ourBigVolumeAllows: 'Nuestro gran volumen nos permite ofrecerte los mejores precios',
|
||||
|
||||
// catalog
|
||||
// Catalog
|
||||
more: 'Más',
|
||||
noItemsFound: 'No se han encontrado artículos',
|
||||
pleaseSetFilter: 'Por favor, establece un filtro usando el menú de la derecha',
|
||||
|
@ -170,19 +174,19 @@ export default {
|
|||
siceAsc: 'Medida ascendente',
|
||||
sizeDesc: 'Medida descendente',
|
||||
|
||||
// orders
|
||||
// Orders
|
||||
pending: 'Pendientes',
|
||||
confirmed: 'Confirmados',
|
||||
|
||||
// orders/pending
|
||||
// Pending
|
||||
pendingConfirmtion: 'Pendientes de confirmar',
|
||||
noOrdersFound: 'No se han encontrado pedidos',
|
||||
|
||||
// orders/confirmed
|
||||
// Confirmed
|
||||
ordersMadeAt: 'Pedidos realizados en',
|
||||
packages: '{n} bultos',
|
||||
|
||||
// order
|
||||
// Order
|
||||
total: 'Total',
|
||||
confirm: 'Confirmar',
|
||||
delivery: 'Fecha de entrega',
|
||||
|
@ -191,7 +195,24 @@ export default {
|
|||
warehouse: 'Almacén',
|
||||
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',
|
||||
orderSummary: 'Resumen del pedido',
|
||||
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.',
|
||||
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).',
|
||||
|
||||
// about
|
||||
// About
|
||||
aboutDesc: 'Verdnatura te ofrece todos los servicios que necesita tu floristería.',
|
||||
|
||||
// connections
|
||||
// Connections
|
||||
nConnections: '{0} connexiones',
|
||||
refreshRate: 'Frecuencia de actualización',
|
||||
lastAction: 'Última acción',
|
||||
|
@ -231,27 +252,27 @@ export default {
|
|||
nSeconds: '{0} segundos',
|
||||
dontRefresh: 'No refrescar',
|
||||
|
||||
// accessLog
|
||||
// AccessLog
|
||||
accessLog: 'Registro de accesos',
|
||||
|
||||
// visits
|
||||
// Visits
|
||||
visitsCount: '{0} visitas, {1} nuevas',
|
||||
|
||||
// new
|
||||
// New
|
||||
title: 'Título',
|
||||
image: 'Imagen',
|
||||
tag: 'Etiqueta',
|
||||
priority: 'Prioridad',
|
||||
text: 'Texto',
|
||||
|
||||
// images
|
||||
// Images
|
||||
collection: 'Colección',
|
||||
updateMatchingId: 'Actualizar ítems con id coincidente',
|
||||
uploadAutomatically: 'Subir automáticamente',
|
||||
imagesUploadSuccess: 'Imágenes subidas correctamente',
|
||||
imagesUploadFailed: 'Algunas imágenes no se ha podido subir',
|
||||
|
||||
// user
|
||||
// User
|
||||
userName: 'Nombre de usuario',
|
||||
nickname: 'Nombre a mostrar',
|
||||
language: 'Idioma',
|
||||
|
@ -270,14 +291,14 @@ export default {
|
|||
passwordsDontMatch: 'Las contraseñas no coinciden',
|
||||
passwordChanged: '¡Contraseña modificada correctamente!',
|
||||
|
||||
// addresses
|
||||
// Addresses
|
||||
setAsDefault: 'Establecer como predeterminada',
|
||||
addressSetAsDefault: 'Dirección establecida como predeterminada',
|
||||
addressRemoved: 'Dirección eliminada',
|
||||
areYouSureDeleteAddress: '¿Seguro que quieres eliminar la dirección?',
|
||||
addressCreated: '¡Dirección creada correctamente!',
|
||||
|
||||
// address
|
||||
// Address
|
||||
consignatary: 'Consignatario',
|
||||
street: 'Dirección',
|
||||
city: 'City',
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<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">
|
||||
<transition name="slide-right">
|
||||
<router-view class="child-view"/>
|
||||
|
@ -9,6 +9,14 @@
|
|||
</template>
|
||||
|
||||
<style lang="stylus" scoped>
|
||||
#bg
|
||||
background: repeating-linear-gradient(
|
||||
45deg,
|
||||
#f18d1a,
|
||||
#f18d1a 20px,
|
||||
$primary 20px,
|
||||
$primary 40px
|
||||
);
|
||||
.q-card
|
||||
border-radius 0
|
||||
width 600px
|
||||
|
|
|
@ -18,13 +18,15 @@
|
|||
{{$state.subtitle}}
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
<div ref="top"/>
|
||||
<q-space/>
|
||||
<div ref="toolbar"/>
|
||||
<q-btn flat
|
||||
v-if="!$state.user.loggedIn"
|
||||
class="q-ml-md"
|
||||
:label="$t('login')"
|
||||
to="/login"
|
||||
/>
|
||||
<div ref="toolbar"/>
|
||||
<q-btn
|
||||
v-if="$state.useRightDrawer"
|
||||
@click="$state.rightDrawerOpen = !$state.rightDrawerOpen"
|
||||
|
|
|
@ -117,7 +117,7 @@ export default {
|
|||
.then(res => (this.address = res.data))
|
||||
} else {
|
||||
this.address = {
|
||||
clientFk: this.$state.userId
|
||||
clientFk: this.$state.user.id
|
||||
}
|
||||
}
|
||||
},
|
|
@ -95,7 +95,7 @@ export default {
|
|||
'postalCode'
|
||||
],
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
clientFk: this.$state.user.id,
|
||||
isActive: true
|
||||
},
|
||||
order: 'nickname'
|
||||
|
@ -106,14 +106,14 @@ export default {
|
|||
filter = {
|
||||
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))
|
||||
},
|
||||
watch: {
|
||||
defaultAddress (value, oldValue) {
|
||||
if (!oldValue) return
|
||||
let data = { defaultAddressFk: value }
|
||||
this.$axios.patch(`Clients/${this.$state.userId}`, data)
|
||||
this.$axios.patch(`Clients/${this.$state.user.id}`, data)
|
||||
.then(res => {
|
||||
this.$q.notify(this.$t('addressSetAsDefault'))
|
||||
})
|
|
@ -132,7 +132,7 @@ export default {
|
|||
'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))
|
||||
|
||||
filter = {
|
||||
|
@ -173,7 +173,7 @@ export default {
|
|||
})
|
||||
},
|
||||
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({
|
||||
message: this.$t('dataSaved'),
|
||||
icon: 'check',
|
|
@ -4,6 +4,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Admin'
|
||||
name: 'AdminIndex'
|
||||
}
|
||||
</script>
|
|
@ -152,7 +152,7 @@ export default {
|
|||
.then(res => (this.myNew = res.data))
|
||||
} else {
|
||||
this.myNew = {
|
||||
userFk: this.$state.userId,
|
||||
userFk: this.$state.user.id,
|
||||
tag: 'new',
|
||||
priority: 1,
|
||||
text: ''
|
|
@ -16,10 +16,12 @@
|
|||
v-model="email"
|
||||
:label="$t('email')"
|
||||
:rules="[ val => !!val || $t('inputEmail')]"
|
||||
autofocus
|
||||
filled
|
||||
/>
|
||||
<q-input
|
||||
v-model="password"
|
||||
ref="password"
|
||||
:label="$t('password')"
|
||||
:type="showPwd ? 'password' : 'text'"
|
||||
:rules="[ val => !!val || $t('inputPassword')]"
|
||||
|
@ -81,12 +83,30 @@ export default {
|
|||
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: {
|
||||
async onLogin () {
|
||||
const params = {
|
||||
username: this.email,
|
||||
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)
|
||||
localStorage.setItem('token', res.data.id)
|
||||
Object.assign(this.$state.user, {
|
|
@ -2,7 +2,7 @@
|
|||
<div>
|
||||
<q-card-section>
|
||||
<q-icon
|
||||
name="nature_people"
|
||||
name="local_florist"
|
||||
class="block q-mx-auto text-accent"
|
||||
style="font-size: 120px;"
|
||||
/>
|
||||
|
@ -16,6 +16,7 @@
|
|||
<q-input
|
||||
v-model="email"
|
||||
:label="$t('email')"
|
||||
autofocus
|
||||
hint=""
|
||||
filled
|
||||
/>
|
|
@ -21,6 +21,7 @@
|
|||
v-model="email"
|
||||
:label="$t('email')"
|
||||
:rules="[ val => !!val || $t('inputEmail')]"
|
||||
autofocus
|
||||
filled
|
||||
/>
|
||||
<div>
|
|
@ -17,6 +17,7 @@
|
|||
v-model="password"
|
||||
:label="$t('password')"
|
||||
:type="showPwd ? 'password' : 'text'"
|
||||
autofocus
|
||||
hint=""
|
||||
filled>
|
||||
<template v-slot:append>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div style="padding-bottom: 5em;">
|
||||
<toolbar>
|
||||
<portal place="top">
|
||||
<q-input
|
||||
v-model="search"
|
||||
debounce="500"
|
||||
|
@ -21,7 +21,7 @@
|
|||
/>
|
||||
</template>
|
||||
</q-input>
|
||||
</toolbar>
|
||||
</portal>
|
||||
<q-drawer
|
||||
v-model="$state.rightDrawerOpen"
|
||||
side="right"
|
||||
|
@ -276,9 +276,9 @@
|
|||
height 40px
|
||||
overflow hidden
|
||||
.category
|
||||
width 25%
|
||||
width 33%
|
||||
&.active
|
||||
background: rgba(0, 0, 0, .08)
|
||||
background rgba(0, 0, 0, .08)
|
||||
.category-img
|
||||
height 3.5em
|
||||
.tags
|
|
@ -53,7 +53,7 @@
|
|||
import Page from 'components/Page'
|
||||
|
||||
export default {
|
||||
name: 'Orders',
|
||||
name: 'OrdersConfirmedIndex',
|
||||
mixins: [Page],
|
||||
data () {
|
||||
return {
|
||||
|
@ -79,7 +79,7 @@ export default {
|
|||
|
||||
let params = { filter: {
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
clientFk: this.$state.user.id,
|
||||
landed: { between: [start, end] }
|
||||
},
|
||||
include: [
|
|
@ -9,6 +9,6 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Ticket'
|
||||
name: 'OrdersConfirmedView'
|
||||
}
|
||||
</script>
|
|
@ -12,7 +12,7 @@
|
|||
<q-item
|
||||
v-for="order in orders"
|
||||
:key="order.id"
|
||||
:to="`/order/${order.id}/view`"
|
||||
:to="`/order/${order.id}/`"
|
||||
clickable
|
||||
v-ripple>
|
||||
<q-item-section>
|
||||
|
@ -40,24 +40,24 @@
|
|||
|
||||
<script>
|
||||
export default {
|
||||
name: 'Orders',
|
||||
name: 'OrdersPendingIndex',
|
||||
data () {
|
||||
return {
|
||||
orders: null
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
let filter = {
|
||||
async mounted () {
|
||||
const filter = {
|
||||
where: {
|
||||
clientFk: this.$state.userId,
|
||||
clientFk: this.$state.user.id,
|
||||
isConfirmed: false
|
||||
},
|
||||
include: 'address',
|
||||
order: 'created DESC',
|
||||
limit: 20
|
||||
}
|
||||
this.$axios.get('Orders', { params: { filter } })
|
||||
.then(res => (this.orders = res.data))
|
||||
const res = await this.$axios.get('Orders', { params: { filter } })
|
||||
this.orders = res.data
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -150,7 +150,7 @@
|
|||
import Page from 'components/Page'
|
||||
|
||||
export default {
|
||||
name: 'OrderCheckout',
|
||||
name: 'OrdersPendingCheckout',
|
||||
mixins: [Page],
|
||||
data () {
|
||||
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'
|
||||
|
||||
export default {
|
||||
name: 'OrderView',
|
||||
name: 'OrdersPendingRows',
|
||||
mixins: [Page],
|
||||
data () {
|
||||
return {
|
|
@ -26,12 +26,9 @@
|
|||
:label="$t('agency')"
|
||||
:value="order.agencyMode.name"
|
||||
readonly/>
|
||||
<q-input
|
||||
v-model="notes"
|
||||
:label="$t('notes')"
|
||||
type="textarea"/>
|
||||
<q-btn
|
||||
:label="$t('configure')"
|
||||
to="configure"
|
||||
color="primary"
|
||||
class="q-mt-md"
|
||||
style="width: 100%;"
|
||||
|
@ -45,7 +42,7 @@
|
|||
import Page from 'components/Page'
|
||||
|
||||
export default {
|
||||
name: 'Order',
|
||||
name: 'OrdersPendingView',
|
||||
mixins: [Page],
|
||||
data () {
|
||||
return {
|
|
@ -6,20 +6,20 @@ const routes = [
|
|||
children: [
|
||||
{
|
||||
name: 'login',
|
||||
path: '',
|
||||
component: () => import('pages/Login.vue')
|
||||
path: '/login/:email?',
|
||||
component: () => import('pages/Login/Login.vue')
|
||||
}, {
|
||||
name: 'register',
|
||||
path: '/register',
|
||||
component: () => import('pages/Register.vue')
|
||||
component: () => import('pages/Login/Register.vue')
|
||||
}, {
|
||||
name: 'rememberPassword',
|
||||
path: '/remember-password',
|
||||
component: () => import('pages/RememberPassword.vue')
|
||||
component: () => import('pages/Login/RememberPassword.vue')
|
||||
}, {
|
||||
name: 'resetPassword',
|
||||
path: '/reset-password',
|
||||
component: () => import('pages/ResetPassword.vue')
|
||||
component: () => import('pages/Login/ResetPassword.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
|
@ -29,115 +29,119 @@ const routes = [
|
|||
{
|
||||
name: '',
|
||||
path: '',
|
||||
component: () => import('pages/Index.vue')
|
||||
component: () => import('pages/Cms/Home.vue')
|
||||
}, {
|
||||
name: 'home',
|
||||
path: '/home',
|
||||
component: () => import('pages/Index.vue')
|
||||
component: () => import('pages/Cms/Home.vue')
|
||||
}, {
|
||||
name: 'catalog',
|
||||
path: '/catalog/:category?/:type?',
|
||||
component: () => import('pages/Catalog.vue')
|
||||
component: () => import('pages/Webshop/Catalog.vue')
|
||||
}, {
|
||||
name: 'orders',
|
||||
path: '/orders',
|
||||
component: () => import('pages/Orders.vue'),
|
||||
component: () => import('pages/Webshop/Orders.vue'),
|
||||
children: [
|
||||
{
|
||||
name: 'pending',
|
||||
path: 'pending',
|
||||
component: () => import('pages/Pending.vue')
|
||||
component: () => import('pages/Webshop/Pending.vue')
|
||||
}, {
|
||||
name: 'confirmed',
|
||||
path: 'confirmed',
|
||||
component: () => import('pages/Confirmed.vue')
|
||||
component: () => import('pages/Webshop/Confirmed.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: 'order',
|
||||
path: '/order/:id',
|
||||
component: () => import('pages/Order.vue'),
|
||||
component: () => import('pages/Webshop/Pending/View.vue'),
|
||||
children: [
|
||||
{
|
||||
name: 'view',
|
||||
path: 'view',
|
||||
component: () => import('pages/OrderView.vue')
|
||||
name: '',
|
||||
path: '',
|
||||
component: () => import('pages/Webshop/Pending/Rows.vue')
|
||||
}, {
|
||||
name: 'configure',
|
||||
path: 'configure',
|
||||
component: () => import('pages/Webshop/Pending/Configure.vue')
|
||||
}, {
|
||||
name: 'checkout',
|
||||
path: 'checkout',
|
||||
component: () => import('pages/OrderCheckout.vue')
|
||||
component: () => import('pages/Webshop/Pending/Checkout.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: 'ticket',
|
||||
path: '/ticket/:id',
|
||||
component: () => import('pages/Ticket.vue')
|
||||
component: () => import('pages/Webshop/Confirmed/View.vue')
|
||||
}, {
|
||||
name: 'conditions',
|
||||
path: '/conditions',
|
||||
component: () => import('pages/Conditions.vue')
|
||||
component: () => import('pages/Cms/Conditions.vue')
|
||||
}, {
|
||||
name: 'about',
|
||||
path: '/about',
|
||||
component: () => import('pages/About.vue')
|
||||
component: () => import('pages/Cms/About.vue')
|
||||
}, {
|
||||
name: 'admin',
|
||||
path: '/admin',
|
||||
component: () => import('pages/Admin.vue'),
|
||||
component: () => import('pages/Admin/Index.vue'),
|
||||
children: [
|
||||
{
|
||||
name: 'panel',
|
||||
path: 'panel',
|
||||
component: () => import('pages/Panel.vue')
|
||||
component: () => import('pages/Admin/Panel.vue')
|
||||
}, {
|
||||
name: 'users',
|
||||
path: 'users',
|
||||
component: () => import('pages/Users.vue')
|
||||
component: () => import('pages/Admin/Users.vue')
|
||||
}, {
|
||||
name: 'connections',
|
||||
path: 'connections',
|
||||
component: () => import('pages/Connections.vue')
|
||||
component: () => import('pages/Admin/Connections.vue')
|
||||
}, {
|
||||
name: 'visits',
|
||||
path: 'visits',
|
||||
component: () => import('pages/Visits.vue')
|
||||
component: () => import('pages/Admin/Visits.vue')
|
||||
}, {
|
||||
name: 'news',
|
||||
path: 'news',
|
||||
component: () => import('pages/News.vue')
|
||||
component: () => import('pages/Admin/News.vue')
|
||||
}, {
|
||||
name: 'images',
|
||||
path: 'images',
|
||||
component: () => import('pages/Images.vue')
|
||||
component: () => import('pages/Admin/Images.vue')
|
||||
}, {
|
||||
name: 'items',
|
||||
path: 'items',
|
||||
component: () => import('pages/Items.vue')
|
||||
component: () => import('pages/Admin/Items.vue')
|
||||
}
|
||||
]
|
||||
}, {
|
||||
name: 'accessLog',
|
||||
path: '/access-log/:user',
|
||||
component: () => import('pages/AccessLog.vue')
|
||||
component: () => import('pages/Admin/AccessLog.vue')
|
||||
}, {
|
||||
name: 'newEdit',
|
||||
path: '/new/:id?',
|
||||
component: () => import('pages/New.vue')
|
||||
component: () => import('pages/Admin/New.vue')
|
||||
}, {
|
||||
name: 'user',
|
||||
path: '/user',
|
||||
component: () => import('pages/User.vue'),
|
||||
component: () => import('pages/Account/User.vue'),
|
||||
props: route => ({
|
||||
changePassword: String(route.query.changePassword) === 'true'
|
||||
})
|
||||
}, {
|
||||
name: 'addresses',
|
||||
path: '/addresses',
|
||||
component: () => import('pages/Addresses.vue')
|
||||
component: () => import('pages/Account/Addresses.vue')
|
||||
}, {
|
||||
name: 'addressEdit',
|
||||
path: '/address/:id?',
|
||||
component: () => import('pages/Address.vue')
|
||||
component: () => import('pages/Account/Address.vue')
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue