Merge branch 'dev' into 8069-Overstocking
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
gitea/salix/pipeline/pr-dev There was a failure building this commit
Details
This commit is contained in:
commit
1b254b138e
|
@ -0,0 +1,56 @@
|
|||
DELIMITER $$
|
||||
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`invoiceOut_newFromAddress`(
|
||||
IN vAddressFk INT,
|
||||
IN vSerial CHAR(2),
|
||||
IN vMaxShipped DATE,
|
||||
IN vCompanyFk INT,
|
||||
IN vTaxArea VARCHAR(25),
|
||||
IN vRef VARCHAR(25),
|
||||
OUT vInvoiceId INT)
|
||||
BEGIN
|
||||
/**
|
||||
* Factura los tickets de un consignatario hasta una fecha dada
|
||||
* @param vAddressFk Id del consignatario a facturar
|
||||
* @param vSerial Serie de factura
|
||||
* @param vMaxShipped Fecha hasta la cual cogerá tickets para facturar
|
||||
* @param vCompanyFk Id de la empresa desde la que se factura
|
||||
* @param vTaxArea Tipo de iva en relacion a la empresa y al cliente, NULL por defecto
|
||||
* @param vRef Referencia de la factura en caso que se quiera forzar, NULL por defecto
|
||||
* @return vInvoiceId factura
|
||||
*/
|
||||
DECLARE vIsRefEditable BOOLEAN;
|
||||
|
||||
IF vRef IS NOT NULL AND vSerial IS NOT NULL THEN
|
||||
SELECT isRefEditable INTO vIsRefEditable
|
||||
FROM invoiceOutSerial
|
||||
WHERE code = vSerial;
|
||||
|
||||
IF NOT vIsRefEditable THEN
|
||||
CALL util.throw('serial non editable');
|
||||
END IF;
|
||||
END IF;
|
||||
|
||||
DROP TEMPORARY TABLE IF EXISTS `tmp`.`ticketToInvoice`;
|
||||
CREATE TEMPORARY TABLE `tmp`.`ticketToInvoice`
|
||||
(PRIMARY KEY (`id`))
|
||||
ENGINE = MEMORY
|
||||
SELECT id FROM ticket t
|
||||
WHERE t.addressFk = vAddressFk
|
||||
AND t.refFk IS NULL
|
||||
AND t.companyFk = vCompanyFk
|
||||
AND t.shipped BETWEEN
|
||||
util.firstDayOfYear(vMaxShipped - INTERVAL 1 YEAR)
|
||||
AND util.dayend(vMaxShipped);
|
||||
|
||||
CALL invoiceOut_new(vSerial, util.VN_CURDATE(), vTaxArea, vInvoiceId);
|
||||
|
||||
UPDATE invoiceOut
|
||||
SET `ref` = vRef
|
||||
WHERE id = vInvoiceId
|
||||
AND vRef IS NOT NULL;
|
||||
|
||||
IF vSerial <> 'R' AND NOT ISNULL(vInvoiceId) AND vInvoiceId <> 0 THEN
|
||||
CALL invoiceOutBooking(vInvoiceId);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -1,16 +1,22 @@
|
|||
DELIMITER $$
|
||||
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemShelving_addList`(vShelvingFk VARCHAR(3), vList TEXT, vIsChecking BOOL, vWarehouseFk INT)
|
||||
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`itemShelving_addList`(
|
||||
vShelvingFk VARCHAR(3),
|
||||
vList TEXT,
|
||||
vIsChecking BOOL,
|
||||
vWarehouseFk INT
|
||||
)
|
||||
BEGIN
|
||||
/* Recorre cada elemento en la colección vList.
|
||||
/**
|
||||
* Recorre cada elemento en la colección vList.
|
||||
* Si el parámetro isChecking = FALSE, llama a itemShelving_add.
|
||||
*
|
||||
* Cuando es TRUE sólo inserta los elementos de la colección que no están ya en
|
||||
* ese shelving, actualizando los valores del campo vn.itemShelving.isChecked
|
||||
* ese shelving, actualizando los valores del campo itemShelving.isChecked
|
||||
*
|
||||
* param vShelvingFk Identificador de vn.shelving
|
||||
* param vList JSON array con esta estructura: '[value1, value2, ...]'
|
||||
* param vIsChecking Define si hay que añadir o comprobar los items
|
||||
* param vWarehouseFk Identificador de vn.warehouse
|
||||
* @param vShelvingFk Identificador de shelving
|
||||
* @param vList JSON array con esta estructura: '[value1, value2, ...]'
|
||||
* @param vIsChecking Define si hay que añadir o comprobar los items
|
||||
* @param vWarehouseFk Identificador de warehouse
|
||||
*/
|
||||
DECLARE vListLength INT DEFAULT JSON_LENGTH(vList);
|
||||
DECLARE vCounter INT DEFAULT 0;
|
||||
|
@ -20,26 +26,27 @@ BEGIN
|
|||
DECLARE vIsChecked BOOL;
|
||||
|
||||
WHILE vCounter < vListLength DO
|
||||
SET vPath = CONCAT('$[',vCounter,']');
|
||||
SET vBarcode = JSON_EXTRACT(vList,vPath);
|
||||
SET vPath = CONCAT('$[', vCounter, ']');
|
||||
SET vBarcode = JSON_EXTRACT(vList, vPath);
|
||||
SET vIsChecked = NULL;
|
||||
IF vIsChecking THEN
|
||||
SELECT barcodeToItem(vBarcode) INTO vItemFk;
|
||||
|
||||
SELECT COUNT(*) INTO vIsChecked
|
||||
FROM vn.itemShelving
|
||||
SELECT IF(COUNT(*), TRUE, FALSE) INTO vIsChecked
|
||||
FROM itemShelving
|
||||
WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk
|
||||
AND itemFk = vItemFk;
|
||||
END IF;
|
||||
|
||||
IF NOT (vIsChecking AND vIsChecked) THEN
|
||||
CALL vn.itemShelving_add(vShelvingFk, vBarcode, 1, NULL, NULL, NULL, vWarehouseFk);
|
||||
IF NOT vIsChecking OR NOT vIsChecked THEN
|
||||
CALL itemShelving_add(vShelvingFk, vBarcode, 1, NULL, NULL, NULL, vWarehouseFk);
|
||||
END IF;
|
||||
|
||||
UPDATE vn.itemShelving
|
||||
UPDATE itemShelving
|
||||
SET isChecked = vIsChecked
|
||||
WHERE shelvingFk COLLATE utf8_unicode_ci = vShelvingFk
|
||||
AND itemFk = vItemFk AND isChecked IS NULL;
|
||||
AND itemFk = vItemFk
|
||||
AND isChecked IS NULL;
|
||||
|
||||
SET vCounter = vCounter + 1;
|
||||
END WHILE;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
UPDATE vn.itemShelving
|
||||
SET isChecked = TRUE
|
||||
WHERE isChecked;
|
||||
|
||||
UPDATE vn.itemShelving
|
||||
SET isChecked = FALSE
|
||||
WHERE NOT isChecked;
|
|
@ -10,6 +10,11 @@ module.exports = Self => {
|
|||
type: 'any',
|
||||
description: 'The invoiceable client id'
|
||||
},
|
||||
{
|
||||
arg: 'addressFk',
|
||||
type: 'any',
|
||||
description: 'The address id'
|
||||
},
|
||||
{
|
||||
arg: 'ticketFk',
|
||||
type: 'any',
|
||||
|
@ -23,7 +28,8 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'serial',
|
||||
type: 'string',
|
||||
description: 'The invoice serial'
|
||||
description: 'The invoice serial',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'taxArea',
|
||||
|
@ -46,7 +52,8 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.createManualInvoice = async(ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options) => {
|
||||
Self.createManualInvoice =
|
||||
async(ctx, clientFk, addressFk, ticketFk, maxShipped, serial, taxArea, reference, options) => {
|
||||
if (!clientFk && !ticketFk) throw new UserError(`Select ticket or client`);
|
||||
const models = Self.app.models;
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
|
@ -60,34 +67,43 @@ module.exports = Self => {
|
|||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let companyId;
|
||||
let companyFk;
|
||||
let newInvoice;
|
||||
let query;
|
||||
try {
|
||||
if (ticketFk) {
|
||||
const ticket = await models.Ticket.findById(ticketFk, null, myOptions);
|
||||
const company = await models.Company.findById(ticket.companyFk, null, myOptions);
|
||||
const ticket = await models.Ticket.findById(ticketFk, {
|
||||
fields: ['clientFk', 'companyFk', 'shipped', 'refFk', 'totalWithVat']
|
||||
}, myOptions);
|
||||
const company = await models.Company.findById(ticket.companyFk, {
|
||||
fields: ['code']
|
||||
}, myOptions);
|
||||
|
||||
clientFk = ticket.clientFk;
|
||||
maxShipped = ticket.shipped;
|
||||
companyId = ticket.companyFk;
|
||||
companyFk = ticket.companyFk;
|
||||
|
||||
// Validates invoiced ticket
|
||||
if (ticket.refFk)
|
||||
throw new UserError('This ticket is already invoiced');
|
||||
|
||||
// Validates ticket amount
|
||||
if (ticket.totalWithVat == 0)
|
||||
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
|
||||
|
||||
// Validates ticket nagative base
|
||||
const hasNegativeBase = await getNegativeBase(maxShipped, clientFk, companyId, myOptions);
|
||||
const hasNegativeBase = await getNegativeBase(maxShipped, clientFk, companyFk, myOptions);
|
||||
if (hasNegativeBase && company.code == 'VNL')
|
||||
throw new UserError(`A ticket with a negative base can't be invoiced`);
|
||||
} else {
|
||||
if (!maxShipped)
|
||||
throw new UserError(`Max shipped required`);
|
||||
|
||||
if (addressFk) {
|
||||
const address = await models.Address.findById(addressFk, {
|
||||
fields: ['clientFk']
|
||||
}, myOptions);
|
||||
|
||||
if (clientFk && clientFk !== address.clientFk)
|
||||
throw new UserError('The provided clientFk does not match');
|
||||
}
|
||||
const company = await models.Ticket.findOne({
|
||||
fields: ['companyFk'],
|
||||
where: {
|
||||
|
@ -95,22 +111,20 @@ module.exports = Self => {
|
|||
shipped: {lte: maxShipped}
|
||||
}
|
||||
}, myOptions);
|
||||
companyId = company.companyFk;
|
||||
companyFk = company.companyFk;
|
||||
}
|
||||
|
||||
// Validate invoiceable client
|
||||
const isClientInvoiceable = await isInvoiceable(clientFk, myOptions);
|
||||
if (!isClientInvoiceable)
|
||||
throw new UserError(`This client is not invoiceable`);
|
||||
|
||||
// Can't invoice tickets into future
|
||||
const tomorrow = Date.vnNew();
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
if (maxShipped >= tomorrow)
|
||||
throw new UserError(`Can't invoice to future`);
|
||||
|
||||
const maxInvoiceDate = await getMaxIssued(serial, companyId, myOptions);
|
||||
const maxInvoiceDate = await getMaxIssued(serial, companyFk, myOptions);
|
||||
if (Date.vnNew() < maxInvoiceDate)
|
||||
throw new UserError(`Can't invoice to past`);
|
||||
|
||||
|
@ -122,13 +136,23 @@ module.exports = Self => {
|
|||
taxArea,
|
||||
reference
|
||||
], myOptions);
|
||||
} else if (addressFk) {
|
||||
query = `CALL invoiceOut_newFromAddress(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
|
||||
await Self.rawSql(query, [
|
||||
addressFk,
|
||||
serial,
|
||||
maxShipped,
|
||||
companyFk,
|
||||
taxArea,
|
||||
reference
|
||||
], myOptions);
|
||||
} else {
|
||||
query = `CALL invoiceOut_newFromClient(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
|
||||
await Self.rawSql(query, [
|
||||
clientFk,
|
||||
serial,
|
||||
maxShipped,
|
||||
companyId,
|
||||
companyFk,
|
||||
taxArea,
|
||||
reference
|
||||
], myOptions);
|
||||
|
@ -159,10 +183,10 @@ module.exports = Self => {
|
|||
return result.invoiceable;
|
||||
}
|
||||
|
||||
async function getNegativeBase(maxShipped, clientFk, companyId, options) {
|
||||
async function getNegativeBase(maxShipped, clientFk, companyFk, options) {
|
||||
const models = Self.app.models;
|
||||
await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)',
|
||||
[maxShipped, clientFk, companyId], options
|
||||
[maxShipped, clientFk, companyFk], options
|
||||
);
|
||||
const query = 'SELECT vn.hasAnyNegativeBase() AS base';
|
||||
const [result] = await models.InvoiceOut.rawSql(query, [], options);
|
||||
|
@ -170,14 +194,14 @@ module.exports = Self => {
|
|||
return result.base;
|
||||
}
|
||||
|
||||
async function getMaxIssued(serial, companyId, options) {
|
||||
async function getMaxIssued(serial, companyFk, options) {
|
||||
const models = Self.app.models;
|
||||
const query = `SELECT MAX(issued) AS issued
|
||||
FROM invoiceOut
|
||||
WHERE serial = ? AND companyFk = ?`;
|
||||
const [maxIssued] = await models.InvoiceOut.rawSql(query,
|
||||
[serial, companyId], options);
|
||||
const maxInvoiceDate = maxIssued && maxIssued.issued || Date.vnNew();
|
||||
[serial, companyFk], options);
|
||||
const maxInvoiceDate = maxIssued?.issued || Date.vnNew();
|
||||
|
||||
return maxInvoiceDate;
|
||||
}
|
||||
|
|
|
@ -6,110 +6,90 @@ describe('InvoiceOut createManualInvoice()', () => {
|
|||
const clientId = 1106;
|
||||
const activeCtx = {accessToken: {userId: 1}};
|
||||
const ctx = {req: activeCtx};
|
||||
let tx; let options;
|
||||
|
||||
beforeEach(async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(Promise.resolve(true));
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
|
||||
tx = await models.InvoiceOut.beginTransaction({});
|
||||
options = {transaction: tx};
|
||||
});
|
||||
|
||||
afterEach(async() => {
|
||||
await tx.rollback();
|
||||
});
|
||||
|
||||
it('should throw an error trying to invoice again', async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
|
||||
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
let error;
|
||||
try {
|
||||
await createInvoice(ctx, options, undefined, ticketId);
|
||||
await createInvoice(ctx, options, undefined, ticketId);
|
||||
|
||||
await tx.rollback();
|
||||
await createInvoice(ctx, options, undefined, undefined, ticketId);
|
||||
await createInvoice(ctx, options, undefined, undefined, ticketId);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain('This ticket is already invoiced');
|
||||
});
|
||||
|
||||
it('should throw an error for a ticket with an amount of zero', async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
let error;
|
||||
try {
|
||||
const ticket = await models.Ticket.findById(ticketId, null, options);
|
||||
await ticket.updateAttributes({totalWithVat: 0}, options);
|
||||
|
||||
await createInvoice(ctx, options, undefined, ticketId);
|
||||
await tx.rollback();
|
||||
await createInvoice(ctx, options, undefined, undefined, ticketId);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`A ticket with an amount of zero can't be invoiced`);
|
||||
});
|
||||
|
||||
it('should throw an error when the clientFk property is set without the max shipped date', async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
|
||||
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
let error;
|
||||
try {
|
||||
await createInvoice(ctx, options, clientId);
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`Max shipped required`);
|
||||
});
|
||||
|
||||
it('should throw an error for a non-invoiceable client', async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
let error;
|
||||
try {
|
||||
const client = await models.Client.findById(clientId, null, options);
|
||||
await client.updateAttributes({isTaxDataChecked: false}, options);
|
||||
|
||||
await createInvoice(ctx, options, undefined, ticketId);
|
||||
|
||||
await tx.rollback();
|
||||
await createInvoice(ctx, options, undefined, undefined, ticketId);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error.message).toContain(`This client is not invoiceable`);
|
||||
});
|
||||
|
||||
it('should create a manual invoice', async() => {
|
||||
spyOn(models.InvoiceOut, 'createPdf').and.returnValue(new Promise(resolve => resolve(true)));
|
||||
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
|
||||
try {
|
||||
const result = await createInvoice(ctx, options, undefined, ticketId);
|
||||
it('should create a manual invoice with ticket', async() => {
|
||||
const result = await createInvoice(ctx, options, undefined, undefined, ticketId);
|
||||
|
||||
expect(result.id).toEqual(jasmine.any(Number));
|
||||
});
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
it('should create a manual invoice with client', async() => {
|
||||
const result = await createInvoice(ctx, options, clientId, undefined, undefined, Date.vnNew());
|
||||
|
||||
expect(result.id).toEqual(jasmine.any(Number));
|
||||
});
|
||||
|
||||
it('should create a manual invoice with address', async() => {
|
||||
const addressFk = 126;
|
||||
const result = await createInvoice(ctx, options, clientId, addressFk, undefined, Date.vnNew());
|
||||
|
||||
expect(result.id).toEqual(jasmine.any(Number));
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -117,6 +97,7 @@ function createInvoice(
|
|||
ctx,
|
||||
options,
|
||||
clientFk = undefined,
|
||||
addressFk = undefined,
|
||||
ticketFk = undefined,
|
||||
maxShipped = undefined,
|
||||
serial = 'T',
|
||||
|
@ -124,6 +105,6 @@ function createInvoice(
|
|||
reference = undefined
|
||||
) {
|
||||
return models.InvoiceOut.createManualInvoice(
|
||||
ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options
|
||||
ctx, clientFk, addressFk, ticketFk, maxShipped, serial, taxArea, reference, options
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
const {models} = require('vn-loopback/server/server');
|
||||
describe('item lastEntriesFilter()', () => {
|
||||
it('should return two entry for the given item', async() => {
|
||||
it('should return one entry for the given item', async() => {
|
||||
const minDate = Date.vnNew();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
const maxDate = Date.vnNew();
|
||||
|
@ -13,7 +13,7 @@ describe('item lastEntriesFilter()', () => {
|
|||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||
|
||||
expect(result.length).toEqual(2);
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -22,7 +22,7 @@ describe('item lastEntriesFilter()', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should return six entries for the given item', async() => {
|
||||
it('should return five entries for the given item', async() => {
|
||||
const minDate = Date.vnNew();
|
||||
minDate.setHours(0, 0, 0, 0);
|
||||
minDate.setMonth(minDate.getMonth() - 2, 1);
|
||||
|
@ -37,7 +37,7 @@ describe('item lastEntriesFilter()', () => {
|
|||
const filter = {where: {itemFk: 1, landed: {between: [minDate, maxDate]}}};
|
||||
const result = await models.Item.lastEntriesFilter(filter, options);
|
||||
|
||||
expect(result.length).toEqual(6);
|
||||
expect(result.length).toEqual(5);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
|
Loading…
Reference in New Issue