8144-devToTest_2444 #3137

Merged
alexm merged 90 commits from 8144-devToTest_2444 into test 2024-10-22 06:07:15 +00:00
43 changed files with 529 additions and 293 deletions

View File

@ -32,8 +32,7 @@ RUN apt-get update \
RUN apt-get update \ RUN apt-get update \
&& apt-get install -y --no-install-recommends \ && apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\ samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/*
&& npm -g install pm2
# Salix # Salix
@ -55,7 +54,4 @@ COPY \
README.md \ README.md \
./ ./
CMD ["pm2-runtime", "./back/process.yml"] CMD ["node", "--tls-min-v1.0", "--openssl-legacy-provider", "./loopback/server/server.js"]
HEALTHCHECK --interval=15s --timeout=10s \
CMD curl -f http://localhost:3000/api/Applications/status || exit 1

View File

@ -16,6 +16,10 @@
"name": { "name": {
"type": "string", "type": "string",
"required": true "required": true
},
"hasDailyInvoice": {
"type": "boolean",
"description": "Indicates if the autonomy has daily invoice enabled"
} }
}, },
"relations": { "relations": {

View File

@ -28,6 +28,10 @@
}, },
"continentFk": { "continentFk": {
"type": "number" "type": "number"
},
"hasDailyInvoice": {
"type": "boolean",
"description": "Indicates if the autonomy has daily invoice enabled"
} }
}, },
"relations": { "relations": {

View File

@ -16,6 +16,9 @@
"name": { "name": {
"type": "string", "type": "string",
"required": true "required": true
},
"autonomyFk": {
"type": "number"
} }
}, },
"relations": { "relations": {

View File

@ -1,7 +0,0 @@
apps:
- script: ./loopback/server/server.js
name: salix-back
instances: 1
max_restarts: 0
autorestart: false
node_args: --tls-min-v1.0 --openssl-legacy-provider

View File

@ -388,23 +388,23 @@ INSERT INTO `vn`.`contactChannel`(`id`, `name`)
(4, 'GCN Channel'), (4, 'GCN Channel'),
(5, 'The Newspaper'); (5, 'The Newspaper');
INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`gestdocFk`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`businessTypeFk`,`typeFk`) INSERT INTO `vn`.`client`(`id`,`name`,`fi`,`socialName`,`contact`,`street`,`city`,`postcode`,`phone`,`mobile`,`isRelevant`,`email`,`iban`,`dueDay`,`accountingAccount`,`isEqualizated`,`provinceFk`,`hasToInvoice`,`credit`,`countryFk`,`isActive`,`quality`,`payMethodFk`,`created`,`isToBeMailed`,`contactChannelFk`,`hasSepaVnl`,`hasCoreVnl`,`hasCoreVnh`,`riskCalculated`, `hasToInvoiceByAddress`,`isTaxDataChecked`,`isFreezed`,`creditInsurance`,`isCreatedAsServed`,`hasInvoiceSimplified`,`salesPersonFk`,`isVies`,`businessTypeFk`,`typeFk`)
VALUES VALUES
(1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'), (1101, 'Bruce Wayne', '84612325V', 'BATMAN', 'Alfred', '1007 MOUNTAIN DRIVE, GOTHAM', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceWayne@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'),
(1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'), (1102, 'Petter Parker', '87945234L', 'SPIDER MAN', 'Aunt May', '20 INGRAM STREET, QUEENS, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'PetterParker@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'),
(1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'), (1103, 'Clark Kent', '06815934E', 'SUPER MAN', 'lois lane', '344 CLINTON STREET, APARTAMENT 3-D', 'Gotham', 46460, 1111111111, 222222222, 1, 'ClarkKent@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 0, 19, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'),
(1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'), (1104, 'Tony Stark', '06089160W', 'IRON MAN', 'Pepper Potts', '10880 MALIBU POINT, 90265', 'Gotham', 46460, 1111111111, 222222222, 1, 'TonyStark@mydomain.com', NULL, 0, 1234567890, 0, 2, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 18, 0, 'florist','normal'),
(1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, NULL, 0, 0, 18, 0, 'florist','normal'), (1105, 'Max Eisenhardt', '251628698', 'MAGNETO', 'Rogue', 'UNKNOWN WHEREABOUTS', 'Gotham', 46460, 1111111111, 222222222, 1, 'MaxEisenhardt@mydomain.com', NULL, 0, 1234567890, 0, 3, 1, 300, 8, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, NULL, 0, 0, 18, 0, 'florist','normal'),
(1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 19, 0, 'florist','normal'), (1106, 'DavidCharlesHaller', '53136686Q', 'LEGION', 'Charles Xavier', 'CITY OF NEW YORK, NEW YORK, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'DavidCharlesHaller@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 0, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, 19, 0, 'florist','normal'),
(1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 0, 0, NULL, 0, 0, 19, 0, 'florist','normal'), (1107, 'Hank Pym', '09854837G', 'ANT MAN', 'Hawk', 'ANTHILL, SAN FRANCISCO, CALIFORNIA', 'Gotham', 46460, 1111111111, 222222222, 1, 'HankPym@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 0, 0, NULL, 0, 0, 19, 0, 'florist','normal'),
(1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, NULL, 0, 0, 19, 0, 'florist','normal'), (1108, 'Charles Xavier', '22641921P', 'PROFESSOR X', 'Beast', '3800 VICTORY PKWY, CINCINNATI, OH 45207, USA', 'Gotham', 46460, 1111111111, 222222222, 1, 'CharlesXavier@mydomain.com', NULL, 0, 1234567890, 0, 5, 1, 300, 13, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 1, NULL, 0, 0, 19, 0, 'florist','normal'),
(1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 0, 0, NULL, 0, 0, 9, 0, 'florist','normal'), (1109, 'Bruce Banner', '16104829E', 'HULK', 'Black widow', 'SOMEWHERE IN NEW YORK', 'Gotham', 46460, 1111111111, 222222222, 1, 'BruceBanner@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 0, 0, NULL, 0, 0, 9, 0, 'florist','normal'),
(1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, NULL, 1, 'florist','normal'), (1110, 'Jessica Jones', '58282869H', 'JESSICA JONES', 'Luke Cage', 'NYCC 2015 POSTER', 'Gotham', 46460, 1111111111, 222222222, 1, 'JessicaJones@mydomain.com', NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 1, 1, 0, NULL, 0, 0, NULL, 1, 'florist','normal'),
(1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 0, 1, 0, NULL, 1, 0, NULL, 0, 'others','loses'), (1111, 'Missing', NULL, 'MISSING MAN', 'Anton', 'THE SPACE, UNIVERSE FAR AWAY', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 0, 1, 0, NULL, 1, 0, NULL, 0, 'others','loses'),
(1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, NULL, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 0, 1, 0, NULL, 1, 0, NULL, 0, 'others','loses'); (1112, 'Trash', NULL, 'GARBAGE MAN', 'Unknown name', 'NEW YORK CITY, UNDERGROUND', 'Gotham', 46460, 1111111111, 222222222, 1, NULL, NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1, 5, 1, 1, 1, '0000-00-00', 0, 1, 0, NULL, 1, 0, NULL, 0, 'others','loses');
INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `gestdocFk`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`) INSERT INTO `vn`.`client`(`id`, `name`, `fi`, `socialName`, `contact`, `street`, `city`, `postcode`, `isRelevant`, `email`, `iban`,`dueDay`,`accountingAccount`, `isEqualizated`, `provinceFk`, `hasToInvoice`, `credit`, `countryFk`, `isActive`, `quality`, `payMethodFk`,`created`, `isTaxDataChecked`)
SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), UPPER(CONCAT(name, 'Social')), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1,NULL, 10, 5, util.VN_CURDATE(), 1 SELECT id, name, CONCAT(RPAD(CONCAT(id,9),8,id),'A'), UPPER(CONCAT(name, 'Social')), CONCAT(name, 'Contact'), CONCAT(name, 'Street'), 'GOTHAM', 46460, 1, CONCAT(name,'@mydomain.com'), NULL, 0, 1234567890, 0, 1, 1, 300, 1, 1, 10, 5, util.VN_CURDATE(), 1
FROM `account`.`role` `r` FROM `account`.`role` `r`
WHERE `r`.`hasLogin` = 1; WHERE `r`.`hasLogin` = 1;
@ -632,14 +632,21 @@ INSERT INTO vn.invoiceOutConfig
SET id = 1, SET id = 1,
parallelism = 8; parallelism = 8;
INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`) INSERT INTO `vn`.`invoiceOutSerial`
(`code`,`description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`)
VALUES VALUES
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'), ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'), ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'), ('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'multiple'), ('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'multiple'),
('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL), ('R', 'Rectificativa', 1, 'NATIONAL', 0, NULL),
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'); ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'),
('H', 'Intracomunitaria rápida', 0, 'CEE', 1, 'quick'),
('P', 'Factura simplificada', 1, 'NATIONAL', 0, NULL),
('PE', 'COOPERATIE FLORAHOLLAND UA', 0, 'CEE', 1, NULL),
('S', 'Simplificada', 1, 'NATIONAL', 0, NULL),
('X', 'Exportación global', 0, 'WORLD', 0, 'global'),
('N', 'Múltiple Intracomunitaria', 0, 'CEE', 1, 'multiple');
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`) INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
VALUES VALUES
@ -2911,7 +2918,8 @@ INSERT INTO `util`.`notification` (`id`, `name`, `description`)
(6, 'book-entry-deleted', 'accounting entries deleted'), (6, 'book-entry-deleted', 'accounting entries deleted'),
(7, 'zone-included','An email to notify zoneCollisions'), (7, 'zone-included','An email to notify zoneCollisions'),
(8, 'backup-printer-selected','A backup printer has been selected'), (8, 'backup-printer-selected','A backup printer has been selected'),
(9, 'mrw-deadline','The MRW deadline has passed'); (9, 'mrw-deadline','The MRW deadline has passed'),
(10,'invoice-ticket-closure','Tickets not invoiced during the nightly closure ticket process');
TRUNCATE `util`.`notificationAcl`; TRUNCATE `util`.`notificationAcl`;
INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`) INSERT INTO `util`.`notificationAcl` (`notificationFk`, `roleFk`)

View File

@ -6,8 +6,8 @@ BLOCK1: BEGIN
DECLARE vShipped DATE; DECLARE vShipped DATE;
DECLARE vPreviousShipped DATE; DECLARE vPreviousShipped DATE;
DECLARE vDone boolean; DECLARE vDone boolean;
DECLARE cur cursor for
DECLARE cur CURSOR FOR
SELECT clientFk, firstShipped SELECT clientFk, firstShipped
FROM bs.clientNewBorn; FROM bs.clientNewBorn;
@ -17,14 +17,16 @@ BLOCK1: BEGIN
DELETE FROM bs.clientNewBorn WHERE isModified = FALSE; DELETE FROM bs.clientNewBorn WHERE isModified = FALSE;
INSERT INTO clientNewBorn(clientFk, firstShipped, lastShipped) INSERT INTO clientNewBorn(clientFk, firstShipped, lastShipped)
SELECT c.id, MAX(t.shipped), MAX(t.shipped) SELECT c.id, DATE(MAX(t.shipped)), DATE(MAX(t.shipped))
FROM vn.client c FROM vn.client c
JOIN vn.ticket t on t.clientFk = c.id JOIN vn.ticket t ON t.clientFk = c.id
LEFT JOIN clientNewBorn cb on cb.clientFk = c.id LEFT JOIN clientNewBorn cb ON cb.clientFk = c.id
WHERE t.shipped BETWEEN TIMESTAMPADD(YEAR, -1, util.VN_CURDATE()) AND util.VN_CURDATE() AND cb.isModified is null WHERE t.shipped BETWEEN util.VN_CURDATE() - INTERVAL 1 YEAR
AND util.VN_CURDATE()
AND cb.isModified IS NULL
GROUP BY c.id; GROUP BY c.id;
OPEN cur;
OPEN cur;
LOOP1: LOOP LOOP1: LOOP
SET vDone := FALSE; SET vDone := FALSE;
FETCH cur INTO vClientFk, vShipped; FETCH cur INTO vClientFk, vShipped;

View File

@ -34,22 +34,19 @@ BEGIN
DECLARE vIsTaxDataChecked TINYINT(1); DECLARE vIsTaxDataChecked TINYINT(1);
DECLARE vHasCoreVnl BOOLEAN; DECLARE vHasCoreVnl BOOLEAN;
DECLARE vMandateTypeFk INT; DECLARE vMandateTypeFk INT;
DECLARE vHasDailyInvoice BOOLEAN;
SELECT cc.defaultPayMethodFk, SELECT cc.defaultPayMethodFk,
cc.defaultDueDay, cc.defaultDueDay,
cc.defaultCredit, cc.defaultCredit,
cc.defaultIsTaxDataChecked, cc.defaultIsTaxDataChecked,
cc.defaultHasCoreVnl, cc.defaultHasCoreVnl,
cc.defaultMandateTypeFk, cc.defaultMandateTypeFk
c.hasDailyInvoice
INTO vPayMethodFk, INTO vPayMethodFk,
vDueDay, vDueDay,
vDefaultCredit, vDefaultCredit,
vIsTaxDataChecked, vIsTaxDataChecked,
vHasCoreVnl, vHasCoreVnl,
vMandateTypeFk, vMandateTypeFk
vHasDailyInvoice
FROM clientConfig cc FROM clientConfig cc
LEFT JOIN province p ON p.id = vProvinceFk LEFT JOIN province p ON p.id = vProvinceFk
LEFT JOIN country c ON c.id = p.countryFk; LEFT JOIN country c ON c.id = p.countryFk;
@ -70,8 +67,7 @@ BEGIN
credit = vDefaultCredit, credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked, isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl, hasCoreVnl = vHasCoreVnl,
isEqualizated = FALSE, isEqualizated = FALSE
hasDailyInvoice = vHasDailyInvoice
ON duplicate KEY UPDATE ON duplicate KEY UPDATE
payMethodFk = vPayMethodFk, payMethodFk = vPayMethodFk,
dueDay = vDueDay, dueDay = vDueDay,

View File

@ -43,7 +43,7 @@ BEGIN
c.isTaxDataChecked, c.isTaxDataChecked,
t.companyFk, t.companyFk,
t.shipped, t.shipped,
IFNULL(a.hasDailyInvoice, co.hasDailyInvoice), c.hasDailyInvoice,
w.isManaged, w.isManaged,
c.hasToInvoice c.hasToInvoice
INTO vClientFk, INTO vClientFk,
@ -55,9 +55,6 @@ BEGIN
vHasToInvoice vHasToInvoice
FROM ticket t FROM ticket t
JOIN `client` c ON c.id = t.clientFk JOIN `client` c ON c.id = t.clientFk
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 warehouse w ON w.id = t.warehouseFk JOIN warehouse w ON w.id = t.warehouseFk
WHERE t.id = vCurTicketFk; WHERE t.id = vCurTicketFk;

View File

@ -1,5 +1,7 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_getTax`(IN vTaxArea VARCHAR(25)) CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`ticket_getTax`(
vTaxArea VARCHAR(25)
)
BEGIN BEGIN
/** /**
* Calcula la base imponible, el IVA y el recargo de equivalencia para * Calcula la base imponible, el IVA y el recargo de equivalencia para
@ -33,30 +35,39 @@ BEGIN
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketTax CREATE OR REPLACE TEMPORARY TABLE tmp.ticketTax
(PRIMARY KEY (ticketFk, code, rate)) (PRIMARY KEY (ticketFk, code, rate))
ENGINE = MEMORY ENGINE = MEMORY
SELECT * FROM ( WITH sales AS (
SELECT tmpTicket.ticketFk, SELECT s.ticketFk,
s.itemFk,
s.quantity * s.price * (100 - s.discount) / 100 total,
t.companyFk,
t.addressFk,
su.countryFk,
ata.areaFk,
itc.taxClassFk
FROM vn.sale s
JOIN tmp.ticket tmp ON tmp.ticketFk = s.ticketFk
JOIN vn.ticket t ON t.id = s.ticketFk
JOIN vn.supplier su ON su.id = t.companyFk
JOIN tmp.addressTaxArea ata ON ata.addressFk = t.addressFk
AND ata.companyFk = t.companyFk
JOIN vn.itemTaxCountry itc ON itc.itemFk = s.itemFk
AND itc.countryFk = su.countryFk
HAVING total
)
SELECT s.ticketFk,
bp.pgcFk, bp.pgcFk,
SUM(s.quantity * s.price * (100 - s.discount) / 100 ) taxableBase, SUM(s.total) taxableBase,
pgc.rate, pgc.rate,
tc.code, tc.code,
bp.priority bp.priority
FROM tmp.ticket tmpTicket FROM sales s
JOIN sale s ON s.ticketFk = tmpTicket.ticketFk JOIN vn.bookingPlanner bp ON bp.countryFk = s.countryFk
JOIN item i ON i.id = s.itemFk AND bp.taxAreaFk = s.areaFk
JOIN ticket t ON t.id = tmpTicket.ticketFk AND bp.taxClassFk = s.taxClassFk
JOIN supplier su ON su.id = t.companyFk JOIN vn.pgc ON pgc.code = bp.pgcFk
JOIN tmp.addressTaxArea ata ON ata.addressFk = t.addressFk JOIN vn.taxClass tc ON tc.id = bp.taxClassFk
AND ata.companyFk = t.companyFk GROUP BY s.ticketFk, pgc.code, pgc.rate
JOIN itemTaxCountry itc ON itc.itemFk = i.id
AND itc.countryFk = su.countryFk
JOIN bookingPlanner bp ON bp.countryFk = su.countryFk
AND bp.taxAreaFk = ata.areaFk
AND bp.taxClassFk = itc.taxClassFk
JOIN pgc ON pgc.code = bp.pgcFk
JOIN taxClass tc ON tc.id = bp.taxClassFk
GROUP BY tmpTicket.ticketFk, pgc.code, pgc.rate
HAVING taxableBase HAVING taxableBase
) t3
ORDER BY priority; ORDER BY priority;
CREATE OR REPLACE TEMPORARY TABLE tmp.ticketServiceTax CREATE OR REPLACE TEMPORARY TABLE tmp.ticketServiceTax

View File

@ -22,7 +22,6 @@ AS SELECT `c`.`id` AS `id_cliente`,
`c`.`credit` AS `credito`, `c`.`credit` AS `credito`,
`c`.`countryFk` AS `Id_Pais`, `c`.`countryFk` AS `Id_Pais`,
`c`.`isActive` AS `activo`, `c`.`isActive` AS `activo`,
`c`.`gestdocFk` AS `gestdoc_id`,
`c`.`quality` AS `calidad`, `c`.`quality` AS `calidad`,
`c`.`payMethodFk` AS `pay_met_id`, `c`.`payMethodFk` AS `pay_met_id`,
`c`.`created` AS `created`, `c`.`created` AS `created`,

View File

@ -0,0 +1,6 @@
ALTER TABLE `vn`.`operator`
ADD COLUMN `machineFk` int(11) DEFAULT NULL,
ADD CONSTRAINT `operator_machine_FK` FOREIGN KEY (`machineFk`) REFERENCES `vn`.`machine` (`id`) ON DELETE SET NULL ON UPDATE CASCADE;
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Machine','*','*','ALLOW','ROLE','productionBoss');

View File

@ -0,0 +1,3 @@
ALTER TABLE `vn`.`ticketConfig`
ADD COLUMN `closureDaysAgo` int(11) NOT NULL DEFAULT 2 COMMENT 'Number of days to look back for ticket closure',
ADD CONSTRAINT `closureDaysAgo_check` CHECK (`closureDaysAgo` > 0);

View File

@ -0,0 +1,13 @@
UPDATE `vn`.`client` c
JOIN `vn`.`country` co ON co.id=c.countryFk
SET c.hasDailyInvoice = co.hasDailyInvoice
WHERE co.hasDailyInvoice IS NOT NULL
AND c.hasDailyInvoice = FALSE;
UPDATE `vn`.`client` c
JOIN `vn`.`province` p ON p.id=c.provinceFk
JOIN `vn`.`autonomy` a ON a.id = p.autonomyFk
SET c.hasDailyInvoice = a.hasDailyInvoice
WHERE a.hasDailyInvoice IS NOT NULL
AND c.hasDailyInvoice = FALSE;

View File

@ -0,0 +1 @@
ALTER TABLE vn.client CHANGE gestdocFk gestdocFk__ int(11) DEFAULT NULL NULL COMMENT '@deprecated 2024-10-17';

View File

@ -240,5 +240,6 @@
"There is already a tray with the same height": "There is already a tray with the same height", "There is already a tray with the same height": "There is already a tray with the same height",
"The height must be greater than 50cm": "The height must be greater than 50cm", "The height must be greater than 50cm": "The height must be greater than 50cm",
"The maximum height of the wagon is 200cm": "The maximum height of the wagon is 200cm", "The maximum height of the wagon is 200cm": "The maximum height of the wagon is 200cm",
"The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line" "The quantity claimed cannot be greater than the quantity of the line": "The quantity claimed cannot be greater than the quantity of the line",
"There are tickets for this area, delete them first": "There are tickets for this area, delete them first"
} }

View File

@ -350,6 +350,7 @@
"Cmr file does not exist": "El archivo del cmr no existe", "Cmr file does not exist": "El archivo del cmr no existe",
"You are not allowed to modify the alias": "No estás autorizado a modificar el alias", "You are not allowed to modify the alias": "No estás autorizado a modificar el alias",
"The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas", "The address of the customer must have information about Incoterms and Customs Agent": "El consignatario del cliente debe tener informado Incoterms y Agente de aduanas",
"No invoice series found for these parameters": "No se encontró una serie para estos parámetros",
"The line could not be marked": "La linea no puede ser marcada", "The line could not be marked": "La linea no puede ser marcada",
"Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado", "Through this procedure, it is not possible to modify the password of users with verified email": "Mediante este procedimiento, no es posible modificar la contraseña de usuarios con correo verificado",
"They're not your subordinate": "No es tu subordinado/a.", "They're not your subordinate": "No es tu subordinado/a.",
@ -382,5 +383,6 @@
"This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha", "This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha",
"No valid travel thermograph found": "No se encontró un termógrafo válido", "No valid travel thermograph found": "No se encontró un termógrafo válido",
"The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea", "The quantity claimed cannot be greater than the quantity of the line": "La cantidad reclamada no puede ser mayor que la cantidad de la línea",
"type cannot be blank": "Se debe rellenar el tipo" "type cannot be blank": "Se debe rellenar el tipo",
"There are tickets for this area, delete them first": "Hay tickets para esta sección, borralos primero"
} }

View File

@ -350,6 +350,7 @@
"Cmr file does not exist": "Le fichier cmr n'existe pas", "Cmr file does not exist": "Le fichier cmr n'existe pas",
"You are not allowed to modify the alias": "Vous n'êtes pas autorisé à modifier l'alias", "You are not allowed to modify the alias": "Vous n'êtes pas autorisé à modifier l'alias",
"The address of the customer must have information about Incoterms and Customs Agent": "L'adresse du client doit contenir des informations sur les Incoterms et l'agent des douanes", "The address of the customer must have information about Incoterms and Customs Agent": "L'adresse du client doit contenir des informations sur les Incoterms et l'agent des douanes",
"No invoice series found for these parameters": "Aucune série de facture trouvée pour ces paramètres",
"The line could not be marked": "La ligne ne peut pas être marquée", "The line could not be marked": "La ligne ne peut pas être marquée",
"This password can only be changed by the user themselves": "Ce mot de passe ne peut être modifié que par l'utilisateur lui-même", "This password can only be changed by the user themselves": "Ce mot de passe ne peut être modifié que par l'utilisateur lui-même",
"They're not your subordinate": "Ce n'est pas votre subordonné.", "They're not your subordinate": "Ce n'est pas votre subordonné.",

View File

@ -350,6 +350,7 @@
"Cmr file does not exist": "O arquivo CMR não existe", "Cmr file does not exist": "O arquivo CMR não existe",
"You are not allowed to modify the alias": "Você não tem permissão para modificar o alias", "You are not allowed to modify the alias": "Você não tem permissão para modificar o alias",
"The address of the customer must have information about Incoterms and Customs Agent": "O endereço do cliente deve ter informações sobre Incoterms e Agente Aduaneiro", "The address of the customer must have information about Incoterms and Customs Agent": "O endereço do cliente deve ter informações sobre Incoterms e Agente Aduaneiro",
"No invoice series found for these parameters": "Nenhuma série de fatura encontrada para esses parâmetros",
"The line could not be marked": "A linha não pôde ser marcada", "The line could not be marked": "A linha não pôde ser marcada",
"This password can only be changed by the user themselves": "Esta senha só pode ser alterada pelo próprio usuário", "This password can only be changed by the user themselves": "Esta senha só pode ser alterada pelo próprio usuário",
"They're not your subordinate": "Eles não são seus subordinados.", "They're not your subordinate": "Eles não são seus subordinados.",

View File

@ -85,17 +85,12 @@ module.exports = Self => {
const updatedClaim = await claim.updateAttributes(args, myOptions); const updatedClaim = await claim.updateAttributes(args, myOptions);
const salesPerson = claim.client().salesPersonUser(); const salesPerson = claim.client().salesPersonUser();
if (salesPerson) { if (salesPerson && args.claimStateFk) {
if (changedPickup && updatedClaim.pickup)
await notifyPickUp(ctx, salesPerson.id, claim);
if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions); const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
await notifyStateChange(ctx, salesPerson.id, claim, newState.description); await notifyStateChange(ctx, salesPerson.id, claim, newState.description);
if (newState.code == 'canceled') if (newState.code == 'canceled')
await notifyStateChange(ctx, claim.workerFk, claim, newState.description); await notifyStateChange(ctx, claim.workerFk, claim, newState.description);
} }
}
if (tx) await tx.commit(); if (tx) await tx.commit();
@ -119,18 +114,4 @@ module.exports = Self => {
}); });
await models.Chat.sendCheckingPresence(ctx, workerId, message); await models.Chat.sendCheckingPresence(ctx, workerId, message);
} }
async function notifyPickUp(ctx, workerId, claim) {
const models = Self.app.models;
const url = await models.Url.getUrl();
const $t = ctx.req.__; // $translate
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${url}claim/${claim.id}/summary`,
claimPickup: $t(claim.pickup)
});
await models.Chat.sendCheckingPresence(ctx, workerId, message);
}
}; };

View File

@ -43,6 +43,23 @@ module.exports = function(Self) {
}; };
try { try {
const province = await models.Province.findOne({
where: {id: data.provinceFk},
fields: ['autonomyFk']
});
const autonomy = province ? await models.Autonomy.findOne({
where: {id: province.autonomyFk},
fields: ['hasDailyInvoice']
}) : null;
const country = await models.Country.findOne({
where: {id: data.countryFk},
fields: ['hasDailyInvoice']
});
const hasDailyInvoice = (autonomy?.hasDailyInvoice ?? country?.hasDailyInvoice) || false;
const account = await models.VnUser.create(user, myOptions); const account = await models.VnUser.create(user, myOptions);
const client = await Self.create({ const client = await Self.create({
id: account.id, id: account.id,
@ -57,7 +74,8 @@ module.exports = function(Self) {
provinceFk: data.provinceFk, provinceFk: data.provinceFk,
countryFk: data.countryFk, countryFk: data.countryFk,
isEqualizated: data.isEqualizated, isEqualizated: data.isEqualizated,
businessTypeFk: data.businessTypeFk businessTypeFk: data.businessTypeFk,
hasDailyInvoice: hasDailyInvoice
}, myOptions); }, myOptions);
const address = await models.Address.create({ const address = await models.Address.create({

View File

@ -1,5 +1,54 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('Client Create', () => { describe('Client Create', () => {
let options;
let tx;
beforeAll.mockLoopBackContext();
beforeEach(async() => {
tx = await models.Client.beginTransaction({});
options = {transaction: tx};
});
afterEach(async() => await tx.rollback());
it('should not find deadpool as he is not created yet', async() => {
try {
const account = await models.VnUser.findOne({where: {name: 'deadpool'}}, options);
const client = await models.Client.findOne({where: {name: 'Wade'}}, options);
expect(account).toBeNull();
expect(client).toBeNull();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should throw an error when creating a new account without email', async() => {
let error;
const newAccountWithoutEmail = {
userName: 'deadpool',
fi: '16195279J',
name: 'Wade',
socialName: 'DEADPOOL MARVEL',
street: 'WALL STREET',
city: 'New York',
businessTypeFk: 'florist',
provinceFk: 1
};
try {
await models.Client.createWithUser(newAccountWithoutEmail, options);
} catch (e) {
error = e;
}
expect(error.message).toEqual('An email is necessary');
});
it('should create a new account with dailyInvoice', async() => {
const newAccount = { const newAccount = {
userName: 'deadpool', userName: 'deadpool',
email: 'deadpool@marvel.com', email: 'deadpool@marvel.com',
@ -11,57 +60,19 @@ describe('Client Create', () => {
businessTypeFk: 'florist', businessTypeFk: 'florist',
provinceFk: 1 provinceFk: 1
}; };
const newAccountWithoutEmail = JSON.parse(JSON.stringify(newAccount));
delete newAccountWithoutEmail.email;
beforeAll.mockLoopBackContext();
it(`should not find deadpool as he's not created yet`, async() => {
const tx = await models.Client.beginTransaction({});
try { try {
const options = {transaction: tx}; const province = await models.Province.findById(newAccount.provinceFk, {
fields: ['id', 'name', 'autonomyFk'],
const account = await models.VnUser.findOne({where: {name: newAccount.userName}}, options); include: {
const client = await models.Client.findOne({where: {name: newAccount.name}}, options); relation: 'autonomy'
expect(account).toEqual(null);
expect(client).toEqual(null);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
} }
}); }, options);
it('should not create a new account', async() => {
const tx = await models.Client.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Client.createWithUser(newAccountWithoutEmail, options);
await tx.rollback();
} catch (e) {
error = e.message;
await tx.rollback();
}
expect(error).toEqual(`An email is necessary`);
});
it('should create a new account', async() => {
const tx = await models.Client.beginTransaction({});
try {
const options = {transaction: tx};
const client = await models.Client.createWithUser(newAccount, options); const client = await models.Client.createWithUser(newAccount, options);
const account = await models.VnUser.findOne({where: {name: newAccount.userName}}, options); const account = await models.VnUser.findOne({where: {name: newAccount.userName}}, options);
expect(province.autonomy().hasDailyInvoice).toBeTruthy();
expect(account.name).toEqual(newAccount.userName); expect(account.name).toEqual(newAccount.userName);
expect(client.id).toEqual(account.id); expect(client.id).toEqual(account.id);
expect(client.name).toEqual(newAccount.name); expect(client.name).toEqual(newAccount.name);
@ -69,8 +80,38 @@ describe('Client Create', () => {
expect(client.fi).toEqual(newAccount.fi); expect(client.fi).toEqual(newAccount.fi);
expect(client.socialName).toEqual(newAccount.socialName); expect(client.socialName).toEqual(newAccount.socialName);
expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk); expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk);
expect(client.hasDailyInvoice).toBeTruthy();
} catch (e) {
await tx.rollback(); await tx.rollback();
throw e;
}
});
it('should create a new account without dailyInvoice', async() => {
const newAccount = {
userName: 'deadpool',
email: 'deadpool@marvel.com',
fi: '16195279J',
name: 'Wade',
socialName: 'DEADPOOL MARVEL',
street: 'WALL STREET',
city: 'New York',
businessTypeFk: 'florist',
provinceFk: 3
};
try {
const province = await models.Province.findById(newAccount.provinceFk, {
fields: ['id', 'name', 'autonomyFk'],
include: {
relation: 'autonomy'
}
}, options);
const client = await models.Client.createWithUser(newAccount, options);
expect(province.autonomy.hasDailyInvoice).toBeFalsy();
expect(client.hasDailyInvoice).toBeFalsy();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
throw e; throw e;
@ -78,26 +119,25 @@ describe('Client Create', () => {
}); });
it('should not be able to create a user if exists', async() => { it('should not be able to create a user if exists', async() => {
const tx = await models.Client.beginTransaction({});
let error; let error;
const newAccount = {
userName: 'deadpool',
email: 'deadpool@marvel.com',
fi: '16195279J',
name: 'Wade',
socialName: 'DEADPOOL MARVEL',
street: 'WALL STREET',
city: 'New York',
businessTypeFk: 'florist',
provinceFk: 1
};
try { try {
const options = {transaction: tx};
await models.Client.createWithUser(newAccount, options); await models.Client.createWithUser(newAccount, options);
const client = await models.Client.createWithUser(newAccount, options); await models.Client.createWithUser(newAccount, options);
expect(client).toBeNull();
await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback();
error = e; error = e;
} }
const errorName = error.details.codes.name[0]; expect(error.message).toContain('already exists');
expect(errorName).toEqual('uniqueness');
}); });
}); });

View File

@ -74,7 +74,8 @@ module.exports = Self => {
pm.name payMethod, pm.name payMethod,
r.finished IS NULL hasRecovery, r.finished IS NULL hasRecovery,
dp.id departmentFk, dp.id departmentFk,
dp.name departmentName dp.name departmentName,
dp.notificationEmail departmentEmail
FROM defaulter d FROM defaulter d
JOIN client c ON c.id = d.clientFk JOIN client c ON c.id = d.clientFk
JOIN country cn ON cn.id = c.countryFk JOIN country cn ON cn.id = c.countryFk

View File

@ -47,7 +47,7 @@ module.exports = Self => {
await models.Mail.create({ await models.Mail.create({
subject: $t('Comment added to client', {clientFk: defaulter.clientFk}), subject: $t('Comment added to client', {clientFk: defaulter.clientFk}),
body: body, body: body,
receiver: `${defaulter.salesPersonName}@verdnatura.es`, receiver: `${defaulter.departmentEmail}`,
replyTo: `${user.name}@verdnatura.es` replyTo: `${user.name}@verdnatura.es`
}, myOptions); }, myOptions);
} }

View File

@ -57,11 +57,11 @@ module.exports = Self => {
const params = { const params = {
bookEntry: bookEntry.ASIEN, bookEntry: bookEntry.ASIEN,
invoiceOutRef: invoiceOut.ref invoiceOutRef: invoiceOut.ref
} };
await Self.rawSql(`SELECT util.notification_send('book-entry-deleted', ?, NULL)`, await Self.rawSql(`SELECT util.notification_send('book-entry-deleted', ?, NULL)`,
[JSON.stringify(params)], [JSON.stringify(params)],
myOptions); myOptions);
}; }
await models.Xdiario.destroyAll({ await models.Xdiario.destroyAll({
ASIEN: bookEntry.ASIEN ASIEN: bookEntry.ASIEN

View File

@ -48,13 +48,13 @@ module.exports = Self => {
let stmt; let stmt;
stmts.push(new ParameterizedSQL( stmts.push(new ParameterizedSQL(
`CREATE OR REPLACE TEMPORARY TABLE tmp.ticket `CREATE OR REPLACE TEMPORARY TABLE tmp.ticket
(KEY (ticketFk)) (INDEX (ticketFk))
ENGINE = MEMORY ENGINE = MEMORY
SELECT id ticketFk SELECT id ticketFk
FROM ticket t FROM ticket
WHERE shipped BETWEEN ? AND util.dayEnd(?) WHERE shipped BETWEEN ? AND util.dayEnd(?)
AND refFk IS NULL`, [args.from, args.to])); AND refFk IS NULL`, [args.from, args.to]));
stmts.push(`CALL vn.ticket_getTax(NULL)`); stmts.push(`CALL ticket_getTax(NULL)`);
stmts.push(new ParameterizedSQL( stmts.push(new ParameterizedSQL(
`CREATE OR REPLACE TEMPORARY TABLE tmp.filter `CREATE OR REPLACE TEMPORARY TABLE tmp.filter
ENGINE = MEMORY ENGINE = MEMORY
@ -71,12 +71,12 @@ module.exports = Self => {
c.isTaxDataChecked, c.isTaxDataChecked,
w.id comercialId, w.id comercialId,
u.name workerName u.name workerName
FROM vn.ticket t FROM ticket t
JOIN vn.company co ON co.id = t.companyFk JOIN company co ON co.id = t.companyFk
JOIN vn.sale s ON s.ticketFk = t.id JOIN sale s ON s.ticketFk = t.id
JOIN vn.client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN vn.country cou ON cou.id = c.countryFk JOIN country cou ON cou.id = c.countryFk
LEFT JOIN vn.worker w ON w.id = c.salesPersonFk LEFT JOIN worker w ON w.id = c.salesPersonFk
JOIN account.user u ON u.id = w.id JOIN account.user u ON u.id = w.id
LEFT JOIN ( LEFT JOIN (
SELECT ticketFk, taxableBase SELECT ticketFk, taxableBase

View File

@ -79,6 +79,8 @@ module.exports = Self => {
type type
], ],
myOptions); myOptions);
if (!serial)
throw new UserError('No invoice series found for these parameters');
const invoiceOutSerial = await Self.app.models.InvoiceOutSerial.findById(serial); const invoiceOutSerial = await Self.app.models.InvoiceOutSerial.findById(serial);
if (invoiceOutSerial?.taxAreaFk == 'WORLD') { if (invoiceOutSerial?.taxAreaFk == 'WORLD') {

View File

@ -0,0 +1,16 @@
Tickets monitor: Monitor de tickets
Clients on website: Clientes activos en la web
Recent order actions: Acciones recientes en pedidos
Search tickets: Buscar tickets
Delete selected elements: Eliminar los elementos seleccionados
All the selected elements will be deleted. Are you sure you want to continue?: Todos los elementos seleccionados serán eliminados. ¿Seguro que quieres continuar?
Component lack: Faltan componentes
Ticket too little: Ticket demasiado pequeño
Minimize/Maximize: Minimizar/Maximizar
Problems: Problemas
Theoretical: Teórica
Practical: Práctica
Preparation: Preparación
Auto-refresh: Auto-refresco
Toggle auto-refresh every 2 minutes: Conmuta el refresco automático cada 2 minutos
Is fragile: Es frágil

View File

@ -31,6 +31,6 @@ module.exports = Self => {
const usesMana = departments.find(department => department.id == workerDepartment.departmentFk); const usesMana = departments.find(department => department.id == workerDepartment.departmentFk);
return usesMana ? true : false; return !!usesMana;
}; };
}; };

View File

@ -59,6 +59,11 @@ module.exports = Self => {
arg: 'state', arg: 'state',
type: 'string', type: 'string',
description: `Search request by request state` description: `Search request by request state`
},
{
arg: 'myTeam',
type: 'boolean',
description: `Team partners`
} }
], ],
returns: { returns: {
@ -75,6 +80,8 @@ module.exports = Self => {
const conn = Self.dataSource.connector; const conn = Self.dataSource.connector;
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const myOptions = {}; const myOptions = {};
const models = Self.app.models;
const args = ctx.args;
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
@ -82,6 +89,21 @@ module.exports = Self => {
if (ctx.args.mine) if (ctx.args.mine)
ctx.args.attenderFk = userId; ctx.args.attenderFk = userId;
const teamMembersId = [];
if (args.myTeam != null) {
const worker = await models.Worker.findById(userId, {
include: {
relation: 'collegues'
}
}, myOptions);
const collegues = worker.collegues() || [];
for (let collegue of collegues)
teamMembersId.push(collegue.collegueFk);
if (teamMembersId.length == 0)
teamMembersId.push(userId);
}
let where = buildFilter(ctx.args, (param, value) => { let where = buildFilter(ctx.args, (param, value) => {
switch (param) { switch (param) {
case 'search': case 'search':
@ -113,6 +135,11 @@ module.exports = Self => {
return {'w.id': value}; return {'w.id': value};
case 'salesPersonFk': case 'salesPersonFk':
return {'c.salesPersonFk': value}; return {'c.salesPersonFk': value};
case 'myTeam':
if (value)
return {'tr.requesterFk': {inq: teamMembersId}};
else
return {'tr.requesterFk': {nin: teamMembersId}};
} }
}); });

View File

@ -1,11 +1,17 @@
const UserError = require('vn-loopback/util/user-error');
const closure = require('./closure'); const closure = require('./closure');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('closeAll', { Self.remoteMethodCtx('closeAll', {
description: 'Makes the closure process from all warehouses', description: 'Makes the closure process from all warehouses',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [], accepts: [
{
arg: 'options',
type: 'object',
http: {source: 'body'},
description: 'Optional parameters, including transaction.'
}
],
returns: { returns: {
type: 'object', type: 'object',
root: true root: true
@ -16,21 +22,20 @@ module.exports = Self => {
} }
}); });
Self.closeAll = async ctx => { Self.closeAll = async(ctx, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
let tx;
// IMPORTANT: Due to its high cost in production, wrapping this process in a transaction may cause timeouts.
const toDate = Date.vnNew(); const toDate = Date.vnNew();
toDate.setHours(0, 0, 0, 0); toDate.setHours(0, 0, 0, 0);
toDate.setDate(toDate.getDate() - 1); toDate.setDate(toDate.getDate() - 1);
const todayMinDate = Date.vnNew();
todayMinDate.setHours(0, 0, 0, 0);
const todayMaxDate = Date.vnNew();
todayMaxDate.setHours(23, 59, 59, 59);
// Prevent closure for current day
if (toDate >= todayMinDate && toDate <= todayMaxDate)
throw new UserError('You cannot close tickets for today');
const tickets = await Self.rawSql(` const tickets = await Self.rawSql(`
SELECT t.id, SELECT t.id,
t.clientFk, t.clientFk,
@ -41,7 +46,7 @@ module.exports = Self => {
c.salesPersonFk, c.salesPersonFk,
c.isToBeMailed, c.isToBeMailed,
c.hasToInvoice, c.hasToInvoice,
co.hasDailyInvoice, c.hasDailyInvoice,
eu.email salesPersonEmail, eu.email salesPersonEmail,
t.addressFk t.addressFk
FROM ticket t FROM ticket t
@ -53,16 +58,17 @@ module.exports = Self => {
JOIN province p ON p.id = c.provinceFk JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
JOIN ticketConfig tc ON TRUE
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'))
AND DATE(t.shipped) BETWEEN ? - INTERVAL 7 DAY AND util.dayEnd(?) AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL
GROUP BY t.id GROUP BY t.id
`, [toDate, toDate]); `, [toDate, toDate], myOptions);
const ticketIds = tickets.map(ticket => ticket.id); const ticketIds = tickets.map(ticket => ticket.id);
await Self.rawSql(` await Self.rawSql(`
INSERT INTO util.debug (variable, value) INSERT INTO util.debug (variable, value)
VALUES ('nightInvoicing', ?) VALUES ('nightInvoicing', ?)
`, [ticketIds.join(',')]); `, [ticketIds.join(',')], myOptions);
await Self.rawSql(` await Self.rawSql(`
WITH ticketNotInvoiceable AS( WITH ticketNotInvoiceable AS(
@ -108,6 +114,7 @@ module.exports = Self => {
JOIN alertLevel al ON al.id = ts.alertLevel JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk JOIN province p ON p.id = c.provinceFk
JOIN ticketConfig tc ON TRUE
LEFT JOIN autonomy a ON a.id = p.autonomyFk LEFT JOIN autonomy a ON a.id = p.autonomyFk
JOIN country co ON co.id = p.countryFk JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
@ -116,9 +123,9 @@ module.exports = Self => {
LEFT JOIN vn.invoiceOutSerial ios ON ios.taxAreaFk = 'WORLD' LEFT JOIN vn.invoiceOutSerial ios ON ios.taxAreaFk = 'WORLD'
AND ios.code = invoiceSerial(t.clientFk, t.companyFk, 'multiple') AND ios.code = invoiceSerial(t.clientFk, t.companyFk, 'multiple')
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')) WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered'))
AND DATE(t.shipped) BETWEEN ? - INTERVAL 2 DAY AND util.dayEnd(?) AND t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?)
AND t.refFk IS NULL AND t.refFk IS NULL
AND IFNULL(a.hasDailyInvoice, co.hasDailyInvoice) AND c.hasDailyInvoice
GROUP BY ticketFk GROUP BY ticketFk
HAVING hasErrorToInvoice HAVING hasErrorToInvoice
OR hasErrorTaxDataChecked OR hasErrorTaxDataChecked
@ -131,9 +138,9 @@ module.exports = Self => {
) SELECT IF(errors = '{"tickets": null}', ) SELECT IF(errors = '{"tickets": null}',
'No errors', 'No errors',
util.notification_send('invoice-ticket-closure', errors, NULL)) util.notification_send('invoice-ticket-closure', errors, NULL))
FROM ticketNotInvoiceable`, [toDate, toDate]); FROM ticketNotInvoiceable`, [toDate, toDate], myOptions);
await closure(ctx, Self, tickets); await closure(ctx, Self, tickets, myOptions);
await Self.rawSql(` await Self.rawSql(`
UPDATE ticket t UPDATE ticket t
@ -141,13 +148,17 @@ module.exports = Self => {
JOIN alertLevel al ON al.id = ts.alertLevel JOIN alertLevel al ON al.id = ts.alertLevel
JOIN agencyMode am ON am.id = t.agencyModeFk JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
JOIN ticketConfig tc ON TRUE
LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
SET t.routeFk = NULL SET t.routeFk = NULL
WHERE DATE(t.shipped) BETWEEN ? - INTERVAL 2 DAY AND util.dayEnd(?) WHERE t.shipped BETWEEN ? - INTERVAL tc.closureDaysAgo DAY AND util.dayEnd(?)
AND al.code NOT IN ('DELIVERED', 'PACKED') AND al.code NOT IN ('DELIVERED', 'PACKED')
AND NOT t.packages AND NOT t.packages
AND tob.id IS NULL AND tob.id IS NULL
AND t.routeFk`, [toDate, toDate], {userId: ctx.req.accessToken.userId}); AND t.routeFk`, [toDate, toDate], myOptions);
if (tx)
await tx.commit();
return { return {
message: 'Success' message: 'Success'

View File

@ -50,7 +50,7 @@ module.exports = Self => {
c.salesPersonFk, c.salesPersonFk,
c.isToBeMailed, c.isToBeMailed,
c.hasToInvoice, c.hasToInvoice,
co.hasDailyInvoice, c.hasDailyInvoice,
eu.email salesPersonEmail, eu.email salesPersonEmail,
t.addressFk t.addressFk
FROM expedition e FROM expedition e
@ -58,8 +58,6 @@ module.exports = Self => {
JOIN ticketState ts ON ts.ticketFk = t.id JOIN ticketState ts ON ts.ticketFk = t.id
JOIN alertLevel al ON al.id = ts.alertLevel JOIN alertLevel al ON al.id = ts.alertLevel
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
JOIN province p ON p.id = c.provinceFk
JOIN country co ON co.id = p.countryFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE al.code = 'PACKED' WHERE al.code = 'PACKED'
AND t.id = ? AND t.id = ?

View File

@ -12,19 +12,21 @@ module.exports = async function(ctx, Self, tickets, options) {
Object.assign(myOptions, options); Object.assign(myOptions, options);
let tx; let tx;
// if (!myOptions.transaction) { // IMPORTANT: Due to its high cost in production, wrapping this process in a transaction may cause timeouts.
// tx = await Self.beginTransaction({});
// myOptions.transaction = tx;
// }
if (tickets.length == 0) return; if (tickets.length == 0) return;
const failedtickets = []; const failedtickets = [];
for (const ticket of tickets) { for (const ticket of tickets) {
try { try {
await Self.rawSql(`CALL util.debugAdd('invoicingTicket', ?)`, [ticket.id], {userId}); await Self.rawSql(`CALL util.debugAdd('invoicingTicket', ?)`, [ticket.id], myOptions);
await Self.app.models.InvoiceOut.getSerial(ticket.clientFk, ticket.companyFk, ticket.addressFk, 'quick'); await Self.app.models.InvoiceOut.getSerial(
ticket.clientFk,
ticket.companyFk,
ticket.addressFk,
'quick',
myOptions);
await Self.rawSql( await Self.rawSql(
`CALL vn.ticket_closeByTicket(?)`, `CALL vn.ticket_closeByTicket(?)`,
[ticket.id], [ticket.id],

View File

@ -228,7 +228,7 @@ module.exports = Self => {
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`
CREATE OR REPLACE TEMPORARY TABLE tmp.filter CREATE OR REPLACE TEMPORARY TABLE tmp.filter
(INDEX (id)) (INDEX (id))
ENGINE = MEMORY ENGINE = InnoDB
SELECT t.id, SELECT t.id,
t.shipped, t.shipped,
CAST(DATE(t.shipped) AS CHAR) shippedDate, CAST(DATE(t.shipped) AS CHAR) shippedDate,
@ -260,7 +260,12 @@ module.exports = Self => {
z.name zoneName, z.name zoneName,
z.id zoneFk, z.id zoneFk,
CAST(z.hour AS CHAR) hour, CAST(z.hour AS CHAR) hour,
a.nickname addressNickname a.nickname addressNickname,
(SELECT GROUP_CONCAT(DISTINCT i2.itemPackingTypeFk ORDER BY i2.itemPackingTypeFk SEPARATOR ',')
FROM sale s2
JOIN item i2 ON i2.id = s2.itemFk
WHERE s2.ticketFk = t.id
) AS packing
FROM ticket t FROM ticket t
LEFT JOIN invoiceOut io ON t.refFk = io.ref LEFT JOIN invoiceOut io ON t.refFk = io.ref
LEFT JOIN zone z ON z.id = t.zoneFk LEFT JOIN zone z ON z.id = t.zoneFk
@ -292,6 +297,7 @@ module.exports = Self => {
} }
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeWhere(filter.where));
stmts.push(stmt); stmts.push(stmt);
stmt = new ParameterizedSQL(` stmt = new ParameterizedSQL(`

View File

@ -49,9 +49,12 @@ module.exports = Self => {
where: {originalTicketFk: id} where: {originalTicketFk: id}
}, myOptions); }, myOptions);
const hasRefund = !!ticketRefunds?.length;
const allDeleted = ticketRefunds.every(refund => refund.refundTicket().isDeleted); const allDeleted = ticketRefunds.every(refund => refund.refundTicket().isDeleted);
if (ticketRefunds?.length && !allDeleted) { if (!hasRefund) await models.TicketRefund.destroyAll({refundTicketFk: id}, myOptions);
if (hasRefund && !allDeleted) {
const notDeleted = []; const notDeleted = [];
for (const refund of ticketRefunds) for (const refund of ticketRefunds)
if (!refund.refundTicket().isDeleted) notDeleted.push(refund.refundTicket().id); if (!refund.refundTicket().isDeleted) notDeleted.push(refund.refundTicket().id);

View File

@ -0,0 +1,54 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Ticket Closure - closeAll function', () => {
let ctx = {
req: {
getLocale: () => 'es',
accessToken: {userId: 1106},
headers: {origin: 'http://localhost'},
__: value => value,
},
args: {}
};
let options;
let tx;
let originalVnNew;
beforeEach(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
tx = await models.Ticket.beginTransaction({});
options = {transaction: tx};
originalVnNew = Date.vnNew;
spyOn(Date, 'vnNew').and.callFake(() => {
const mockDate = originalVnNew();
mockDate.setDate(mockDate.getDate() + 1);
return mockDate;
});
});
afterEach(async() => {
if (tx)
await tx.rollback();
});
it('should set routeFk to NULL when conditions are met', async() => {
const ticketsBefore = await models.Ticket.find({
where: {
routeFk: {neq: null}
}
}, options);
await models.Ticket.closeAll(ctx, options);
const ticketsAfter = await models.Ticket.find({
where: {
id: {inq: ticketsBefore.map(ticket => ticket.id)},
routeFk: {neq: null}
}
}, options);
expect(ticketsBefore.length).toBeGreaterThan(ticketsAfter.length);
});
});

View File

@ -50,9 +50,9 @@ describe('Ticket closure functionality', () => {
expect(ticketStateBefore.code).not.toBe(ticketStateAfter.code); expect(ticketStateBefore.code).not.toBe(ticketStateAfter.code);
const ticketAfter = await models.TicketState.findById(ticketId, null, options); const ticketAfter = await models.Ticket.findById(ticketId, null, options);
expect(ticketAfter.refFk).toBeUndefined(); expect(ticketAfter.refFk).toBeNull();
}); });
it('should send Incoterms authorization email on first order', async() => { it('should send Incoterms authorization email on first order', async() => {

View File

@ -113,5 +113,27 @@ describe('ticket setDeleted()', () => {
expect(error.message).not.toContain('Tickets with associated refunds'); expect(error.message).not.toContain('Tickets with associated refunds');
}); });
it('should delete the refund - original ticket relation', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const ticketId = 24;
const refundTicket = await models.TicketRefund.findOne({where: {refundTicketFk: ticketId}}, options);
expect(refundTicket).toBeTruthy();
await models.Ticket.setDeleted(ctx, ticketId, options);
const removedRefundTicket = await models.TicketRefund.findOne({
where: {refundTicketFk: ticketId}},
options);
expect(removedRefundTicket).toBeNull();
await tx.rollback();
} catch (e) {
await tx.rollback();
}
});
}); });
}); });

View File

@ -24,12 +24,27 @@
"warehouseFk": { "warehouseFk": {
"type": "number" "type": "number"
}, },
"sectorFk": {
"type": "number"
},
"labelerFk": { "labelerFk": {
"type": "number" "type": "number"
}, },
"isOnReservationMode": { "isOnReservationMode": {
"type": "boolean", "type": "boolean",
"required": true "required": true
},
"machineFk": {
"type": "number"
},
"linesLimit": {
"type": "number"
},
"volumeLimit": {
"type": "number"
},
"sizeLimit": {
"type": "number"
} }
}, },
"relations": { "relations": {
@ -53,6 +68,11 @@
"model": "ItemPackingType", "model": "ItemPackingType",
"foreignKey": "itemPackingTypeFk", "foreignKey": "itemPackingTypeFk",
"primaryKey": "code" "primaryKey": "code"
},
"machine": {
"type": "belongsTo",
"model": "Machine",
"foreignKey": "machineFk"
} }
} }
} }

View File

@ -1,3 +1,4 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('deleteZone', { Self.remoteMethodCtx('deleteZone', {
description: 'Delete a zone', description: 'Delete a zone',
@ -49,26 +50,12 @@ module.exports = Self => {
} }
} }
}; };
const promises = [];
const ticketList = await models.Ticket.find(filter, myOptions); const ticketList = await models.Ticket.find(filter, myOptions);
const fixingState = await models.State.findOne({where: {code: 'FIXING'}}, myOptions);
const worker = await models.Worker.findOne({
where: {id: userId}
}, myOptions);
await models.Ticket.rawSql('UPDATE ticket SET zoneFk = NULL WHERE zoneFk = ?', [id], myOptions); if (ticketList.length > 0)
throw new UserError('There are tickets for this area, delete them first');
for (ticket of ticketList) {
if (ticket.ticketState().alertLevel == 0) {
promises.push(models.Ticket.state(ctx, {
ticketFk: ticket.id,
stateFk: fixingState.id,
userFk: worker.id
}, myOptions));
}
}
await Promise.all(promises);
await models.Zone.destroyById(id, myOptions); await models.Zone.destroyById(id, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -8,9 +8,9 @@ describe('zone deletezone()', () => {
__: value => value __: value => value
}; };
const ctx = {req: activeCtx}; const ctx = {req: activeCtx};
const zoneId = 9; const zoneId = 4;
const zoneId2 = 3;
let ticketIDs; let ticketIDs;
let originalTicketStates;
beforeAll(async() => { beforeAll(async() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
@ -35,18 +35,8 @@ describe('zone deletezone()', () => {
await models.Zone.deleteZone(ctx, zoneId, options); await models.Zone.deleteZone(ctx, zoneId, options);
const updatedZone = await models.Zone.findById(zoneId, null, options); const updatedZone = await models.Zone.findById(zoneId, null, options);
const anUpdatedTicket = await models.Ticket.findById(ticketIDs[0], null, options);
const updatedTicketStates = await models.TicketState.find({
where: {
ticketFk: {inq: ticketIDs},
code: 'FIXING'
}
}, options);
expect(updatedZone).toBeNull(); expect(updatedZone).toBeNull();
expect(anUpdatedTicket.zoneFk).toBeNull();
expect(updatedTicketStates.length).toBeGreaterThan(originalTicketStates.length);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -54,4 +44,20 @@ describe('zone deletezone()', () => {
throw e; throw e;
} }
}); });
it('should not delete the zone if it has tickets', async() => {
const tx = await models.Zone.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Zone.deleteZone(ctx, zoneId2, options);
await tx.rollback();
} catch (e) {
error = e.message;
await tx.rollback();
}
expect(error).toEqual('There are tickets for this area, delete them first');
});
}); });

View File

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

View File

@ -58,6 +58,7 @@
<th width="5%">{{$t('reference')}}</th> <th width="5%">{{$t('reference')}}</th>
<th class="number">{{$t('quantity')}}</th> <th class="number">{{$t('quantity')}}</th>
<th width="50%">{{$t('concept')}}</th> <th width="50%">{{$t('concept')}}</th>
<th width="50%">{{$t('producer')}}</th>
<th class="number" v-if="showPrices">{{$t('price')}}</th> <th class="number" v-if="showPrices">{{$t('price')}}</th>
<th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th> <th class="centered" width="5%" v-if="showPrices">{{$t('discount')}}</th>
<th class="centered" v-if="showPrices">{{$t('vat')}}</th> <th class="centered" v-if="showPrices">{{$t('vat')}}</th>
@ -69,6 +70,7 @@
<td width="5%">{{sale.itemFk}}</td> <td width="5%">{{sale.itemFk}}</td>
<td class="number">{{sale.quantity}}</td> <td class="number">{{sale.quantity}}</td>
<td width="50%">{{sale.concept}}</td> <td width="50%">{{sale.concept}}</td>
<td width="5%" class="font light-gray" v-if="sale.subName">{{sale.subName}}</td>
<td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td> <td class="number" v-if="showPrices">{{sale.price | currency('EUR', $i18n.locale)}}</td>
<td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td> <td class="centered" width="5%" v-if="showPrices">{{(sale.discount / 100) | percentage}}</td>
<td class="centered" v-if="showPrices">{{sale.vatType}}</td> <td class="centered" v-if="showPrices">{{sale.vatType}}</td>
@ -81,7 +83,6 @@
<span v-if="sale.value5"> <strong>{{sale.tag5}}</strong> {{sale.value5}} </span> <span v-if="sale.value5"> <strong>{{sale.tag5}}</strong> {{sale.value5}} </span>
<span v-if="sale.value6"> <strong>{{sale.tag6}}</strong> {{sale.value6}} </span> <span v-if="sale.value6"> <strong>{{sale.tag6}}</strong> {{sale.value6}} </span>
<span v-if="sale.value7"> <strong>{{sale.tag7}}</strong> {{sale.value7}} </span> <span v-if="sale.value7"> <strong>{{sale.tag7}}</strong> {{sale.value7}} </span>
<span v-if="sale.subName"> <strong>{{$t('producer')}}</strong> {{ sale.subName }}</span>
</td> </td>
</tr> </tr>
</tbody> </tbody>