feat refs #7296 routesMonitor.expeditionTruckFk to routesMonitor.roadmapStopFk #2510

Merged
robert merged 11 commits from 7296-expeditionTruckToRoadmapStop into dev 2024-06-04 10:03:57 +00:00
51 changed files with 648 additions and 425 deletions
Showing only changes of commit 3720f7acfe - Show all commits

8
Jenkinsfile vendored
View File

@ -121,7 +121,7 @@ pipeline {
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version
env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
}
sh 'docker-compose build back'
}
@ -159,7 +159,7 @@ pipeline {
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version
env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
}
sh 'gulp build'
sh 'docker-compose build front'
@ -179,7 +179,7 @@ pipeline {
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version
env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
}
sh 'docker login --username $CREDENTIALS_USR --password $CREDENTIALS_PSW $REGISTRY'
sh 'docker-compose push'
@ -210,7 +210,7 @@ pipeline {
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = packageJson.version
env.VERSION = "${packageJson.version}-vn${env.BUILD_ID}"
}
withKubeConfig([
serverUrl: "$KUBERNETES_API",

View File

@ -26,10 +26,11 @@
<mrw:Nif><%= expeditionData.fi %></mrw:Nif>
<mrw:Nombre><%= expeditionData.clientName %></mrw:Nombre>
<mrw:Telefono><%= expeditionData.phone %></mrw:Telefono>
<mrw:Observaciones><%= expeditionData.deliveryObservation %></mrw:Observaciones>
</mrw:DatosEntrega>
<mrw:DatosServicio>
<mrw:Fecha><%= expeditionData.created %></mrw:Fecha>
<mrw:Referencia><%= expeditionData.expeditionDataId %></mrw:Referencia>
<mrw:Referencia><%= expeditionData.reference %></mrw:Referencia>
<mrw:CodigoServicio><%= expeditionData.serviceType %></mrw:CodigoServicio>
<mrw:NumeroBultos>1</mrw:NumeroBultos>
<mrw:EntregaSabado><%= expeditionData.weekDays %></mrw:EntregaSabado>

View File

@ -45,7 +45,7 @@ module.exports = Self => {
`SELECT
CASE co.code
WHEN 'ES' THEN a.postalCode
WHEN 'PT' THEN LEFT(a.postalCode, 4)
WHEN 'PT' THEN LEFT(a.postalCode, mc.portugalPostCodeTrim)
WHEN 'AD' THEN REPLACE(a.postalCode, 'AD', '00')
END postalCode,
a.city,
@ -56,9 +56,10 @@ module.exports = Self => {
c.phone,
DATE_FORMAT(t.shipped, '%d/%m/%Y') created,
t.shipped,
e.id expeditionId,
LPAD(IF(mw.params IS NULL, ms.serviceType, mw.serviceType), 4 ,'0') serviceType,
IF(mw.weekdays, 'S', 'N') weekDays
CONCAT( e.ticketFk, LPAD(e.counter, mc.counterWidth, '0')) reference,
LPAD(IF(mw.params IS NULL, ms.serviceType, mw.serviceType), mc.serviceTypeWidth,'0') serviceType,
IF(mw.weekdays, 'S', 'N') weekDays,
oa.description deliveryObservation
FROM expedition e
JOIN ticket t ON e.ticketFk = t.id
JOIN agencyMode am ON am.id = t.agencyModeFk
@ -66,8 +67,12 @@ module.exports = Self => {
LEFT JOIN mrwServiceWeekday mw ON mw.weekdays = DATE_FORMAT(t.shipped, '%a')
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
LEFT JOIN observationType ot ON ot.id = oa.observationTypeFk
AND ot.code = 'delivery'
JOIN province p ON a.provinceFk = p.id
JOIN country co ON co.id = p.countryFk
JOIN mrwConfig mc
WHERE e.id = ?
LIMIT 1`;

View File

@ -1,6 +1,16 @@
{
"name": "Collection",
"base": "VnModel",
"properties": {
"id": {
"id": true,
"type": "number",
"required": true
},
"workerFk": {
"type": "number"
}
},
"options": {
"mysql": {
"table": "collection"

View File

@ -762,7 +762,12 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(30, 1, 8, 1, 1, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(31, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(32, 1, 8, 1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), DATE_ADD(util.VN_CURDATE(), INTERVAL + 2 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, util.VN_CURDATE(), NULL, NULL),
(33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL);
(33, 1, 7, 1, 6, util.VN_CURDATE(), DATE_ADD(util.VN_CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, util.VN_CURDATE(), NULL, NULL),
(34, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1103, 'BEJAR', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(35, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Somewhere in Philippines', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(36, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1102, 'Ant-Man Adventure', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL),
(37, 1, 1, 1, 3, util.VN_CURDATE(), util.VN_CURDATE(), 1110, 'Deadpool swords', 123, NULL, 0, 1, 16, 0, util.VN_CURDATE(), NULL, NULL);
INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `description`)
VALUES
(1, 11, 1, 'ready'),
@ -808,7 +813,10 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `userFk`, `created`)
(21, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(util.VN_NOW(), INTERVAL +1 MONTH)),
(23, 16, 21, util.VN_NOW()),
(24, 16, 21, util.VN_NOW());
(24, 16, 21, util.VN_NOW()),
(34, 14, 49, util.VN_NOW()),
(35, 14, 18, util.VN_NOW()),
(36, 14, 18, util.VN_NOW());
INSERT INTO `vn`.`deliveryPoint` (`id`, `name`, `ubication`)
VALUES
@ -1068,7 +1076,10 @@ INSERT INTO `vn`.`sale`(`id`, `itemFk`, `ticketFk`, `concept`, `quantity`, `pric
(37, 4, 31, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(36, 4, 30, 'Melee weapon heavy shield 100cm', 20, 1.72, 0, 0, 0, util.VN_CURDATE()),
(38, 2, 32, 'Melee weapon combat fist 15cm', 30, 7.07, 0, 0, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL +1 MONTH)),
(39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE());
(39, 1, 32, 'Ranged weapon longbow 200cm', 2, 103.49, 0, 0, 0, util.VN_CURDATE()),
(40, 2, 34, 'Melee weapon combat fist 15cm', 10.00, 3.91, 0, 0, 0, util.VN_CURDATE()),
(41, 2, 35, 'Melee weapon combat fist 15cm', 8.00, 3.01, 0, 0, 0, util.VN_CURDATE()),
(42, 2, 36, 'Melee weapon combat fist 15cm', 6.00, 2.50, 0, 0, 0, util.VN_CURDATE());
INSERT INTO `vn`.`saleComponent`(`saleFk`, `componentFk`, `value`)
VALUES
@ -1247,14 +1258,20 @@ INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPacki
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),
(2, 1106, 14, util.VN_CURDATE(), 1);
(2, 1106, 14, util.VN_CURDATE(), 1),
(4, 49, 5, util.VN_CURDATE(), 1),
(5, 18, 5, util.VN_CURDATE(), 1),
(6, 18, 5, util.VN_CURDATE(), 1);
INSERT INTO `vn`.`ticketCollection`(`ticketFk`, `collectionFk`, `level`)
VALUES
(1, 1, 1),
(2, 1, NULL),
(3, 2, NULL),
(23, 1, NULL);
(23, 1, NULL),
(34, 4, 1),
(35, 5, 1),
(8, 6, 1);
INSERT INTO `vn`.`genus`(`id`, `name`)
VALUES
@ -3705,7 +3722,8 @@ INSERT IGNORE INTO vn.saleGroup
SET id = 4,
userFk = 1,
parkingFk = 9,
sectorFk = 9992;
sectorFk = 9992,
ticketFk = 36;
INSERT IGNORE INTO vn.sectorCollectionSaleGroup
SET id = 9999,
@ -3807,3 +3825,27 @@ INSERT INTO `vn`.`ledgerCompany` SET
INSERT INTO `vn`.`ledgerConfig` SET
maxTolerance = 0.01;
INSERT INTO vn.sectorCollection
SET id = 2,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 8,
sectorCollectionFk = 2,
saleGroupFk = 4;
INSERT INTO vn.saleGroup (userFk, parkingFk, sectorFk, ticketFk)
VALUES
(1, 1, 1, 37);
INSERT INTO vn.sectorCollection
SET id = 3,
userFk = 18,
sectorFk = 1;
INSERT INTO vn.sectorCollectionSaleGroup
SET id = 9,
sectorCollectionFk = 3,
saleGroupFk = 6;

View File

@ -11,10 +11,7 @@ BEGIN
*/
DECLARE vClient INT DEFAULT NULL;
-- SET vPhone = vPhone COLLATE 'utf8_unicode_ci';
DROP TEMPORARY TABLE IF EXISTS tClient;
CREATE TEMPORARY TABLE tClient
CREATE OR REPLACE TEMPORARY TABLE tClient
ENGINE = MEMORY
SELECT id clientFk
FROM `client`
@ -27,13 +24,14 @@ BEGIN
OR mobile = vPhone
UNION
SELECT clientFk
FROM vn.clientContact
FROM clientContact
WHERE phone = vPhone;
SELECT t.clientFk INTO vClient
FROM tClient t
JOIN `client` c ON c.id = t.clientFk
WHERE c.isActive
AND c.salesPersonFk
LIMIT 1;
DROP TEMPORARY TABLE tClient;

View File

@ -0,0 +1 @@
ALTER TABLE vn.item ADD COLUMN photoMotivation VARCHAR(255);

View File

@ -0,0 +1,46 @@
use account;
INSERT INTO role
SET name = 'reviewer',
description = 'Revisor de producción',
hasLogin = TRUE,
created = util.VN_CURDATE(),
modified = util.VN_CURDATE(),
editorFk = NULL;
INSERT INTO roleInherit(
role,
inheritsFrom
)
SELECT r1.id,
r2.id
FROM role r1
JOIN role r2
WHERE r1.name = 'reviewer'
AND r2.name = 'production'
UNION
SELECT ri.role,
r2.id
FROM roleInherit ri
JOIN role r1 ON r1.id = ri.role
JOIN role r2 ON r2.name = 'reviewer'
WHERE r1.name IN ('claimManager', 'productionBoss')
GROUP BY ri.role;
DELETE ri
FROM roleInherit ri
JOIN role r1 ON ri.role = r1.id
JOIN role r2 ON ri.inheritsFrom = r2.id
WHERE r1.name = 'replenisher'
AND r2.name = 'buyer';
UPDATE salix.ACL
SET principalId = 'reviewer'
WHERE property = 'isInPreparing';
UPDATE user u
JOIN vn.workerDepartment wd ON wd.workerFk = u.id
JOIN vn.department d ON wd.departmentFk = d.id
JOIN role r ON r.name = 'reviewer'
SET u.role = r.id
WHERE d.name IN ('REVISION', 'PREVIA');

View File

@ -0,0 +1,3 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS expeditionDeadLine TIME NULL
COMMENT 'This field stores the latest time by which expeditions can be generated to be sent today';

View File

@ -0,0 +1,9 @@
-- Place your SQL code here
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS counterWidth INT UNSIGNED NULL
COMMENT 'If it does not reach the required value, it will be padded with zeros on the left to meet the specified length.';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS serviceTypeWidth INT UNSIGNED NULL
COMMENT 'If it does not reach the required value, it will be padded with zeros on the left to meet the specified length.';
ALTER TABLE vn.mrwConfig ADD IF NOT EXISTS portugalPostCodeTrim INT UNSIGNED NULL
COMMENT 'It will trim the last characters of the postal code';

View File

@ -0,0 +1,3 @@
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Ticket','refund','WRITE','ALLOW','ROLE','logistic');

View File

@ -225,7 +225,7 @@ describe('Ticket Edit sale path', () => {
});
it('should show error trying to delete a ticket with a refund', async() => {
await page.loginAndModule('production', 'ticket');
await page.loginAndModule('salesPerson', 'ticket');
await page.accessToSearchResult('8');
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);

View File

@ -164,6 +164,7 @@ export default class UploadPhoto extends Component {
const options = {
type: 'blob',
size: 'original'
};
return this.editor.result(options)
.then(blob => this.newPhoto.blob = blob)

View File

@ -61,7 +61,8 @@
"Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "I have created the claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "I have changed the price of [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "I have changed the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "I have changed {{changes}} of the ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}*",
"Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Bought {{quantity}} units of [{{itemId}} {{concept}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",

View File

@ -124,7 +124,8 @@
"Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "He cambiado la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "He cambiado {{changes}} del ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}*",
"State": "Estado",
"regular": "normal",
"reserved": "reservado",
@ -358,8 +359,8 @@
"Select ticket or client": "Elija un ticket o un client",
"It was not able to create the invoice": "No se pudo crear la factura",
"ticketCommercial": "El ticket {{ ticket }} para el vendedor {{ salesMan }} está en preparación. (mensaje generado automáticamente)",
"This PDA is already assigned to another user": "Este PDA ya está asignado a otro usuario",
"You can only have one PDA": "Solo puedes tener un PDA",
"Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE",
"This PDA is already assigned to another user": "Esta PDA ya está asignado a otro usuario",
"You can only have one PDA": "Solo puedes tener una PDA",
"Incoterms and Customs agent are required for a non UEE member": "Se requieren Incoterms y agente de aduanas para un no miembro de la UEE",
"You can not use the same password": "No puedes usar la misma contraseña"
}

View File

@ -123,8 +123,9 @@
"Added sale to ticket": "J'ai ajouté la ligne suivante au ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
"Changed sale discount": "J'ai changé le rabais des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "J'ai créé la réclamation [{{claimId}}]({{{claimUrl}}}) des lignes suivantes du ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "J'ai changé le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "J'ai changé la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}} du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale price": " le prix de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* du ticket [{{ticketId}}]({{{ticketUrl}}})",,
"Changed sale quantity": "J'ai changé {{changes}} du ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": "la quantité de {{itemId}} {{concept}} de {{oldQuantity}} ➔ {{newQuantity}}",
"State": "État",
"regular": "normal",
"reserved": "réservé",
@ -357,4 +358,4 @@
"This workCenter is already assigned to this agency": "Ce centre de travail est déjà assigné à cette agence",
"Select ticket or client": "Choisissez un ticket ou un client",
"It was not able to create the invoice": "Il n'a pas été possible de créer la facture"
}
}

View File

@ -124,7 +124,8 @@
"Changed sale discount": "Desconto da venda alterado no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Created claim": "Reclamação criada [{{claimId}}]({{{claimUrl}}}) no ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Changed sale price": "Preço da venda alterado para [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "Quantidade da venda alterada para [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changed sale quantity": "Quantidade da venda alterada para {{changes}} no ticket [{{ticketId}}]({{{ticketUrl}}})",
"Changes in sales": " [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* ",
"State": "Estado",
"regular": "normal",
"reserved": "reservado",
@ -357,4 +358,4 @@
"This workCenter is already assigned to this agency": "Este centro de trabalho já está atribuído a esta agência",
"Select ticket or client": "Selecione um ticket ou cliente",
"It was not able to create the invoice": "Não foi possível criar a fatura"
}
}

View File

@ -85,8 +85,12 @@ exports.translateValues = async(instance, changes, options = {}) => {
exports.getChanges = (original, changes) => {
const oldChanges = {};
const newChanges = {};
const dateRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
for (let property in changes) {
if (dateRegex.test(original[property]))
original[property] = new Date(Date.parse(original[property]));
const firstChar = property.substring(0, 1);
const isPrivate = firstChar == '$';
if (isPrivate) return;

View File

@ -6,6 +6,7 @@ describe('claimBeginning', () => {
const claimManagerId = 72;
const activeCtx = {
accessToken: {userId: claimManagerId},
__: value => value
};
const ctx = {req: activeCtx};

View File

@ -88,15 +88,17 @@ module.exports = Self => {
const updatedClaim = await claim.updateAttributes(args, myOptions);
// When pickup has been changed
if (salesPerson && changedPickup && updatedClaim.pickup)
await notifyPickUp(ctx, salesPerson.id, claim);
if (salesPerson) {
if (changedPickup && updatedClaim.pickup)
await notifyPickUp(ctx, salesPerson.id, claim);
// When claimState has been changed
if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
await notifyStateChange(ctx, salesPerson.id, claim, newState.description);
if (newState.code == 'canceled')
await notifyStateChange(ctx, claim.workerFk, claim, newState.description);
// When claimState has been changed
if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
await notifyStateChange(ctx, salesPerson.id, claim, newState.description);
if (newState.code == 'canceled')
await notifyStateChange(ctx, claim.workerFk, claim, newState.description);
}
}
if (tx) await tx.commit();

View File

@ -22,5 +22,8 @@
},
"EntryObservation": {
"dataSource": "vn"
},
"EntryType": {
"dataSource": "vn"
}
}
}

View File

@ -0,0 +1,25 @@
{
"name": "EntryType",
"base": "VnModel",
"mixins": {
"Loggable": true
},
"options": {
"mysql": {
"table": "entryType"
}
},
"properties": {
"code": {
"type": "string",
"id": true,
"description": "Identifier"
},
"description": {
"type": "string"
},
"isInformal": {
"type": "boolean"
}
}
}

View File

@ -35,9 +35,9 @@
},
"isVirtual": {
"type": "boolean",
"mysql": {
"columnName": "isRaid"
}
"mysql": {
"columnName": "isRaid"
}
},
"isRaid": {
"type": "boolean"
@ -53,9 +53,9 @@
},
"observation": {
"type": "string",
"mysql": {
"columnName": "evaNotes"
}
"mysql": {
"columnName": "evaNotes"
}
},
"loadPriority": {
"type": "number"
@ -101,6 +101,11 @@
"type": "belongsTo",
"model": "Account",
"foreignKey": "observationEditorFk"
},
"entryType": {
"type": "belongsTo",
"model": "EntryType",
"foreignKey": "typeFk"
}
}
}
}

View File

@ -54,6 +54,20 @@ module.exports = Self => {
value: rate
});
}
const monday = 1;
if (xmlDateWithoutTime.getDay() === monday) {
const saturday = new Date(xmlDateWithoutTime);
saturday.setDate(xmlDateWithoutTime.getDate() - 2);
const sunday = new Date(xmlDateWithoutTime);
sunday.setDate(xmlDateWithoutTime.getDate() - 1);
for (const date of [saturday, sunday]) {
await models.ReferenceRate.upsertWithWhere(
{currencyFk: currency.id, dated: date},
{currencyFk: currency.id, dated: date, value: rate}
);
}
}
}
}
}

View File

@ -4,7 +4,7 @@ module.exports = Self => {
accessType: 'READ',
accepts: [{
arg: 'barcode',
type: 'number',
type: 'string',
required: true,
description: 'barcode'
}],
@ -18,7 +18,7 @@ module.exports = Self => {
}
});
Self.toItem = async(barcode, options) => {
Self.toItem = async (barcode, options) => {
const myOptions = {};
if (typeof options == 'object')

View File

@ -64,7 +64,7 @@ describe('item getBalance()', () => {
const secondItemBalance = await models.Item.getBalance(ctx, secondFilter, options);
expect(firstItemBalance[9].claimFk).toEqual(null);
expect(secondItemBalance[4].claimFk).toEqual(2);
expect(secondItemBalance[7].claimFk).toEqual(2);
await tx.rollback();
} catch (e) {

View File

@ -155,6 +155,9 @@
"minQuantity": {
"type": "number",
"description": "Min quantity"
},
"photoMotivation": {
"type": "string"
}
},
"relations": {

View File

@ -151,7 +151,7 @@ describe('SalesMonitor salesFilter()', () => {
const result = await models.SalesMonitor.salesFilter(ctx, filter, options);
const firstRow = result[0];
expect(result.length).toEqual(12);
expect(result.length).toEqual(15);
expect(firstRow.alertLevel).not.toEqual(0);
await tx.rollback();

View File

@ -11,6 +11,12 @@
"Sector": {
"dataSource": "vn"
},
"SectorCollection": {
"dataSource": "vn"
},
"SectorCollectionSaleGroup": {
"dataSource": "vn"
},
"Train": {
"dataSource": "vn"
}

View File

@ -0,0 +1,24 @@
{
"name": "SectorCollection",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollection"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
},
"userFk": {
"type": "number"
},
"sectorFk": {
"type": "number"
}
}
}

View File

@ -0,0 +1,30 @@
{
"name": "SectorCollectionSaleGroup",
"base": "VnModel",
"options": {
"mysql": {
"table": "sectorCollectionSaleGroup"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"created": {
"type": "date"
}
},
"relations": {
"sectorCollection": {
"type": "belongsTo",
"model": "SectorCollection",
"foreignKey": "sectorCollectionFk"
},
"saleGroup": {
"type": "belongsTo",
"model": "SaleGroup",
"foreignKey": "saleGroupFk"
}
}
}

View File

@ -33,7 +33,7 @@ module.exports = Self => {
JOIN vn.item i ON i.id = b.itemFk
WHERE e.id = ? AND e.supplierFk = ?
GROUP BY i.id
) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal, et.printedStickers
) SELECT i.id, i.name, et.quantity, SUM(b.quantity) quantityTotal, et.printedStickers, ic.url
FROM vn.buy b
JOIN vn.item i ON i.id = b.itemFk
JOIN vn.entry e ON e.id = b.entryFk
@ -41,6 +41,7 @@ module.exports = Self => {
JOIN vn.buyConfig bc ON bc.monthsAgo
JOIN vn.travel t ON t.id = e.travelFk
LEFT JOIN entryTmp et ON et.id = i.id
JOIN hedera.imageConfig ic
WHERE e.supplierFk = ?
AND i.family IN ('EMB', 'CONT')
AND b.created > (util.VN_CURDATE() - INTERVAL bc.monthsAgo MONTH)

View File

@ -2,10 +2,12 @@ const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket setDelivered()', () => {
const userId = 50;
const userId = 49;
const activeCtx = {
accessToken: {userId: userId},
__: value => value
};
const ctx = {req: activeCtx};
beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
@ -19,8 +21,6 @@ describe('ticket setDelivered()', () => {
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 49}}};
const originalTicketOne = await models.Ticket.findById(8, null, options);
const originalTicketTwo = await models.Ticket.findById(10, null, options);

View File

@ -10,8 +10,8 @@ module.exports = Self => {
http: {source: 'path'}
},
{
arg: 'itemId',
type: 'number',
arg: 'barcode',
type: 'any',
required: true
},
{
@ -29,7 +29,7 @@ module.exports = Self => {
}
});
Self.addSale = async(ctx, id, itemId, quantity, options) => {
Self.addSale = async(ctx, id, barcode, quantity, options) => {
const $t = ctx.req.__; // $translate
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
@ -46,7 +46,9 @@ module.exports = Self => {
try {
await models.Ticket.isEditableOrThrow(ctx, id, myOptions);
const itemId = await models.ItemBarcode.toItem(barcode, myOptions);
const item = await models.Item.findById(itemId, null, myOptions);
const ticket = await models.Ticket.findById(id, {
include: {
relation: 'client',

View File

@ -1,56 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('addSaleByCode', {
description: 'Add a collection',
accessType: 'WRITE',
accepts: [
{
arg: 'barcode',
type: 'string',
required: true
}, {
arg: 'quantity',
type: 'number',
required: true
}, {
arg: 'ticketFk',
type: 'number',
required: true
}, {
arg: 'warehouseFk',
type: 'number',
required: true
},
],
http: {
path: `/addSaleByCode`,
verb: 'POST'
},
});
Self.addSaleByCode = async(ctx, barcode, quantity, ticketFk, warehouseFk, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const [[item]] = await Self.rawSql('CALL vn.item_getInfo(?,?)', [barcode, warehouseFk], myOptions);
if (!item?.available) throw new UserError('We do not have availability for the selected item');
await Self.rawSql('CALL vn.collection_addItem(?, ?, ?)', [item.id, quantity, ticketFk], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -8,18 +8,13 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
const state = await models.TicketState.findOne({
where: {ticketFk: id}
}, myOptions);
const state = await models.TicketState.findOne({where: {ticketFk: id}}, myOptions);
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const isProductionReviewer = await models.ACL.checkAccessAcl(ctx, 'Sale', 'isInPreparing', '*');
const canEditWeeklyTicket = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'canEditWeekly', 'WRITE');
const alertLevel = state ? state.alertLevel : null;
const ticket = await models.Ticket.findById(id, {
fields: ['clientFk'],
include: {
relation: 'client'
}
fields: ['clientFk'], include: {relation: 'client'}
}, myOptions);
const isLocked = await models.Ticket.isLocked(id, myOptions);
@ -29,10 +24,24 @@ module.exports = Self => {
const isNormalClient = ticket && ticket.client().typeFk == 'normal';
const isEditable = !(alertLevelGreaterThanZero && isNormalClient);
const ticketCollection = await models.TicketCollection.findOne({
include: {relation: 'collection'}, where: {ticketFk: id}
}, myOptions);
let isOwner = ticketCollection?.collection()?.workerFk === ctx.req.accessToken.userId;
if (!isOwner) {
const saleGroup = await models.SaleGroup.findOne({fields: ['id'], where: {ticketFk: id}}, myOptions);
const sectorCollectionSaleGroup = saleGroup && await models.SectorCollectionSaleGroup.findOne({
include: {relation: 'sectorCollection'}, where: {saleGroupFk: saleGroup.id}
}, myOptions);
isOwner = sectorCollectionSaleGroup?.sectorCollection()?.userFk === ctx.req.accessToken.userId;
}
if (!ticket)
throw new ForbiddenError(`The ticket doesn't exist.`);
if (!isEditable && !isRoleAdvanced)
if (!isEditable && !isRoleAdvanced && !isProductionReviewer && !isOwner)
throw new ForbiddenError(`This ticket is not editable.`);
if (isLocked && !isWeekly)

View File

@ -1,39 +0,0 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Ticket addSaleByCode()', () => {
const quantity = 3;
const ticketFk = 13;
const warehouseFk = 1;
beforeAll(async() => {
activeCtx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost'},
__: value => value
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should add a new sale', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const code = '1111111111';
const salesBefore = await models.Sale.find(null, options);
await models.Ticket.addSaleByCode(activeCtx, code, quantity, ticketFk, warehouseFk, options);
const salesAfter = await models.Sale.find(null, options);
expect(salesAfter.length).toEqual(salesBefore.length + 1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -68,7 +68,7 @@ describe('ticket filter()', () => {
const filter = {};
const result = await models.Ticket.filter(ctx, filter, options);
expect(result.length).toEqual(7);
expect(result.length).toEqual(10);
await tx.rollback();
} catch (e) {

View File

@ -42,7 +42,7 @@ describe('sale priceDifference()', () => {
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 1106}}};
const ctx = {req: {accessToken: {userId: 1105}}};
ctx.args = {
id: 1,
landed: Date.vnNew(),
@ -84,7 +84,7 @@ describe('sale priceDifference()', () => {
const {items} = await models.Ticket.priceDifference(ctx, options);
expect(items[0].movable).toEqual(410);
expect(items[0].movable).toEqual(386);
expect(items[1].movable).toEqual(1810);
await tx.rollback();

View File

@ -7,6 +7,7 @@ describe('ticket state()', () => {
const productionId = 49;
const activeCtx = {
accessToken: {userId: 9},
__: value => value
};
const ctx = {req: activeCtx};
const now = Date.vnNew();
@ -88,7 +89,8 @@ describe('ticket state()', () => {
const ticket = await models.Ticket.create(sampleTicket, options);
activeCtx.accessToken.userId = productionId;
const params = {ticketFk: ticket.id, stateFk: 3};
const stateOk = await models.State.findOne({where: {code: 'OK'}}, options);
const params = {ticketFk: ticket.id, stateFk: stateOk.id};
const ticketTracking = await models.Ticket.state(ctx, params, options);
@ -112,16 +114,68 @@ describe('ticket state()', () => {
const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options);
const ctx = {req: {accessToken: {userId: 18}}};
activeCtx.accessToken.userId = salesPersonId;
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const params = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const res = await models.Ticket.state(ctx, params, options);
const paramsAssigned = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const resAssigned = await models.Ticket.state(ctx, paramsAssigned, options);
expect(res.ticketFk).toBe(params.ticketFk);
expect(res.stateFk).toBe(params.stateFk);
expect(res.userFk).toBe(params.userFk);
expect(res.userFk).toBe(1);
expect(res.id).toBeDefined();
expect(resAssigned.ticketFk).toBe(paramsAssigned.ticketFk);
expect(resAssigned.stateFk).toBe(paramsAssigned.stateFk);
expect(resAssigned.userFk).toBe(paramsAssigned.userFk);
expect(resAssigned.userFk).toBe(1);
expect(resAssigned.id).toBeDefined();
activeCtx.accessToken.userId = productionId;
const packedState = await models.State.findOne({where: {code: 'PACKED'}}, options);
const paramsPacked = {ticketFk: ticket.id, stateFk: packedState.id, userFk: salesPersonId};
const resPacked = await models.Ticket.state(ctx, paramsPacked, options);
expect(resPacked.stateFk).toBe(paramsPacked.stateFk);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('Should equalize the quantities of quantity and originalQuantity' +
' if they are different', async() => {
const tx = await models.TicketTracking.beginTransaction({});
try {
const options = {transaction: tx};
const ticket = await models.Ticket.create(sampleTicket, options);
activeCtx.accessToken.userId = salesPersonId;
const sampleSale = {
ticketFk: ticket.id,
itemFk: 1,
concept: 'Test',
quantity: 10,
originalQuantity: 6
};
await models.Sale.create(sampleSale, options);
const assignedState = await models.State.findOne({where: {code: 'PICKER_DESIGNED'}}, options);
const paramsAssigned = {ticketFk: ticket.id, stateFk: assignedState.id, userFk: 1};
const resAssigned = await models.Ticket.state(ctx, paramsAssigned, options);
expect(resAssigned.ticketFk).toBe(paramsAssigned.ticketFk);
expect(resAssigned.stateFk).toBe(paramsAssigned.stateFk);
expect(resAssigned.userFk).toBe(paramsAssigned.userFk);
expect(resAssigned.userFk).toBe(1);
expect(resAssigned.id).toBeDefined();
activeCtx.accessToken.userId = productionId;
const packedState = await models.State.findOne({where: {code: 'PACKED'}}, options);
const paramsPacked = {ticketFk: ticket.id, stateFk: packedState.id, userFk: salesPersonId};
const resPacked = await models.Ticket.state(ctx, paramsPacked, options);
const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
expect(resPacked.stateFk).toBe(paramsPacked.stateFk);
expect(sale.quantity).toBe(sale.originalQuantity);
await tx.rollback();
} catch (e) {

View File

@ -64,7 +64,63 @@ module.exports = Self => {
if ((ticketState && !oldStateAllowed) || !newStateAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [params.ticketFk, params.code], myOptions);
const ticket = await models.Ticket.findById(params.ticketFk, {
include: [{
relation: 'client',
scope: {
fields: ['salesPersonFk']
}
}],
fields: ['id', 'clientFk']
}, myOptions);
const salesPersonFk = ticket.client().salesPersonFk;
if (salesPersonFk) {
const sales = await Self.rawSql(`
SELECT DISTINCT s.id,
s.itemFk,
s.concept,
s.originalQuantity AS oldQuantity,
s.quantity AS newQuantity
FROM vn.sale s
JOIN vn.saleTracking st ON st.saleFk = s.id
JOIN vn.ticket t ON t.id = s.ticketFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.ticketState ts ON ts.ticketFk = t.id
JOIN vn.state s2 ON s2.id = ts.stateFk
WHERE s.ticketFk = ?
AND st.isChecked
AND s.originalQuantity IS NOT NULL
AND s.originalQuantity <> s.quantity
AND s2.\`order\` < (SELECT \`order\` FROM vn.state WHERE code = 'CHECKED')
ORDER BY st.created DESC
`, [params.ticketFk], myOptions);
let changes = '';
const url = await models.Url.getUrl();
const $t = ctx.req.__;
for (let sale of sales) {
changes += `\r\n-` + $t('Changes in sales', {
itemId: sale.itemFk,
concept: sale.concept,
oldQuantity: sale.oldQuantity,
newQuantity: sale.newQuantity,
itemUrl: `${url}item/${sale.itemFk}/summary`
});
const currentSale = await models.Sale.findById(sale.id, null, myOptions);
await currentSale.updateAttributes({
originalQuantity: currentSale.quantity
}, myOptions);
}
const message = $t('Changed sale quantity', {
ticketId: ticket.id,
changes: changes,
ticketUrl: `${url}ticket/${ticket.id}/sale`
});
await models.Chat.sendCheckingPresence(ctx, salesPersonFk, message, myOptions);
}
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticket.id, params.code], myOptions);
const ticketTracking = await models.TicketTracking.findOne({
where: {ticketFk: params.ticketFk},

View File

@ -14,6 +14,9 @@
},
"parkingFk": {
"type": "number"
},
"ticketFk": {
"type": "number"
}
},
"relations": {

View File

@ -1,361 +1,318 @@
/* eslint max-len: ["error", { "code": 150 }]*/
const models = require('vn-loopback/server/server').models;
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('sale model ', () => {
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
function getActiveCtx(userId) {
return {
active: {
accessToken: {userId},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
}
};
const developerId = 9;
const buyerId = 35;
const employeeId = 1;
const productionId = 49;
const salesPersonId = 18;
const reviewerId = 130;
const barcode = '4444444444';
const ticketCollectionProd = 34;
const ticketCollectionSalesPerson = 35;
const previaTicketSalesPerson = 36;
const previaTicketProd = 37;
const notEditableError = 'This ticket is not editable.';
const ctx = getCtx(developerId);
let tx;
let opts;
function getCtx(userId, active = false) {
const accessToken = {userId};
const headers = {origin: 'localhost:5000'};
if (!active) return {req: {accessToken, headers, __: () => {}}};
return {active: {accessToken, http: {req: {headers}}}};
}
beforeEach(async() => {
tx = await models.Sale.beginTransaction({});
opts = {transaction: tx};
});
afterEach(async() => await tx.rollback());
describe('quantity field ', () => {
it('should add quantity if the quantity is greater than it should be and is role advanced', async() => {
const saleId = 17;
const buyerId = 35;
const ctx = {
req: {
accessToken: {userId: buyerId},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
const tx = await models.Sale.beginTransaction({});
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(buyerId));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const ctx = getCtx(buyerId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(buyerId, true));
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
return models.Ticket.rawSql(sqlStatement, params, opts);
});
try {
const options = {transaction: tx};
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
const isRoleAdvanced = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'isRoleAdvanced', '*');
expect(isRoleAdvanced).toEqual(true);
expect(isRoleAdvanced).toEqual(true);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(30);
expect(originalLine.quantity).toEqual(30);
const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
const newQuantity = originalLine.quantity + 1;
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(modifiedLine.quantity).toEqual(newQuantity);
});
it('should update the quantity of a given sale current line', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 25;
const newQuantity = 4;
try {
const options = {transaction: tx};
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const originalLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(originalLine.quantity).toEqual(20);
expect(originalLine.quantity).toEqual(20);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(modifiedLine.quantity).toEqual(newQuantity);
});
it('should throw an error if the quantity is negative and it is not a refund ticket', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const saleId = 17;
const newQuantity = -10;
const tx = await models.Sale.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).toEqual(new Error('You can only add negative amounts in refund tickets'));
}
expect(error).toEqual(new Error('You can only add negative amounts in refund tickets'));
});
it('should update a negative quantity when is a ticket refund', async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(developerId, true));
const tx = await models.Sale.beginTransaction({});
const saleId = 32;
const newQuantity = -10;
try {
const options = {transaction: tx};
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, opts);
const modifiedLine = await models.Sale.findOne({where: {id: saleId}, fields: ['quantity']}, options);
expect(modifiedLine.quantity).toEqual(newQuantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(modifiedLine.quantity).toEqual(newQuantity);
});
it('should throw an error if the quantity is less than the minimum quantity of the item', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT 100 as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
return models.Ticket.rawSql(sqlStatement, params, opts);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).toEqual(new Error('The amount cannot be less than the minimum'));
}
expect(error).toEqual(new Error('The amount cannot be less than the minimum'));
});
it('should change quantity if has minimum quantity and new quantity is equal than item available', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = minQuantity - 1;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY
SELECT ${newQuantity} as available;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, opts);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
describe('newPrice', () => {
it('should increase quantity if you have enough available and the new price is the same as the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 7.07 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, opts);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
it('should increase quantity when the new price is lower than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 1 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
});
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, opts);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
});
it('should throw error when increase quantity and the new price is higher than the previous one', async() => {
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(1));
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(employeeId, true));
const tx = await models.Sale.beginTransaction({});
const itemId = 2;
const saleId = 17;
const minQuantity = 30;
const newQuantity = 31;
let error;
try {
const options = {transaction: tx};
const item = await models.Item.findById(itemId, null, options);
await item.updateAttribute('minQuantity', minQuantity, options);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, options) => {
const item = await models.Item.findById(itemId, null, opts);
await item.updateAttribute('minQuantity', minQuantity, opts);
spyOn(models.Sale, 'rawSql').and.callFake((sqlStatement, params, opts) => {
if (sqlStatement.includes('catalog_calcFromItem')) {
sqlStatement = `
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketCalculateItem ENGINE = MEMORY SELECT ${newQuantity} as available;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketComponentPrice ENGINE = MEMORY SELECT 1 as grouping, 100000 as price;`;
params = null;
}
return models.Ticket.rawSql(sqlStatement, params, options);
return models.Ticket.rawSql(sqlStatement, params, opts);
});
await models.Sale.updateQuantity(ctx, saleId, newQuantity, options);
await tx.rollback();
await models.Sale.updateQuantity(ctx, saleId, newQuantity, opts);
} catch (e) {
await tx.rollback();
error = e;
expect(e).toEqual(new Error('The price of the item changed'));
}
expect(error).toEqual(new Error('The price of the item changed'));
});
});
});
describe('add a sale from a collection or previa ticket', () => {
it('if is allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionProd}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
await models.Ticket.addSale(ctx, previaTicketProd, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketProd}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as Production role', async() => {
const ctx = getCtx(productionId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(productionId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
try {
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
});
it('if is allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
it('should throw an error if is not allocated to them and alert level higher than 0 as salesPerson role', async() => {
const ctx = getCtx(salesPersonId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(salesPersonId, true));
try {
await models.Ticket.addSale(ctx, ticketCollectionProd, barcode, 20, opts);
} catch ({message}) {
expect(message).toEqual(notEditableError);
}
});
it('if is a reviewer', async() => {
const ctx = getCtx(reviewerId);
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getCtx(reviewerId, true));
const beforeSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
await models.Ticket.addSale(ctx, ticketCollectionSalesPerson, barcode, 20, opts);
const afterSalesCollection = await models.Sale.count({ticketFk: ticketCollectionSalesPerson}, opts);
expect(afterSalesCollection).toEqual(beforeSalesCollection + 1);
const beforeSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
await models.Ticket.addSale(ctx, previaTicketSalesPerson, barcode, 20, opts);
const afterSalesPrevia = await models.Sale.count({ticketFk: previaTicketSalesPerson}, opts);
expect(afterSalesPrevia).toEqual(beforeSalesPrevia + 1);
});
});
});

View File

@ -46,6 +46,5 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/addSaleByCode')(Self);
require('../methods/ticket/clone')(Self);
};

View File

@ -152,7 +152,7 @@
</vn-item>
<vn-item class="dropdown"
vn-click-stop="refundMenu.show($event, 'left')"
vn-acl="invoicing, claimManager, salesAssistant"
vn-acl="invoicing, claimManager, salesAssistant, logistic"
vn-acl-action="remove"
vn-tooltip="Create a single ticket with all the content of the current ticket"
translate>

View File

@ -476,7 +476,7 @@ class Controller extends Section {
*/
addSale(sale) {
const data = {
itemId: sale.itemFk,
barcode: sale.itemFk,
quantity: sale.quantity
};
const query = `tickets/${this.ticket.id}/addSale`;

View File

@ -659,7 +659,7 @@ describe('Ticket', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis();
const newSale = {itemFk: 4, quantity: 10};
const expectedParams = {itemId: 4, quantity: 10};
const expectedParams = {barcode: 4, quantity: 10};
const expectedResult = {
id: 30,
quantity: 10,

View File

@ -20,7 +20,7 @@ module.exports = Self => {
const notifications = await models.NotificationQueue.find(
{where: {created: {gte: new Date(Date.vnNow() - (backupPrinterNotificationDelay * 1000))},
notificationFk: notificationName,
status: 'sent'
status: {neq: 'error'}
}
});

View File

@ -54,8 +54,8 @@
<vn-autocomplete
ng-model="$ctrl.worker.originCountryFk"
url="Countries"
fields="['id', 'country', 'code']"
show-field="country"
fields="['id', 'name', 'code']"
show-field="name"
value-field="id"
label="Origin country">
</vn-autocomplete>

View File

@ -5,6 +5,7 @@ describe('zone deletezone()', () => {
const userId = 9;
const activeCtx = {
accessToken: {userId: userId},
__: value => value
};
const ctx = {req: activeCtx};
const zoneId = 9;
@ -15,19 +16,15 @@ describe('zone deletezone()', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
try {
const originalTickets = await models.Ticket.find({
where: {
zoneFk: zoneId
}
});
ticketIDs = originalTickets.map(ticket => ticket.id);
originalTicketStates = await models.TicketState.find({where: {
ticketFk: {inq: ticketIDs},
code: 'FIXING'}});
} catch (error) {
console.error(error);
}
const originalTickets = await models.Ticket.find({
where: {
zoneFk: zoneId
}
});
ticketIDs = originalTickets.map(ticket => ticket.id);
originalTicketStates = await models.TicketState.find({where: {
ticketFk: {inq: ticketIDs},
code: 'FIXING'}});
});
it('should delete a zone and update their tickets', async() => {

View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "24.24.1",
"version": "24.24.3",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",