Merge branch 'dev' into 6436_jasmine_beforeAll
gitea/salix/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Jon Elias 2024-07-01 10:22:11 +00:00
commit 0e84749c07
72 changed files with 1001 additions and 292 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');
module.exports = Self => {
@ -23,23 +19,20 @@ module.exports = Self => {
}
});
Self.createShipment = async(expeditionFk, options) => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
Self.createShipment = async expeditionFk => {
const models = Self.app.models;
const mrw = await models.MrwConfig.findOne(null, myOptions);
const mrw = await Self.getConfig();
if (!mrw)
throw new UserError(`Some mrwConfig parameters are not set`);
const today = Date.vnNew();
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 =
`SELECT
@ -64,7 +57,8 @@ module.exports = Self => {
JOIN ticket t ON e.ticketFk = t.id
JOIN agencyMode am ON am.id = t.agencyModeFk
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 address a ON t.addressFk = a.id
LEFT JOIN addressObservation oa ON oa.addressFk = a.id
@ -76,44 +70,25 @@ module.exports = Self => {
WHERE e.id = ?
LIMIT 1`;
const [expeditionData] = await Self.rawSql(query, [expeditionFk], myOptions);
const [expeditionData] = await Self.rawSql(query, [expeditionFk]);
if (!expeditionData)
throw new UserError(`This expedition is not a MRW shipment`);
const today = Date.vnNew();
today.setHours(0, 0, 0, 0);
if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today)
if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0))
throw new UserError(`This ticket has a shipped date earlier than today`);
const shipmentResponse = await sendXmlDoc('createShipment', {mrw, expeditionData}, 'application/soap+xml');
const shipmentId = getTextByTag(shipmentResponse, 'NumeroEnvio');
const shipmentResponse = await Self.sendXmlDoc(
__dirname + `/createShipment.ejs`,
{mrw, expeditionData},
'application/soap+xml'
);
const shipmentId = Self.getTextByTag(shipmentResponse, 'NumeroEnvio');
if (!shipmentId)
throw new UserError(getTextByTag(shipmentResponse, 'Mensaje'));
if (!shipmentId) throw new UserError(Self.getTextByTag(shipmentResponse, 'Mensaje'));
const getLabelResponse = await sendXmlDoc('getLabel', {mrw, shipmentId}, 'text/xml');
const file = getTextByTag(getLabelResponse, 'EtiquetaFile');
if (tx) await tx.commit();
const file = await models.MrwConfig.getLabel(shipmentId);
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 fs = require('fs');
const filter = {notificationFk: 'mrw-deadline'};
const mockBase64Binary = 'base64BinaryString';
const ticket1 = {
'id': '44',
@ -28,25 +29,52 @@ const expedition1 = {
'editorFk': 100
};
let tx;
let options;
describe('MRWConfig createShipment()', () => {
beforeEach(async() => {
options = tx = undefined;
tx = await models.MrwConfig.beginTransaction({});
options = {transaction: tx};
beforeAll(async() => {
await models.Agency.create(
{'id': 999, 'name': 'mrw'},
options
{'id': 999, 'name': 'mrw'}
);
await models.AgencyMode.create(
{'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'},
options
{'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'}
);
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(
{
'id': 1,
@ -55,67 +83,80 @@ describe('MRWConfig createShipment()', () => {
'password': 'password',
'franchiseCode': 'franchiseCode',
'subscriberCode': 'subscriberCode'
}, options
}
);
}
await models.Application.rawSql(
`INSERT INTO vn.mrwService
SET agencyModeCodeFk = 'mrw',
clientType = 1,
serviceType = 1,
kg = 1`, null, options
);
await models.Ticket.create(ticket1, options);
await models.Expedition.create(expedition1, options);
});
afterEach(async() => {
await tx.rollback();
async function getLastNotification() {
return models.NotificationQueue.findOne({
order: 'id DESC',
where: filter
});
}
it('should create a shipment and return a base64Binary label', 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()));
const {file} = await models.MrwConfig.createShipment(expedition1.id, options);
const {file} = await models.MrwConfig.createShipment(expedition1.id);
expect(file).toEqual(mockBase64Binary);
});
it('should fail if mrwConfig has no data', async() => {
let error;
await models.MrwConfig.destroyAll();
await models.MrwConfig.createShipment(expedition1.id).catch(e => {
error = e;
}).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();
});
it('should fail if expeditionFk is not a MrwExpedition', async() => {
let error;
await models.MrwConfig.createShipment(undefined, options).catch(e => {
await models.MrwConfig.createShipment(undefined).catch(e => {
error = e;
}).finally(async() => {
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;
const yesterday = Date.vnNew();
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;
}).finally(async() => {
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": {
"type": "boolean"
},
"continentFk": {
"type": "number"
}
},
"relations": {
@ -32,6 +35,11 @@
"type": "belongsTo",
"model": "Currency",
"foreignKey": "currencyFk"
},
"continent": {
"type": "belongsTo",
"model": "Continent",
"foreignKey": "continentFk"
}
},
"acls": [

View File

@ -1,4 +1,35 @@
module.exports = Self => {
require('../methods/mrw-config/createShipment')(Self);
require('../methods/mrw-config/getLabel')(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": {
"type": "number"
},
"expeditionDeadLine": {
"type": "string"
},
"notified":{
"type": "date"
}
}
}

View File

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

View File

@ -2868,7 +2868,8 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
(5, 'modified-entry', 'An entry has been modified'),
(6, 'book-entry-deleted', 'accounting entries deleted'),
(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`;
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
@ -2881,7 +2882,8 @@ INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)
(5, 9),
(6, 9),
(7, 9),
(8, 66);
(8, 66),
(9, 56);
TRUNCATE `util`.`notificationQueue`;
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-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,
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);
INSERT INTO vn.workerIncome
(debit, credit, incomeTypeFk, paymentDate, workerFk, concept)
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

@ -1,16 +1,15 @@
DROP PROCEDURE IF EXISTS floranet.catalogue_get;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE floranet.catalogue_get(vLanded DATE, vPostalCode VARCHAR(15))
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE floranet.catalogue_get(vLanded DATE, vPostalCode VARCHAR(15))
READS SQL DATA
proc:BEGIN
/**
* Returns list, price and all the stuff regarding the floranet items.
* Returns list, price and all the stuff regarding the floranet items, for the designed shop
*
* @param vLanded Delivery date
* @param vPostalCode Delivery address postal code
*/
DECLARE vAddressFk INT;
DECLARE vLastCatalogueFk INT;
DECLARE vLockName VARCHAR(20);
DECLARE vLockTime INT;
@ -32,6 +31,15 @@ proc:BEGIN
SELECT MAX(id) INTO vLastCatalogueFk
FROM catalogue;
SELECT addressFk
INTO vAddressFk
FROM addressPostCode apc
WHERE apc.dayOfWeek = dayOfWeek(vLanded)
AND NOW() < vLanded - INTERVAL apc.hoursInAdvance HOUR
AND apc.postCode = vPostalCode
-- Aquí hay que incluir los criterios de selección de tienda
LIMIT 1;
INSERT INTO catalogue(
name,
price,
@ -51,17 +59,14 @@ proc:BEGIN
it.name,
CONCAT('https://cdn.verdnatura.es/image/catalog/1600x900/', i.image),
i.description,
apc.addressFk
vAddressFk
FROM vn.item i
JOIN (SELECT itemFk, SUM(quantity * cost) price
FROM recipe
GROUP BY itemFk) r ON r.itemFk = i.id
JOIN vn.itemType it ON it.id = i.typeFk
JOIN addressPostCode apc
ON apc.dayOfWeek = dayOfWeek(vLanded)
AND NOW() < vLanded - INTERVAL apc.hoursInAdvance HOUR
AND apc.postCode = vPostalCode
JOIN vn.address a ON a.id = apc.addressFk;
JOIN addressPostCode apc ON addressFk = vAddressFk
JOIN vn.address a ON a.id = vAddressFk;
SELECT *
FROM catalogue

View File

@ -1,8 +1,6 @@
DROP PROCEDURE IF EXISTS floranet.deliveryDate_get;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `floranet`.`deliveryDate_get`(vPostalCode VARCHAR(15))
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `floranet`.`deliveryDate_get`(vPostalCode VARCHAR(15))
READS SQL DATA
BEGIN
/**

View File

@ -101,7 +101,7 @@ proc:BEGIN
vNewTicketFk,
c.itemFk,
CONCAT('Entrega: ',c.name),
- c.price,
- apc.deliveryCost,
1
FROM catalogue c
JOIN addressPostCode apc
@ -119,7 +119,7 @@ proc:BEGIN
vNewTicketFk,
r.elementFk,
i.longName,
r.cost,
0,
r.quantity
FROM catalogue c
JOIN recipe r ON r.itemFk = c.itemFk

View File

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

View File

@ -13,12 +13,39 @@ BEGIN
* @param vCollectionFk Id de colección
*/
DECLARE vHasTooMuchCollections BOOL;
DECLARE vItemPackingTypeFk VARCHAR(1);
DECLARE vWarehouseFk INT;
DECLARE vLockName VARCHAR(215);
DECLARE vLockTime INT DEFAULT 30;
DECLARE vErrorNumber INT;
DECLARE vErrorMsg TEXT;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
GET DIAGNOSTICS CONDITION 1
vErrorNumber = MYSQL_ERRNO,
vErrorMsg = MESSAGE_TEXT;
IF vLockName IS NOT NULL THEN
DO RELEASE_LOCK(vLockName);
CALL util.debugAdd('collection_assign', JSON_OBJECT(
'errorNumber', vErrorNumber,
'errorMsg', vErrorMsg,
'lockName', vLockName,
'userFk', vUserFk
)); -- Tmp
END IF;
RESIGNAL;
END;
-- Si hay colecciones sin terminar, sale del proceso
CALL collection_get(vUserFk);
SELECT (pc.maxNotReadyCollections - COUNT(*)) <= 0
INTO vHasTooMuchCollections
SELECT (pc.maxNotReadyCollections - COUNT(*)) <= 0,
collection_assign_lockname
INTO vHasTooMuchCollections,
vLockName
FROM productionConfig pc
LEFT JOIN tCollection ON TRUE;
@ -28,6 +55,21 @@ BEGIN
CALL util.throw('Hay colecciones pendientes');
END IF;
SELECT warehouseFk, itemPackingTypeFk
INTO vWarehouseFk, vItemPackingTypeFk
FROM operator
WHERE workerFk = vUserFk;
SET vLockName = CONCAT_WS('/',
vLockName,
vWarehouseFk,
vItemPackingTypeFk
);
IF NOT GET_LOCK(vLockName, vLockTime) THEN
CALL util.throw(CONCAT('Cannot get lock: ', vLockName));
END IF;
-- Se eliminan las colecciones sin asignar que estan obsoletas
INSERT INTO ticketTracking(stateFk, ticketFk)
SELECT s.id, tc.ticketFk
@ -75,5 +117,7 @@ BEGIN
UPDATE `collection`
SET workerFk = vUserFk
WHERE id = vCollectionFk;
DO RELEASE_LOCK(vLockName);
END$$
DELIMITER ;

View File

@ -59,7 +59,8 @@ BEGIN
'errorNumber', vErrorNumber,
'errorMsg', vErrorMsg,
'lockName', vLockName,
'userFk', vUserFk
'userFk', vUserFk,
'ticketFk', vTicketFk
)); -- Tmp
END IF;

View File

@ -9,6 +9,15 @@ BEGIN
*/
DECLARE vPrinterFk INT;
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
FROM operator o

View File

@ -36,9 +36,12 @@ proc: BEGIN
ADD `life` TINYINT NOT NULL DEFAULT '0';
-- Calculo del inventario
UPDATE tmp.itemInventory ai
JOIN (
SELECT itemFk Id_Article, SUM(quantity) Subtotal
CREATE OR REPLACE TEMPORARY TABLE tItemInventoryCalc
(PRIMARY KEY (itemFk))
ENGINE = MEMORY
SELECT itemFk,
SUM(quantity) quantity,
SUM(quantity) visible
FROM (
SELECT s.itemFk, - s.quantity quantity
FROM sale s
@ -69,18 +72,13 @@ proc: BEGIN
AND w.isComparative
AND NOT e.isExcludedFromAvailable
AND NOT e.isRaid
) sub2
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;
) sub
GROUP BY itemFk;
-- Cálculo del visible
UPDATE tmp.itemInventory ai
UPDATE tItemInventoryCalc iic
JOIN (
SELECT itemFk Id_Article, SUM(quantity) Subtotal
SELECT itemFk, SUM(quantity) visible
FROM (
SELECT s.itemFk, s.quantity
FROM sale s
@ -117,8 +115,15 @@ proc: BEGIN
AND w.isComparative
) sub2
GROUP BY itemFk
) sub ON ai.id = sub.Id_Article
SET ai.visible = ai.visible + sub.Subtotal;
) sub ON sub.itemFk = iic.itemFk
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
CREATE OR REPLACE TEMPORARY TABLE tmp.itemCalc
@ -189,6 +194,7 @@ proc: BEGIN
DROP TEMPORARY TABLE
tmp.itemTravel,
tmp.itemCalc,
tItemInventoryCalc,
tmp.itemAtp;
END$$
DELIMITER ;

View File

@ -1,5 +1,7 @@
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
/**
* Splita las lineas de ticket que no estan ubicadas
@ -50,7 +52,7 @@ BEGIN
SET s.ticketFk = vNewTicketFk;
END IF;
CALL ticketStateUpdate(vNewTicketFk, 'FIXING');
CALL ticket_setState(vNewTicketFk, 'FIXING');
DROP TEMPORARY TABLE tmp.SalesToSplit;
END$$

View File

@ -1,5 +1,8 @@
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
DECLARE vIsDone BOOL;
DECLARE vLanding DATE;
@ -41,7 +44,14 @@ BEGIN
DECLARE vMessage TEXT;
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
LEAVE myLoop;
@ -67,7 +77,7 @@ BEGIN
AND isDefaultAddress;
END IF;
CALL zone_getLanded(vShipment, vAddressFk, vAgencyModeFk, vWarehouseFk,FALSE);
CALL zone_getLanded(vShipment, vAddressFk, vAgencyModeFk, vWarehouseFk, FALSE);
SET vLanding = NULL;
SELECT landed INTO vLanding FROM tmp.zoneGetLanded LIMIT 1;
@ -88,8 +98,14 @@ BEGIN
SET clonedFrom = vTicketFk
WHERE id = vNewTicket;
INSERT INTO sale (ticketFk, itemFk, concept, quantity, price,
discount, priceFixed, isPriceFixed)
INSERT INTO sale (ticketFk,
itemFk,
concept,
quantity,
price,
discount,
priceFixed,
isPriceFixed)
SELECT vNewTicket,
saleOrig.itemFk,
saleOrig.concept,
@ -189,7 +205,7 @@ BEGIN
IF NOT vIsDuplicateMail THEN
CALL mail_insert(vSalesPersonEmail, NULL, vSubject, vMessage);
END IF;
CALL ticketStateUpdate (vNewTicket, 'FIXING');
CALL ticket_setState(vNewTicket, 'FIXING');
ELSE
CALL ticketCalculateClon(vNewTicket, vTicketFk);
END IF;

View File

@ -1,5 +1,9 @@
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
/**
* Mueve las lineas con problemas a otro ticket existente o a uno nuevo.
@ -17,45 +21,41 @@ proc:BEGIN
FROM tmp.salesToSplit
WHERE ticketFk = vTicketFk;
SELECT count(*) INTO vTotalLines
FROM vn.sale s
SELECT COUNT(*) INTO vTotalLines
FROM sale s
WHERE s.ticketFk = vTicketFk;
SET vHasFullProblem = (vTotalLines = vProblemLines);
-- Ticket completo
IF vHasFullProblem THEN
UPDATE vn.ticket
UPDATE ticket
SET landed = vDated + INTERVAL 1 DAY,
shipped = vDated,
nickname = CONCAT('(',DAY(util.VN_CURDATE()),') ', nickname )
nickname = CONCAT('(',DAY(util.VN_CURDATE()),') ', nickname)
WHERE id = vTicketFk;
SELECT "moved" message, NULL ticketFuture;
SELECT 'moved' message, NULL ticketFuture;
LEAVE proc;
END IF;
-- Ticket a futuro existe
IF vTicketFutureFk THEN
UPDATE vn.sale s
UPDATE sale s
JOIN tmp.salesToSplit ss ON s.id = ss.saleFk
SET s.ticketFk = vTicketFutureFk,
s.concept = CONCAT('(s) ', s.concept)
WHERE ss.ticketFk = vTicketFk;
SELECT "future" message, NULL ticketFuture;
SELECT 'future' message, NULL ticketFuture;
LEAVE proc;
END IF;
-- Ticket nuevo
CALL vn.ticket_Clone(vTicketFk, vTicketFutureFk);
CALL ticket_Clone(vTicketFk, vTicketFutureFk);
UPDATE vn.ticket t
JOIN vn.productionConfig pc
UPDATE ticket t
JOIN productionConfig pc
SET t.routeFk = IF(t.shipped = vDated , t.routeFk, NULL),
t.landed = vDated + INTERVAL 1 DAY,
t.shipped = vDated,
@ -63,14 +63,14 @@ proc:BEGIN
t.zoneFk = pc.defaultZone
WHERE t.id = vTicketFutureFk;
UPDATE vn.sale s
UPDATE sale s
JOIN tmp.salesToSplit sts ON sts.saleFk = s.id
SET s.ticketFk = vTicketFutureFk,
s.concept = CONCAT('(s) ', s.concept)
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$$
DELIMITER ;

View File

@ -1,6 +1,9 @@
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_splitItemPackingType`(vTicketFk INT, vOriginalItemPackingTypeFk VARCHAR(1))
proc:BEGIN
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_splitItemPackingType`(
vTicketFk INT,
vOriginalItemPackingTypeFk VARCHAR(1)
)
BEGIN
/**
* Clona y reparte las ventas de un ticket en funcion del tipo de empaquetado.
* Respeta el id inicial para el tipo propuesto.
@ -22,22 +25,28 @@ proc:BEGIN
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
RESIGNAL;
END;
DELETE FROM vn.sale
WHERE quantity = 0
AND ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
CREATE OR REPLACE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (id))
ENGINE = MEMORY
SELECT s.id, i.itemPackingTypeFk , IFNULL(sv.litros, 0) litros
FROM vn.sale s
JOIN vn.item i ON i.id = s.itemFk
LEFT JOIN vn.saleVolume sv ON sv.saleFk = s.id
WHERE s.ticketFk = vTicketFk;
DROP TEMPORARY TABLE IF EXISTS tmp.saleGroup;
CREATE TEMPORARY TABLE tmp.saleGroup
SELECT itemPackingTypeFk , sum(litros) AS totalLitros
CREATE OR REPLACE TEMPORARY TABLE tmp.saleGroup
(PRIMARY KEY (itemPackingTypeFk))
ENGINE = MEMORY
SELECT itemPackingTypeFk, SUM(litros) totalLitros
FROM tmp.sale
GROUP BY itemPackingTypeFk;
@ -45,10 +54,11 @@ proc:BEGIN
FROM tmp.saleGroup
WHERE itemPackingTypeFk IS NOT NULL;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketIPT;
CREATE TEMPORARY TABLE tmp.ticketIPT
(ticketFk INT,
itemPackingTypeFk VARCHAR(1));
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketIPT (
ticketFk INT,
itemPackingTypeFk VARCHAR(1),
PRIMARY KEY (ticketFk)
) ENGINE = MEMORY;
CASE vPackingTypesToSplit
WHEN 0 THEN
@ -89,7 +99,7 @@ proc:BEGIN
SELECT itemPackingTypeFk INTO vItemPackingTypeFk
FROM tmp.saleGroup sg
WHERE NOT ISNULL(sg.itemPackingTypeFk)
WHERE sg.itemPackingTypeFk IS NOT NULL
ORDER BY sg.itemPackingTypeFk
LIMIT 1;
@ -100,7 +110,8 @@ proc:BEGIN
WHERE ts.itemPackingTypeFk IS NULL;
END CASE;
DROP TEMPORARY TABLE tmp.sale;
DROP TEMPORARY TABLE tmp.saleGroup;
DROP TEMPORARY TABLE
tmp.sale,
tmp.saleGroup;
END$$
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)
);
INSERT INTO vn.travelKgPercentage (value, className)
INSERT IGNORE INTO vn.travelKgPercentage (value, className)
VALUES
(80, 'primary'),
(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,8 @@
-- Place your SQL code here
USE vn;
INSERT INTO salix.ACL ( model, property, accessType, permission, principalType, principalId) VALUES('ItemShelvingLog', '*', 'READ', 'ALLOW', 'ROLE', 'production');
-- redmine regularitzar parking per a que no tinguen espais
ALTER TABLE parking DROP CONSTRAINT chkParkingCodeFormat;
ALTER TABLE parking
ADD CONSTRAINT chkParkingCodeFormat CHECK (CHAR_LENGTH(code) > 4 AND code REGEXP ('^[^ ]+-[^ ]+$'));

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"
ng-model="$ctrl.data.countryFk"
url="Countries"
fields="['id', 'country', 'code']"
fields="['id', 'name', 'code']"
show-field="name"
value-field="id"
label="Country">

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 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",
"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
if (err.sqlState == '45000')
if (err.sqlState == '45000' || err?.errno == 4025)
return next(new UserError(req.__(err.sqlMessage)));
// Logs error to console

View File

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

View File

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

View File

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

View File

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

View File

@ -28,7 +28,7 @@ class Controller extends Section {
field: 'countryFk',
autocomplete: {
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/addFromPackaging')(Self);
require('../methods/entry/addFromBuy')(Self);
require('../methods/entry/buyLabel')(Self);
Self.observe('before save', async function(ctx, options) {
if (ctx.isNewInstance) return;

View File

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

View File

@ -50,6 +50,9 @@
"ItemShelving": {
"dataSource": "vn"
},
"ItemShelvingLog": {
"dataSource": "vn"
},
"ItemShelvingSale": {
"dataSource": "vn"
},

View File

@ -0,0 +1,52 @@
{
"name": "ItemShelvingLog",
"base": "VnModel",
"mixins": {
"Loggable": true
},
"options": {
"mysql": {
"table": "itemShelvingLog"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"created": {
"type": "date"
},
"shelvingFk": {
"type": "string"
},
"itemFk": {
"type": "number"
},
"visible": {
"type": "number"
},
"accion": {
"type": "string"
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"shelving": {
"type": "belongsTo",
"model": "Shelving",
"foreignKey": "shelvingFk",
"primaryKey": "code"
}
}
}

View File

@ -55,7 +55,7 @@ module.exports = Self => {
{
relation: 'country',
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 => {
Self.remoteMethodCtx('getMailStates', {
description: 'Get the states of a month about time control mail',
@ -36,6 +38,8 @@ module.exports = Self => {
if (typeof options == 'object')
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({
fields: ['week'],
where: {

View File

@ -1,4 +1,5 @@
const moment = require('moment');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('resendWeeklyHourEmail', {
@ -34,6 +35,11 @@ module.exports = Self => {
const yearNumber = dated.getFullYear();
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({
where: {

View File

@ -1,28 +1,36 @@
const models = require('vn-loopback/server/server').models;
describe('workerTimeControl getMailStates()', () => {
const workerId = 9;
const ctx = {args: {
month: 12,
year: 2000
}};
const developerId = 9;
const developerBossId = 120;
const employeeId = 1;
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() => {
const tx = await models.WorkerTimeControl.beginTransaction({});
try {
const options = {transaction: tx};
const response = await models.WorkerTimeControl.getMailStates(ctx, workerId, options);
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');
});
await tx.rollback();
it('should throw an error if they are not subordinates', async() => {
ctx.req.accessToken.userId = employeeId;
try {
await models.WorkerTimeControl.getMailStates(ctx, developerId, opts);
} catch (e) {
await tx.rollback();
throw e;
expect(e.message).toEqual('You don\'t have enough privileges');
}
});
});

View File

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

View File

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

View File

@ -35,7 +35,7 @@ module.exports = Self => {
root: true
},
http: {
path: '/weekly-hour-hecord-email',
path: '/weekly-hour-record-email',
verb: 'POST'
}
});
@ -61,7 +61,7 @@ module.exports = Self => {
const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`;
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');
};

View File

@ -125,6 +125,12 @@
"Locker": {
"dataSource": "vn"
},
"PayrollComponent": {
"dataSource": "vn"
},
"WorkerIncome": {
"dataSource": "vn"
},
"TrainingCourse": {
"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/updateTimeEntry')(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/getMailStates')(Self);
require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);

View File

@ -46,5 +46,14 @@
"model": "Warehouse",
"foreignKey": "warehouseFk"
}
},
"acls": [
{
"property": "updateMailState",
"accessType": "WRITE",
"permission": "ALLOW",
"principalType": "ROLE",
"principalId": "$owner"
}
]
}

View File

@ -116,6 +116,11 @@
"model": "Locker",
"foreignKey": "workerFk"
},
"incomes": {
"type": "hasMany",
"model": "WorkerIncome",
"foreignKey": "workerFk"
},
"trainingCourse": {
"type": "hasMany",
"model": "TrainingCourse",

View File

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

View File

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

View File

@ -1,19 +1,16 @@
<footer>
<!-- Action button block -->
<div class="buttons">
<div class="columns">
<div class="size50">
<a href="https://verdnatura.es" target="_blank">
<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>
</div>
</a>
</div>
<div class="size50">
<a href="https://goo.gl/forms/j8WSL151ZW6QtlT72" target="_blank">
<a href="https://form.jotformeu.com/91673677858377" target="_blank">
<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>
</div>
</a>
@ -21,7 +18,6 @@
</div>
</div>
<!-- Networks block -->
<div class="networks">
<a href="https://www.facebook.com/Verdnatura" target="_blank">
<img v-bind:src="getEmailSrc('facebook.png')" alt="Facebook"/>
@ -37,11 +33,9 @@
</a>
</div>
<!-- Privacy block -->
<div class="privacy">
<p>{{$t('fiscalAddress')}}</p>
<p>{{$t('disclaimer')}}</p>
<p>{{$t('privacy')}}</p>
</div>
<!-- Privacy block end -->
</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.item i ON i.id = s.itemFk
JOIN vn.intrastat ir ON ir.id = i.intrastatFk
)SELECT SUM(t.packages),
)SELECT SUM(t.packages) packages,
a.incotermsFk,
ic.name incotermsName,
MAX(t.weight) weight,