refs #6184 saveCmr #1788
|
@ -0,0 +1,77 @@
|
||||||
|
const {ParameterizedSQL} = require('loopback-connector');
|
||||||
|
const {buildFilter, mergeFilters} = require('vn-loopback/util/filter');
|
||||||
|
// const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('filter', {
|
||||||
|
description:
|
||||||
|
'Find all postcodes of the model matched by postcode, town, province or country.',
|
||||||
|
accessType: 'READ',
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true,
|
||||||
|
},
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
||||||
|
http: {source: 'query'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'search',
|
||||||
|
type: 'string',
|
||||||
|
description: 'Value to filter',
|
||||||
|
http: {source: 'query'}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: `/filter`,
|
||||||
|
verb: 'GET',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
Self.filter = async(ctx, filter, options) => {
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const conn = Self.dataSource.connector;
|
||||||
|
const where = buildFilter(ctx.args, (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return {or: [
|
||||||
|
{'pc.code': {like: `%${value}%`}},
|
||||||
|
{'t.name': {like: `%${value}%`}},
|
||||||
|
{'p.name': {like: `%${value}%`}},
|
||||||
|
{'c.country': {like: `%${value}%`}}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}) ?? {};
|
||||||
|
|
||||||
|
filter = mergeFilters(ctx.args?.filter ?? {}, {where});
|
||||||
|
|
||||||
|
const stmts = [];
|
||||||
|
let stmt;
|
||||||
|
stmt = new ParameterizedSQL(`
|
||||||
|
SELECT
|
||||||
|
pc.code,
|
||||||
|
t.name as town,
|
||||||
|
p.name as province,
|
||||||
|
c.country
|
||||||
|
FROM
|
||||||
|
postCode pc
|
||||||
|
JOIN town t on t.id = pc.townFk
|
||||||
|
JOIN province p on p.id = t.provinceFk
|
||||||
|
JOIN country c on c.id = p.countryFk
|
||||||
|
`);
|
||||||
|
|
||||||
|
stmt.merge(conn.makeSuffix(filter));
|
||||||
|
const itemsIndex = stmts.push(stmt) - 1;
|
||||||
|
|
||||||
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
const result = await conn.executeStmt(sql, myOptions);
|
||||||
|
return itemsIndex === 0 ? result : result[itemsIndex];
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,103 @@
|
||||||
|
const {models} = require('vn-loopback/server/server');
|
||||||
|
|
||||||
|
describe('Postcode filter()', () => {
|
||||||
|
it('should retrieve with no filter', async() => {
|
||||||
|
const tx = await models.Postcode.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = {
|
||||||
|
args: {
|
||||||
|
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await models.Postcode.filter(ctx, options);
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve with filter as postcode', async() => {
|
||||||
|
const tx = await models.Postcode.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = {
|
||||||
|
args: {
|
||||||
|
search: 46,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await models.Postcode.filter(ctx, options);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve with filter as city', async() => {
|
||||||
|
const tx = await models.Postcode.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = {
|
||||||
|
args: {
|
||||||
|
search: 'Alz',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await models.Postcode.filter(ctx, options);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve with filter as province', async() => {
|
||||||
|
const tx = await models.Postcode.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = {
|
||||||
|
args: {
|
||||||
|
search: 'one',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await models.Postcode.filter(ctx, options);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(4);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should retrieve with filter as country', async() => {
|
||||||
|
const tx = await models.Postcode.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const ctx = {
|
||||||
|
args: {
|
||||||
|
search: 'Ec',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const results = await models.Postcode.filter(ctx, options);
|
||||||
|
|
||||||
|
expect(results.length).toEqual(1);
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -1,6 +1,7 @@
|
||||||
let UserError = require('vn-loopback/util/user-error');
|
let UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
|
require('../methods/postcode/filter.js')(Self);
|
||||||
Self.rewriteDbError(function(err) {
|
Self.rewriteDbError(function(err) {
|
||||||
if (err.code === 'ER_DUP_ENTRY')
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
return new UserError(`This postcode already exists`);
|
return new UserError(`This postcode already exists`);
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
|
const SalixError = require('../../util/salixError');
|
||||||
const UserError = require('../../util/user-error');
|
const UserError = require('../../util/user-error');
|
||||||
const logToConsole = require('strong-error-handler/lib/logger');
|
const logToConsole = require('strong-error-handler/lib/logger');
|
||||||
|
|
||||||
module.exports = function() {
|
module.exports = function() {
|
||||||
return function(err, req, res, next) {
|
return function(err, req, res, next) {
|
||||||
// Thrown user errors
|
// Thrown user errors
|
||||||
if (err instanceof UserError) {
|
if (err instanceof SalixError) {
|
||||||
err.message = req.__(err.message, ...err.translateArgs);
|
err.message = req.__(err.message, ...err.translateArgs);
|
||||||
return next(err);
|
return next(err);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +14,7 @@ module.exports = function() {
|
||||||
if (err.statusCode == 422) {
|
if (err.statusCode == 422) {
|
||||||
try {
|
try {
|
||||||
let code;
|
let code;
|
||||||
let messages = err.details.messages;
|
let {messages} = err.details;
|
||||||
for (code in messages) break;
|
for (code in messages) break;
|
||||||
err.message = req.__(messages[code][0]);
|
err.message = req.__(messages[code][0]);
|
||||||
return next(err);
|
return next(err);
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
module.exports = class ForbiddenError extends Error {
|
const SalixError = require('./salixError');
|
||||||
|
module.exports = class ForbiddenError extends SalixError {
|
||||||
constructor(message, code, ...translateArgs) {
|
constructor(message, code, ...translateArgs) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'ForbiddenError';
|
this.name = ForbiddenError.name;
|
||||||
this.statusCode = 403;
|
this.statusCode = 403;
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.translateArgs = translateArgs;
|
this.translateArgs = translateArgs;
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = class SalixError extends Error {
|
||||||
|
constructor(message) {
|
||||||
|
super(message);
|
||||||
|
}
|
||||||
|
};
|
|
@ -4,10 +4,11 @@
|
||||||
* the final user, so they cannot contain sensitive data and must
|
* the final user, so they cannot contain sensitive data and must
|
||||||
* be understandable by people who do not have a technical profile.
|
* be understandable by people who do not have a technical profile.
|
||||||
*/
|
*/
|
||||||
module.exports = class UserError extends Error {
|
const SalixError = require('./salixError');
|
||||||
|
module.exports = class UserError extends SalixError {
|
||||||
constructor(message, code, ...translateArgs) {
|
constructor(message, code, ...translateArgs) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = 'UserError';
|
this.name = UserError.name;
|
||||||
this.statusCode = 400;
|
this.statusCode = 400;
|
||||||
this.code = code;
|
this.code = code;
|
||||||
this.translateArgs = translateArgs;
|
this.translateArgs = translateArgs;
|
||||||
|
|
|
@ -42,14 +42,15 @@
|
||||||
translate-attr="{title: 'Set as default'}">
|
translate-attr="{title: 'Set as default'}">
|
||||||
</vn-icon-button>
|
</vn-icon-button>
|
||||||
</vn-none>
|
</vn-none>
|
||||||
<vn-one
|
<vn-one
|
||||||
style="overflow: hidden; min-width: 14em;">
|
style="overflow: hidden; min-width: 14em;">
|
||||||
<div class="ellipsize"><b>{{::address.nickname}} - #{{::address.id}}</b></div>
|
<div class="ellipsize"><b>{{::address.nickname}} - #{{::address.id}}</b></div>
|
||||||
<div class="ellipsize" name="street">{{::address.street}}</div>
|
<div class="ellipsize" name="street">{{::address.street}}</div>
|
||||||
<div class="ellipsize">
|
<div class="ellipsize">
|
||||||
<span ng-show="::address.postalCode">{{::address.postalCode}} -</span>
|
<span ng-show="::address.postalCode">{{::address.postalCode}} -</span>
|
||||||
<span ng-show="::address.city">{{::address.city}},</span>
|
<span ng-show="::address.city">{{::address.city}},</span>
|
||||||
{{::address.province.name}}
|
<span ng-show="::address.province.name">{{::address.province.name}},</span>
|
||||||
|
{{::address.province.country.country}}
|
||||||
</div>
|
</div>
|
||||||
<div class="ellipsize">
|
<div class="ellipsize">
|
||||||
{{::address.phone}}<span ng-if="::address.mobile">, </span>
|
{{::address.phone}}<span ng-if="::address.mobile">, </span>
|
||||||
|
@ -72,7 +73,7 @@
|
||||||
class="vn-hide-narrow vn-px-md border-solid-left"
|
class="vn-hide-narrow vn-px-md border-solid-left"
|
||||||
style="height: 6em; overflow: auto;">
|
style="height: 6em; overflow: auto;">
|
||||||
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'vn-pt-sm': $index}">
|
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'vn-pt-sm': $index}">
|
||||||
<b>{{::observation.observationType.description}}:</b>
|
<b>{{::observation.observationType.description}}:</b>
|
||||||
<span>{{::observation.description}}</span>
|
<span>{{::observation.description}}</span>
|
||||||
</vn-one>
|
</vn-one>
|
||||||
</vn-vertical>
|
</vn-vertical>
|
||||||
|
|
|
@ -33,7 +33,13 @@ class Controller extends Section {
|
||||||
}, {
|
}, {
|
||||||
relation: 'province',
|
relation: 'province',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id', 'name']
|
fields: ['id', 'name', 'countryFk'],
|
||||||
|
include: {
|
||||||
|
relation: 'country',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'country']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
Loading…
Reference in New Issue