Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 7197-fixInvoiceIn
gitea/salix/pipeline/pr-dev There was a failure building this commit Details

This commit is contained in:
Jorge Penadés 2024-07-01 11:56:30 +02:00
commit dcfc156209
64 changed files with 869 additions and 271 deletions

View File

@ -1,7 +1,3 @@
const axios = require('axios');
const {DOMParser} = require('xmldom');
const fs = require('fs');
const ejs = require('ejs');
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
@ -23,23 +19,20 @@ module.exports = Self => {
} }
}); });
Self.createShipment = async(expeditionFk, options) => { Self.createShipment = async expeditionFk => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const models = Self.app.models; const models = Self.app.models;
const mrw = await models.MrwConfig.findOne(null, myOptions); const mrw = await Self.getConfig();
if (!mrw) const today = Date.vnNew();
throw new UserError(`Some mrwConfig parameters are not set`); const [hours, minutes] = mrw?.expeditionDeadLine ? mrw.expeditionDeadLine.split(':').map(Number) : [0, 0];
const deadLine = Date.vnNew();
deadLine.setHours(hours, minutes, 0);
if (today > deadLine && (!mrw.notified || mrw.notified.setHours(0, 0, 0, 0) !== today.setHours(0, 0, 0, 0))) {
await models.NotificationQueue.create({notificationFk: 'mrw-deadline'});
await mrw.updateAttributes({notified: Date.vnNow()});
}
const query = const query =
`SELECT `SELECT
@ -64,7 +57,8 @@ module.exports = Self => {
JOIN ticket t ON e.ticketFk = t.id JOIN ticket t ON e.ticketFk = t.id
JOIN agencyMode am ON am.id = t.agencyModeFk JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN mrwService ms ON ms.agencyModeCodeFk = am.code JOIN mrwService ms ON ms.agencyModeCodeFk = am.code
LEFT JOIN mrwServiceWeekday mw ON mw.weekdays | 1 << WEEKDAY(t.landed) LEFT JOIN mrwServiceWeekday mw ON mw.agencyModeCodeFk = am.code
AND mw.weekDays & (1 << WEEKDAY(t.landed))
JOIN client c ON t.clientFk = c.id JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id JOIN address a ON t.addressFk = a.id
LEFT JOIN addressObservation oa ON oa.addressFk = a.id LEFT JOIN addressObservation oa ON oa.addressFk = a.id
@ -76,44 +70,25 @@ module.exports = Self => {
WHERE e.id = ? WHERE e.id = ?
LIMIT 1`; LIMIT 1`;
const [expeditionData] = await Self.rawSql(query, [expeditionFk], myOptions); const [expeditionData] = await Self.rawSql(query, [expeditionFk]);
if (!expeditionData) if (!expeditionData)
throw new UserError(`This expedition is not a MRW shipment`); throw new UserError(`This expedition is not a MRW shipment`);
const today = Date.vnNew(); if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0))
today.setHours(0, 0, 0, 0);
if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today)
throw new UserError(`This ticket has a shipped date earlier than today`); throw new UserError(`This ticket has a shipped date earlier than today`);
const shipmentResponse = await sendXmlDoc('createShipment', {mrw, expeditionData}, 'application/soap+xml'); const shipmentResponse = await Self.sendXmlDoc(
const shipmentId = getTextByTag(shipmentResponse, 'NumeroEnvio'); __dirname + `/createShipment.ejs`,
{mrw, expeditionData},
'application/soap+xml'
);
const shipmentId = Self.getTextByTag(shipmentResponse, 'NumeroEnvio');
if (!shipmentId) if (!shipmentId) throw new UserError(Self.getTextByTag(shipmentResponse, 'Mensaje'));
throw new UserError(getTextByTag(shipmentResponse, 'Mensaje'));
const getLabelResponse = await sendXmlDoc('getLabel', {mrw, shipmentId}, 'text/xml'); const file = await models.MrwConfig.getLabel(shipmentId);
const file = getTextByTag(getLabelResponse, 'EtiquetaFile');
if (tx) await tx.commit();
return {shipmentId, file}; return {shipmentId, file};
}; };
function getTextByTag(xmlDoc, tag) {
return xmlDoc?.getElementsByTagName(tag)[0]?.textContent;
}
async function sendXmlDoc(xmlDock, params, contentType) {
const parser = new DOMParser();
const xmlTemplate = fs.readFileSync(__dirname + `/${xmlDock}.ejs`, 'utf-8');
const renderedTemplate = ejs.render(xmlTemplate, params);
const data = await axios.post(params.mrw.url, renderedTemplate, {
headers: {
'Content-Type': `${contentType}; charset=utf-8`
}
});
return parser.parseFromString(data.data, 'text/xml');
}
}; };

View File

@ -0,0 +1,27 @@
module.exports = Self => {
Self.remoteMethod('getLabel', {
description: 'Return a base64Binary label from de MRW WebService',
accessType: 'READ',
accepts: [{
arg: 'shipmentId',
type: 'string',
required: true
}],
returns: {
type: 'string',
root: true
},
http: {
path: `/getLabel`,
verb: 'GET'
}
});
Self.getLabel = async shipmentId => {
const mrw = await Self.getConfig();
const getLabelResponse = await Self.sendXmlDoc(__dirname + `/getLabel.ejs`, {mrw, shipmentId}, 'text/xml');
return Self.getTextByTag(getLabelResponse, 'EtiquetaFile');
};
};

View File

