This commit is contained in:
Juan Ferrer 2019-11-14 15:05:49 +01:00
commit 09a187ecfe
16 changed files with 168 additions and 52 deletions

View File

@ -111,9 +111,7 @@
"This phone already exists": "Este teléfono ya existe", "This phone already exists": "Este teléfono ya existe",
"You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos", "You cannot move a parent to its own sons": "No puedes mover un elemento padre a uno de sus hijos",
"You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado", "You can't create a claim for a removed ticket": "No puedes crear una reclamación para un ticket eliminado",
"You cannot delete this ticket because is already invoiced, deleted or prepared": "No puedes eliminar este tiquet porque ya está facturado, eliminado o preparado",
"You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada", "You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada",
"You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero", "You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero",
"Has deleted the ticket id": "Ha eliminado el ticket id [#{{id}}]({{{url}}})", "Has deleted the ticket id": "Ha eliminado el ticket id [#{{id}}]({{{url}}})"
"You cannot remove this ticket because is already invoiced, deleted or prepared": "You cannot remove this ticket because is already invoiced, deleted or prepared"
} }

View File

@ -0,0 +1,34 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('calculate', {
description: 'Calculates the price of a sale and its components',
accessType: 'WRITE',
accepts: [{
arg: 'id',
description: 'The sale id',
type: 'number',
required: true,
http: {source: 'path'}
}],
returns: {
type: 'Number',
root: true
},
http: {
path: `/:id/calculate`,
verb: 'post'
}
});
Self.calculate = async(ctx, id) => {
const models = Self.app.models;
const sale = await Self.findById(id);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`);
return Self.rawSql('CALL vn.ticketCalculateSale(?)', [id]);
};
};

View File

@ -0,0 +1,24 @@
const app = require('vn-loopback/server/server');
describe('sale calculate()', () => {
const saleId = 7;
it('should update the sale price', async() => {
const ctx = {req: {accessToken: {userId: 9}}};
const response = await app.models.Sale.calculate(ctx, saleId);
expect(response.affectedRows).toBeDefined();
});
it('should throw an error if the ticket is not editable', async() => {
const ctx = {req: {accessToken: {userId: 9}}};
const immutableSaleId = 1;
await app.models.Sale.calculate(ctx, immutableSaleId)
.catch(response => {
expect(response).toEqual(new Error(`The sales of this ticket can't be modified`));
error = response;
});
expect(error).toBeDefined();
});
});

View File

