This commit is contained in:
parent
c9cd1b19dd
commit
633ab28c41
|
@ -12,16 +12,11 @@ export default class Controller extends Component {
|
|||
|
||||
element.addEventListener('focus', () => {
|
||||
if (this.field || this.disabled) return;
|
||||
|
||||
$transclude((tClone, tScope) => {
|
||||
this.field = tClone;
|
||||
this.tScope = tScope;
|
||||
this.element.querySelector('.field').appendChild(this.field[0]);
|
||||
element.tabIndex = -1;
|
||||
this.timer = $timeout(() => {
|
||||
this.timer = null;
|
||||
focus(this.field[0]);
|
||||
});
|
||||
}, null, 'field');
|
||||
element.classList.add('selected');
|
||||
});
|
||||
|
|
|
@ -19,6 +19,9 @@ vn-td-editable {
|
|||
display: block
|
||||
}
|
||||
}
|
||||
&[disabled="true"] {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
&.selected > .text {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
@import "variables";
|
||||
|
||||
vn-textfield {
|
||||
margin: 20px 0!important;
|
||||
margin: 20px 0;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
|
||||
|
@ -145,3 +145,9 @@ vn-textfield {
|
|||
background-color: #d50000;
|
||||
}
|
||||
}
|
||||
|
||||
vn-table {
|
||||
vn-textfield {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
|
@ -40,7 +40,6 @@ export default class Th {
|
|||
this.order = 'DESC';
|
||||
else
|
||||
this.order = 'ASC';
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -71,7 +70,6 @@ export default class Th {
|
|||
this.column.classList.add('desc');
|
||||
else
|
||||
this.column.classList.add('asc');
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -79,5 +79,6 @@
|
|||
"EXTENSION_INVALID_FORMAT": "La extensión es invalida",
|
||||
"We weren't able to send this SMS": "No hemos podido enviar el SMS",
|
||||
"This client can't be invoiced": "Este cliente no puede ser facturado",
|
||||
"This ticket can't be invoiced": "Este ticket no puede ser facturado"
|
||||
"This ticket can't be invoiced": "Este ticket no puede ser facturado",
|
||||
"That item is not available on that day": "That item is not available on that day"
|
||||
}
|
|
@ -5,7 +5,7 @@ const app = require('vn-loopback/server/server');
|
|||
* Envio SMS de prueba a servicio real Masmovil. No llega a enviarse
|
||||
* por destinatario inválido, pero puede llegar a fallar.
|
||||
*/
|
||||
fdescribe('sms send()', () => {
|
||||
describe('sms send()', () => {
|
||||
it('should call the send method', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 1}}};
|
||||
let result = await app.models.Sms.send(ctx, null, 'Invalid', 'My SMS Body');
|
||||
|
|
|
@ -12,6 +12,8 @@ import './fetched-tags';
|
|||
import './tags';
|
||||
import './tax';
|
||||
import './log';
|
||||
import './request';
|
||||
import './request-search-panel';
|
||||
import './last-entries';
|
||||
import './niche';
|
||||
import './botanical';
|
||||
|
|
|
@ -9,11 +9,23 @@
|
|||
<div class="content-block">
|
||||
<div class="vn-list">
|
||||
<vn-card pad-medium-h>
|
||||
<vn-horizontal>
|
||||
<vn-searchbar
|
||||
vn-three
|
||||
panel="vn-item-search-panel"
|
||||
on-search="$ctrl.onSearch($params)"
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
<vn-icon-menu
|
||||
vn-id="more-button"
|
||||
icon="more_vert"
|
||||
show-filter="false"
|
||||
value-field="callback"
|
||||
translate-fields="['name']"
|
||||
on-change="$ctrl.onMoreChange(value)"
|
||||
on-open="$ctrl.onMoreOpen()">
|
||||
</vn-icon-menu>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
</div>
|
||||
<vn-card margin-medium-v>
|
||||
|
|
|
@ -2,7 +2,8 @@ import ngModule from '../module';
|
|||
import './style.scss';
|
||||
|
||||
class Controller {
|
||||
constructor($http, $state, $scope, $stateParams) {
|
||||
constructor($http, $state, $scope, aclService) {
|
||||
this.aclService = aclService;
|
||||
this.$http = $http;
|
||||
this.$state = $state;
|
||||
this.$ = $scope;
|
||||
|
@ -13,6 +14,22 @@ class Controller {
|
|||
id: false,
|
||||
actions: false
|
||||
};
|
||||
this.moreOptions = [
|
||||
{callback: this.goToTicketRequest, name: 'Ticket request', acl: 'buyer'}
|
||||
];
|
||||
}
|
||||
|
||||
onMoreOpen() {
|
||||
let options = this.moreOptions.filter(o => this.aclService.hasAny([o.acl]));
|
||||
this.$.moreButton.data = options;
|
||||
}
|
||||
|
||||
onMoreChange(callback) {
|
||||
callback.call(this);
|
||||
}
|
||||
|
||||
goToTicketRequest() {
|
||||
this.$state.go('item.request');
|
||||
}
|
||||
|
||||
stopEvent(event) {
|
||||
|
@ -86,7 +103,7 @@ class Controller {
|
|||
this.$.preview.show();
|
||||
}
|
||||
}
|
||||
Controller.$inject = ['$http', '$state', '$scope', '$stateParams'];
|
||||
Controller.$inject = ['$http', '$state', '$scope', 'aclService'];
|
||||
|
||||
ngModule.component('vnItemIndex', {
|
||||
template: require('./index.html'),
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
picture: Foto
|
||||
Ticket request: Peticiones de compra
|
|
@ -1,5 +1,42 @@
|
|||
@import "variables";
|
||||
|
||||
vn-item-index {
|
||||
vn-icon-menu{
|
||||
padding-top: 30px;
|
||||
padding-left: 10px;
|
||||
color: $color-main;
|
||||
li {
|
||||
color: initial;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vn-item-product {
|
||||
display: block;
|
||||
|
||||
.id {
|
||||
background-color: $color-main;
|
||||
color: $color-font-dark;
|
||||
margin-bottom: 0em;
|
||||
}
|
||||
.image {
|
||||
height: 7em;
|
||||
width: 7em;
|
||||
|
||||
& > img {
|
||||
max-height: 100%;
|
||||
max-width: 100%;
|
||||
border-radius: .2em;
|
||||
}
|
||||
}
|
||||
vn-label-value:first-of-type section{
|
||||
margin-top: 0.6em;
|
||||
}
|
||||
vn-fetched-tags vn-horizontal{
|
||||
margin-top: 0.9em;
|
||||
}
|
||||
}
|
||||
|
||||
vn-table {
|
||||
img {
|
||||
border-radius: 50%;
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<div class="search-panel">
|
||||
<form pad-large ng-submit="$ctrl.onSearch()">
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="General search"
|
||||
model="filter.search"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Ticket id"
|
||||
model="filter.ticketFk">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
field="filter.atenderFk"
|
||||
url="/client/api/Clients/activeWorkersWithRole"
|
||||
search-function="{firstName: $search}"
|
||||
value-field="id"
|
||||
where="{role: 'employee'}"
|
||||
label="Atender">
|
||||
<tpl-item>{{nickname}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Client id"
|
||||
model="filter.clientFk">
|
||||
</vn-textfield>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
label="Warehouse"
|
||||
field="filter.warehouseFk"
|
||||
url="/api/Warehouses">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="From"
|
||||
model="filter.from">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="To"
|
||||
model="filter.to">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal margin-large-top>
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
</vn-horizontal>
|
||||
</form>
|
||||
</div>
|
|
@ -0,0 +1,7 @@
|
|||
import ngModule from '../module';
|
||||
import SearchPanel from 'core/components/searchbar/search-panel';
|
||||
|
||||
ngModule.component('vnRequestSearchPanel', {
|
||||
template: require('./index.html'),
|
||||
controller: SearchPanel
|
||||
});
|
|
@ -0,0 +1,45 @@
|
|||
import './index.js';
|
||||
|
||||
describe('Item', () => {
|
||||
describe('Component vnItemSearchPanel', () => {
|
||||
let $element;
|
||||
let controller;
|
||||
|
||||
beforeEach(ngModule('item'));
|
||||
|
||||
beforeEach(angular.mock.inject($componentController => {
|
||||
$element = angular.element(`<div></div>`);
|
||||
controller = $componentController('vnItemSearchPanel', {$element});
|
||||
}));
|
||||
|
||||
describe('getSourceTable()', () => {
|
||||
it(`should return null if there's no selection`, () => {
|
||||
let selection = null;
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it(`should return null if there's a selection but its isFree property is truthy`, () => {
|
||||
let selection = {isFree: true};
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toBeNull();
|
||||
});
|
||||
|
||||
it(`should return the formated sourceTable concatenated to a path`, () => {
|
||||
let selection = {sourceTable: 'hello guy'};
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toEqual('/api/Hello guys');
|
||||
});
|
||||
|
||||
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
|
||||
let selection = {id: 99};
|
||||
let result = controller.getSourceTable(selection);
|
||||
|
||||
expect(result).toEqual(`/api/ItemTags/filterItemTags/${selection.id}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,3 @@
|
|||
Ink: Tinta
|
||||
Origin: Origen
|
||||
Producer: Productor
|
|
@ -0,0 +1,118 @@
|
|||
<vn-crud-model
|
||||
vn-id="model"
|
||||
url="/api/TicketRequests/filter"
|
||||
limit="20"
|
||||
data="requests"
|
||||
auto-load="false">
|
||||
</vn-crud-model>
|
||||
<form name="form">
|
||||
<div margin-medium>
|
||||
<vn-card pad-medium-h class="vn-list">
|
||||
<vn-horizontal>
|
||||
<vn-searchbar
|
||||
panel="vn-request-search-panel"
|
||||
on-search="$ctrl.onSearch($params)"
|
||||
vn-one
|
||||
vn-focus>
|
||||
</vn-searchbar>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-card margin-medium-v pad-medium>
|
||||
<vn-table model="model" auto-load="false">
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th number field="ticketFk">Ticket ID</vn-th>
|
||||
<vn-th field="shipped">Shipped</vn-th>
|
||||
<vn-th field="warehouse">Warehouse</vn-th>
|
||||
<vn-th field="salesPersonNickname">SalesPerson</vn-th>
|
||||
<vn-th field="description">Description</vn-th>
|
||||
<vn-th field="quantity" editable>Quantity</vn-th>
|
||||
<vn-th field="price">Price</vn-th>
|
||||
<vn-th field="atenderNickname">Atender</vn-th>
|
||||
<vn-th field="itemFk">itemFk</vn-th>
|
||||
<vn-th field="description">Concept</vn-th>
|
||||
<vn-th field="">Quantity</vn-th>
|
||||
<vn-th>State</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="request in requests">
|
||||
<vn-td number>
|
||||
<span class="link"
|
||||
ng-click="$ctrl.showTicketDescriptor($event, request.ticketFk)">
|
||||
{{request.ticketFk}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td class="{{$ctrl.compareDate(request.shipped)}}">
|
||||
{{::request.shipped | dateTime: 'dd/MM/yyyy'}}
|
||||
</vn-td>
|
||||
<vn-td>{{::request.warehouse}}</vn-td>
|
||||
<vn-td>{{::request.salesPersonNickname}}</vn-td>
|
||||
<vn-td>{{::request.description}}</vn-td>
|
||||
<vn-td>{{::request.quantity}}</vn-td>
|
||||
<vn-td>{{::request.price}}</vn-td>
|
||||
<vn-td>{{::request.atenderNickname}}</vn-td>
|
||||
<vn-td-editable>
|
||||
<text>{{request.itemFk}}</text>
|
||||
<field>
|
||||
<vn-textfield
|
||||
vn-focus
|
||||
model="request.itemFk"
|
||||
on-change="$ctrl.confirmRequest(request)"
|
||||
type="number">
|
||||
</vn-textfield>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td>{{::request.itemDescription}}</vn-td>
|
||||
<vn-td-editable disabled="!request.saleFk && request.itemFk">
|
||||
<text>{{request.saleQuantity}}</text>
|
||||
<field>
|
||||
<vn-textfield
|
||||
vn-focus
|
||||
model="request.saleQuantity"
|
||||
on-change="$ctrl.changeQuantity(request)"
|
||||
type="number">
|
||||
</vn-textfield>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
<vn-td>{{::$ctrl.getState(request.isOk)}}</vn-td>
|
||||
<vn-td>
|
||||
<vn-icon-button
|
||||
disabled="request.isOk === 0"
|
||||
number
|
||||
icon="thumb_down"
|
||||
ng-click="$ctrl.showDenyReason($event, request.id)"
|
||||
vn-tooltip="Discard">
|
||||
</vn-icon-button>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
<vn-pagination model="model"></vn-pagination>
|
||||
</div>
|
||||
</form>
|
||||
<vn-client-descriptor-popover vn-id="clientDescriptor"></vn-client-descriptor-popover>
|
||||
<vn-ticket-descriptor-popover
|
||||
vn-id="ticketDescriptor">
|
||||
</vn-ticket-descriptor-popover>
|
||||
<vn-dialog
|
||||
vn-id="denyReason"
|
||||
class="modal-form">
|
||||
<tpl-body>
|
||||
<vn-horizontal class="header">
|
||||
<h5><span translate>Indicate the reasons to deny this request</span></h5>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal pad-medium>
|
||||
<vn-textarea
|
||||
model="$ctrl.denyObservation">
|
||||
</vn-textarea>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal pad-medium>
|
||||
<vn-button
|
||||
label="Save"
|
||||
ng-click="$ctrl.denyRequest()">
|
||||
</vn-button>
|
||||
</vn-horizontal>
|
||||
</tpl-body>
|
||||
</vn-dialog>
|
|
@ -0,0 +1,142 @@
|
|||
import ngModule from '../module';
|
||||
import './style.scss';
|
||||
|
||||
export default class Controller {
|
||||
constructor($scope, vnApp, $translate, $http, $state, $stateParams) {
|
||||
this.$state = $state;
|
||||
this.$stateParams = $stateParams;
|
||||
this.$http = $http;
|
||||
this.$scope = $scope;
|
||||
this.vnApp = vnApp;
|
||||
this._ = $translate;
|
||||
if (!$stateParams.q)
|
||||
this.filter = {isOk: false, mine: true};
|
||||
}
|
||||
|
||||
$postLink() {
|
||||
if (this.filter)
|
||||
this.onSearch(this.filter);
|
||||
}
|
||||
|
||||
getState(isOk) {
|
||||
if (isOk === null)
|
||||
return 'Nueva';
|
||||
else if (isOk === -1 || isOk === 1)
|
||||
return 'Aceptada';
|
||||
else
|
||||
return 'Denegada';
|
||||
}
|
||||
|
||||
confirmRequest(request) {
|
||||
if (request.itemFk && request.quantity) {
|
||||
let params = {
|
||||
itemFk: request.itemFk,
|
||||
quantity: request.quantity || request.saleQuantity
|
||||
};
|
||||
|
||||
let endpoint = `/api/TicketRequests/${request.id}/confirm`;
|
||||
|
||||
this.$http.post(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
}).catch( e => {
|
||||
this.$scope.model.refresh();
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
changeQuantity(request) {
|
||||
if (request.saleFk) {
|
||||
let params = {
|
||||
quantity: request.saleQuantity
|
||||
};
|
||||
|
||||
let endpoint = `/api/Sales/${request.saleFk}/`;
|
||||
|
||||
this.$http.patch(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
}).catch( e => {
|
||||
this.$scope.model.refresh();
|
||||
throw e;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
compareDate(date) {
|
||||
let today = new Date();
|
||||
today.setHours(0, 0, 0, 0);
|
||||
let timeTicket = new Date(date);
|
||||
timeTicket.setHours(0, 0, 0, 0);
|
||||
|
||||
let comparation = today - timeTicket;
|
||||
|
||||
if (comparation == 0)
|
||||
return 'warning';
|
||||
if (comparation < 0)
|
||||
return 'success';
|
||||
}
|
||||
|
||||
onSearch(params) {
|
||||
if (params)
|
||||
this.$scope.model.applyFilter(null, params);
|
||||
else
|
||||
this.$scope.model.clear();
|
||||
}
|
||||
|
||||
showDenyReason(event, requestId) {
|
||||
this.denyRequestId = requestId;
|
||||
this.$scope.denyReason.parent = event.target;
|
||||
this.$scope.denyReason.show();
|
||||
}
|
||||
|
||||
clear() {
|
||||
delete this.denyRequestId;
|
||||
}
|
||||
|
||||
denyRequest() {
|
||||
let params = {
|
||||
observation: this.denyObservation
|
||||
};
|
||||
|
||||
let endpoint = `/api/TicketRequests/${this.denyRequestId}/deny`;
|
||||
|
||||
this.$http.post(endpoint, params).then(() => {
|
||||
this.vnApp.showSuccess(this._.instant('Data saved!'));
|
||||
this.$scope.model.refresh();
|
||||
this.$scope.denyReason.hide();
|
||||
this.denyObservation = null;
|
||||
});
|
||||
}
|
||||
|
||||
showClientDescriptor(event, clientFk) {
|
||||
this.$scope.clientDescriptor.clientFk = clientFk;
|
||||
this.$scope.clientDescriptor.parent = event.target;
|
||||
this.$scope.clientDescriptor.show();
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
showTicketDescriptor(event, ticketFk) {
|
||||
this.$scope.ticketDescriptor.ticketFk = ticketFk;
|
||||
this.$scope.ticketDescriptor.parent = event.target;
|
||||
this.$scope.ticketDescriptor.show();
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
|
||||
onDescriptorLoad() {
|
||||
this.$scope.popover.relocate();
|
||||
}
|
||||
|
||||
preventNavigation(event) {
|
||||
event.preventDefault();
|
||||
event.stopImmediatePropagation();
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$scope', 'vnApp', '$translate', '$http', '$state', '$stateParams'];
|
||||
|
||||
ngModule.component('vnItemRequest', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
Discard: Descartar
|
||||
Indicate the reasons to deny this request: Indique las razones para descartar esta peticion
|
|
@ -0,0 +1,12 @@
|
|||
vn-dialog[vn-id="denyReason"] {
|
||||
button.close {
|
||||
display: none
|
||||
}
|
||||
& vn-button {
|
||||
margin: 0 auto
|
||||
}
|
||||
|
||||
vn-textarea {
|
||||
width: 100%
|
||||
}
|
||||
}
|
|
@ -122,6 +122,15 @@
|
|||
"state": "item.card.log",
|
||||
"component": "vn-item-log",
|
||||
"description": "Log"
|
||||
}, {
|
||||
"url" : "/request?q",
|
||||
"state": "item.request",
|
||||
"component": "vn-item-request",
|
||||
"description": "Item request",
|
||||
"params": {
|
||||
"item": "$ctrl.item"
|
||||
},
|
||||
"acl": ["employee"]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('confirm', {
|
||||
description: '',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Integer',
|
||||
required: true,
|
||||
description: 'The request ID',
|
||||
}, {
|
||||
arg: 'itemFk',
|
||||
type: 'Integer',
|
||||
required: true,
|
||||
description: 'The request observation',
|
||||
}, {
|
||||
arg: 'quantity',
|
||||
type: 'Integer',
|
||||
required: true,
|
||||
description: 'The request observation',
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/confirm`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.confirm = async ctx => {
|
||||
let transaction = await Self.beginTransaction({});
|
||||
let options = {transaction: transaction};
|
||||
|
||||
try {
|
||||
let item = await Self.app.models.Item.findById(ctx.args.itemFk);
|
||||
if (!item)
|
||||
throw new UserError(`That item doesn't exists`);
|
||||
|
||||
let request = await Self.app.models.TicketRequest.findById(ctx.args.id, {
|
||||
include: {relation: 'ticket'}
|
||||
});
|
||||
|
||||
let query = `CALL vn.getItemVisibleAvailable(?,?,?,?)`;
|
||||
|
||||
let params = [
|
||||
ctx.args.itemFk,
|
||||
request.ticket().warehouseFk,
|
||||
request.ticket().shipped,
|
||||
false
|
||||
];
|
||||
|
||||
let [res] = await Self.rawSql(query, params);
|
||||
let available = res[0].available;
|
||||
|
||||
if (!available)
|
||||
throw new UserError(`That item is not available on that day`);
|
||||
|
||||
|
||||
if (request.saleFk) {
|
||||
let sale = await Self.app.models.Sale.findById(request.saleFk);
|
||||
sale.updateAttributes({itemFk: ctx.args.itemFk, quantity: ctx.args.quantity, description: item.description}, options);
|
||||
} else {
|
||||
params = {
|
||||
ticketFk: request.ticketFk,
|
||||
itemFk: ctx.args.itemFk,
|
||||
quantity: ctx.args.quantity
|
||||
};
|
||||
sale = await Self.app.models.Sale.create(params, options);
|
||||
request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk}, options);
|
||||
}
|
||||
|
||||
query = `CALL vn.ticketCalculateSale(?)`;
|
||||
params = [sale.id];
|
||||
await Self.rawSql(query, params, options);
|
||||
|
||||
await transaction.commit();
|
||||
} catch (error) {
|
||||
await transaction.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('deny', {
|
||||
description: 'Create a newticket and returns the new ID',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Integer',
|
||||
required: true,
|
||||
description: 'The request ID',
|
||||
}, {
|
||||
arg: 'observation',
|
||||
type: 'String',
|
||||
required: true,
|
||||
description: 'The request observation',
|
||||
}],
|
||||
returns: {
|
||||
type: 'number',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/deny`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.deny = async ctx => {
|
||||
let userId = ctx.req.accessToken.userId;
|
||||
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}});
|
||||
|
||||
let params = {
|
||||
isOk: false,
|
||||
atenderFk: worker.id,
|
||||
observation: ctx.args.observation,
|
||||
};
|
||||
|
||||
let request = await Self.app.models.TicketRequest.findById(ctx.args.id);
|
||||
return request.updateAttributes(params);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
|
||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('filter', {
|
||||
description: 'Find all instances of the model matched by filter from the data source.',
|
||||
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`
|
||||
}, {
|
||||
arg: 'search',
|
||||
type: 'String',
|
||||
description: `If it's and integer searchs by id, otherwise it searchs by nickname`
|
||||
}, {
|
||||
arg: 'ticketFk',
|
||||
type: 'Number',
|
||||
description: `Searchs by ticketFk`
|
||||
}, {
|
||||
arg: 'warehouseFk',
|
||||
type: 'Number',
|
||||
description: `Search by warehouse`
|
||||
}, {
|
||||
arg: 'atenderFk',
|
||||
type: 'Number',
|
||||
description: `Search requests atended by the given worker`
|
||||
}, {
|
||||
arg: 'mine',
|
||||
type: 'Boolean',
|
||||
description: `Search requests attended by the connected worker`
|
||||
}, {
|
||||
arg: 'from',
|
||||
type: 'Date',
|
||||
description: `Date from`
|
||||
}, {
|
||||
arg: 'to',
|
||||
type: 'Date',
|
||||
description: `Date to`
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/filter',
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.filter = async(ctx, filter) => {
|
||||
let conn = Self.dataSource.connector;
|
||||
let userId = ctx.req.accessToken.userId;
|
||||
let worker = await Self.app.models.Worker.findOne({where: {userFk: userId}});
|
||||
|
||||
if (ctx.args.mine)
|
||||
ctx.args.atenderFk = worker.id;
|
||||
|
||||
let where = buildFilter(ctx.args, (param, value) => {
|
||||
switch (param) {
|
||||
case 'search':
|
||||
return /^\d+$/.test(value)
|
||||
? {'t.ticketFk': {inq: value}}
|
||||
: {'t.nickname': {like: `%${value}%`}};
|
||||
case 'ticketFk':
|
||||
return {'t.id': value};
|
||||
case 'atenderFk':
|
||||
return {'tr.atenderFk': value};
|
||||
case 'isOk':
|
||||
return {'tr.isOk': value};
|
||||
case 'clientFk':
|
||||
return {'t.clientFk': value};
|
||||
case 'from':
|
||||
return {'t.shipped': {gte: value}};
|
||||
case 'to':
|
||||
return {'t.shipped': {lte: value}};
|
||||
case 'warehouse':
|
||||
return {'w.id': value};
|
||||
case 'salesPersonFk':
|
||||
return {'c.salesPersonFk': value};
|
||||
}
|
||||
});
|
||||
|
||||
filter = mergeFilters(filter, {where});
|
||||
|
||||
let stmt;
|
||||
|
||||
stmt = new ParameterizedSQL(
|
||||
`SELECT
|
||||
tr.id,
|
||||
tr.ticketFk,
|
||||
tr.quantity,
|
||||
tr.price,
|
||||
tr.atenderFk,
|
||||
tr.description,
|
||||
tr.itemFk,
|
||||
tr.saleFk,
|
||||
tr.isOk,
|
||||
s.quantity AS saleQuantity,
|
||||
i.description AS itemDescription,
|
||||
t.shipped,
|
||||
t.nickname,
|
||||
t.warehouseFk,
|
||||
t.clientFk,
|
||||
w.name AS warehouse,
|
||||
u.nickname AS salesPersonNickname,
|
||||
ua.nickname AS atenderNickname
|
||||
FROM ticketRequest tr
|
||||
LEFT JOIN ticket t ON t.id = tr.ticketFk
|
||||
LEFT JOIN warehouse w ON w.id = t.warehouseFk
|
||||
LEFT JOIN client c ON c.id = t.clientFk
|
||||
LEFT JOIN item i ON i.id = tr.itemFk
|
||||
LEFT JOIN sale s ON s.id = tr.saleFk
|
||||
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
|
||||
LEFT JOIN account.user u ON u.id = wk.userFk
|
||||
LEFT JOIN worker wka ON wka.id = tr.atenderFk
|
||||
LEFT JOIN account.user ua ON ua.id = wka.userFk`);
|
||||
stmt.merge(conn.makeSuffix(filter));
|
||||
|
||||
let result = await conn.executeStmt(stmt);
|
||||
|
||||
return result;
|
||||
};
|
||||
};
|
|
@ -1,6 +1,10 @@
|
|||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
module.exports = function(Self) {
|
||||
require('../methods/ticket-request/filter')(Self);
|
||||
require('../methods/ticket-request/deny')(Self);
|
||||
require('../methods/ticket-request/confirm')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (ctx.isNewInstance) {
|
||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
||||
|
|
Loading…
Reference in New Issue