#599 Advanced ticket search, eslint rules fixed, e2e ticket_04 refactor
This commit is contained in:
parent
ddcd7baaf6
commit
260f548019
|
@ -26,5 +26,7 @@ rules:
|
|||
bracketSpacing: 0
|
||||
space-infix-ops: 1
|
||||
prefer-const: 0
|
||||
curly: ["error", "multi-or-nest"]
|
||||
curly: [error, multi]
|
||||
indent: [error, 4]
|
||||
arrow-parens: [error, as-needed]
|
||||
no-focused-tests: 0
|
|
@ -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 = {
|
||||
|
|
|
@ -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;
|
||||
|
||||
if (this.paramBuilder)
|
||||
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) {
|
||||
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: '&?',
|
||||
|
|
|
@ -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>
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,98 +1,89 @@
|
|||
import './index.js';
|
||||
|
||||
describe('ticket', () => {
|
||||
describe('Component vnTicketIndex', () => {
|
||||
let $componentController;
|
||||
let controller;
|
||||
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');
|
||||
ngModule('client');
|
||||
ngModule('item');
|
||||
ngModule('ticket');
|
||||
});
|
||||
|
||||
beforeEach(angular.mock.inject(_$componentController_ => {
|
||||
$componentController = _$componentController_;
|
||||
controller = $componentController('vnTicketIndex');
|
||||
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();
|
||||
}));
|
||||
|
||||
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'});
|
||||
});
|
||||
afterEach(() => {
|
||||
$element.remove();
|
||||
});
|
||||
|
||||
describe('compareDate()', () => {
|
||||
it('should return warning when the date is the present', () => {
|
||||
let date = new Date();
|
||||
let result = controller.compareDate(date);
|
||||
let curDate = new Date();
|
||||
let result = $ctrl.compareDate(curDate);
|
||||
|
||||
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);
|
||||
let futureDate = new Date();
|
||||
futureDate = futureDate.setDate(futureDate.getDate() + 10);
|
||||
let result = $ctrl.compareDate(futureDate);
|
||||
|
||||
expect(result).toEqual('success');
|
||||
});
|
||||
|
||||
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(undefined);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showDescriptor()', () => {
|
||||
it('should show the descriptor popover', () => {
|
||||
spyOn($ctrl.$.descriptor, 'show');
|
||||
|
||||
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 call preventDefault and stopImmediatePropagation from event and show', () => {
|
||||
let event = jasmine.createSpyObj('event', ['preventDefault', 'stopImmediatePropagation']);
|
||||
it('should show the dialog summary', () => {
|
||||
spyOn($ctrl.$.summary, 'show');
|
||||
|
||||
controller.$scope = {dialogSummaryTicket: {show: () => {}}};
|
||||
spyOn(controller.$scope.dialogSummaryTicket, 'show');
|
||||
let ticket = {};
|
||||
controller.preview(event, ticket);
|
||||
let event = new MouseEvent('click', {
|
||||
view: $window,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
$ctrl.preview(event, tickets[0]);
|
||||
|
||||
expect(event.preventDefault).toHaveBeenCalledWith();
|
||||
expect(event.stopImmediatePropagation).toHaveBeenCalledWith();
|
||||
expect(controller.$scope.dialogSummaryTicket.show).toHaveBeenCalledWith();
|
||||
});
|
||||
expect($ctrl.$.summary.show).toHaveBeenCalledWith();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -5,3 +5,7 @@ From: Desde
|
|||
To: Hasta
|
||||
Agency: Agencia
|
||||
Warehouse: Almacén
|
||||
Sales person: Comercial
|
||||
Province: Provincia
|
||||
My team: Mi equipo
|
||||
My tickets: Mis tickets
|
|
@ -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) => {
|
||||
.parsedUrl();
|
||||
|
||||
expect(url.hash).toEqual('#!/ticket/index');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.countElement(selectors.ticketsIndex.searchResult);
|
||||
|
||||
expect(result).toEqual(1);
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.url();
|
||||
|
||||
expect(url).toContain('package/index');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Some fields are invalid');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Some fields are invalid');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Some fields are invalid');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Package cannot be blank');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.waitForLastSnackbar();
|
||||
|
||||
expect(result).toEqual('Data saved!');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.getInputValue(selectors.ticketPackages.firstPackageSelect);
|
||||
|
||||
expect(result).toEqual('Legendary Box');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
|
||||
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) => {
|
||||
.getInputValue(selectors.ticketPackages.firstQuantityInput);
|
||||
|
||||
expect(result).toEqual('99');
|
||||
done();
|
||||
}).catch(done.fail);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -29,7 +29,8 @@ module.exports = Self => {
|
|||
|
||||
Self.filter = async (filter, params) => {
|
||||
let stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
`SELECT * FROM (
|
||||
SELECT
|
||||
r.id,
|
||||
r.isConciliate,
|
||||
r.payed,
|
||||
|
@ -51,24 +52,26 @@ module.exports = Self => {
|
|||
i.id,
|
||||
TRUE,
|
||||
i.dued,
|
||||
c.code AS company,
|
||||
c.code,
|
||||
i.created,
|
||||
CONCAT(' N/FRA ', i.ref) description,
|
||||
i.amount AS debit,
|
||||
CONCAT(' N/FRA ', i.ref),
|
||||
i.amount,
|
||||
0 credit,
|
||||
NULL bank,
|
||||
NULL firstName,
|
||||
NULL name,
|
||||
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
|
||||
]);
|
||||
]
|
||||
);
|
||||
|
||||
stmt.merge(Self.buildPagination(filter));
|
||||
stmt.merge(Self.makeLimit(filter));
|
||||
return await Self.rawStmt(stmt);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
}
|
|
@ -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,7 +123,8 @@ module.exports = Self => {
|
|||
|
||||
stmt = new ParameterizedSQL(
|
||||
`CREATE TEMPORARY TABLE tmp.filter
|
||||
(INDEX (id)) ENGINE = MEMORY
|
||||
(INDEX (id))
|
||||
ENGINE = MEMORY
|
||||
SELECT
|
||||
t.id,
|
||||
t.shipped,
|
||||
|
@ -58,20 +151,22 @@ module.exports = Self => {
|
|||
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'));
|
||||
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];
|
||||
};
|
||||
|
|
|
@ -2,8 +2,10 @@ const app = require(`${servicesDir}/ticket/server/server`);
|
|||
|
||||
describe('ticket filter()', () => {
|
||||
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);
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,47 +1,19 @@
|
|||
|
||||
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) {
|
||||
dataSource.driver = mysql;
|
||||
dataSource.connector = new VnMySQL(dataSource.settings);
|
||||
dataSource.connector.dataSource = dataSource;
|
||||
|
||||
var modelBuilder = dataSource.modelBuilder;
|
||||
var defineType = modelBuilder.defineValueType ?
|
||||
modelBuilder.defineValueType.bind(modelBuilder) :
|
||||
modelBuilder.constructor.registerType.bind(modelBuilder.constructor);
|
||||
|
||||
defineType(function Point() {});
|
||||
|
||||
dataSource.EnumFactory = EnumFactory;
|
||||
|
||||
if (callback) {
|
||||
if (dataSource.settings.lazyConnect) {
|
||||
process.nextTick(function() {
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
dataSource.connector.connect(callback);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.VnMySQL = VnMySQL;
|
||||
|
||||
function VnMySQL(settings) {
|
||||
class VnMySQL extends MySQL {
|
||||
constructor(settings) {
|
||||
super();
|
||||
SqlConnector.call(this, 'mysql', settings);
|
||||
}
|
||||
|
||||
VnMySQL.prototype = Object.create(MySQL.prototype);
|
||||
VnMySQL.constructor = VnMySQL;
|
||||
|
||||
VnMySQL.prototype.toColumnValue = function(prop, val) {
|
||||
toColumnValue(prop, val) {
|
||||
if (val == null || !prop || prop.type !== Date)
|
||||
return MySQL.prototype.toColumnValue.call(this, prop, val);
|
||||
|
||||
|
@ -57,139 +29,220 @@ VnMySQL.prototype.toColumnValue = function(prop, val) {
|
|||
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
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
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);
|
||||
});
|
||||
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
|
||||
* 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
|
||||
*/
|
||||
VnMySQL.prototype.makeWhere = function(model, where) {
|
||||
var whereClause = this._makeWhere(model, where);
|
||||
if (whereClause.sql) {
|
||||
whereClause.sql = 'WHERE ' + whereClause.sql;
|
||||
}
|
||||
return whereClause;
|
||||
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;
|
||||
|
||||
const modelBuilder = dataSource.modelBuilder;
|
||||
const defineType = modelBuilder.defineValueType ?
|
||||
modelBuilder.defineValueType.bind(modelBuilder) :
|
||||
modelBuilder.constructor.registerType.bind(modelBuilder.constructor);
|
||||
|
||||
defineType(function Point() {});
|
||||
|
||||
dataSource.EnumFactory = EnumFactory;
|
||||
|
||||
if (callback) {
|
||||
if (dataSource.settings.lazyConnect) {
|
||||
process.nextTick(function() {
|
||||
callback();
|
||||
});
|
||||
} else
|
||||
dataSource.connector.connect(callback);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue