#1608 item.request
gitea/salix/dev This commit has test failures Details

This commit is contained in:
Carlos Jimenez Ruiz 2019-07-25 09:55:09 +02:00
parent 6d5412227a
commit e1cbf781e1
11 changed files with 440 additions and 66 deletions

View File

@ -3,26 +3,29 @@
url="/ticket/api/TicketRequests/filter"
limit="20"
data="requests"
order="isOk ASC"
auto-load="false">
</vn-crud-model>
<form name="form">
<div margin-medium>
<vn-card pad-medium-h class="vn-list">
<vn-horizontal>
<vn-searchbar
<vn-searchbar
auto-load="false"
panel="vn-request-search-panel"
on-search="$ctrl.onSearch($params)"
info="Search request by id or alias"
vn-one
vn-focus>
</vn-searchbar>
</vn-horizontal>
</vn-card>
<vn-card margin-medium-v>
<vn-table model="model">
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th number field="ticketFk">Ticket ID</vn-th>
<vn-th field="shipped" default-order="DESC">Shipped</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>
@ -31,8 +34,8 @@
<vn-th field="atenderNickname">Atender</vn-th>
<vn-th field="itemFk">Item</vn-th>
<vn-th field="description">Concept</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th>State</vn-th>
<vn-th field="saleQuantity" number>Sale quantity</vn-th>
<vn-th field="isOk">State</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -56,14 +59,8 @@
ng-click="$ctrl.showWorkerDescriptor($event, request.salesPersonFk)">
{{::request.salesPersonNickname}}
</span>
</vn-td>
<vn-td>
<span
class="link"
ng-click="$ctrl.showItemDescriptor($event, request.itemFk)">
{{::request.description}}
</span>
</vn-td>
<vn-td title="{{::request.description}}">{{::request.description}}</vn-td>
<vn-td number>{{::request.quantity}}</vn-td>
<vn-td number>{{::request.price | currency: 'EUR':2}}</vn-td>
<vn-td>
@ -73,7 +70,7 @@
{{::request.atenderNickname}}
</span>
</vn-td>
<vn-td-editable number>
<vn-td-editable disabled="request.isOk === 0" number>
<text>{{request.itemFk}}</text>
<field>
<vn-input-number vn-focus
@ -82,8 +79,15 @@
</vn-input-number>
</field>
</vn-td-editable>
<vn-td>{{::request.itemDescription}}</vn-td>
<vn-td-editable disabled="!request.saleFk && request.itemFk" number>
<vn-td>
<span
class="link"
ng-click="$ctrl.showItemDescriptor($event, request.itemFk)"
title="{{::request.itemDescription}}">
{{::request.itemDescription}}
</span>
</vn-td>
<vn-td-editable disabled="request.isOk === 0" number>
<text number>{{request.saleQuantity}}</text>
<field>
<vn-input-number vn-focus
@ -94,8 +98,13 @@
</vn-td-editable>
<vn-td>{{::$ctrl.getState(request.isOk)}}</vn-td>
<vn-td>
<vn-icon
ng-if="request.response.length"
vn-tooltip="{{request.response}}"
icon="insert_drive_file">
</vn-icon>
<vn-icon-button
disabled="request.isOk === 0"
ng-if="request.isOk != 0"
number
icon="thumb_down"
ng-click="$ctrl.showDenyReason($event, request.id)"

View File

