Merge branch 'dev' into export_production
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2021-12-14 09:23:15 +00:00
commit b45263af30
16 changed files with 214 additions and 50 deletions

View File

@ -130,8 +130,7 @@ BEGIN
END END
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED' LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
LEFT JOIN claim cl ON cl.ticketFk = t.id LEFT JOIN claimBeginning cb ON s.id = cb.saleFk
LEFT JOIN claimBeginning cb ON cl.id = cb.claimFk AND s.id = cb.saleFk
WHERE t.shipped >= vDateInventory WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemId AND s.itemFk = vItemId
AND vWarehouse =t.warehouseFk AND vWarehouse =t.warehouseFk

View File

@ -0,0 +1,120 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_recalcComponent`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT)
proc: BEGIN
/**
* Actualiza los componentes
*
* @table tmp.recalculateSales
*/
DECLARE vShipped DATE;
DECLARE vWarehouseFk SMALLINT;
DECLARE vAgencyModeFk INT;
DECLARE vAddressFk INT;
DECLARE vTicketFk BIGINT;
DECLARE vItemFk BIGINT;
DECLARE vLanded DATE;
DECLARE vIsEditable BOOLEAN;
DECLARE vZoneFk INTEGER;
DECLARE vOption INTEGER;
DECLARE vSale INTEGER;
DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vCur CURSOR FOR
SELECT id from tmp.recalculateSales;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
OPEN vCur;
l: LOOP
SET vDone = FALSE;
FETCH vCur INTO vSale;
IF vDone THEN
LEAVE l;
END IF;
SELECT t.refFk IS NULL AND (IFNULL(ts.alertLevel, 0) = 0 OR s.price = 0),
s.ticketFk,
s.itemFk ,
t.zoneFk,
t.warehouseFk,
t.shipped,
t.addressFk,
t.agencyModeFk,
t.landed
INTO vIsEditable,
vTicketFk,
vItemFk,
vZoneFk,
vWarehouseFk,
vShipped,
vAddressFk,
vAgencyModeFk,
vLanded
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
WHERE s.id = vSale;
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');
END IF;
IF vLanded IS NULL OR vZoneFk IS NULL THEN
UPDATE ticket t
SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
WHERE t.id = vTicketFk AND t.landed IS NULL;
IF vZoneFk IS NULL THEN
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
UPDATE ticket t
SET t.zoneFk = vZoneFk
WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
END IF;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetLanded;
-- rellena la tabla buyUltimate con la ultima compra
CALL buyUltimate (vWarehouseFk, vShipped);
DELETE FROM tmp.buyUltimate WHERE itemFk != vItemFk;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot
SELECT vWarehouseFk warehouseFk, NULL available, vItemFk itemFk, buyFk, vZoneFk zoneFk
FROM tmp.buyUltimate
WHERE itemFk = vItemFk;
CALL catalog_componentPrepare();
CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk);
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT vSale saleFk,vWarehouseFk warehouseFk;
IF vOption IS NULL THEN
SET vOption = IF(vIsEditable, 1, 6);
END IF;
CALL ticketComponentUpdateSale(vOption);
CALL catalog_componentPurge();
DROP TEMPORARY TABLE tmp.buyUltimate;
DROP TEMPORARY TABLE tmp.sale;
END LOOP;
CLOSE vCur;
END$$
DELIMITER ;

View File

@ -0,0 +1,23 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_calculateComponent`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponent`(vSale INT, vOption INT)
proc: BEGIN
/**
* Crea tabla temporal para vn.sale_recalcComponent() para recalcular los componentes
*
* @param vSale Id de la venta
* @param vOption indica en que componente pone el descuadre, NULL en casos habituales
*/
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
CREATE TEMPORARY TABLE tmp.recalculateSales
SELECT s.id
FROM sale s
WHERE s.id = vSale;
CALL vn.sale_recalcComponent(vOption);
DROP TEMPORARY TABLE tmp.recalculateSales;
END$$
DELIMITER ;

View File

@ -557,6 +557,7 @@ export default {
moreMenuReserve: 'vn-item[name="reserve"]', moreMenuReserve: 'vn-item[name="reserve"]',
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]', moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',

View File

@ -195,6 +195,17 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50'); expect(result).toContain('22.50');
}); });
it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuRecalculatePrice);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and create a claim of it', async() => {
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);

View File

@ -28,7 +28,6 @@
} }
.icon-account:before { .icon-account:before {
content: "\e900"; content: "\e900";
color: #000;
} }
.icon-actions:before { .icon-actions:before {
content: "\e901"; content: "\e901";
@ -71,7 +70,6 @@
} }
.icon-bucket:before { .icon-bucket:before {
content: "\e90e"; content: "\e90e";
color: #000;
} }
.icon-buscaman:before { .icon-buscaman:before {
content: "\e90f"; content: "\e90f";

View File

@ -56,7 +56,7 @@ class Controller extends Section {
this.$http.get(query).then(response => { this.$http.get(query).then(response => {
if (!response.data) if (!response.data)
throw new UserError(`The route's vehicle doesn't have a departing warehouse`); throw new UserError(`The route's vehicle doesn't have a delivery point`);
return response.data; return response.data;
}).then(address => { }).then(address => {

View File

@ -9,6 +9,6 @@ Tickets to add: Tickets a añadir
Ticket not found: No se ha encontrado el ticket Ticket not found: No se ha encontrado el ticket
The selected ticket is not suitable for this route: El ticket seleccionado no es apto para esta ruta The selected ticket is not suitable for this route: El ticket seleccionado no es apto para esta ruta
PC: CP PC: CP
The route's vehicle doesn't have a departing warehouse: El vehículo de la ruta no tiene un almacén de salida The route's vehicle doesn't have a delivery point: El vehículo de la ruta no tiene un punto de entrega
The route doesn't have a vehicle: La ruta no tiene un vehículo The route doesn't have a vehicle: La ruta no tiene un vehículo
Population: Población Population: Población

View File

@ -16,7 +16,6 @@
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="supplierAccount in $ctrl.supplierAccounts"> <vn-horizontal ng-repeat="supplierAccount in $ctrl.supplierAccounts">
<vn-textfield <vn-textfield
ng-show="supplierAccount.iban || supplierAccount.iban == undefined"
label="Iban" label="Iban"
ng-model="supplierAccount.iban" ng-model="supplierAccount.iban"
on-change="supplierAccount.bankEntityFk = supplierAccount.iban.slice(4,8)" on-change="supplierAccount.bankEntityFk = supplierAccount.iban.slice(4,8)"
@ -30,10 +29,10 @@
rule> rule>
<append> <append>
<vn-icon-button <vn-icon-button
vn-auto
icon="add_circle" icon="add_circle"
vn-tooltip="New bank entity" vn-click-stop="bankEntity.show({index: $index})"
ng-click="$ctrl.showBankEntity($event, $index)" vn-tooltip="New bank entity">
tabindex="-1">
</vn-icon-button> </vn-icon-button>
</append> </append>
</vn-autocomplete> </vn-autocomplete>
@ -41,14 +40,6 @@
label="Beneficiary" label="Beneficiary"
ng-model="supplierAccount.beneficiary" ng-model="supplierAccount.beneficiary"
info="Beneficiary information"> info="Beneficiary information">
<append>
<vn-icon-button
vn-auto
icon="add_circle"
vn-click-stop="bankEntity.show({index: $index})"
vn-tooltip="New bank entity">
</vn-icon-button>
</append>
</vn-textfield> </vn-textfield>
<vn-none> <vn-none>
<vn-icon-button <vn-icon-button

View File

@ -1,26 +1,26 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('recalculatePrice', { Self.remoteMethodCtx('recalculatePrice', {
description: 'Calculates the price of a sale and its components', description: 'Calculates the price of sales and its components',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'sales',
description: 'The sale id', description: 'The sales',
type: 'number', type: ['object'],
required: true, required: true,
http: {source: 'path'} http: {source: 'body'}
}], }],
returns: { returns: {
type: 'Number', type: 'number',
root: true root: true
}, },
http: { http: {
path: `/:id/recalculatePrice`, path: `/recalculatePrice`,
verb: 'post' verb: 'post'
} }
}); });
Self.recalculatePrice = async(ctx, id, options) => { Self.recalculatePrice = async(ctx, sales, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -34,18 +34,33 @@ module.exports = Self => {
} }
try { try {
const sale = await Self.findById(id, null, myOptions); const salesIds = [];
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions); const params = [];
sales.forEach(sale => {
salesIds.push(sale.id);
params.push('?');
});
const isEditable = await models.Ticket.isEditable(ctx, sales[0].ticketFk, myOptions);
if (!isEditable) if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
if (!canEditSale) if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`); throw new UserError(`Sale(s) blocked, please contact production`);
const recalculation = await Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id], myOptions); const paramsString = params.join();
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
CREATE TEMPORARY TABLE tmp.recalculateSales
SELECT s.id
FROM sale s
WHERE s.id IN (${paramsString});
CALL vn.sale_recalcComponent(null);
DROP TEMPORARY TABLE tmp.recalculateSales;`;
const recalculation = await Self.rawSql(query, salesIds, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -1,18 +1,20 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('sale recalculatePrice()', () => { describe('sale recalculatePrice()', () => {
const saleId = 7;
it('should update the sale price', async() => { it('should update the sale price', async() => {
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}}; const ctx = {req: {accessToken: {userId: 9}}};
const response = await models.Sale.recalculatePrice(ctx, saleId, options); const response = await models.Sale.recalculatePrice(ctx, sales, options);
expect(response.affectedRows).toBeDefined(); expect(response[0].affectedRows).toBeDefined();
expect(response[1].affectedRows).toBeDefined();
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -29,8 +31,8 @@ describe('sale recalculatePrice()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}}; const ctx = {req: {accessToken: {userId: 9}}};
const immutableSaleId = 1; const immutableSale = [{id: 1, ticketFk: 1}];
await models.Sale.recalculatePrice(ctx, immutableSaleId, options); await models.Sale.recalculatePrice(ctx, immutableSale, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -177,6 +177,8 @@ module.exports = Self => {
return {'a.provinceFk': value}; return {'a.provinceFk': value};
case 'stateFk': case 'stateFk':
return {'ts.stateFk': value}; return {'ts.stateFk': value};
case 'collectionFk':
return {'cll.id': value};
case 'mine': case 'mine':
case 'myTeam': case 'myTeam':
if (value) if (value)
@ -276,9 +278,8 @@ module.exports = Self => {
if (args.collectionFk) { if (args.collectionFk) {
stmt.merge({ stmt.merge({
sql: ` sql: `
JOIN collection cll ON cll.id = ? JOIN collection cll
JOIN ticketCollection tc ON tc.collectionFk = cll.id AND tc.ticketFk = t.id`, JOIN ticketCollection tc ON tc.collectionFk = cll.id AND tc.ticketFk = t.id`
params: [args.collectionFk]
}); });
} }

View File

@ -8,6 +8,7 @@ export default class Ticket extends ModuleMain {
'to', 'to',
'search', 'search',
'clientFk', 'clientFk',
'collectionFk',
'orderFk', 'orderFk',
'refFk', 'refFk',
'scopeDays' 'scopeDays'

View File

@ -464,7 +464,7 @@
<vn-item translate <vn-item translate
name="calculatePrice" name="calculatePrice"
ng-click="$ctrl.calculateSalePrice()" ng-click="$ctrl.calculateSalePrice()"
ng-if="$ctrl.isEditable && $ctrl.hasOneSaleSelected()"> ng-if="$ctrl.isEditable">
Recalculate price Recalculate price
</vn-item> </vn-item>
<vn-item translate <vn-item translate

View File

@ -450,10 +450,11 @@ class Controller extends Section {
} }
calculateSalePrice() { calculateSalePrice() {
const sale = this.selectedValidSales()[0]; const sales = this.selectedValidSales();
if (!sale) return; if (!sales) return;
const query = `Sales/${sale.id}/recalculatePrice`;
this.$http.post(query).then(() => { const query = `Sales/recalculatePrice`;
this.$http.post(query, sales).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.model.refresh(); this.$.model.refresh();
}); });

View File

@ -688,10 +688,11 @@ describe('Ticket', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
const selectedSale = controller.sales[0]; controller.sales.forEach(sale => {
selectedSale.checked = true; sale.checked = true;
});
$httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200); $httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200);
controller.calculateSalePrice(); controller.calculateSalePrice();
$httpBackend.flush(); $httpBackend.flush();