@ -2,6 +2,7 @@ const models = require('vn-loopback/server/server').models;
const axios = require('axios'); const axios = require('axios');
const fs = require('fs'); const fs = require('fs');
const filter = {notificationFk: 'mrw-deadline'};
const mockBase64Binary = 'base64BinaryString'; const mockBase64Binary = 'base64BinaryString';
const ticket1 = { const ticket1 = {
'id': '44', 'id': '44',
@ -28,25 +29,52 @@ const expedition1 = {
'editorFk': 100 'editorFk': 100
}; };
let tx;
let options;
describe('MRWConfig createShipment()', () => { describe('MRWConfig createShipment()', () => {
beforeEach(async() => { beforeAll(async() => {
options = tx = undefined;
tx = await models.MrwConfig.beginTransaction({});
options = {transaction: tx};
await models.Agency.create( await models.Agency.create(
{'id': 999, 'name': 'mrw'}, {'id': 999, 'name': 'mrw'}
options
); );
await models.AgencyMode.create( await models.AgencyMode.create(
{'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'}, {'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'}
options
); );
await createMrwConfig();
await models.Application.rawSql(
`INSERT INTO vn.mrwService
SET agencyModeCodeFk = 'mrw',
clientType = 1,
serviceType = 1,
kg = 1`, null
);
await models.Ticket.create(ticket1);
await models.Expedition.create(expedition1);
});
afterAll(async() => {
await cleanFixtures();
await models.Ticket.destroyAll(ticket1);
await models.Expedition.destroyAll(ticket1);
});
beforeEach(async() => {
const mockPostResponses = [
{data: fs.readFileSync(__dirname + '/mockGetLabel.xml', 'utf-8')},
{data: fs.readFileSync(__dirname + '/mockCreateShipment.xml', 'utf-8')}
];
spyOn(axios, 'post').and.callFake(() => Promise.resolve(mockPostResponses.pop()));
await cleanFixtures();
});
async function cleanFixtures() {
await models.NotificationQueue.destroyAll(filter);
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: null, notified: null});
}
async function createMrwConfig() {
await models.MrwConfig.create( await models.MrwConfig.create(
{ {
'id': 1, 'id': 1,
@ -55,67 +83,80 @@ describe('MRWConfig createShipment()', () => {
'password': 'password', 'password': 'password',
'franchiseCode': 'franchiseCode', 'franchiseCode': 'franchiseCode',
'subscriberCode': 'subscriberCode' 'subscriberCode': 'subscriberCode'
}, options }
); );
}
await models.Application.rawSql( async function getLastNotification() {
`INSERT INTO vn.mrwService return models.NotificationQueue.findOne({
SET agencyModeCodeFk = 'mrw', order: 'id DESC',
clientType = 1, where: filter
serviceType = 1, });
kg = 1`, null, options }
);
await models.Ticket.create(ticket1, options);
await models.Expedition.create(expedition1, options);
});
afterEach(async() => {
await tx.rollback();
});
it('should create a shipment and return a base64Binary label', async() => { it('should create a shipment and return a base64Binary label', async() => {
const mockPostResponses = [ const {file} = await models.MrwConfig.createShipment(expedition1.id);
{data: fs.readFileSync(__dirname + '/mockGetLabel.xml', 'utf-8')},
{data: fs.readFileSync(__dirname + '/mockCreateShipment.xml', 'utf-8')}
];
spyOn(axios, 'post').and.callFake(() => Promise.resolve(mockPostResponses.pop()));
const {file} = await models.MrwConfig.createShipment(expedition1.id, options);
expect(file).toEqual(mockBase64Binary); expect(file).toEqual(mockBase64Binary);
}); });
it('should fail if mrwConfig has no data', async() => { it('should fail if mrwConfig has no data', async() => {
let error; let error;
await models.MrwConfig.destroyAll();
await models.MrwConfig.createShipment(expedition1.id).catch(e => { await models.MrwConfig.createShipment(expedition1.id).catch(e => {
error = e; error = e;
}).finally(async() => { }).finally(async() => {
expect(error.message).toEqual(`Some mrwConfig parameters are not set`); expect(error.message).toEqual(`MRW service is not configured`);
}); });
await createMrwConfig();
expect(error).toBeDefined(); expect(error).toBeDefined();
}); });
it('should fail if expeditionFk is not a MrwExpedition', async() => { it('should fail if expeditionFk is not a MrwExpedition', async() => {
let error; let error;
await models.MrwConfig.createShipment(undefined, options).catch(e => { await models.MrwConfig.createShipment(undefined).catch(e => {
error = e; error = e;
}).finally(async() => { }).finally(async() => {
expect(error.message).toEqual(`This expedition is not a MRW shipment`); expect(error.message).toEqual(`This expedition is not a MRW shipment`);
}); });
}); });
it(' should fail if the creation date of this ticket is before the current date it', async() => { it('should fail if the creation date of this ticket is before the current date', async() => {
let error; let error;
const yesterday = Date.vnNew(); const yesterday = Date.vnNew();
yesterday.setDate(yesterday.getDate() - 1); yesterday.setDate(yesterday.getDate() - 1);
await models.Ticket.updateAll({id: ticket1.id}, {shipped: yesterday}, options);
await models.MrwConfig.createShipment(expedition1.id, options).catch(e => { await models.Ticket.updateAll({id: ticket1.id}, {shipped: yesterday});
await models.MrwConfig.createShipment(expedition1.id).catch(e => {
error = e; error = e;
}).finally(async() => { }).finally(async() => {
expect(error.message).toEqual(`This ticket has a shipped date earlier than today`); expect(error.message).toEqual(`This ticket has a shipped date earlier than today`);
}); });
await models.Ticket.updateAll({id: ticket1.id}, {shipped: Date.vnNew()});
});
it('should send mail if you are past the dead line and is not notified today', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: null});
await models.MrwConfig.createShipment(expedition1.id);
const notification = await getLastNotification();
expect(notification.notificationFk).toEqual(filter.notificationFk);
});
it('should send mail if you are past the dead line and it is notified from another day', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: new Date()});
await models.MrwConfig.createShipment(expedition1.id);
const notification = await getLastNotification();
expect(notification.notificationFk).toEqual(filter.notificationFk);
});
it('should not send mail if you are past the dead line and it is notified', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: Date.vnNew()});
await models.MrwConfig.createShipment(expedition1.id);
const notification = await getLastNotification();
expect(notification).toEqual(null);
}); });
}); });

View File

@ -25,6 +25,9 @@
}, },
"isSocialNameUnique": { "isSocialNameUnique": {
"type": "boolean" "type": "boolean"
},
"continentFk": {
"type": "number"
} }
}, },
"relations": { "relations": {
@ -32,6 +35,11 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Currency", "model": "Currency",
"foreignKey": "currencyFk" "foreignKey": "currencyFk"
},
"continent": {
"type": "belongsTo",
"model": "Continent",
"foreignKey": "continentFk"
} }
}, },
"acls": [ "acls": [

View File

@ -1,4 +1,35 @@
module.exports = Self => { module.exports = Self => {
require('../methods/mrw-config/createShipment')(Self); require('../methods/mrw-config/createShipment')(Self);
require('../methods/mrw-config/getLabel')(Self);
require('../methods/mrw-config/cancelShipment')(Self); require('../methods/mrw-config/cancelShipment')(Self);
const fs = require('fs');
const ejs = require('ejs');
const UserError = require('vn-loopback/util/user-error');
const {DOMParser} = require('xmldom');
const axios = require('axios');
Self.getConfig = async function() {
const mrw = await Self.app.models.MrwConfig.findOne(null);
if (!mrw) throw new UserError(`MRW service is not configured`);
return mrw;
};
Self.getTextByTag = function(xmlDoc, tag) {
return xmlDoc?.getElementsByTagName(tag)[0]?.textContent;
};
Self.sendXmlDoc = async function(path, params, contentType) {
const parser = new DOMParser();
const xmlTemplate = fs.readFileSync(path, 'utf-8');
const renderedTemplate = ejs.render(xmlTemplate, params);
const data = await axios.post(params.mrw.url, renderedTemplate, {
headers: {
'Content-Type': `${contentType}; charset=utf-8`
}
});
return parser.parseFromString(data.data, 'text/xml');
};
}; };

View File

@ -39,6 +39,12 @@
}, },
"defaultWeight": { "defaultWeight": {
"type": "number" "type": "number"
},
"expeditionDeadLine": {
"type": "string"
},
"notified":{
"type": "date"
} }
} }
} }

View File

