refs #6184 saveCmr #1788

Merged
guillermo merged 58 commits from 6184-saveCmr into dev 2024-02-13 06:47:06 +00:00
9 changed files with 208 additions and 12 deletions
Showing only changes of commit 6701f70a33 - Show all commits

View File

@ -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];
};
};

View File

@ -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;
}
});
});

View File

@ -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`);

View File

@ -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);

View File

@ -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;

View File

@ -0,0 +1,5 @@
module.exports = class SalixError extends Error {
constructor(message) {
super(message);
}
};

View File

@ -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;

View File

@ -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>

View File

@ -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']
}
}
} }
} }
] ]