@ -27,7 +27,7 @@ module.exports = Self => {
const $t = ctx.req.__; // $translate const $t = ctx.req.__; // $translate
if (!isEditable) if (!isEditable)
throw new UserError('You cannot delete this ticket because is already invoiced, deleted or prepared'); throw new UserError(`The sales of this ticket can't be modified`);
// Check if has sales with shelving // Check if has sales with shelving
const sales = await models.Sale.find({ const sales = await models.Sale.find({

View File

@ -5,6 +5,7 @@ module.exports = Self => {
require('../methods/sale/updatePrice')(Self); require('../methods/sale/updatePrice')(Self);
require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateQuantity')(Self);
require('../methods/sale/updateConcept')(Self); require('../methods/sale/updateConcept')(Self);
require('../methods/sale/calculate')(Self);
Self.validatesPresenceOf('concept', { Self.validatesPresenceOf('concept', {
message: `Concept cannot be blank` message: `Concept cannot be blank`

View File

@ -11,4 +11,4 @@
</vn-step-control> </vn-step-control>
</vn-button-bar> </vn-button-bar>
<ui-view></ui-view> <ui-view></ui-view>
</div> </div>

View File

@ -5,7 +5,7 @@
auto-load="true"> auto-load="true">
</vn-crud-model> </vn-crud-model>
<form name="form"> <form name="form">
<vn-card class="vn-pa-lg"> <vn-card class="vn-w-md vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one
vn-id="client" vn-id="client"

View File

@ -1,5 +1,5 @@
<form name="form"> <form name="form">
<vn-card class="vn-pa-lg"> <vn-card class="vn-w-md vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<vn-autocomplete vn-one <vn-autocomplete vn-one
url="TicketUpdateActions" url="TicketUpdateActions"

View File

@ -13,8 +13,14 @@ class Controller {
this.data.registerChild(this); this.data.registerChild(this);
} }
$onChanges() { get ticket() {
this.ticket.option = 1; return this._ticket;
}
set ticket(value) {
this._ticket = value;
if (value) this.ticket.option = 1;
} }
onStepChange(state) { onStepChange(state) {

View File

@ -21,8 +21,7 @@ describe('ticket', () => {
describe('onSubmit()', () => { describe('onSubmit()', () => {
it(`should return an error if the item doesn't have option property in the controller`, () => { it(`should return an error if the item doesn't have option property in the controller`, () => {
controller.ticket = {}; controller._ticket = {id: 1};
controller.onSubmit(); controller.onSubmit();
expect(vnApp.showError).toHaveBeenCalledWith('Choose an option'); expect(vnApp.showError).toHaveBeenCalledWith('Choose an option');

View File

@ -1,42 +1,40 @@
<form name="form"> <form name="form">
<vn-card class="vn-pa-lg"> <vn-card class="vn-w-lg vn-pa-lg">
<vn-horizontal> <vn-table>
<table class="vn-table"> <vn-thead>
<thead> <vn-tr>
<tr> <vn-th number>Item</vn-th>
<th number translate>Item</th> <vn-th style="text-align:center">Description</vn-th>
<th translate style="text-align:center">Description</th> <vn-th number>Quantity</vn-th>
<th number translate>Quantity</th> <vn-th number>Price (PPU)</vn-th>
<th number translate>Price (PPU)</th> <vn-th number>New price (PPU)</vn-th>
<th number translate>New price (PPU)</th> <vn-th number>Price difference</vn-th>
<th number translate>Price difference</th> </vn-tr>
</tr> </vn-thead>
</thead> <vn-tbody>
<tbody> <vn-tr ng-repeat="sale in $ctrl.ticket.sale.items track by sale.id">
<tr ng-repeat="sale in $ctrl.ticket.sale.items track by sale.id"> <vn-td number>{{("000000"+sale.itemFk).slice(-6)}}</vn-td>
<td number>{{("000000"+sale.itemFk).slice(-6)}}</td> <vn-td expand>
<td expand> <vn-fetched-tags
<vn-fetched-tags max-length="6"
max-length="6" item="::sale.item"
item="::sale.item" name="::sale.concept">
name="::sale.concept"> </vn-fetched-tags>
</vn-fetched-tags> </vn-td>
</td> <vn-td number>{{::sale.quantity}}</vn-td>
<td number>{{::sale.quantity}}</td> <vn-td number>{{::sale.price | currency: 'EUR': 2}}</vn-td>
<td number>{{::sale.price | currency: 'EUR': 2}}</td> <vn-td number>{{::sale.component.newPrice | currency: 'EUR': 2}}</vn-td>
<td number>{{::sale.component.newPrice | currency: 'EUR': 2}}</td> <vn-td number>{{::sale.component.difference | currency: 'EUR': 2}}</vn-td>
<td number>{{::sale.component.difference | currency: 'EUR': 2}}</td> </vn-tr>
</tr> </vn-tbody>
</tbody> <vn-tfoot>
<tfoot> <vn-tr>
<tr> <vn-td colspan="3"></vn-td>
<td colspan="3"></td> <vn-td number><strong>{{$ctrl.totalPrice | currency: 'EUR': 2}}</strong></vn-td>
<td number><strong>{{$ctrl.totalPrice | currency: 'EUR': 2}}</strong></td> <vn-td number><strong>{{$ctrl.totalNewPrice | currency: 'EUR': 2}}</strong></vn-td>
<td number><strong>{{$ctrl.totalNewPrice | currency: 'EUR': 2}}</strong></td> <vn-td number><strong>{{$ctrl.totalPriceDifference | currency: 'EUR': 2}}</strong></vn-td>
<td number><strong>{{$ctrl.totalPriceDifference | currency: 'EUR': 2}}</strong></td> </vn-tr>
</tr> </vn-tfoot>
</tfoot> </vn-table>
</table>
</vn-horizontal>
</vn-card> </vn-card>
</form> </form>

View File

@ -7,6 +7,17 @@ class Controller {
$onInit() { $onInit() {
this.data.registerChild(this); this.data.registerChild(this);
}
get ticket() {
return this._ticket;
}
set ticket(value) {
this._ticket = value;
if (!value) return;
this.getTotalPrice(); this.getTotalPrice();
this.getTotalNewPrice(); this.getTotalNewPrice();
this.getTotalDifferenceOfPrice(); this.getTotalDifferenceOfPrice();

View File

@ -40,7 +40,7 @@ class Controller extends Component {
showChangeShipped() { showChangeShipped() {
if (!this.isEditable) { if (!this.isEditable) {
this.vnApp.showError(this.$translate.instant('This ticket can\'t be modified')); this.vnApp.showError(this.$translate.instant(`This ticket can't be modified`));
return; return;
} }
this.newShipped = this.ticket.shipped; this.newShipped = this.ticket.shipped;

View File

@ -32,6 +32,11 @@ class Controller {
callback: this.createClaim, callback: this.createClaim,
show: () => this.isEditable show: () => this.isEditable
}, },
{
name: 'Recalculate price',
callback: this.calculateSalePrice,
show: () => this.hasOneSaleSelected()
},
]; ];
this._sales = []; this._sales = [];
this.imagesPath = '//verdnatura.es/vn-image-data/catalog'; this.imagesPath = '//verdnatura.es/vn-image-data/catalog';
@ -534,6 +539,21 @@ class Controller {
this.isEditable = res.data; this.isEditable = res.data;
}); });
} }
hasOneSaleSelected() {
if (this.totalCheckedLines() === 1)
return true;
return false;
}
calculateSalePrice() {
const sale = this.checkedLines()[0];
const query = `Sales/${sale.id}/calculate`;
this.$http.post(query).then(res => {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.$scope.model.refresh();
});
}
} }
Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate']; Controller.$inject = ['$scope', '$state', '$http', 'vnApp', '$translate'];

View File

@ -29,4 +29,5 @@ SMSAvailability: >-
{{notAvailables}} no disponible/s. Disculpe las molestias. {{notAvailables}} no disponible/s. Disculpe las molestias.
Continue anyway?: ¿Continuar de todas formas? Continue anyway?: ¿Continuar de todas formas?
This ticket is now empty: El ticket ha quedado vacio This ticket is now empty: El ticket ha quedado vacio
Do you want to delete it?: ¿Quieres borrarlo? Do you want to delete it?: ¿Quieres borrarlo?
Recalculate price: Recalcular precio

View File

@ -1,5 +1,6 @@
import '../index.js'; import '../index.js';
import watcher from 'core/mocks/watcher'; import watcher from 'core/mocks/watcher';
import crudModel from 'core/mocks/crud-model';
describe('Ticket', () => { describe('Ticket', () => {
describe('Component vnTicketSale', () => { describe('Component vnTicketSale', () => {
@ -40,6 +41,7 @@ describe('Ticket', () => {
$scope.watcher = watcher; $scope.watcher = watcher;
$scope.sms = {open: () => {}}; $scope.sms = {open: () => {}};
$scope.ticket = ticket; $scope.ticket = ticket;
$scope.model = crudModel;
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
Object.defineProperties($state.params, { Object.defineProperties($state.params, {
id: { id: {
@ -334,5 +336,27 @@ describe('Ticket', () => {
expect(window.open).toHaveBeenCalledWith('/somePath', '_blank'); expect(window.open).toHaveBeenCalledWith('/somePath', '_blank');
}); });
}); });
describe('hasOneSaleSelected()', () => {
it('should return true if just one sale is selected', () => {
controller.sales[0].checked = true;
expect(controller.hasOneSaleSelected()).toBeTruthy();
});
});
describe('calculateSalePrice()', () => {
it('should make an HTTP post query ', () => {
controller.sales[0].checked = true;
$httpBackend.when('POST', `Sales/4/calculate`).respond(200);
$httpBackend.whenGET(`Tickets/1/subtotal`).respond(200, 227.5);
$httpBackend.whenGET(`Tickets/1/getVAT`).respond(200, 10.5);
$httpBackend.whenGET(`Tickets/1/isEditable`).respond();
controller.calculateSalePrice();
$httpBackend.flush();
});
});
}); });
}); });