@ -24,6 +24,16 @@
}, },
"isManaged":{ "isManaged":{
"type": "boolean" "type": "boolean"
},
"countryFk": {
"type": "number"
}
},
"relations": {
"country": {
"type": "belongsTo",
"model": "Country",
"foreignKey": "countryFk"
} }
}, },
"acls": [ "acls": [

View File

@ -2868,7 +2868,8 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
(5, 'modified-entry', 'An entry has been modified'), (5, 'modified-entry', 'An entry has been modified'),
(6, 'book-entry-deleted', 'accounting entries deleted'), (6, 'book-entry-deleted', 'accounting entries deleted'),
(7, 'zone-included','An email to notify zoneCollisions'), (7, 'zone-included','An email to notify zoneCollisions'),
(8, 'backup-printer-selected','A backup printer has been selected'); (8, 'backup-printer-selected','A backup printer has been selected'),
(9, 'mrw-deadline','The MRW deadline has passed');
TRUNCATE `util`.`notificationAcl`; TRUNCATE `util`.`notificationAcl`;
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
@ -2881,7 +2882,8 @@ INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
(5, 9), (5, 9),
(6, 9), (6, 9),
(7, 9), (7, 9),
(8, 66); (8, 66),
(9, 56);
TRUNCATE `util`.`notificationQueue`; TRUNCATE `util`.`notificationQueue`;
INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`) INSERT INTO `util`.`notificationQueue` (`id`, `notificationFk`, `params`, `authorFk`, `status`, `created`)
@ -3889,11 +3891,25 @@ INSERT INTO `vn`.`calendarHolidays` (calendarHolidaysTypeFk, dated, calendarHoli
(1, '2001-05-17', 1, 5), (1, '2001-05-17', 1, 5),
(1, '2001-05-18', 1, 5); (1, '2001-05-18', 1, 5);
INSERT INTO vn.payrollComponent
(id, name, isSalaryAgreed, isVariable, isException)
VALUES
(1, 'Salario1', 1, 0, 0),
(2, 'Salario2', 1, 1, 0),
(3, 'Salario3', 1, 0, 1);
INSERT INTO dipole.printer (id, description)
VALUES(1, '');
INSERT INTO dipole.expedition_PrintOut (expeditionFk, ticketFk, addressFk, street, postalCode, city, shopName, isPrinted, created, printerFk, routeFk, parkingCode, INSERT INTO vn.workerIncome
truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments) (debit, credit, incomeTypeFk, paymentDate, workerFk, concept)
VALUES(1, 1, 0, ' ', ' ', ' ', ' ', 0, '2001-01-01 00:00:00', 1, 0, ' ', ' ', 0, NULL, '', NULL, 0.000, NULL, 10, NULL, NULL, 'NCC', NULL); VALUES
(1000.00, 900.00, 2, '2000-01-01', 1106, NULL),
(1001.00, 800.00, 2, '2000-01-01', 1106, NULL);
INSERT INTO dipole.printer (id, description)
VALUES(1, '');
INSERT INTO dipole.expedition_PrintOut (expeditionFk, ticketFk, addressFk, street, postalCode, city, shopName, isPrinted, created, printerFk, routeFk, parkingCode,
truckName, clientFk, phone, province, agency, m3, workerCode, itemFk, quantity, longName, shelvingFk, comments)
VALUES(1, 1, 0, ' ', ' ', ' ', ' ', 0, '2001-01-01 00:00:00', 1, 0, ' ', ' ', 0, NULL, '', NULL, 0.000, NULL, 10, NULL, NULL, 'NCC', NULL);

View File

@ -26,6 +26,7 @@ BEGIN
JOIN province p ON p.id = c.provinceFk JOIN province p ON p.id = c.provinceFk
LEFT JOIN autonomy a ON a.id = p.autonomyFk LEFT JOIN autonomy a ON a.id = p.autonomyFk
JOIN country co ON co.id = p.countryFk JOIN country co ON co.id = p.countryFk
JOIN bs.clientDiedPeriod cdp ON cdp.countryFk = co.id
WHERE cd.warning = 'third' WHERE cd.warning = 'third'
AND cp.clientFk IS NULL AND cp.clientFk IS NULL
AND sp.salesPersonFk IS NULL AND sp.salesPersonFk IS NULL

View File

@ -9,7 +9,16 @@ BEGIN
*/ */
DECLARE vPrinterFk INT; DECLARE vPrinterFk INT;
DECLARE vUserFk INT DEFAULT account.myUser_getId(); DECLARE vUserFk INT DEFAULT account.myUser_getId();
DECLARE vIsInExpeditionPallet BOOL;
SELECT COUNT(id) INTO vIsInExpeditionPallet
FROM expeditionPallet
WHERE id = vSelf;
IF NOT vIsInExpeditionPallet THEN
CALL util.throw("ExpeditionPallet not exists");
END IF;
SELECT o.labelerFk INTO vPrinterFk SELECT o.labelerFk INTO vPrinterFk
FROM operator o FROM operator o
WHERE o.workerFk = vUserFk; WHERE o.workerFk = vUserFk;

View File

@ -1,9 +1,9 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`multipleInventory`( CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`multipleInventory`(
vDate DATE, vDate DATE,
vWarehouseFk TINYINT, vWarehouseFk TINYINT,
vMaxDays TINYINT vMaxDays TINYINT
) )
proc: BEGIN proc: BEGIN
DECLARE vDateTomorrow DATE DEFAULT vDate + INTERVAL 1 DAY; DECLARE vDateTomorrow DATE DEFAULT vDate + INTERVAL 1 DAY;
DECLARE vDateFrom DATE DEFAULT vDate; DECLARE vDateFrom DATE DEFAULT vDate;
@ -36,9 +36,12 @@ proc: BEGIN
ADD `life` TINYINT NOT NULL DEFAULT '0'; ADD `life` TINYINT NOT NULL DEFAULT '0';
-- Calculo del inventario -- Calculo del inventario
UPDATE tmp.itemInventory ai CREATE OR REPLACE TEMPORARY TABLE tItemInventoryCalc
JOIN ( (PRIMARY KEY (itemFk))
SELECT itemFk Id_Article, SUM(quantity) Subtotal ENGINE = MEMORY
SELECT itemFk,
SUM(quantity) quantity,
SUM(quantity) visible
FROM ( FROM (
SELECT s.itemFk, - s.quantity quantity SELECT s.itemFk, - s.quantity quantity
FROM sale s FROM sale s
@ -69,18 +72,13 @@ proc: BEGIN
AND w.isComparative AND w.isComparative
AND NOT e.isExcludedFromAvailable AND NOT e.isExcludedFromAvailable
AND NOT e.isRaid AND NOT e.isRaid
) sub2 ) sub
GROUP BY itemFk GROUP BY itemFk;
) sub ON ai.id = sub.Id_Article
SET ai.inventory = sub.Subtotal,
ai.visible = sub.Subtotal,
ai.avalaible = sub.Subtotal,
ai.sd = sub.Subtotal;
-- Cálculo del visible -- Cálculo del visible
UPDATE tmp.itemInventory ai UPDATE tItemInventoryCalc iic
JOIN ( JOIN (
SELECT itemFk Id_Article, SUM(quantity) Subtotal SELECT itemFk, SUM(quantity) visible
FROM ( FROM (
SELECT s.itemFk, s.quantity SELECT s.itemFk, s.quantity
FROM sale s FROM sale s
@ -117,8 +115,15 @@ proc: BEGIN
AND w.isComparative AND w.isComparative
) sub2 ) sub2
GROUP BY itemFk GROUP BY itemFk
) sub ON ai.id = sub.Id_Article ) sub ON sub.itemFk = iic.itemFk
SET ai.visible = ai.visible + sub.Subtotal; SET iic.visible = iic.visible + sub.visible;
UPDATE tmp.itemInventory ai
JOIN tItemInventoryCalc iic ON iic.itemFk = ai.id
SET ai.inventory = iic.quantity,
ai.visible = iic.visible,
ai.avalaible = iic.quantity,
ai.sd = iic.quantity;
-- Calculo del disponible -- Calculo del disponible
CREATE OR REPLACE TEMPORARY TABLE tmp.itemCalc CREATE OR REPLACE TEMPORARY TABLE tmp.itemCalc
@ -189,6 +194,7 @@ proc: BEGIN
DROP TEMPORARY TABLE DROP TEMPORARY TABLE
tmp.itemTravel, tmp.itemTravel,
tmp.itemCalc, tmp.itemCalc,
tItemInventoryCalc,
tmp.itemAtp; tmp.itemAtp;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,5 +1,7 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_DelayTruckSplit`(vTicketFk INT) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_DelayTruckSplit`(
vTicketFk INT
)
BEGIN BEGIN
/** /**
* Splita las lineas de ticket que no estan ubicadas * Splita las lineas de ticket que no estan ubicadas
@ -50,8 +52,8 @@ BEGIN
SET s.ticketFk = vNewTicketFk; SET s.ticketFk = vNewTicketFk;
END IF; END IF;
CALL ticketStateUpdate(vNewTicketFk, 'FIXING'); CALL ticket_setState(vNewTicketFk, 'FIXING');
DROP TEMPORARY TABLE tmp.SalesToSplit; DROP TEMPORARY TABLE tmp.SalesToSplit;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,5 +1,8 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_cloneWeekly`(vDateFrom DATE, vDateTo DATE) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_cloneWeekly`(
vDateFrom DATE,
vDateTo DATE
)
BEGIN BEGIN
DECLARE vIsDone BOOL; DECLARE vIsDone BOOL;
DECLARE vLanding DATE; DECLARE vLanding DATE;
@ -39,9 +42,16 @@ BEGIN
DECLARE vIsDuplicateMail BOOL; DECLARE vIsDuplicateMail BOOL;
DECLARE vSubject VARCHAR(150); DECLARE vSubject VARCHAR(150);
DECLARE vMessage TEXT; DECLARE vMessage TEXT;
SET vIsDone = FALSE; SET vIsDone = FALSE;
FETCH rsTicket INTO vTicketFk,vClientFk, vWarehouseFk, vCompanyFk, vAddressFk, vAgencyModeFk,vShipment; FETCH rsTicket INTO
vTicketFk,
vClientFk,
vWarehouseFk,
vCompanyFk,
vAddressFk,
vAgencyModeFk,
vShipment;
IF vIsDone THEN IF vIsDone THEN
LEAVE myLoop; LEAVE myLoop;
@ -67,7 +77,7 @@ BEGIN
AND isDefaultAddress; AND isDefaultAddress;
END IF; END IF;
CALL zone_getLanded(vShipment, vAddressFk, vAgencyModeFk, vWarehouseFk,FALSE); CALL zone_getLanded(vShipment, vAddressFk, vAgencyModeFk, vWarehouseFk, FALSE);
SET vLanding = NULL; SET vLanding = NULL;
SELECT landed INTO vLanding FROM tmp.zoneGetLanded LIMIT 1; SELECT landed INTO vLanding FROM tmp.zoneGetLanded LIMIT 1;
@ -88,16 +98,22 @@ BEGIN
SET clonedFrom = vTicketFk SET clonedFrom = vTicketFk
WHERE id = vNewTicket; WHERE id = vNewTicket;
INSERT INTO sale (ticketFk, itemFk, concept, quantity, price, INSERT INTO sale (ticketFk,
discount, priceFixed, isPriceFixed) itemFk,
concept,
quantity,
price,
discount,
priceFixed,
isPriceFixed)
SELECT vNewTicket, SELECT vNewTicket,
saleOrig.itemFk, saleOrig.itemFk,
saleOrig.concept, saleOrig.concept,
saleOrig.quantity, saleOrig.quantity,
saleOrig.price, saleOrig.price,
saleOrig.discount, saleOrig.discount,
saleOrig.priceFixed, saleOrig.priceFixed,
saleOrig.isPriceFixed saleOrig.isPriceFixed
FROM sale saleOrig FROM sale saleOrig
WHERE saleOrig.ticketFk = vTicketFk; WHERE saleOrig.ticketFk = vTicketFk;
@ -123,20 +139,20 @@ BEGIN
,attenderFk, ,attenderFk,
ticketFk) ticketFk)
SELECT description, SELECT description,
ordered, ordered,
shipped, shipped,
quantity, quantity,
price, price,
itemFk, itemFk,
clientFk, clientFk,
response, response,
total, total,
buyed, buyed,
requesterFk, requesterFk,
attenderFk, attenderFk,
vNewTicket vNewTicket
FROM ticketRequest FROM ticketRequest
WHERE ticketFk =vTicketFk; WHERE ticketFk =vTicketFk;
SELECT id INTO vSalesPersonFK SELECT id INTO vSalesPersonFK
FROM observationType FROM observationType
@ -189,7 +205,7 @@ BEGIN
IF NOT vIsDuplicateMail THEN IF NOT vIsDuplicateMail THEN
CALL mail_insert(vSalesPersonEmail, NULL, vSubject, vMessage); CALL mail_insert(vSalesPersonEmail, NULL, vSubject, vMessage);
END IF; END IF;
CALL ticketStateUpdate (vNewTicket, 'FIXING'); CALL ticket_setState(vNewTicket, 'FIXING');
ELSE ELSE
CALL ticketCalculateClon(vNewTicket, vTicketFk); CALL ticketCalculateClon(vNewTicket, vTicketFk);
END IF; END IF;

View File

@ -1,5 +1,9 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_split`(vTicketFk INT, vTicketFutureFk INT, vDated DATE) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_split`(
vTicketFk INT,
vTicketFutureFk INT,
vDated DATE
)
proc:BEGIN proc:BEGIN
/** /**
* Mueve las lineas con problemas a otro ticket existente o a uno nuevo. * Mueve las lineas con problemas a otro ticket existente o a uno nuevo.
@ -17,60 +21,56 @@ proc:BEGIN
FROM tmp.salesToSplit FROM tmp.salesToSplit
WHERE ticketFk = vTicketFk; WHERE ticketFk = vTicketFk;
SELECT count(*) INTO vTotalLines SELECT COUNT(*) INTO vTotalLines
FROM vn.sale s FROM sale s
WHERE s.ticketFk = vTicketFk; WHERE s.ticketFk = vTicketFk;
SET vHasFullProblem = (vTotalLines = vProblemLines); SET vHasFullProblem = (vTotalLines = vProblemLines);
-- Ticket completo -- Ticket completo
IF vHasFullProblem THEN IF vHasFullProblem THEN
UPDATE ticket
UPDATE vn.ticket SET landed = vDated + INTERVAL 1 DAY,
SET landed = vDated + INTERVAL 1 DAY,
shipped = vDated, shipped = vDated,
nickname = CONCAT('(',DAY(util.VN_CURDATE()),') ', nickname ) nickname = CONCAT('(',DAY(util.VN_CURDATE()),') ', nickname)
WHERE id = vTicketFk; WHERE id = vTicketFk;
SELECT "moved" message, NULL ticketFuture; SELECT 'moved' message, NULL ticketFuture;
LEAVE proc; LEAVE proc;
END IF; END IF;
-- Ticket a futuro existe -- Ticket a futuro existe
IF vTicketFutureFk THEN IF vTicketFutureFk THEN
UPDATE sale s
UPDATE vn.sale s JOIN tmp.salesToSplit ss ON s.id = ss.saleFk
JOIN tmp.salesToSplit ss ON s.id = ss.saleFk
SET s.ticketFk = vTicketFutureFk, SET s.ticketFk = vTicketFutureFk,
s.concept = CONCAT('(s) ', s.concept) s.concept = CONCAT('(s) ', s.concept)
WHERE ss.ticketFk = vTicketFk; WHERE ss.ticketFk = vTicketFk;
SELECT "future" message, NULL ticketFuture; SELECT 'future' message, NULL ticketFuture;
LEAVE proc; LEAVE proc;
END IF; END IF;
-- Ticket nuevo -- Ticket nuevo
CALL vn.ticket_Clone(vTicketFk, vTicketFutureFk); CALL ticket_Clone(vTicketFk, vTicketFutureFk);
UPDATE vn.ticket t UPDATE ticket t
JOIN vn.productionConfig pc JOIN productionConfig pc
SET t.routeFk = IF(t.shipped = vDated , t.routeFk, NULL), SET t.routeFk = IF(t.shipped = vDated , t.routeFk, NULL),
t.landed = vDated + INTERVAL 1 DAY, t.landed = vDated + INTERVAL 1 DAY,
t.shipped = vDated, t.shipped = vDated,
t.agencyModeFk = pc.defautlAgencyMode, t.agencyModeFk = pc.defautlAgencyMode,
t.zoneFk = pc.defaultZone t.zoneFk = pc.defaultZone
WHERE t.id = vTicketFutureFk; WHERE t.id = vTicketFutureFk;
UPDATE vn.sale s UPDATE sale s
JOIN tmp.salesToSplit sts ON sts.saleFk = s.id JOIN tmp.salesToSplit sts ON sts.saleFk = s.id
SET s.ticketFk = vTicketFutureFk, SET s.ticketFk = vTicketFutureFk,
s.concept = CONCAT('(s) ', s.concept) s.concept = CONCAT('(s) ', s.concept)
WHERE sts.ticketFk = vTicketFk; WHERE sts.ticketFk = vTicketFk;
CALL vn.ticketStateUpdate(vTicketFutureFk, 'FIXING'); CALL ticket_setState(vTicketFutureFk, 'FIXING');
SELECT "new" message,vTicketFutureFk ticketFuture; SELECT 'new' message, vTicketFutureFk ticketFuture;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -1,6 +1,9 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_splitItemPackingType`(vTicketFk INT, vOriginalItemPackingTypeFk VARCHAR(1)) CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_splitItemPackingType`(
proc:BEGIN vTicketFk INT,
vOriginalItemPackingTypeFk VARCHAR(1)
)
BEGIN
/** /**
* Clona y reparte las ventas de un ticket en funcion del tipo de empaquetado. * Clona y reparte las ventas de un ticket en funcion del tipo de empaquetado.
* Respeta el id inicial para el tipo propuesto. * Respeta el id inicial para el tipo propuesto.
@ -22,22 +25,28 @@ proc:BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
RESIGNAL;
END;
DELETE FROM vn.sale DELETE FROM vn.sale
WHERE quantity = 0 WHERE quantity = 0
AND ticketFk = vTicketFk; AND ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.sale; CREATE OR REPLACE TEMPORARY TABLE tmp.sale
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (id)) (PRIMARY KEY (id))
ENGINE = MEMORY
SELECT s.id, i.itemPackingTypeFk , IFNULL(sv.litros, 0) litros SELECT s.id, i.itemPackingTypeFk , IFNULL(sv.litros, 0) litros
FROM vn.sale s FROM vn.sale s
JOIN vn.item i ON i.id = s.itemFk JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id
WHERE s.ticketFk = vTicketFk; WHERE s.ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.saleGroup; CREATE OR REPLACE TEMPORARY TABLE tmp.saleGroup
CREATE TEMPORARY TABLE tmp.saleGroup (PRIMARY KEY (itemPackingTypeFk))
SELECT itemPackingTypeFk , sum(litros) AS totalLitros ENGINE = MEMORY
SELECT itemPackingTypeFk, SUM(litros) totalLitros
FROM tmp.sale FROM tmp.sale
GROUP BY itemPackingTypeFk; GROUP BY itemPackingTypeFk;
@ -45,10 +54,11 @@ proc:BEGIN
FROM tmp.saleGroup FROM tmp.saleGroup
WHERE itemPackingTypeFk IS NOT NULL; WHERE itemPackingTypeFk IS NOT NULL;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketIPT; CREATE OR REPLACE TEMPORARY TABLE tmp.ticketIPT (
CREATE TEMPORARY TABLE tmp.ticketIPT ticketFk INT,
(ticketFk INT, itemPackingTypeFk VARCHAR(1),
itemPackingTypeFk VARCHAR(1)); PRIMARY KEY (ticketFk)
) ENGINE = MEMORY;
CASE vPackingTypesToSplit CASE vPackingTypesToSplit
WHEN 0 THEN WHEN 0 THEN
@ -89,7 +99,7 @@ proc:BEGIN
SELECT itemPackingTypeFk INTO vItemPackingTypeFk SELECT itemPackingTypeFk INTO vItemPackingTypeFk
FROM tmp.saleGroup sg FROM tmp.saleGroup sg
WHERE NOT ISNULL(sg.itemPackingTypeFk) WHERE sg.itemPackingTypeFk IS NOT NULL
ORDER BY sg.itemPackingTypeFk ORDER BY sg.itemPackingTypeFk
LIMIT 1; LIMIT 1;
@ -100,7 +110,8 @@ proc:BEGIN
WHERE ts.itemPackingTypeFk IS NULL; WHERE ts.itemPackingTypeFk IS NULL;
END CASE; END CASE;
DROP TEMPORARY TABLE tmp.sale; DROP TEMPORARY TABLE
DROP TEMPORARY TABLE tmp.saleGroup; tmp.sale,
tmp.saleGroup;
END$$ END$$
DELIMITER ; DELIMITER ;

View File

@ -0,0 +1,12 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS notified TIMESTAMP NULL
COMMENT 'Date when it was notified that the web service deadline was exceeded';
INSERT IGNORE INTO util.notification
SET name = 'mrw-deadline',
description = 'The MRW deadline has passed';
INSERT IGNORE INTO util.notificationAcl (notificationFk, roleFK)
SELECT LAST_INSERT_ID(), r.id
FROM account.role r
WHERE r.name = 'delivery'

View File

@ -0,0 +1,18 @@
UPDATE salix.ACL
SET principalId = 'teamBoss'
WHERE property IN ('addTimeEntry', 'deleteTimeEntry', 'updateTimeEntry', 'weeklyHourRecordEmail');
UPDATE salix.ACL SET principalId = 'developer' WHERE property = 'sendMail';
UPDATE salix.ACL
SET property = 'updateMailState'
WHERE property = 'updateWorkerTimeControlMail';
INSERT INTO salix.ACL(model, property, accessType, permission, principalType, principalId)
VALUES
('WorkerTimeControl', 'addTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('WorkerTimeControl', 'deleteTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('WorkerTimeControl', 'updateTimeEntry', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('WorkerTimeControl', 'weeklyHourRecordEmail', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('WorkerTimeControl', 'sendMail', 'WRITE', 'ALLOW', 'ROLE', 'hr'),
('WorkerTimeControl', 'updateMailState', 'WRITE', 'ALLOW', 'ROLE', 'hr');

View File

@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS vn.travelKgPercentage (
className VARCHAR(50) className VARCHAR(50)
); );
INSERT INTO vn.travelKgPercentage (value, className) INSERT IGNORE INTO vn.travelKgPercentage (value, className)
VALUES VALUES
(80, 'primary'), (80, 'primary'),
(100, 'alert'); (100, 'alert');

View File

@ -0,0 +1,6 @@
-- Place your SQL code here
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
VALUES
('WorkerIncome', '*', '*', 'ALLOW', 'ROLE', 'hr'),
('PayrollComponent', '*', '*', 'ALLOW', 'ROLE', 'hr'),
('Worker', '__get__incomes', '*', 'ALLOW', 'ROLE', 'hr');

View File

@ -1,18 +0,0 @@
-- Place your SQL code here
CREATE TABLE IF NOT EXISTS vn.travelKgPercentage (
value INT(3) PRIMARY KEY,
className VARCHAR(50)
);
INSERT IGNORE INTO vn.travelKgPercentage (value, className)
VALUES
(80, 'primary'),
(100, 'alert');
INSERT IGNORE INTO salix.ACL
SET model = 'TravelKgPercentage',
property = '*',
accessType = 'READ',
permission = 'ALLOW',
principalType = 'ROLE',
principalId = 'employee';

View File

@ -0,0 +1,2 @@
-- Place your SQL code here
ALTER TABLE vn.routesMonitor DROP COLUMN expeditionTruckFk;

View File

@ -27,7 +27,7 @@
vn-id="country" vn-id="country"
ng-model="$ctrl.data.countryFk" ng-model="$ctrl.data.countryFk"
url="Countries" url="Countries"
fields="['id', 'country', 'code']" fields="['id', 'name', 'code']"
show-field="name" show-field="name"
value-field="id" value-field="id"
label="Country"> label="Country">

View File

@ -234,4 +234,4 @@
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated", "It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
"Cannot add holidays on this day": "Cannot add holidays on this day", "Cannot add holidays on this day": "Cannot add holidays on this day",
"Cannot send mail": "Cannot send mail" "Cannot send mail": "Cannot send mail"
} }

View File

@ -366,5 +366,6 @@
"It has been invoiced but the PDF could not be generated": "Se ha facturado pero no se ha podido generar el PDF", "It has been invoiced but the PDF could not be generated": "Se ha facturado pero no se ha podido generar el PDF",
"It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono", "It has been invoiced but the PDF of refund not be generated": "Se ha facturado pero no se ha podido generar el PDF del abono",
"Payment method is required": "El método de pago es obligatorio", "Payment method is required": "El método de pago es obligatorio",
"Cannot send mail": "Não é possível enviar o email" "Cannot send mail": "Não é possível enviar o email",
"CONSTRAINT `supplierAccountTooShort` failed for `vn`.`supplier`": "La cuenta debe tener exactamente 10 dígitos"
} }

View File

@ -22,7 +22,7 @@ module.exports = function() {
} }
// MySQL user-defined exceptions // MySQL user-defined exceptions
if (err.sqlState == '45000') if (err.sqlState == '45000' || err?.errno == 4025)
return next(new UserError(req.__(err.sqlMessage))); return next(new UserError(req.__(err.sqlMessage)));
// Logs error to console // Logs error to console

View File

@ -6,6 +6,9 @@
"table": "account.mailAliasAccount" "table": "account.mailAliasAccount"
} }
}, },
"mixins": {
"Loggable": true
},
"properties": { "properties": {
"id": { "id": {
"type": "number", "type": "number",

View File

@ -50,7 +50,7 @@ module.exports = function(Self) {
{ {
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['id', 'country'] fields: ['id', 'name']
} }
}, },
{ {

View File

@ -54,7 +54,7 @@ module.exports = Self => {
{ {
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['country'] fields: ['name']
} }
}, },
{ {

View File

@ -37,7 +37,7 @@ class Controller extends Section {
include: { include: {
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['id', 'country'] fields: ['id', 'name']
} }
} }
} }

View File

@ -28,7 +28,7 @@ class Controller extends Section {
field: 'countryFk', field: 'countryFk',
autocomplete: { autocomplete: {
url: 'Countries', url: 'Countries',
showField: 'country', showField: 'name',
} }
}, },
{ {

View File

@ -0,0 +1,36 @@
module.exports = Self => {
Self.remoteMethodCtx('buyLabel', {
description: 'Returns the entry buys labels',
accessType: 'READ',
accepts: [
{
arg: 'id',
type: 'number',
required: true,
description: 'The entry id',
http: {source: 'path'}
}
],
returns: [
{
arg: 'body',
type: 'file',
root: true
}, {
arg: 'Content-Type',
type: 'String',
http: {target: 'header'}
}, {
arg: 'Content-Disposition',
type: 'String',
http: {target: 'header'}
}
],
http: {
path: '/:id/buy-label',
verb: 'GET'
}
});
Self.buyLabel = (ctx, id) => Self.printReport(ctx, id, 'buy-label');
};

View File

@ -9,6 +9,7 @@ module.exports = Self => {
require('../methods/entry/entryOrderPdf')(Self); require('../methods/entry/entryOrderPdf')(Self);
require('../methods/entry/addFromPackaging')(Self); require('../methods/entry/addFromPackaging')(Self);
require('../methods/entry/addFromBuy')(Self); require('../methods/entry/addFromBuy')(Self);
require('../methods/entry/buyLabel')(Self);
Self.observe('before save', async function(ctx, options) { Self.observe('before save', async function(ctx, options) {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) return;

View File

@ -59,7 +59,7 @@ module.exports = Self => {
include: [{ include: [{
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['id', 'country'] fields: ['id', 'name']
} }
}, { }, {
relation: 'taxClass', relation: 'taxClass',

View File

@ -162,7 +162,7 @@ module.exports = Self => {
const stmts = []; const stmts = [];
let stmt; let stmt;
stmts.push(`SET @_optimizer_search_depth = @@optimizer_search_depth`); stmts.push(`SET @_optimizer_search_depth = @@optimizer_search_depth`);
stmts.push(`SET SESSION optimizer_search_depth = 0`); stmts.push(`SET SESSION optimizer_search_depth = 0`);

View File

@ -55,7 +55,7 @@ module.exports = Self => {
{ {
relation: 'country', relation: 'country',
scope: { scope: {
fields: ['id', 'country', 'code'] fields: ['id', 'name', 'code']
} }
}, },
{ {

View File

@ -1,3 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('getMailStates', { Self.remoteMethodCtx('getMailStates', {
description: 'Get the states of a month about time control mail', description: 'Get the states of a month about time control mail',
@ -36,6 +38,8 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
if (!await models.Worker.isSubordinate(ctx, workerId)) throw new UserError(`You don't have enough privileges`);
const times = await models.Time.find({ const times = await models.Time.find({
fields: ['week'], fields: ['week'],
where: { where: {

View File

@ -1,4 +1,5 @@
const moment = require('moment'); const moment = require('moment');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('resendWeeklyHourEmail', { Self.remoteMethodCtx('resendWeeklyHourEmail', {
@ -34,6 +35,11 @@ module.exports = Self => {
const yearNumber = dated.getFullYear(); const yearNumber = dated.getFullYear();
const weekNumber = moment(dated).isoWeek(); const weekNumber = moment(dated).isoWeek();
const isSubordinate = await models.Worker.isSubordinate(ctx, workerId, myOptions);
const isTeamBoss = await models.ACL.checkAccessAcl(ctx, 'Worker', 'isTeamBoss', 'WRITE');
if (!isSubordinate || (workerId === ctx.req.accessToken.userId && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({ const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({
where: { where: {

View File

@ -1,28 +1,36 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('workerTimeControl getMailStates()', () => { describe('workerTimeControl getMailStates()', () => {
const workerId = 9; const developerId = 9;
const ctx = {args: { const developerBossId = 120;
month: 12, const employeeId = 1;
year: 2000
}}; let ctx;
let tx;
let opts;
beforeEach(async() => {
ctx = {req: {accessToken: {userId: developerBossId}}, args: {month: 12, year: 2000}};
tx = await models.WorkerTimeControl.beginTransaction({});
opts = {transaction: tx};
});
afterEach(async() => await tx.rollback());
it('should get the states of a month about time control mail', async() => { it('should get the states of a month about time control mail', async() => {
const tx = await models.WorkerTimeControl.beginTransaction({}); const response = await models.WorkerTimeControl.getMailStates(ctx, developerId, opts);
expect(response[0].state).toEqual('REVISE');
expect(response[1].state).toEqual('SENDED');
expect(response[2].state).toEqual('CONFIRMED');
});
it('should throw an error if they are not subordinates', async() => {
ctx.req.accessToken.userId = employeeId;
try { try {
const options = {transaction: tx}; await models.WorkerTimeControl.getMailStates(ctx, developerId, opts);
const response = await models.WorkerTimeControl.getMailStates(ctx, workerId, options);
expect(response[0].state).toEqual('REVISE');
expect(response[1].state).toEqual('SENDED');
expect(response[2].state).toEqual('CONFIRMED');
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); expect(e.message).toEqual('You don\'t have enough privileges');
throw e;
} }
}); });
}); });

View File

@ -1,10 +1,11 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('updateWorkerTimeControlMail()', () => { describe('updateMailState()', () => {
const developerId = 9;
const employeeId = 1;
it('should update WorkerTimeControlMail if exist record', async() => { it('should update WorkerTimeControlMail if exist record', async() => {
const tx = await models.WorkerTimeControlMail.beginTransaction({}); const tx = await models.WorkerTimeControlMail.beginTransaction({});
const args = { const args = {
workerId: 9,
week: 50, week: 50,
year: 2000, year: 2000,
state: 'CONFIRMED' state: 'CONFIRMED'
@ -15,15 +16,15 @@ describe('updateWorkerTimeControlMail()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const beforeMail = await models.WorkerTimeControlMail.findOne({ const beforeMail = await models.WorkerTimeControlMail.findOne({
where: { where: {
workerFk: args.workerId, workerFk: developerId,
year: args.year, year: args.year,
week: args.week, week: args.week,
} }
}, options); }, options);
await models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, options); await models.WorkerTimeControl.updateMailState(ctx, developerId, options);
const afterMail = await models.WorkerTimeControlMail.findOne({ const afterMail = await models.WorkerTimeControlMail.findOne({
where: { where: {
workerFk: args.workerId, workerFk: developerId,
year: args.year, year: args.year,
week: args.week, week: args.week,
} }
@ -42,7 +43,6 @@ describe('updateWorkerTimeControlMail()', () => {
it('should insert WorkerTimeControlMail if exist record', async() => { it('should insert WorkerTimeControlMail if exist record', async() => {
const tx = await models.WorkerTimeControlMail.beginTransaction({}); const tx = await models.WorkerTimeControlMail.beginTransaction({});
const args = { const args = {
workerId: 1,
week: 51, week: 51,
year: 2000, year: 2000,
state: 'SENDED' state: 'SENDED'
@ -53,15 +53,15 @@ describe('updateWorkerTimeControlMail()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const beforeMail = await models.WorkerTimeControlMail.find({ const beforeMail = await models.WorkerTimeControlMail.find({
where: { where: {
workerFk: args.workerId, workerFk: employeeId,
year: args.year, year: args.year,
week: args.week, week: args.week,
} }
}, options); }, options);
await models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, options); await models.WorkerTimeControl.updateMailState(ctx, employeeId, options);
const afterMail = await models.WorkerTimeControlMail.find({ const afterMail = await models.WorkerTimeControlMail.find({
where: { where: {
workerFk: args.workerId, workerFk: employeeId,
year: args.year, year: args.year,
week: args.week, week: args.week,
} }
@ -80,7 +80,7 @@ describe('updateWorkerTimeControlMail()', () => {
it('should throw error if not exist any record in this week', async() => { it('should throw error if not exist any record in this week', async() => {
const tx = await models.WorkerTimeControlMail.beginTransaction({}); const tx = await models.WorkerTimeControlMail.beginTransaction({});
const ctx = {args: { const ctx = {args: {
workerId: 1, workerId: employeeId,
week: 1, week: 1,
year: 0, year: 0,
state: 'SENDED' state: 'SENDED'
@ -89,7 +89,7 @@ describe('updateWorkerTimeControlMail()', () => {
let error; let error;
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
await models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, options); await models.WorkerTimeControl.updateMailState(ctx, employeeId, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -1,12 +1,13 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('updateWorkerTimeControlMail', { Self.remoteMethodCtx('updateMailState', {
description: 'Updates the state of WorkerTimeControlMail', description: 'Updates the state of WorkerTimeControlMail',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'workerId', arg: 'id',
type: 'number', type: 'number',
required: true description: 'The worker id',
http: {source: 'path'}
}, },
{ {
arg: 'year', arg: 'year',
@ -32,12 +33,12 @@ module.exports = Self => {
root: true root: true
}, },
http: { http: {
path: `/updateWorkerTimeControlMail`, path: `/:id/updateMailState`,
verb: 'POST' verb: 'POST'
} }
}); });
Self.updateWorkerTimeControlMail = async(ctx, options) => { Self.updateMailState = async(ctx, id, options) => {
const models = Self.app.models; const models = Self.app.models;
const args = ctx.args; const args = ctx.args;
const myOptions = {}; const myOptions = {};
@ -59,14 +60,14 @@ module.exports = Self => {
{ {
year: args.year, year: args.year,
week: args.week, week: args.week,
workerFk: args.workerId workerFk: id
}, },
{ {
state: args.state, state: args.state,
reason: args.workerId, reason: args.reason,
year: args.year, year: args.year,
week: args.week, week: args.week,
workerFk: args.workerId workerFk: id
}, },
myOptions); myOptions);

View File

@ -35,7 +35,7 @@ module.exports = Self => {
root: true root: true
}, },
http: { http: {
path: '/weekly-hour-hecord-email', path: '/weekly-hour-record-email',
verb: 'POST' verb: 'POST'
} }
}); });
@ -61,7 +61,7 @@ module.exports = Self => {
const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`; const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`;
ctx.args.url = url; ctx.args.url = url;
await models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, myOptions); await models.WorkerTimeControl.updateMailState(ctx, ctx.workerId, myOptions);
return Self.sendTemplate(ctx, 'weekly-hour-record'); return Self.sendTemplate(ctx, 'weekly-hour-record');
}; };

View File

@ -125,6 +125,12 @@
"Locker": { "Locker": {
"dataSource": "vn" "dataSource": "vn"
}, },
"PayrollComponent": {
"dataSource": "vn"
},
"WorkerIncome": {
"dataSource": "vn"
},
"TrainingCourse": { "TrainingCourse": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,27 @@
{
"name": "PayrollComponent",
"base": "VnModel",
"options": {
"mysql": {
"table": "payrollComponent"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
},
"name": {
"type": "string"
},
"isSalaryAgreed": {
"type": "number"
},
"isVariable": {
"type": "number"
},
"isException": {
"type": "number"
}
}
}

View File

@ -0,0 +1,45 @@
{
"name": "WorkerIncome",
"base": "VnModel",
"options": {
"mysql": {
"table": "workerIncome"
}
},
"properties": {
"id": {
"id": true,
"type": "number"
},
"debit": {
"type": "number"
},
"credit": {
"type": "number"
},
"incomeTypeFk": {
"type": "number"
},
"paymentDate": {
"type": "date"
},
"workerFk": {
"type": "number"
},
"concept": {
"type": "string"
}
},
"relations": {
"payrollComponent": {
"type": "belongsTo",
"model": "PayrollComponent",
"foreignKey": "id"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
}
}
}

View File

@ -6,7 +6,7 @@ module.exports = Self => {
require('../methods/worker-time-control/deleteTimeEntry')(Self); require('../methods/worker-time-control/deleteTimeEntry')(Self);
require('../methods/worker-time-control/updateTimeEntry')(Self); require('../methods/worker-time-control/updateTimeEntry')(Self);
require('../methods/worker-time-control/sendMail')(Self); require('../methods/worker-time-control/sendMail')(Self);
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self); require('../methods/worker-time-control/updateMailState')(Self);
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self); require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
require('../methods/worker-time-control/getMailStates')(Self); require('../methods/worker-time-control/getMailStates')(Self);
require('../methods/worker-time-control/resendWeeklyHourEmail')(Self); require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);

View File

@ -36,15 +36,24 @@
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "userFk"
}, },
"worker": { "worker": {
"type": "hasOne", "type": "hasOne",
"model": "Worker", "model": "Worker",
"foreignKey": "id" "foreignKey": "id"
}, },
"warehouse": { "warehouse": {
"type": "belongsTo", "type": "belongsTo",
"model": "Warehouse", "model": "Warehouse",
"foreignKey": "warehouseFk" "foreignKey": "warehouseFk"
} }
} },
} "acls": [
{
"property": "updateMailState",
"accessType": "WRITE",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "$owner"
}
]
}

View File

@ -116,6 +116,11 @@
"model": "Locker", "model": "Locker",
"foreignKey": "workerFk" "foreignKey": "workerFk"
}, },
"incomes": {
"type": "hasMany",
"model": "WorkerIncome",
"foreignKey": "workerFk"
},
"trainingCourse": { "trainingCourse": {
"type": "hasMany", "type": "hasMany",
"model": "TrainingCourse", "model": "TrainingCourse",
@ -131,4 +136,4 @@
"principalId": "$owner" "principalId": "$owner"
} }
] ]
} }

View File

@ -430,7 +430,7 @@ class Controller extends Section {
workerId: this.worker.id, workerId: this.worker.id,
state: 'SENDED' state: 'SENDED'
}; };
this.$http.post(`WorkerTimeControls/weekly-hour-hecord-email`, params) this.$http.post(`WorkerTimeControls/weekly-hour-record-email`, params)
.then(() => { .then(() => {
this.getMailStates(this.date); this.getMailStates(this.date);
this.vnApp.showSuccess(this.$t('Email sended')); this.vnApp.showSuccess(this.$t('Email sended'));

View File

@ -260,7 +260,7 @@ describe('Component vnWorkerTimeControl', () => {
controller.date = today; controller.date = today;
controller.weekNumber = 1; controller.weekNumber = 1;
$httpBackend.expect('POST', 'WorkerTimeControls/weekly-hour-hecord-email').respond(); $httpBackend.expect('POST', 'WorkerTimeControls/weekly-hour-record-email').respond();
controller.resendEmail(); controller.resendEmail();
$httpBackend.flush(); $httpBackend.flush();

View File

@ -1,19 +1,16 @@
<footer> <footer>
<!-- Action button block -->
<div class="buttons"> <div class="buttons">
<div class="columns"> <div class="columns">
<div class="size50"> <div class="size50">
<a href="https://verdnatura.es" target="_blank"> <a href="https://verdnatura.es" target="_blank">
<div class="btn"> <div class="btn">
<!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('action.png')"/></span> -->
<span class="text vn-pa-sm">{{ $t('buttons.webAcccess')}}</span> <span class="text vn-pa-sm">{{ $t('buttons.webAcccess')}}</span>
</div> </div>
</a> </a>
</div> </div>
<div class="size50"> <div class="size50">
<a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank"> <a href="https://form.jotformeu.com/91673677858377" target="_blank">
<div class="btn"> <div class="btn">
<!-- <span class="icon vn-pa-sm"><img v-bind:src="getEmailSrc('info.png')"/></span> -->
<span class="text vn-pa-sm">{{ $t('buttons.info')}}</span> <span class="text vn-pa-sm">{{ $t('buttons.info')}}</span>
</div> </div>
</a> </a>
@ -21,7 +18,6 @@
</div> </div>
</div> </div>
<!-- Networks block -->
<div class="networks"> <div class="networks">
<a href="https://www.facebook.com/Verdnatura" target="_blank"> <a href="https://www.facebook.com/Verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('facebook.png')" alt="Facebook"/> <img v-bind:src="getEmailSrc('facebook.png')" alt="Facebook"/>
@ -37,11 +33,9 @@
</a> </a>
</div> </div>
<!-- Privacy block -->
<div class="privacy"> <div class="privacy">
<p>{{$t('fiscalAddress')}}</p> <p>{{$t('fiscalAddress')}}</p>
<p>{{$t('disclaimer')}}</p> <p>{{$t('disclaimer')}}</p>
<p>{{$t('privacy')}}</p> <p>{{$t('privacy')}}</p>
</div> </div>
<!-- Privacy block end -->
</footer> </footer>

View File

@ -0,0 +1,11 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/email.css`])
.mergeStyles();

View File

@ -0,0 +1,5 @@
subject: Exceeding MRW Cut-off Time
title: Exceeding MRW Cut-off Time
greeting: Dear Team.
body: Please be informed that we have exceeded the cut-off time indicated by MRW. From this moment, all generated labels will have a delivery date for tomorrow.It is necessary to contact the MRW representatives to manage any urgencies or clarifications that may arise.
footer: Best regards.

View File

@ -0,0 +1,5 @@
subject: Superación de la Hora de Corte de MRW
title: Superación de la Hora de Corte de MRW
greeting: Estimado equipo.
body: Les informo que hemos superado la hora de corte indicada por MRW. A partir de este momento, todas las etiquetas generadas tendrán fecha de entrega para mañana.Es necesario que se pongan en contacto con los responsables de MRW para gestionar cualquier urgencia o aclaración que puedan necesitar.
footer: Saludos cordiales.

View File

@ -0,0 +1,10 @@
<email-body>
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title') }}</h1>
<p>{{$t('greeting')}}</p>
<p>{{$t('body')}}</p>
<p>{{$t('footer')}}</p>
</div>
</div>
</email-body>

View File

@ -0,0 +1,9 @@
const Component = require(`vn-print/core/component`);
const emailBody = new Component('email-body');
module.exports = {
name: 'mrw-deadline',
components: {
'email-body': emailBody.build(),
}
};

View File

@ -0,0 +1,12 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/report.css`,
`${__dirname}/style.css`])
.mergeStyles();

View File

@ -0,0 +1,41 @@
html {
font-family: "Roboto", "Helvetica", "Arial", sans-serif;
margin-top: -7px;
font-size: 28px;
}
table {
border: 1px solid;
width: 100%;
font-size: inherit;
}
td {
border: 1px solid;
padding: 5px;
width: 100%;
}
span {
font-size: 48px;
font-weight: bold;
}
.lbl {
color: gray;
font-weight: lighter;
font-size: 18px;
display: block;
}
.cell {
width: 157px;
height: 50px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.barcode {
text-align: center;
}
#variant {
width: 314px;
}
#producer {
width: 471px;
}

View File

@ -0,0 +1,89 @@
<!DOCTYPE html>
<html>
<table v-for="buy in buys" style="break-before: page">
<tbody>
<tr>
<td colspan="2">
<div id="variant" class="cell">
<span class="lbl">{{$t('variety')}}</span>
{{buy.name}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('size')}}</span>
{{buy.size}}
</div>
</td>
</tr>
<tr>
<td>
<div class="cell">
<span class="lbl">{{$t('category')}}</span>
{{buy.category}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('color')}}</span>
{{buy.color}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('origin')}}</span>
{{buy.code}}
</div>
</td>
</tr>
<tr>
<td>
<div class="cell">
<span class="lbl">{{$t('packing')}}</span>
{{buy.packing}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('grouping')}}</span>
{{buy.grouping}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('saleUnit')}}</span>
{{buy.stems}}
</div>
</td>
</tr>
<tr>
<td colspan="3" class="barcode">
<div v-html="getBarcode(buy.id)"></div>
<span>{{buy.id}}</span>
</td>
</tr>
<tr>
<td colspan="3">
<div id="producer" class="cell">
<span class="lbl">{{$t('producer')}}</span>
{{buy.producer}}
</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="cell">
<span class="lbl">{{$t('control')}}</span>
{{`${weekNum} / ${dayNum}`}}
</div>
</td>
<td>
<div class="cell">
<span class="lbl">{{$t('boxNum')}}</span>
{{`${buy.labelNum} / ${maxLabelNum}`}}
</div>
</td>
</tr>
</tbody>
</table>
</html>

View File

@ -0,0 +1,39 @@
const vnReport = require('../../../core/mixins/vn-report.js');
const {DOMImplementation, XMLSerializer} = require('xmldom');
const jsBarcode = require('jsbarcode');
const moment = require('moment');
module.exports = {
name: 'buy-label',
mixins: [vnReport],
async serverPrefetch() {
this.buys = await this.rawSqlFromDef('buys', [this.id]);
this.maxLabelNum = Math.max(...this.buys.map(buy => buy.labelNum));
const date = new Date();
this.weekNum = moment(date).isoWeek();
this.dayNum = moment(date).day();
},
methods: {
getBarcode(id) {
const xmlSerializer = new XMLSerializer();
const document = new DOMImplementation().createDocument('http://www.w3.org/1999/xhtml', 'html', null);
const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
jsBarcode(svgNode, id, {
xmlDocument: document,
format: 'code128',
displayValue: false,
width: 3.8,
height: 115,
});
return xmlSerializer.serializeToString(svgNode);
}
},
props: {
id: {
type: Number,
required: true,
description: 'The entry id'
}
}
};

View File

@ -0,0 +1,12 @@
reportName: Entry buys
variety: Bariety
size: Size
category: Category
color: Color
origin: Origin
packing: Packing
grouping: Grouping
unitSale: Un. sale
producer: Producer
control: Control
boxNum: Box no.

View File

@ -0,0 +1,12 @@
reportName: Etiqueta de compras
variety: Variedad
size: Medida
category: Categoría
color: Color
origin: Origen
packing: Packing
grouping: Grouping
saleUnit: Sale un.
producer: Productor
control: Control
boxNum: Caja nº

View File

@ -0,0 +1,11 @@
{
"width": "10cm",
"height": "10cm",
"margin": {
"top": "0.17cm",
"right": "0.2cm",
"bottom": "0cm",
"left": "0cm"
},
"printBackground": true
}

View File

@ -0,0 +1,17 @@
SELECT ROW_NUMBER() OVER(ORDER BY b.id) labelNum,
i.name,
i.`size`,
i.category,
ink.id color,
o.code,
b.packing,
b.`grouping`,
i.stems,
b.id,
p.name producer
FROM buy b
JOIN item i ON i.id = b.itemFk
LEFT JOIN producer p ON p.id = i.producerFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN origin o ON o.id = i.originFk
WHERE b.entryFk = ?

View File

@ -12,7 +12,7 @@ SELECT GROUP_CONCAT(DISTINCT ir.description ORDER BY ir.description SEPARATOR '
JOIN vn.sale s ON t.id = s.ticketFk JOIN vn.sale s ON t.id = s.ticketFk
JOIN vn.item i ON i.id = s.itemFk JOIN vn.item i ON i.id = s.itemFk
JOIN vn.intrastat ir ON ir.id = i.intrastatFk JOIN vn.intrastat ir ON ir.id = i.intrastatFk
)SELECT SUM(t.packages), )SELECT SUM(t.packages) packages,
a.incotermsFk, a.incotermsFk,
ic.name incotermsName, ic.name incotermsName,
MAX(t.weight) weight, MAX(t.weight) weight,