@ -28,16 +28,17 @@ export default class Controller {
}
confirmRequest(request) {
if (request.itemFk && request.quantity) {
if (request.itemFk && request.saleQuantity) {
let params = {
itemFk: request.itemFk,
quantity: request.quantity || request.saleQuantity
quantity: request.saleQuantity
};
let endpoint = `/api/TicketRequests/${request.id}/confirm`;
this.$http.post(endpoint, params).then(() => {
this.vnApp.showSuccess(this._.instant('Data saved!'));
this.$.model.refresh();
}).catch( e => {
this.$.model.refresh();
throw e;
@ -59,7 +60,8 @@ export default class Controller {
this.$.model.refresh();
throw e;
});
}
} else
this.confirmRequest(request);
}
compareDate(date) {
@ -87,6 +89,7 @@ export default class Controller {
this.denyRequestId = requestId;
this.$.denyReason.parent = event.target;
this.$.denyReason.show();
document.querySelector('vn-item-request vn-textarea textArea').focus();
}
clear() {

View File

@ -0,0 +1,129 @@
import './index.js';
import crudModel from 'core/mocks/crud-model';
describe('Item', () => {
describe('Component vnItemRequest', () => {
let $scope;
let controller;
let $httpBackend;
beforeEach(ngModule('item'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.denyReason = {hide: () => {}};
controller = $componentController('vnItemRequest', {$scope: $scope});
}));
describe('getState()', () => {
it(`should return an string depending to the isOK value`, () => {
let isOk = null;
let result = controller.getState(isOk);
expect(result).toEqual('Nueva');
isOk = 1;
result = controller.getState(isOk);
expect(result).toEqual('Aceptada');
isOk = 0;
result = controller.getState(isOk);
expect(result).toEqual('Denegada');
});
});
describe('confirmRequest()', () => {
it(`should do nothing if the request does't have itemFk or saleQuantity`, () => {
let request = {};
spyOn(controller.vnApp, 'showSuccess');
controller.confirmRequest(request);
expect(controller.vnApp.showSuccess).not.toHaveBeenCalledWith();
});
it('should perform a query and call vnApp.showSuccess() and refresh if the conditions are met', () => {
spyOn(controller.vnApp, 'showSuccess');
let model = controller.$.model;
spyOn(model, 'refresh');
let request = {itemFk: 1, saleQuantity: 1, id: 1};
$httpBackend.when('POST', `/api/TicketRequests/${request.id}/confirm`).respond();
$httpBackend.expect('POST', `/api/TicketRequests/${request.id}/confirm`).respond();
controller.confirmRequest(request);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect($scope.model.refresh).toHaveBeenCalledWith();
});
});
describe('changeQuantity()', () => {
it(`should call confirmRequest() if there's no sale id in the request`, () => {
let request = {};
spyOn(controller, 'confirmRequest');
controller.changeQuantity(request);
expect(controller.confirmRequest).toHaveBeenCalledWith(jasmine.any(Object));
});
it(`should perform a query and call vnApp.showSuccess() if the conditions are met`, () => {
let request = {saleFk: 1, saleQuantity: 1};
spyOn(controller.vnApp, 'showSuccess');
$httpBackend.when('PATCH', `/api/Sales/${request.saleFk}/`).respond();
$httpBackend.expect('PATCH', `/api/Sales/${request.saleFk}/`).respond();
controller.changeQuantity(request);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
describe('compareDate()', () => {
it(`should return "success" if receives a future date`, () => {
let date = '3019-02-18T11:00:00.000Z';
let result = controller.compareDate(date);
expect(result).toEqual('success');
});
it(`should return "warning" if date is today`, () => {
let date = new Date();
let result = controller.compareDate(date);
expect(result).toEqual('warning');
});
});
describe('denyRequest()', () => {
it(`should perform a query and call vnApp.showSuccess(), refresh(), hide() and set denyObservation to null in the controller`, () => {
spyOn(controller.vnApp, 'showSuccess');
let model = controller.$.model;
spyOn(model, 'refresh');
spyOn(controller.$.denyReason, 'hide');
controller.denyRequestId = 1;
$httpBackend.when('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond();
$httpBackend.expect('POST', `/api/TicketRequests/${controller.denyRequestId}/deny`).respond();
controller.denyRequest();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
expect($scope.model.refresh).toHaveBeenCalledWith();
expect($scope.denyReason.hide).toHaveBeenCalledWith();
});
});
});
});

View File

@ -1,3 +1,5 @@
Discard: Descartar
Indicate the reasons to deny this request: Indique las razones para descartar esta peticion
Buy requests: Peticiones de compra
Buy requests: Peticiones de compra
Search request by id or alias: Buscar peticiones por identificador o alias
Sale quantity: C. conseguida

View File

@ -1,3 +1,5 @@
@import "variables";
vn-dialog[vn-id="denyReason"] {
button.close {
display: none
@ -9,4 +11,8 @@ vn-dialog[vn-id="denyReason"] {
vn-textarea {
width: 100%
}
}
vn-icon[icon=insert_drive_file]{
color: $color-font-secondary;
}

View File

@ -13,12 +13,12 @@ module.exports = Self => {
arg: 'itemFk',
type: 'Integer',
required: true,
description: 'The request observation',
description: 'The requested item ID',
}, {
arg: 'quantity',
type: 'Integer',
required: true,
description: 'The request observation',
description: 'The requested item quantity',
}],
returns: {
type: 'Object',
@ -32,6 +32,7 @@ module.exports = Self => {
Self.confirm = async ctx => {
const models = Self.app.models;
let sale;
let tx = await Self.beginTransaction({});
try {
@ -52,32 +53,32 @@ module.exports = Self => {
false
]);
if (stock.available < quantity)
if (stock.available < ctx.args.quantity)
throw new UserError(`This item is not available`);
if (request.saleFk) {
let sale = await models.Sale.findById(request.saleFk);
sale = await models.Sale.findById(request.saleFk);
sale.updateAttributes({
itemFk: ctx.args.itemFk,
quantity: ctx.args.quantity,
description: item.description
concept: item.name,
}, options);
} else {
params = {
sale = await models.Sale.create({
ticketFk: request.ticketFk,
itemFk: ctx.args.itemFk,
quantity: ctx.args.quantity
};
sale = await models.Sale.create(params, options);
request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk}, options);
quantity: ctx.args.quantity,
concept: item.name
}, options);
request.updateAttributes({saleFk: sale.id, itemFk: sale.itemFk, isOk: true}, options);
}
query = `CALL vn.ticketCalculateSale(?)`;
params = [sale.id];
await Self.rawSql(query, params, options);
await Self.rawSql(query, [sale.id], options);
const message = `Se ha comprado ${params.quantity} unidades de "${item.description}" (#${params.itemFk}) `
+ `para el ticket #${params.ticketFk}`;
const message = `Se ha comprado ${sale.quantity} unidades de "${sale.concept}" (#${sale.itemFk}) `
+ `para el ticket #${sale.ticketFk}`;
await models.Message.send(ctx, {
recipientFk: request.requesterFk,

View File

@ -1,6 +1,6 @@
module.exports = Self => {
Self.remoteMethodCtx('deny', {
description: 'Create a newticket and returns the new ID',
description: 'sets a ticket request to denied and returns the changes',
accessType: 'WRITE',
accepts: [{
arg: 'id',
@ -30,7 +30,7 @@ module.exports = Self => {
let params = {
isOk: false,
atenderFk: worker.id,
observation: ctx.args.observation,
response: ctx.args.observation,
};
let request = await Self.app.models.TicketRequest.findById(ctx.args.id);

View File

@ -71,7 +71,7 @@ module.exports = Self => {
switch (param) {
case 'search':
return /^\d+$/.test(value)
? {'t.ticketFk': {inq: value}}
? {'tr.ticketFk': {inq: value}}
: {'t.nickname': {like: `%${value}%`}};
case 'ticketFk':
return {'t.id': value};
@ -92,41 +92,47 @@ module.exports = Self => {
}
});
if (!where)
where = {};
where['tw.ticketFk'] = null;
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,
c.salesPersonFk
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`);
tr.id,
tr.ticketFk,
tr.quantity,
tr.price,
tr.atenderFk,
tr.description,
tr.response,
tr.saleFk,
tr.isOk,
s.quantity AS saleQuantity,
s.itemFK,
i.name AS itemDescription,
t.shipped,
t.nickname,
t.warehouseFk,
t.clientFk,
w.name AS warehouse,
u.nickname AS salesPersonNickname,
ua.nickname AS atenderNickname,
c.salesPersonFk
FROM ticketRequest tr
LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk
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);

View File

@ -0,0 +1,110 @@
const app = require('vn-loopback/server/server');
describe('ticket-request confirm()', () => {
let request;
let sale;
let createdSaleId;
afterAll(async done => {
const paramsForRequest = {
saleFk: request.saleFk,
isOk: request.isOk,
itemFk: request.itemFk,
ticketFk: request.ticketFk
};
const paramsForSale = {
itemFk: sale.itemFk,
quantity: sale.quantity,
concept: sale.concept,
};
await request.updateAttributes(paramsForRequest);
await sale.updateAttributes(paramsForSale);
app.models.Sale.destroyById(createdSaleId);
done();
});
it(`should throw an error if the item doesn't exist`, async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {itemFk: 999}};
let error;
try {
await app.models.TicketRequest.confirm(ctx);
} catch (err) {
error = err;
}
expect(error.message).toEqual(`That item doesn't exists`);
});
it(`should throw an error if the item is not available`, async() => {
const requestId = 4;
const itemId = 1;
const quantity = 99999;
let ctx = {req: {accessToken: {userId: 9}}, args: {
itemFk: itemId,
id: requestId,
quantity: quantity
}};
let error;
try {
await app.models.TicketRequest.confirm(ctx);
} catch (err) {
error = err;
}
expect(error.message).toEqual(`This item is not available`);
});
it(`should update the sale details if the request already contains a sale id`, async() => {
const requestId = 4;
const saleId = 11;
const itemId = 1;
const quantity = 10;
request = await app.models.TicketRequest.findById(requestId);
sale = await app.models.Sale.findById(saleId);
request.updateAttributes({saleFk: saleId});
let ctx = {req: {accessToken: {userId: 9}}, args: {
itemFk: itemId,
id: requestId,
quantity: quantity
}};
await app.models.TicketRequest.confirm(ctx);
let updatedSale = await app.models.Sale.findById(saleId);
expect(updatedSale.itemFk).toEqual(itemId);
expect(updatedSale.quantity).toEqual(quantity);
});
it(`should create a new sale for the the request if there's no sale id`, async() => {
const requestId = 4;
const itemId = 1;
const quantity = 10;
request.updateAttributes({saleFk: null});
let ctx = {req: {accessToken: {userId: 9}}, args: {
itemFk: itemId,
id: requestId,
quantity: quantity,
ticketFk: 1
}};
await app.models.TicketRequest.confirm(ctx);
let updatedRequest = await app.models.TicketRequest.findById(requestId);
createdSaleId = updatedRequest.saleFk;
expect(updatedRequest.saleFk).toEqual(createdSaleId);
expect(updatedRequest.isOk).toEqual(true);
expect(updatedRequest.itemFk).toEqual(itemId);
});
});

View File

@ -0,0 +1,25 @@
const app = require('vn-loopback/server/server');
describe('ticket-request deny()', () => {
let request;
afterAll(async done => {
let params = {
isOk: null,
atenderFk: request.atenderFk,
response: null,
};
await request.updateAttributes(params);
done();
});
it('should return all ticket requests', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
request = await app.models.TicketRequest.findById(ctx.args.id);
let result = await app.models.TicketRequest.deny(ctx);
expect(result.id).toEqual(4);
});
});

View File

@ -0,0 +1,83 @@
const app = require('vn-loopback/server/server');
describe('ticket-request filter()', () => {
it('should return all ticket requests', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {}};
let result = await app.models.TicketRequest.filter(ctx);
expect(result.length).toEqual(1);
});
it('should return the ticket request matching a generic search value which is the ticket ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {search: 11}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching a generic search value which is the client address alias', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {search: 'NY roofs'}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the ticket ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {ticketFk: 11}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the atender ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {atenderFk: 35}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the isOk triple-state', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {isOk: null}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the client ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {clientFk: 102}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the warehouse ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {warehouse: 1}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
it('should return the ticket request matching the salesPerson ID', async() => {
let ctx = {req: {accessToken: {userId: 9}}, args: {salesPersonFk: 18}};
let result = await app.models.TicketRequest.filter(ctx);
let requestId = result[0].id;
expect(requestId).toEqual(4);
});
});