#599 Advanced ticket search, eslint rules fixed, e2e ticket_04 refactor

This commit is contained in:
Juan 2018-10-30 13:58:02 +01:00
parent ddcd7baaf6
commit 260f548019
19 changed files with 696 additions and 642 deletions

View File

@ -26,5 +26,7 @@ rules:
bracketSpacing: 0
space-infix-ops: 1
prefer-const: 0
curly: ["error", "multi-or-nest"]
indent: [error, 4]
curly: [error, multi]
indent: [error, 4]
arrow-parens: [error, as-needed]
no-focused-tests: 0

View File

@ -203,7 +203,7 @@ export default class CrudModel extends ModelProxy {
this.canceler = this.$q.defer();
let params = Object.assign(
{filter: filter},
{filter},
this.buildParams()
);
let options = {

View File

@ -1,6 +1,7 @@
import ngModule from '../../module';
import Component from '../../lib/component';
import './style.scss';
import {buildFilter} from 'vn-loopback/common/filter.js';
/**
* An input specialized to perform searches, it allows to use a panel
@ -84,41 +85,28 @@ export default class Controller extends Component {
this.pushFilterToState(this.filter);
if (this.onSearch)
this.onSearch({filter: this.filter});
this.onSearch({$params: this.filter});
if (this.model) {
let and = [];
let where = buildFilter(this.filter,
(param, value) => this.exprBuilder({param, value}));
let userParams = {};
let hasParams = false;
for (let param in this.filter) {
let value = this.filter[param];
if (value == null) continue;
let expr = this.exprBuilder({param, value});
if (expr)
and.push(expr);
if (this.paramBuilder) {
if (this.paramBuilder)
for (let param in this.filter) {
let value = this.filter[param];
if (value == null) continue;
let expr = this.paramBuilder({param, value});
if (expr) {
Object.assign(userParams, expr);
hasParams = true;
}
}
}
let where;
if (and.length == 1)
where = and[0];
else if (and.length > 1)
where = {and};
else
where = null;
this.model.applyFilter(
and.length > 0 ? {where: where} : null,
where ? {where} : null,
hasParams ? userParams : null
);
}
@ -235,7 +223,7 @@ ngModule.component('vnSearchbar', {
template: require('./searchbar.html'),
bindings: {
filter: '<?',
onSearch: '&',
onSearch: '&?',
panel: '@',
model: '<?',
exprBuilder: '&?',

View File

@ -1,9 +1,9 @@
<vn-crud-model
vn-id="model"
url="/ticket/api/Tickets/filter"
filter="::$ctrl.filter"
limit="20"
data="tickets"
order="shipped DESC"
auto-load="false">
</vn-crud-model>
<div margin-medium>
@ -11,8 +11,7 @@
<vn-card pad-medium-h>
<vn-searchbar
panel="vn-ticket-search-panel"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)"
on-search="model.applyFilter(null, $params);"
vn-focus>
</vn-searchbar>
</vn-card>
@ -85,10 +84,12 @@
scroll-selector="ui-view">
</vn-pagination>
</div>
<vn-dialog class="dialog-summary"
vn-id="dialog-summary-ticket">
<vn-dialog
vn-id="summary"
class="dialog-summary">
<tpl-body>
<vn-ticket-summary ticket="$ctrl.ticketSelected"></vn-ticket-summary>
<vn-ticket-summary ticket="$ctrl.selectedTicket"></vn-ticket-summary>
</tpl-body>
</vn-dialog>
<vn-client-descriptor-popover vn-id="descriptor"></vn-client-descriptor-popover>
<vn-client-descriptor-popover vn-id="descriptor">
</vn-client-descriptor-popover>

View File

@ -2,32 +2,8 @@ import ngModule from '../module';
export default class Controller {
constructor($scope) {
this.$scope = $scope;
this.ticketSelected = null;
this.filter = {
order: 'shipped DESC'
};
}
exprBuilder(param, value) {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {id: value}
: {nickname: {like: value}};
case 'from':
return {shipped: {gte: value}};
case 'to':
return {shipped: {lte: value}};
case 'nickname':
return {[param]: {like: value}};
case 'id':
case 'clientFk':
case 'agencyModeFk':
case 'warehouseFk':
return {[param]: value};
}
this.$ = $scope;
this.selectedTicket = null;
}
compareDate(date) {
@ -40,28 +16,23 @@ export default class Controller {
if (comparation == 0)
return 'warning';
if (comparation < 0)
return 'success';
}
showDescriptor(event, clientFk) {
this.$scope.descriptor.clientFk = clientFk;
this.$scope.descriptor.parent = event.target;
this.$scope.descriptor.show();
event.preventDefault();
event.stopImmediatePropagation();
}
onDescriptorLoad() {
this.$scope.popover.relocate();
this.$.descriptor.clientFk = clientFk;
this.$.descriptor.parent = event.target;
this.$.descriptor.show();
}
preview(event, ticket) {
event.preventDefault();
event.stopImmediatePropagation();
this.$scope.dialogSummaryTicket.show();
this.ticketSelected = ticket;
this.selectedTicket = ticket;
this.$.summary.show();
}
}

View File

@ -1,98 +1,89 @@
import './index.js';
describe('ticket', () => {
describe('Component vnTicketIndex', () => {
let $componentController;
let controller;
describe('Component vnTicketIndex', () => {
let $element;
let $ctrl;
let $window;
let tickets = [{
id: 1,
clientFk: 1,
salesPersonFk: 9,
shipped: new Date(),
nickname: 'Test',
total: 10.5
}];
beforeEach(() => {
angular.mock.module('ticket');
beforeEach(() => {
ngModule('client');
ngModule('item');
ngModule('ticket');
});
beforeEach(inject(($compile, $rootScope, $httpBackend, _$window_) => {
$window = _$window_;
$element = $compile('<vn-ticket-index></vn-ticket-index>')($rootScope);
$ctrl = $element.controller('vnTicketIndex');
$httpBackend.whenGET(/\/ticket\/api\/Tickets\/filter.*/).respond(tickets);
$httpBackend.flush();
}));
afterEach(() => {
$element.remove();
});
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let curDate = new Date();
let result = $ctrl.compareDate(curDate);
expect(result).toEqual('warning');
});
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
controller = $componentController('vnTicketIndex');
}));
it('should return sucess when the date is in the future', () => {
let futureDate = new Date();
futureDate = futureDate.setDate(futureDate.getDate() + 10);
let result = $ctrl.compareDate(futureDate);
describe('exprBuilder()', () => {
it('should return a formated object with the id in case of search', () => {
let param = 'search';
let value = 1;
let result = controller.exprBuilder(param, value);
expect(result).toEqual({id: 1});
});
it('should return a formated object with the nickname in case of search', () => {
let param = 'search';
let value = 'Bruce';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({nickname: {like: 'Bruce'}});
});
it('should return a formated object with the date in case of from', () => {
let param = 'from';
let value = 'Fri Aug 10 2018 11:39:21 GMT+0200';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({shipped: {gte: 'Fri Aug 10 2018 11:39:21 GMT+0200'}});
});
it('should return a formated object with the date in case of to', () => {
let param = 'to';
let value = 'Fri Aug 10 2018 11:39:21 GMT+0200';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({shipped: {lte: 'Fri Aug 10 2018 11:39:21 GMT+0200'}});
});
it('should return a formated object with the nickname in case of nickname', () => {
let param = 'nickname';
let value = 'Bruce';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({nickname: {like: 'Bruce'}});
});
it('should return a formated object with the warehouseFk in case of warehouseFk', () => {
let param = 'warehouseFk';
let value = 'Silla';
let result = controller.exprBuilder(param, value);
expect(result).toEqual({warehouseFk: 'Silla'});
});
expect(result).toEqual('success');
});
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let date = new Date();
let result = controller.compareDate(date);
it('should return undefined when the date is in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 10);
let result = $ctrl.compareDate(pastDate);
expect(result).toEqual('warning');
});
it('should return sucess when the date is in the future', () => {
let futureDate = '2518-05-19T00:00:00.000Z';
let result = controller.compareDate(futureDate);
expect(result).toEqual('success');
});
expect(result).toEqual(undefined);
});
});
describe('preview()', () => {
it('should call preventDefault and stopImmediatePropagation from event and show', () => {
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopImmediatePropagation']);
describe('showDescriptor()', () => {
it('should show the descriptor popover', () => {
spyOn($ctrl.$.descriptor, 'show');
controller.$scope = {dialogSummaryTicket: {show: () => {}}};
spyOn(controller.$scope.dialogSummaryTicket, 'show');
let ticket = {};
controller.preview(event, ticket);
expect(event.preventDefault).toHaveBeenCalledWith();
expect(event.stopImmediatePropagation).toHaveBeenCalledWith();
expect(controller.$scope.dialogSummaryTicket.show).toHaveBeenCalledWith();
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
$ctrl.showDescriptor(event, tickets[0].clientFk);
expect($ctrl.$.descriptor.show).toHaveBeenCalledWith();
});
});
describe('preview()', () => {
it('should show the dialog summary', () => {
spyOn($ctrl.$.summary, 'show');
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
$ctrl.preview(event, tickets[0]);
expect($ctrl.$.summary.show).toHaveBeenCalledWith();
});
});
});

View File

@ -37,20 +37,42 @@
vn-one
label="Agency"
field="filter.agencyModeFk"
url="/api/AgencyModes"
show-field="name"
value-field="id">
<tpl-item>{{name}}</tpl-item>
url="/api/AgencyModes">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Warehouse"
field="filter.warehouseFk"
url="/api/Warehouses"
show-field="name"
value-field="id">
url="/api/Warehouses">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Sales person"
field="filter.salesPersonFk"
url="/api/Workers">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Province"
field="filter.provinceFk"
url="/api/Provinces">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="State"
field="filter.stateFk"
url="/api/States">
</vn-autocomplete>
<vn-check
vn-one
label="My tickets"
field="filter.myTeam">
</vn-check>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>

View File

@ -4,4 +4,8 @@ Nickname: Alias
From: Desde
To: Hasta
Agency: Agencia
Warehouse: Almacén
Warehouse: Almacén
Sales person: Comercial
Province: Provincia
My team: Mi equipo
My tickets: Mis tickets

View File

@ -9,128 +9,108 @@ describe('Ticket Create packages path', () => {
.waitForLogin('employee');
});
it('should click on the Tickets button of the top bar menu', (done) => {
return nightmare
it('should click on the Tickets button of the top bar menu', async () => {
let url = await nightmare
.waitToClick(selectors.globalItems.applicationsMenuButton)
.wait(selectors.globalItems.applicationsMenuVisible)
.waitToClick(selectors.globalItems.ticketsButton)
.wait(selectors.ticketsIndex.searchResult)
.parsedUrl()
.then((url) => {
expect(url.hash).toEqual('#!/ticket/index');
done();
}).catch(done.fail);
.parsedUrl();
expect(url.hash).toEqual('#!/ticket/index');
});
it('should search for the ticket 1', (done) => {
return nightmare
it('should search for the ticket 1', async () => {
let result = await nightmare
.wait(selectors.ticketsIndex.searchResult)
.type(selectors.ticketsIndex.searchTicketInput, 'id:1')
.click(selectors.ticketsIndex.searchButton)
.waitForNumberOfElements(selectors.ticketsIndex.searchResult, 1)
.countElement(selectors.ticketsIndex.searchResult)
.then((result) => {
expect(result).toEqual(1);
done();
}).catch(done.fail);
.countElement(selectors.ticketsIndex.searchResult);
expect(result).toEqual(1);
});
it(`should click on the search result to access to the ticket packages`, (done) => {
return nightmare
it(`should click on the search result to access to the ticket packages`, async () => {
let url = await nightmare
.waitForTextInElement(selectors.ticketsIndex.searchResultAddress, 'address 21')
.waitToClick(selectors.ticketsIndex.searchResult)
.waitToClick(selectors.ticketPackages.packagesButton)
.waitForURL('package/index')
.url()
.then((url) => {
expect(url).toContain('package/index');
done();
}).catch(done.fail);
.url();
expect(url).toContain('package/index');
});
it(`should delete the first package and receive and error to save a new one with blank quantity`, (done) => {
return nightmare
it(`should delete the first package and receive and error to save a new one with blank quantity`, async () => {
let result = await nightmare
.waitToClick(selectors.ticketPackages.firstRemovePackageButton)
.waitToClick(selectors.ticketPackages.addPackageButton)
.waitToClick(selectors.ticketPackages.firstPackageSelect)
.waitToClick(selectors.ticketPackages.firstPackageSelectOptionTwo)
.click(selectors.ticketPackages.savePackagesButton)
.waitForLastSnackbar()
.then((result) => {
expect(result).toEqual('Some fields are invalid');
done();
}).catch(done.fail);
.waitForLastSnackbar();
expect(result).toEqual('Some fields are invalid');
});
it(`should attempt create a new package but receive an error if quantity is a string`, (done) => {
return nightmare
it(`should attempt create a new package but receive an error if quantity is a string`, async () => {
let result = await nightmare
.type(selectors.ticketPackages.firstQuantityInput, 'ninety 9')
.click(selectors.ticketPackages.savePackagesButton)
.waitForLastSnackbar()
.then((result) => {
expect(result).toEqual('Some fields are invalid');
done();
}).catch(done.fail);
.waitForLastSnackbar();
expect(result).toEqual('Some fields are invalid');
});
it(`should attempt create a new package but receive an error if quantity is 0`, (done) => {
return nightmare
it(`should attempt create a new package but receive an error if quantity is 0`, async () => {
let result = await nightmare
.clearInput(selectors.ticketPackages.firstQuantityInput)
.type(selectors.ticketPackages.firstQuantityInput, 0)
.click(selectors.ticketPackages.savePackagesButton)
.waitForLastSnackbar()
.then((result) => {
expect(result).toEqual('Some fields are invalid');
done();
}).catch(done.fail);
.waitForLastSnackbar();
expect(result).toEqual('Some fields are invalid');
});
it(`should attempt create a new package but receive an error if package is blank`, (done) => {
return nightmare
it(`should attempt create a new package but receive an error if package is blank`, async () => {
let result = await nightmare
.clearInput(selectors.ticketPackages.firstQuantityInput)
.type(selectors.ticketPackages.firstQuantityInput, 99)
.click(selectors.ticketPackages.clearPackageSelectButton)
.click(selectors.ticketPackages.savePackagesButton)
.waitForLastSnackbar()
.then((result) => {
expect(result).toEqual('Package cannot be blank');
done();
}).catch(done.fail);
.waitForLastSnackbar();
expect(result).toEqual('Package cannot be blank');
});
it(`should create a new package with correct data`, (done) => {
return nightmare
it(`should create a new package with correct data`, async () => {
let result = await nightmare
.waitToClick(selectors.ticketPackages.firstPackageSelect)
.waitToClick(selectors.ticketPackages.firstPackageSelectOptionTwo)
.waitForTextInInput(selectors.ticketPackages.firstPackageSelect, 'Legendary Box')
.click(selectors.ticketPackages.savePackagesButton)
.waitForLastSnackbar()
.then((result) => {
expect(result).toEqual('Data saved!');
done();
}).catch(done.fail);
.waitForLastSnackbar();
expect(result).toEqual('Data saved!');
});
it(`should confirm the first select is the expected one`, (done) => {
return nightmare
it(`should confirm the first select is the expected one`, async () => {
let result = await nightmare
.click(selectors.ticketSales.saleButton)
.wait(selectors.ticketSales.firstPackageSelect)
.click(selectors.ticketPackages.packagesButton)
.waitForTextInInput(selectors.ticketPackages.firstPackageSelect, 'Legendary Box')
.getInputValue(selectors.ticketPackages.firstPackageSelect)
.then((result) => {
expect(result).toEqual('Legendary Box');
done();
}).catch(done.fail);
.getInputValue(selectors.ticketPackages.firstPackageSelect);
expect(result).toEqual('Legendary Box');
});
it(`should confirm the first quantity is the expected one`, (done) => {
return nightmare
it(`should confirm the first quantity is the expected one`, async () => {
let result = await nightmare
.waitForTextInInput(selectors.ticketPackages.firstQuantityInput, '99')
.getInputValue(selectors.ticketPackages.firstQuantityInput)
.then((result) => {
expect(result).toEqual('99');
done();
}).catch(done.fail);
.getInputValue(selectors.ticketPackages.firstQuantityInput);
expect(result).toEqual('99');
});
});

60
package-lock.json generated
View File

@ -250,7 +250,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
@ -1482,7 +1482,7 @@
"bluebird": {
"version": "3.5.1",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.1.tgz",
"integrity": "sha1-2VUfnemPH82h5oPRfukaBgLuLrk=",
"integrity": "sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA==",
"dev": true
},
"bn.js": {
@ -1677,7 +1677,7 @@
"string-width": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"integrity": "sha1-q5Pyeo3BPSjKyBXEYhQ6bZASrp4=",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
@ -4340,7 +4340,7 @@
"dot-prop": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-4.2.0.tgz",
"integrity": "sha512-tUMXrxlExSW6U2EXiiKGSBVdYgtV8qlHL+C10TsW4PURY/ic+eaysnSkwB4kA/mBlCyy/IKDJ+Lc3wbWeaXtuQ==",
"integrity": "sha1-HxngwuGqDjJ5fEl5nyg3rGr2nFc=",
"dev": true,
"requires": {
"is-obj": "^1.0.0"
@ -4447,7 +4447,7 @@
},
"jsonfile": {
"version": "2.4.0",
"resolved": "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz",
"integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=",
"dev": true,
"requires": {
@ -5545,7 +5545,7 @@
},
"readable-stream": {
"version": "2.3.6",
"resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz",
"integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==",
"dev": true,
"requires": {
@ -10019,7 +10019,7 @@
"jasmine-spec-reporter": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/jasmine-spec-reporter/-/jasmine-spec-reporter-4.2.1.tgz",
"integrity": "sha1-HWMq7ANBZwrTJPkrqEtLMrNeniI=",
"integrity": "sha512-FZBoZu7VE5nR7Nilzy+Np8KuVIOxF4oXDPDknehCYBDE080EnlPu0afdZNmpGDBRCUBv3mj5qgqCRmk6W/K8vg==",
"dev": true,
"requires": {
"colors": "1.1.2"
@ -10141,7 +10141,7 @@
"karma": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/karma/-/karma-1.7.1.tgz",
"integrity": "sha1-hcwI6eCiLXzpzKN8ShvoJPaisa4=",
"integrity": "sha512-k5pBjHDhmkdaUccnC7gE3mBzZjcxyxYsYVaqiL2G5AqlfLyBO5nw2VdNK+O16cveEPd/gIOWULH7gkiYYwVNHg==",
"dev": true,
"requires": {
"bluebird": "^3.3.0",
@ -11724,12 +11724,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -11749,7 +11751,8 @@
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
@ -11897,6 +11900,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -19187,7 +19191,7 @@
"split2": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-2.2.0.tgz",
"integrity": "sha512-RAb22TG39LhI31MbreBgIuKiIKhVsawfTgEGqKHTK87aG+ul/PB8Sqoi3I7kVdRWiCfrKxK3uo4/YUkpNvhPbw==",
"integrity": "sha1-GGsldbz4PoW30YRldWI47k7kJJM=",
"dev": true,
"requires": {
"through2": "^2.0.2"
@ -19789,7 +19793,7 @@
"touch": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz",
"integrity": "sha512-WBx8Uy5TLtOSRtIq+M03/sKDrXCLHxwDcquSP2c43Le03/9serjQBIztjRz6FkJez9D/hleyAXTBGLwwZUw9lA==",
"integrity": "sha1-/jZfX3XsntTlaCXgu3bSSrdK+Ds=",
"dev": true,
"requires": {
"nopt": "~1.0.10"
@ -20286,7 +20290,7 @@
"useragent": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/useragent/-/useragent-2.3.0.tgz",
"integrity": "sha1-IX+UOtVAyyEoZYqyP8lg9qiMmXI=",
"integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==",
"dev": true,
"requires": {
"lru-cache": "4.1.x",
@ -20754,12 +20758,14 @@
"balanced-match": {
"version": "1.0.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"brace-expansion": {
"version": "1.1.11",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@ -20774,17 +20780,20 @@
"code-point-at": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"concat-map": {
"version": "0.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"console-control-strings": {
"version": "1.1.0",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"core-util-is": {
"version": "1.0.2",
@ -20901,7 +20910,8 @@
"inherits": {
"version": "2.0.3",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"ini": {
"version": "1.3.5",
@ -20913,6 +20923,7 @@
"version": "1.0.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -20927,6 +20938,7 @@
"version": "3.0.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@ -20934,12 +20946,14 @@
"minimist": {
"version": "0.0.8",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"minipass": {
"version": "2.2.4",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"safe-buffer": "^5.1.1",
"yallist": "^3.0.0"
@ -20958,6 +20972,7 @@
"version": "0.5.1",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"minimist": "0.0.8"
}
@ -21038,7 +21053,8 @@
"number-is-nan": {
"version": "1.0.1",
"bundled": true,
"dev": true
"dev": true,
"optional": true
},
"object-assign": {
"version": "4.1.1",
@ -21050,6 +21066,7 @@
"version": "1.4.0",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"wrappy": "1"
}
@ -21171,6 +21188,7 @@
"version": "1.0.2",
"bundled": true,
"dev": true,
"optional": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -22930,7 +22948,7 @@
"write-file-atomic": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz",
"integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==",
"integrity": "sha1-H/YVdcLipOjlENb6TiQ8zhg5mas=",
"dev": true,
"requires": {
"graceful-fs": "^4.1.11",

View File

@ -29,46 +29,49 @@ module.exports = Self => {
Self.filter = async (filter, params) => {
let stmt = new ParameterizedSQL(
`SELECT
r.id,
r.isConciliate,
r.payed,
c.code AS company,
r.created,
'' description,
0 AS debit,
r.amountPaid AS credit,
r.bankFk,
firstName,
name,
r.clientFk
FROM vn.receipt r
LEFT JOIN vn.worker w ON w.id = r.workerFk
JOIN vn.company c ON c.id = r.companyFk
WHERE clientFk = ?
UNION ALL
SELECT
i.id,
TRUE,
i.dued,
c.code AS company,
i.created,
CONCAT(' N/FRA ', i.ref) description,
i.amount AS debit,
0 credit,
NULL bank,
NULL firstName,
NULL name,
i.clientFk
FROM vn.invoiceOut i
JOIN vn.company c ON c.id = i.companyFk
WHERE clientFk = ?
`SELECT * FROM (
SELECT
r.id,
r.isConciliate,
r.payed,
c.code AS company,
r.created,
'' description,
0 AS debit,
r.amountPaid AS credit,
r.bankFk,
firstName,
name,
r.clientFk
FROM vn.receipt r
LEFT JOIN vn.worker w ON w.id = r.workerFk
JOIN vn.company c ON c.id = r.companyFk
WHERE clientFk = ?
UNION ALL
SELECT
i.id,
TRUE,
i.dued,
c.code,
i.created,
CONCAT(' N/FRA ', i.ref),
i.amount,
0 credit,
NULL,
NULL,
NULL,
i.clientFk
FROM vn.invoiceOut i
JOIN vn.company c ON c.id = i.companyFk
WHERE clientFk = ?
) t
ORDER BY payed DESC, created DESC`, [
params.clientFk,
params.clientFk
]);
params.clientFk,
params.clientFk
]
);
stmt.merge(Self.buildPagination(filter));
stmt.merge(Self.makeLimit(filter));
return await Self.rawStmt(stmt);
};
};

View File

@ -46,15 +46,7 @@ function mergeWhere(src, dst) {
let and = [];
if (src) and.push(src);
if (dst) and.push(dst);
switch (and.length) {
case 0:
return undefined;
case 1:
return and[0];
default:
return {and};
}
return simplifyOperation(and, 'and');
}
/**
@ -80,13 +72,40 @@ function mergeFilters(src, dst) {
res.order = src.order;
if (src.limit)
res.limit = src.limit;
if (src.offset)
res.offset = src.offset;
if (src.skip)
res.skip = src.skip;
return res;
}
function simplifyOperation(operation, operator) {
switch(operation.length) {
case 0:
return undefined;
case 1:
return operation[0];
default:
return {[operator]: operation};
}
}
function buildFilter(params, builderFunc) {
let and = [];
for (let param in params) {
let value = params[param];
if (value == null) continue;
let expr = builderFunc(param, value);
if (expr) and.push(expr);
}
return simplifyOperation(and, 'and');
}
module.exports = {
fieldsToObject: fieldsToObject,
mergeFields: mergeFields,
mergeWhere: mergeWhere,
mergeFilters: mergeFilters
mergeFilters: mergeFilters,
buildFilter: buildFilter
};

View File

@ -37,5 +37,6 @@
"You don't have enough privileges to do that": "You don't have enough privileges to do that",
"You don't have enough privileges to change that field": "You don't have enough privileges to change that field",
"You don't have enough privileges": "You don't have enough privileges",
"You can't make changes on a client with verified data": "You can't make changes on a client with verified data"
"You can't make changes on a client with verified data": "You can't make changes on a client with verified data",
"That payment method requires a BIC": "That payment method requires a BIC"
}

View File

@ -1,16 +1,68 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('../../filter.js').buildFilter;
const mergeFilters = require('../../filter.js').mergeFilters;
module.exports = Self => {
Self.remoteMethod('filter', {
description: 'Find all instances of the model matched by filter from the data source.',
accessType: 'READ',
accepts: [
{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}, {
arg: 'filter',
type: 'Object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}, {
arg: 'search',
type: 'String',
description: `If it's and integer searchs by id, otherwise it searchs by nickname`
}, {
arg: 'from',
type: 'Date',
description: `The from date filter`
}, {
arg: 'to',
type: 'Date',
description: `The to date filter`
}, {
arg: 'nickname',
type: 'String',
description: `The nickname filter`
}, {
arg: 'id',
type: 'Integer',
description: `The ticket id filter`
}, {
arg: 'clientFk',
type: 'Integer',
description: `The client id filter`
}, {
arg: 'agencyModeFk',
type: 'Integer',
description: `The agency mode id filter`
}, {
arg: 'warehouseFk',
type: 'Integer',
description: `The warehouse id filter`
}, {
arg: 'salesPersonFk',
type: 'Integer',
description: `The salesperson id filter`
}, {
arg: 'provinceFk',
type: 'Integer',
description: `The province id filter`
}, {
arg: 'stateFk',
type: 'Number',
description: `The state id filter`
}, {
arg: 'myTeam',
type: 'Boolean',
description: `Whether to show only tickets for the current logged user team (Ignored until implemented)`
}
],
returns: {
@ -18,12 +70,52 @@ module.exports = Self => {
root: true
},
http: {
path: `/filter`,
path: '/filter',
verb: 'GET'
}
});
Self.filter = async filter => {
Self.filter = async (ctx, filter) => {
let conn = Self.dataSource.connector;
// TODO: Using the current worker id until WorkerTeam model is created
let worker = await Self.app.models.Worker.findOne({
fields: ['id'],
where: {userFk: ctx.req.accessToken.userId}
});
let teamIds = [worker && worker.id];
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'t.id': value}
: {'t.nickname': {like: `%${value}%`}};
case 'from':
return {'t.shipped': {gte: value}};
case 'to':
return {'t.shipped': {lte: value}};
case 'nickname':
return {'t.nickname': {like: `%${value}%`}};
case 'salesPersonFk':
return {'c.salesPersonFk': value};
case 'provinceFk':
return {'a.provinceFk': value};
case 'stateFk':
return {'ts.stateFk': value};
case 'myTeam':
return {'c.salesPersonFk': {inq: teamIds}};
case 'id':
case 'clientFk':
case 'agencyModeFk':
case 'warehouseFk':
param = `t.${param}`;
return {[param]: value};
}
});
filter = mergeFilters(filter, {where});
let stmts = [];
let stmt;
@ -31,12 +123,13 @@ module.exports = Self => {
stmt = new ParameterizedSQL(
`CREATE TEMPORARY TABLE tmp.filter
(INDEX (id)) ENGINE = MEMORY
(INDEX (id))
ENGINE = MEMORY
SELECT
t.id,
t.shipped,
t.nickname,
t.refFk,
t.shipped,
t.nickname,
t.refFk,
t.routeFk,
t.agencyModeFk,
t.warehouseFk,
@ -44,34 +137,36 @@ module.exports = Self => {
c.salesPersonFk,
a.provinceFk,
ts.stateFk,
p.name AS province,
w.name AS warehouse,
am.name AS agencyMode,
st.name AS state,
p.name AS province,
w.name AS warehouse,
am.name AS agencyMode,
st.name AS state,
wk.name AS salesPerson
FROM ticket t
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk`);
stmt.merge(Self.buildSuffix(filter, 't'));
FROM ticket t
LEFT JOIN address a ON a.id = t.addressFk
LEFT JOIN province p ON p.id = a.provinceFk
LEFT JOIN warehouse w ON w.id = t.warehouseFk
LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
LEFT JOIN state st ON st.id = ts.stateFk
LEFT JOIN client c ON c.id = t.clientFk
LEFT JOIN worker wk ON wk.id = c.salesPersonFk`);
stmt.merge(conn.makeSuffix(filter));
stmts.push(stmt);
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticket');
stmts.push(`
CREATE TEMPORARY TABLE tmp.ticket
(INDEX (ticketFk)) ENGINE = MEMORY
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk FROM tmp.filter`);
stmts.push('CALL ticketGetTotal()');
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.ticketGetProblems');
stmts.push(`
CREATE TEMPORARY TABLE tmp.ticketGetProblems
(INDEX (ticketFk)) ENGINE = MEMORY
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk, clientFk, warehouseFk, shipped
FROM tmp.filter`);
stmts.push('CALL ticketGetProblems()');
@ -84,7 +179,7 @@ module.exports = Self => {
FROM tmp.filter f
LEFT JOIN tmp.ticketProblems tp ON tp.ticketFk = f.id
LEFT JOIN tmp.ticketTotal tt ON tt.ticketFk = f.id`);
stmt.merge(Self.buildOrderBy(filter));
stmt.merge(conn.makeOrderBy(filter.order));
let ticketsIndex = stmts.push(stmt) - 1;
stmts.push(
@ -95,7 +190,7 @@ module.exports = Self => {
tmp.ticketGetProblems`);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await Self.rawStmt(sql);
let result = await conn.executeStmt(sql);
return result[ticketsIndex];
};

View File

@ -1,9 +1,11 @@
const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket filter()', () => {
it('should call the filter method', async() => {
it('should call the filter method', async () => {
let ctx = {req: {accessToken: {userId: 9}}};
let filter = {order: 'shipped DESC'};
let result = await app.models.Ticket.filter(filter);
let result = await app.models.Ticket.filter(ctx, filter);
let ticketId = result[0].id;
expect(ticketId).toEqual(15);

View File

@ -5,6 +5,10 @@ const UserError = require('../helpers').UserError;
module.exports = function(Self) {
Self.ParameterizedSQL = ParameterizedSQL;
require('../methods/vn-model/validateBinded')(Self);
require('../methods/vn-model/rewriteDbError')(Self);
require('../methods/vn-model/getSetValues')(Self);
Self.setup = function() {
Self.super_.setup.call(this);
@ -93,7 +97,7 @@ module.exports = function(Self) {
};
Self.remoteMethodCtx = function(methodName, args) {
var ctx = {
let ctx = {
arg: 'context',
type: 'object',
http: function(ctx) {
@ -131,9 +135,9 @@ module.exports = function(Self) {
let options = {transaction: transaction};
try {
if (actions.delete && actions.delete.length) {
if (actions.delete && actions.delete.length)
await this.destroyAll({id: {inq: actions.delete}}, options);
}
if (actions.update) {
try {
let promises = [];
@ -159,131 +163,6 @@ module.exports = function(Self) {
}
};
/**
* Executes an SQL query
* @param {String} query - SQL query
* @param {Object} params - Query data params
* @param {Object} options - Query options (Ex: {transaction})
* @param {Object} cb - Callback
* @return {Object} Connector promise
*/
Self.rawSql = function(query, params, options = {}, cb) {
var connector = this.dataSource.connector;
return new Promise(function(resolve, reject) {
connector.execute(query, params, options, function(error, response) {
if (cb)
cb(error, response);
if (error)
reject(error);
else
resolve(response);
});
});
};
/**
* Executes an SQL query from an Stmt
* @param {ParameterizedSql} stmt - Stmt object
* @param {Object} options - Query options (Ex: {transaction})
* @return {Object} Connector promise
*/
Self.rawStmt = function(stmt, options = {}) {
return this.rawSql(stmt.sql, stmt.params, options);
};
Self.escapeName = function(name) {
return this.dataSource.connector.escapeName(name);
};
/**
* Constructs SQL where clause from Loopback filter
* @param {Object} filter - filter
* @param {String} tableAlias - Query main table alias
* @return {String} Builded SQL where
*/
Self.buildWhere = function(filter, tableAlias) {
let connector = this.dataSource.connector;
let wrappedConnector = Object.create(connector);
wrappedConnector.columnEscaped = function(model, property) {
let sql = tableAlias
? connector.escapeName(tableAlias) + '.'
: '';
return sql + connector.columnEscaped(model, property);
};
return wrappedConnector.makeWhere(this.modelName, filter.where);
};
/**
* Constructs SQL limit clause from Loopback filter
* @param {Object} filter - filter
* @return {String} Builded SQL limit
*/
Self.buildLimit = function(filter) {
let sql = new ParameterizedSQL('');
this.dataSource.connector.applyPagination(this.modelName, sql, filter);
return sql;
};
/**
* Constructs SQL order clause from Loopback filter
* @param {Object} filter - filter
* @return {String} Builded SQL order
*/
Self.buildOrderBy = function(filter) {
let order = filter.order;
if (!order)
return '';
if (typeof order === 'string')
order = [order];
let clauses = [];
for (let clause of order) {
let sqlOrder = '';
let t = clause.split(/[\s,]+/);
let names = t[0].split('.');
if (names.length > 1)
sqlOrder += this.escapeName(names[0]) + '.';
sqlOrder += this.escapeName(names[names.length - 1]);
if (t.length > 1)
sqlOrder += ' ' + (t[1].toUpperCase() == 'ASC' ? 'ASC' : 'DESC');
clauses.push(sqlOrder);
}
return `ORDER BY ${clauses.join(', ')}`;
};
/**
* Constructs SQL pagination from Loopback filter
* @param {Object} filter - filter
* @return {String} Builded SQL pagination
*/
Self.buildPagination = function(filter) {
return ParameterizedSQL.join([
this.buildOrderBy(filter),
this.buildLimit(filter)
]);
};
/**
* Constructs SQL filter including where, order and limit
* clauses from Loopback filter
* @param {Object} filter - filter
* @param {String} tableAlias - Query main table alias
* @return {String} Builded SQL limit
*/
Self.buildSuffix = function(filter, tableAlias) {
return ParameterizedSQL.join([
this.buildWhere(filter, tableAlias),
this.buildPagination(filter)
]);
};
Self.checkAcls = async function(ctx, actionType) {
let userId = ctx.req.accessToken.userId;
let models = this.app.models;
@ -294,9 +173,8 @@ module.exports = function(Self) {
function modifiedProperties(data) {
let properties = [];
for (property in data) {
for (property in data)
properties.push(property);
}
return properties;
}
@ -350,10 +228,38 @@ module.exports = function(Self) {
return this.checkAcls(ctx, 'insert');
};
// Action bindings
require('../methods/vn-model/validateBinded')(Self);
// Handle MySql errors
require('../methods/vn-model/rewriteDbError')(Self);
// Get table set of values
require('../methods/vn-model/getSetValues')(Self);
/*
* Shortcut to VnMySQL.executeP()
*/
Self.rawSql = function(query, params, options, cb) {
return this.dataSource.connector.executeP(query, params, options, cb);
};
/*
* Shortcut to VnMySQL.executeStmt()
*/
Self.rawStmt = function(stmt, options) {
return this.dataSource.connector.executeStmt(stmt, options);
};
/*
* Shortcut to VnMySQL.makeLimit()
*/
Self.makeLimit = function(filter) {
return this.dataSource.connector.makeLimit(filter);
};
/*
* Shortcut to VnMySQL.makeSuffix()
*/
Self.makeSuffix = function(filter) {
return this.dataSource.connector.makeSuffix(filter);
};
/*
* Shortcut to VnMySQL.buildModelSuffix()
*/
Self.buildSuffix = function(filter, tableAlias) {
return this.dataSource.connector.buildModelSuffix(this.modelName, filter, tableAlias);
};
};

View File

@ -1,19 +1,235 @@
var mysql = require('mysql');
const mysql = require('mysql');
const loopbackConnector = require('loopback-connector');
const SqlConnector = loopbackConnector.SqlConnector;
const ParameterizedSQL = loopbackConnector.ParameterizedSQL;
var MySQL = require('loopback-connector-mysql').MySQL;
var EnumFactory = require('loopback-connector-mysql').EnumFactory;
var debug = require('debug')('loopback-connector-sql');
const MySQL = require('loopback-connector-mysql').MySQL;
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
const fs = require('fs');
exports.initialize = function(dataSource, callback) {
class VnMySQL extends MySQL {
constructor(settings) {
super();
SqlConnector.call(this, 'mysql', settings);
}
toColumnValue(prop, val) {
if (val == null || !prop || prop.type !== Date)
return MySQL.prototype.toColumnValue.call(this, prop, val);
val = new Date(val);
return val.getFullYear() + '-' +
fillZeros(val.getMonth() + 1) + '-' +
fillZeros(val.getDate()) + ' ' +
fillZeros(val.getHours()) + ':' +
fillZeros(val.getMinutes()) + ':' +
fillZeros(val.getSeconds());
function fillZeros(v) {
return v < 10 ? '0' + v : v;
}
}
/**
* Promisified version of execute().
*
* @param {String} query The SQL query string
* @param {Array} params The query parameters
* @param {Object} options The loopback options
* @param {Function} cb The callback
* @return {Promise} The operation promise
*/
executeP(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);
});
});
}
/**
* Executes an SQL query from an Stmt.
*
* @param {ParameterizedSql} stmt - Stmt object
* @param {Object} options Query options (Ex: {transaction})
* @return {Object} Connector promise
*/
executeStmt(stmt, options) {
return this.executeP(stmt.sql, stmt.params, options);
}
/**
* Executes a query from an SQL script.
*
* @param {String} sqlScript The sql script file
* @param {Array} params The query parameters
* @param {Object} options Query options (Ex: {transaction})
* @return {Object} Connector promise
*/
executeScript(sqlScript, params, options) {
return new Promise((resolve, reject) => {
fs.readFile(sqlScript, 'utf8', (err, contents) => {
if (err) return reject(err);
this.execute(contents, params, options)
.then(resolve, reject);
});
});
}
/**
* Build the SQL WHERE clause for the where object without checking that
* properties exists in the model.
*
* @param {object} where An object for the where conditions
* @return {ParameterizedSQL} The SQL WHERE clause
*/
makeWhere(where) {
let wrappedConnector = Object.create(this);
Object.assign(wrappedConnector, {
getModelDefinition() {
return {
properties: new Proxy({}, {
get: () => true
})
};
},
toColumnValue(_, val) {
return val;
},
columnEscaped(_, property) {
return this.escapeName(property);
}
});
return wrappedConnector.buildWhere(null, where);
}
/**
* Constructs SQL order clause from Loopback filter.
*
* @param {Object} order The order definition
* @return {String} Built SQL order
*/
makeOrderBy(order) {
if (!order)
return '';
if (typeof order === 'string')
order = [order];
let clauses = [];
for (let clause of order) {
let sqlOrder = '';
let t = clause.split(/[\s,]+/);
sqlOrder += this.escapeName(t[0]);
if (t.length > 1)
sqlOrder += ' ' + (t[1].toUpperCase() == 'ASC' ? 'ASC' : 'DESC');
clauses.push(sqlOrder);
}
return `ORDER BY ${clauses.join(', ')}`;
}
/**
* Constructs SQL limit clause from Loopback filter.
*
* @param {Object} filter The loopback filter
* @return {String} Built SQL limit
*/
makeLimit(filter) {
let limit = parseInt(filter.limit);
let offset = parseInt(filter.offset || filter.skip);
return this._buildLimit(null, limit, offset);
}
/**
* Constructs SQL pagination from Loopback filter.
*
* @param {Object} filter The loopback filter
* @return {String} Built SQL pagination
*/
makePagination(filter) {
return ParameterizedSQL.join([
this.makeOrderBy(filter.order),
this.makeLimit(filter)
]);
}
/**
* Constructs SQL filter including where, order and limit
* clauses from Loopback filter.
*
* @param {Object} filter The loopback filter
* @return {String} Built SQL filter
*/
makeSuffix(filter) {
return ParameterizedSQL.join([
this.makeWhere(filter.where),
this.makePagination(filter)
]);
}
/**
* Constructs SQL where clause from Loopback filter discarding
* properties that not pertain to the model. If defined, appends
* the table alias to each field.
*
* @param {String} model The model name
* @param {Object} where The loopback where filter
* @param {String} tableAlias Query main table alias
* @return {String} Built SQL where
*/
buildModelWhere(model, where, tableAlias) {
let parent = this;
let wrappedConnector = Object.create(this);
Object.assign(wrappedConnector, {
columnEscaped(model, property) {
let sql = tableAlias
? this.escapeName(tableAlias) + '.'
: '';
return sql + parent.columnEscaped(model, property);
}
});
return wrappedConnector.buildWhere(model, where);
}
/**
* Constructs SQL where clause from Loopback filter discarding
* properties that not pertain to the model. If defined, appends
* the table alias to each field.
*
* @param {String} model The model name
* @param {Object} filter The loopback filter
* @param {String} tableAlias Query main table alias
* @return {String} Built SQL suffix
*/
buildModelSuffix(model, filter, tableAlias) {
return ParameterizedSQL.join([
this.buildModelWhere(model, filter.where, tableAlias),
this.makePagination(filter)
]);
}
}
exports.VnMySQL = VnMySQL;
exports.initialize = function initialize(dataSource, callback) {
dataSource.driver = mysql;
dataSource.connector = new VnMySQL(dataSource.settings);
dataSource.connector.dataSource = dataSource;
var modelBuilder = dataSource.modelBuilder;
var defineType = modelBuilder.defineValueType ?
const modelBuilder = dataSource.modelBuilder;
const defineType = modelBuilder.defineValueType ?
modelBuilder.defineValueType.bind(modelBuilder) :
modelBuilder.constructor.registerType.bind(modelBuilder.constructor);
@ -26,170 +242,7 @@ exports.initialize = function(dataSource, callback) {
process.nextTick(function() {
callback();
});
} else {
} else
dataSource.connector.connect(callback);
}
}
};
exports.VnMySQL = VnMySQL;
function VnMySQL(settings) {
SqlConnector.call(this, 'mysql', settings);
}
VnMySQL.prototype = Object.create(MySQL.prototype);
VnMySQL.constructor = VnMySQL;
VnMySQL.prototype.toColumnValue = function(prop, val) {
if (val == null || !prop || prop.type !== Date)
return MySQL.prototype.toColumnValue.call(this, prop, val);
val = new Date(val);
return val.getFullYear() + '-' +
fillZeros(val.getMonth() + 1) + '-' +
fillZeros(val.getDate()) + ' ' +
fillZeros(val.getHours()) + ':' +
fillZeros(val.getMinutes()) + ':' +
fillZeros(val.getSeconds());
function fillZeros(v) {
return v < 10 ? '0' + v : v;
}
};
/**
* Private make method
* @param {Object} model Model instance
* @param {Object} where Where filter
* @return {ParameterizedSQL} Parametized object
*/
VnMySQL.prototype._makeWhere = function(model, where) {
let columnValue;
let sqlExp;
if (!where) {
return new ParameterizedSQL('');
}
if (typeof where !== 'object' || Array.isArray(where)) {
debug('Invalid value for where: %j', where);
return new ParameterizedSQL('');
}
var self = this;
var props = self.getModelDefinition(model).properties;
var whereStmts = [];
for (var key in where) {
var stmt = new ParameterizedSQL('', []);
// Handle and/or operators
if (key === 'and' || key === 'or') {
var branches = [];
var branchParams = [];
var clauses = where[key];
if (Array.isArray(clauses)) {
for (var i = 0, n = clauses.length; i < n; i++) {
var stmtForClause = self._makeWhere(model, clauses[i]);
if (stmtForClause.sql) {
stmtForClause.sql = '(' + stmtForClause.sql + ')';
branchParams = branchParams.concat(stmtForClause.params);
branches.push(stmtForClause.sql);
}
}
stmt.merge({
sql: branches.join(' ' + key.toUpperCase() + ' '),
params: branchParams
});
whereStmts.push(stmt);
continue;
}
// The value is not an array, fall back to regular fields
}
var p = props[key];
// eslint-disable one-var
var expression = where[key];
var columnName = self.columnEscaped(model, key);
// eslint-enable one-var
if (expression === null || expression === undefined) {
stmt.merge(columnName + ' IS NULL');
} else if (expression && expression.constructor === Object) {
var operator = Object.keys(expression)[0];
// Get the expression without the operator
expression = expression[operator];
if (operator === 'inq' || operator === 'nin' || operator === 'between') {
columnValue = [];
if (Array.isArray(expression)) {
// Column value is a list
for (var j = 0, m = expression.length; j < m; j++) {
columnValue.push(this.toColumnValue(p, expression[j]));
}
} else {
columnValue.push(this.toColumnValue(p, expression));
}
if (operator === 'between') {
// BETWEEN v1 AND v2
var v1 = columnValue[0] === undefined ? null : columnValue[0];
var v2 = columnValue[1] === undefined ? null : columnValue[1];
columnValue = [v1, v2];
} else {
// IN (v1,v2,v3) or NOT IN (v1,v2,v3)
if (columnValue.length === 0) {
if (operator === 'inq') {
columnValue = [null];
} else {
// nin () is true
continue;
}
}
}
} else if (operator === 'regexp' && expression instanceof RegExp) {
// do not coerce RegExp based on property definitions
columnValue = expression;
} else {
columnValue = this.toColumnValue(p, expression);
}
sqlExp = self.buildExpression(columnName, operator, columnValue, p);
stmt.merge(sqlExp);
} else {
// The expression is the field value, not a condition
columnValue = self.toColumnValue(p, expression);
if (columnValue === null) {
stmt.merge(columnName + ' IS NULL');
} else if (columnValue instanceof ParameterizedSQL) {
stmt.merge(columnName + '=').merge(columnValue);
} else {
stmt.merge({
sql: columnName + '=?',
params: [columnValue]
});
}
}
whereStmts.push(stmt);
}
var params = [];
var sqls = [];
for (var k = 0, s = whereStmts.length; k < s; k++) {
sqls.push(whereStmts[k].sql);
params = params.concat(whereStmts[k].params);
}
var whereStmt = new ParameterizedSQL({
sql: sqls.join(' AND '),
params: params
});
return whereStmt;
};
/**
* Build the SQL WHERE clause for the where object
* @param {string} model Model name
* @param {object} where An object for the where conditions
* @return {ParameterizedSQL} The SQL WHERE clause
*/
VnMySQL.prototype.makeWhere = function(model, where) {
var whereClause = this._makeWhere(model, where);
if (whereClause.sql) {
whereClause.sql = 'WHERE ' + whereClause.sql;
}
return whereClause;
};

View File

@ -8,12 +8,10 @@ module.exports = Self => {
accepts: [{
arg: 'filter',
type: 'Object',
required: false,
description: 'Filter defining where and paginated data',
http: {source: 'query'}
description: 'Filter defining where and paginated data'
}],
returns: {
type: ["Object"],
type: ['Object'],
root: true
},
http: {
@ -41,7 +39,7 @@ module.exports = Self => {
JOIN worker w ON w.id = st.workerFk
JOIN state ste ON ste.id = st.stateFk`);
stmt.merge(Self.buildSuffix(filter));
stmt.merge(Self.makeSuffix(filter));
let trackings = await Self.rawStmt(stmt);

View File

@ -1,14 +1,14 @@
const app = require(`${servicesDir}/ticket/server/server`);
describe('ticket listSaleTracking()', () => {
it('should call the listSaleTracking method and return the response', async() => {
it('should call the listSaleTracking method and return the response', async () => {
let filter = {where: {ticketFk: 1}};
let result = await app.models.SaleTracking.listSaleTracking(filter);
expect(result[0].concept).toEqual('Gem of Time');
});
it(`should call the listSaleTracking method and return zero if doesn't have lines`, async() => {
it(`should call the listSaleTracking method and return zero if doesn't have lines`, async () => {
let filter = {where: {ticketFk: 2}};
let result = await app.models.SaleTracking.listSaleTracking(filter);