3430-ticket_step-two ticket without negatives #823

Merged
joan merged 31 commits from 3430-ticket_step-two into dev 2022-02-01 08:34:41 +00:00
203 changed files with 13284 additions and 47425 deletions
Showing only changes of commit 442d58f59b - Show all commits

View File

@ -120,7 +120,7 @@ module.exports = Self => {
} }
const defaultOptions = { const defaultOptions = {
body: params form: params
}; };
if (options) Object.assign(defaultOptions, options); if (options) Object.assign(defaultOptions, options);

View File

@ -1,4 +0,0 @@
DELETE FROM `salix`.`ACL` WHERE id = 48;
DELETE FROM `salix`.`ACL` WHERE id = 49;
DELETE FROM `salix`.`ACL` WHERE id = 50;
DELETE FROM `salix`.`ACL` WHERE id = 107;

View File

@ -1,197 +0,0 @@
drop procedure `vn`.`sale_getProblems`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`sale_getProblems`(IN vIsTodayRelative tinyint(1))
BEGIN
/**
* Calcula los problemas de cada venta
* para un conjunto de tickets.
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.sale_problems
*/
DECLARE vWarehouse INT;
DECLARE vDate DATE;
DECLARE vAvailableCache INT;
DECLARE vDone INT DEFAULT 0;
DECLARE vComponentCount INT;
DECLARE vCursor CURSOR FOR
SELECT DISTINCT tt.warehouseFk, IF(vIsTodayRelative, CURDATE(), date(tt.shipped))
FROM tmp.sale_getProblems tt
WHERE DATE(tt.shipped) BETWEEN CURDATE()
AND TIMESTAMPADD(DAY, IF(vIsTodayRelative, 9.9, 1.9), CURDATE());
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
DROP TEMPORARY TABLE IF EXISTS tmp.sale_problems;
CREATE TEMPORARY TABLE tmp.sale_problems (
ticketFk INT(11),
saleFk INT(11),
isFreezed INTEGER(1) DEFAULT 0,
risk DECIMAL(10,2) DEFAULT 0,
hasHighRisk TINYINT(1) DEFAULT 0,
hasTicketRequest INTEGER(1) DEFAULT 0,
isAvailable INTEGER(1) DEFAULT 1,
itemShortage VARCHAR(250),
isTaxDataChecked INTEGER(1) DEFAULT 1,
itemDelay VARCHAR(250),
hasComponentLack INTEGER(1),
PRIMARY KEY (ticketFk, saleFk)
) ENGINE = MEMORY;
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_list;
CREATE TEMPORARY TABLE tmp.ticket_list
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT tp.ticketFk, c.id clientFk
FROM tmp.sale_getProblems tp
JOIN vn.client c ON c.id = tp.clientFk;
SELECT COUNT(*) INTO vComponentCount
FROM vn.component c
WHERE c.isRequired;
INSERT INTO tmp.sale_problems(ticketFk, hasComponentLack, saleFk)
SELECT tl.ticketFk, (COUNT(DISTINCT s.id) * vComponentCount > COUNT(c.id)), s.id
FROM tmp.ticket_list tl
JOIN vn.sale s ON s.ticketFk = tl.ticketFk
LEFT JOIN vn.saleComponent sc ON sc.saleFk = s.id
LEFT JOIN vn.component c ON c.id = sc.componentFk AND c.isRequired
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.agencyMode am ON am.id = t.agencyModeFk
JOIN vn.deliveryMethod dm ON dm.id = am.deliveryMethodFk
WHERE dm.code IN('AGENCY','DELIVERY','PICKUP')
GROUP BY tl.ticketFk, s.id;
INSERT INTO tmp.sale_problems(ticketFk, isFreezed)
SELECT DISTINCT tl.ticketFk, TRUE
FROM tmp.ticket_list tl
JOIN vn.client c ON c.id = tl.clientFk
WHERE c.isFreezed
ON DUPLICATE KEY UPDATE
isFreezed = c.isFreezed;
DROP TEMPORARY TABLE IF EXISTS tmp.clientGetDebt;
CREATE TEMPORARY TABLE tmp.clientGetDebt
(PRIMARY KEY (clientFk))
ENGINE = MEMORY
SELECT DISTINCT clientFk
FROM tmp.ticket_list;
CALL clientGetDebt(CURDATE());
INSERT INTO tmp.sale_problems(ticketFk, risk, hasHighRisk)
SELECT DISTINCT tl.ticketFk, r.risk, ((r.risk - cc.riskTolerance) > c.credit + 10)
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN tmp.risk r ON r.clientFk = t.clientFk
JOIN vn.client c ON c.id = t.clientFk
JOIN vn.clientConfig cc
WHERE r.risk > c.credit + 10
AND a.isRiskFree = FALSE
ON DUPLICATE KEY UPDATE
risk = r.risk, hasHighRisk = ((r.risk - cc.riskTolerance) > c.credit + 10);
INSERT INTO tmp.sale_problems(ticketFk, hasTicketRequest)
SELECT DISTINCT tl.ticketFk, TRUE
FROM tmp.ticket_list tl
JOIN vn.ticketRequest tr ON tr.ticketFk = tl.ticketFk
WHERE tr.isOK IS NULL
ON DUPLICATE KEY UPDATE
hasTicketRequest = TRUE;
OPEN vCursor;
WHILE NOT vDone
DO
FETCH vCursor INTO vWarehouse, vDate;
CALL cache.available_refresh(vAvailableCache, FALSE, vWarehouse, vDate);
INSERT INTO tmp.sale_problems(ticketFk, isAvailable, saleFk)
SELECT tl.ticketFk, FALSE, s.id
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN cache.available av ON av.item_id = i.id
AND av.calc_id = vAvailableCache
WHERE date(t.shipped) = vDate
AND it.categoryFk != 6
AND IFNULL(av.available, 0) < 0
AND s.isPicked = FALSE
AND NOT i.generic
AND vWarehouse = t.warehouseFk
GROUP BY tl.ticketFk
ON DUPLICATE KEY UPDATE
isAvailable = FALSE, saleFk = VALUES(saleFk);
INSERT INTO tmp.sale_problems(ticketFk, itemShortage, saleFk)
SELECT ticketFk, problem, saleFk
FROM (
SELECT tl.ticketFk, CONCAT('F: ',GROUP_CONCAT(i.id, ' ', i.longName, ' ')) problem, s.id AS saleFk
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN vn.itemShelvingStock_byWarehouse issw ON issw.itemFk = i.id AND issw.warehouseFk = t.warehouseFk
LEFT JOIN cache.available av ON av.item_id = i.id AND av.calc_id = vAvailableCache
WHERE IFNULL(av.available, 0) < 0
AND s.quantity > IFNULL(issw.visible, 0)
AND s.quantity > 0
AND s.isPicked = FALSE
AND s.reserved = FALSE
AND it.categoryFk != 6
AND IF(vIsTodayRelative, TRUE, date(t.shipped) = vDate)
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
ON DUPLICATE KEY UPDATE
itemShortage = sub.problem, saleFk = sub.saleFk;
INSERT INTO tmp.sale_problems(ticketFk, itemDelay, saleFk)
SELECT ticketFk, problem, saleFk
FROM (
SELECT tl.ticketFk, GROUP_CONCAT('I: ',i.id, ' ', i.longName, ' ') problem, s.id AS saleFk
FROM tmp.ticket_list tl
JOIN vn.ticket t ON t.id = tl.ticketFk
JOIN vn.sale s ON s.ticketFk = t.id
JOIN vn.item i ON i.id = s.itemFk
JOIN vn.itemType it on it.id = i.typeFk
LEFT JOIN vn.itemShelvingStock_byWarehouse issw ON issw.itemFk = i.id AND issw.warehouseFk = t.warehouseFk
WHERE s.quantity > IFNULL(issw.visible, 0)
AND s.quantity > 0
AND s.isPicked = FALSE
AND s.reserved = FALSE
AND it.categoryFk != 6
AND IF(vIsTodayRelative, TRUE, date(t.shipped) = vDate)
AND NOT i.generic
AND CURDATE() = vDate
AND t.warehouseFk = vWarehouse
GROUP BY tl.ticketFk LIMIT 1) sub
ON DUPLICATE KEY UPDATE
itemDelay = sub.problem, saleFk = sub.saleFk;
END WHILE;
CLOSE vCursor;
INSERT INTO tmp.sale_problems(ticketFk, isTaxDataChecked)
SELECT DISTINCT tl.ticketFk, FALSE
FROM tmp.ticket_list tl
JOIN vn.client c ON c.id = tl.clientFk
WHERE c.isTaxDataChecked = FALSE
ON DUPLICATE KEY UPDATE
isTaxDataChecked = FALSE;
DROP TEMPORARY TABLE
tmp.clientGetDebt,
tmp.ticket_list;
END;;$$
DELIMITER ;

View File

@ -1,48 +0,0 @@
drop procedure `vn`.`ticket_getProblems`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_getProblems`(IN vIsTodayRelative tinyint(1))
BEGIN
/**
* Calcula los problemas para un conjunto de tickets.
* Agrupados por ticket
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.ticket_problems
*/
CALL sale_getProblems(vIsTodayRelative);
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_problems;
CREATE TEMPORARY TABLE tmp.ticket_problems
(INDEX (ticketFk))
ENGINE = MEMORY
SELECT
ticketFk,
MAX(p.isFreezed) AS isFreezed,
MAX(p.risk) AS risk,
MAX(p.hasHighRisk) AS hasHighRisk,
MAX(p.hasTicketRequest) AS hasTicketRequest,
MIN(p.isAvailable) AS isAvailable,
MAX(p.itemShortage) AS itemShortage,
MIN(p.isTaxDataChecked) AS isTaxDataChecked,
MAX(p.hasComponentLack) AS hasComponentLack,
0 AS totalProblems
FROM tmp.sale_problems p
GROUP BY ticketFk;
UPDATE tmp.ticket_problems tp
SET tp.totalProblems = (
(tp.isFreezed) +
IF(tp.risk, TRUE, FALSE) +
(tp.hasTicketRequest) +
(tp.isAvailable = 0) +
(tp.isTaxDataChecked = 0) +
(tp.hasComponentLack)
);
DROP TEMPORARY TABLE
tmp.sale_problems;
END;;$$
DELIMITER ;

View File

@ -1 +0,0 @@
alter table `vn`.`travelThermograph` modify `temperature` enum('COOL', 'WARM') null;

View File

@ -1 +0,0 @@
UPDATE salix.ACL t SET t.principalId = 'employee' WHERE t.id = 269;

View File

@ -1,3 +0,0 @@
ALTER TABLE vn.accountingType ADD maxAmount INT DEFAULT NULL NULL;
UPDATE vn.accountingType SET maxAmount = 1000 WHERE code = 'cash';

View File

@ -1,4 +0,0 @@
ALTER TABLE vn.payMethod CHANGE ibanRequired ibanRequiredForClients tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod ADD ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL AFTER ibanRequiredForClients;
UPDATE vn.payMethod SET ibanRequiredForSuppliers = 1 WHERE code = 'wireTransfer';

View File

@ -1,2 +0,0 @@
ALTER TABLE vn.silexACL MODIFY module VARCHAR(50) NOT NULL;
ALTER TABLE vn.silexACL MODIFY method VARCHAR(50) NOT NULL;

View File

@ -1,48 +0,0 @@
drop procedure `vn`.`ticket_getProblems`;
DELIMITER $$
$$
create
definer = root@`%` procedure `vn`.`ticket_getProblems`(IN vIsTodayRelative tinyint(1))
BEGIN
/**
* Calcula los problemas para un conjunto de tickets.
* Agrupados por ticket
*
* @table tmp.sale_getProblems(ticketFk, clientFk, warehouseFk, shipped) Identificadores de los tickets a calcular
* @return tmp.ticket_problems
*/
CALL sale_getProblems(vIsTodayRelative);
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_problems;
CREATE TEMPORARY TABLE tmp.ticket_problems
(PRIMARY KEY (ticketFk))
ENGINE = MEMORY
SELECT
ticketFk,
MAX(p.isFreezed) AS isFreezed,
MAX(p.risk) AS risk,
MAX(p.hasHighRisk) AS hasHighRisk,
MAX(p.hasTicketRequest) AS hasTicketRequest,
MIN(p.isAvailable) AS isAvailable,
MAX(p.itemShortage) AS itemShortage,
MIN(p.isTaxDataChecked) AS isTaxDataChecked,
MAX(p.hasComponentLack) AS hasComponentLack,
0 AS totalProblems
FROM tmp.sale_problems p
GROUP BY ticketFk;
UPDATE tmp.ticket_problems tp
SET tp.totalProblems = (
(tp.isFreezed) +
IF(tp.risk, TRUE, FALSE) +
(tp.hasTicketRequest) +
(tp.isAvailable = 0) +
(tp.isTaxDataChecked = 0) +
(tp.hasComponentLack)
);
DROP TEMPORARY TABLE
tmp.sale_problems;
END;;$$
DELIMITER ;

View File

@ -1,14 +0,0 @@
CREATE TABLE `salix`.`defaultViewConfig`
(
tableCode VARCHAR(25) not null,
columns JSON not null
)
comment 'The default configuration of columns for views';
INSERT INTO `salix`.`defaultViewConfig` (tableCode, columns)
VALUES
('itemsIndex', '{"intrastat":false,"stemMultiplier":false,"landed":false}'),
('latestBuys', '{"intrastat":false,"description":false,"density":false,"isActive":false,"freightValue":false,"packageValue":false,"isIgnored":false,"price2":false,"minPrice":true,"ektFk":false,"weight":false,"id":true,"packing":true,"grouping":true,"quantity":true,"size":false,"name":true,"code":true,"origin":true,"family":true,"entryFk":true,"buyingValue":true,"comissionValue":false,"price3":true,"packageFk":true,"packingOut":true}'),
('ticketsMonitor', '{"id":false}');

View File

@ -0,0 +1,2 @@
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Sale','payBack','WRITE','ALLOW','ROLE','employee');

View File

@ -0,0 +1,90 @@
DROP PROCEDURE IF EXISTS `vn`.`ticket_doRefund`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticket_doRefund`(IN vOriginTicket INT, OUT vNewTicket INT)
BEGIN
DECLARE vDone BIT DEFAULT 0;
DECLARE vCustomer MEDIUMINT;
DECLARE vWarehouse TINYINT;
DECLARE vCompany MEDIUMINT;
DECLARE vAddress MEDIUMINT;
DECLARE vRefundAgencyMode INT;
DECLARE vItemFk INT;
DECLARE vQuantity DECIMAL (10,2);
DECLARE vConcept VARCHAR(50);
DECLARE vPrice DECIMAL (10,2);
DECLARE vDiscount TINYINT;
DECLARE vSaleNew INT;
DECLARE vSaleMain INT;
DECLARE vZoneFk INT;
DECLARE vRsMainTicket CURSOR FOR
SELECT *
FROM tmp.sale;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = 1;
SELECT id INTO vRefundAgencyMode
FROM agencyMode WHERE `name` = 'ABONO';
SELECT clientFk, warehouseFk, companyFk, addressFk
INTO vCustomer, vWarehouse, vCompany, vAddress
FROM ticket
WHERE id = vOriginTicket;
SELECT id INTO vZoneFk
FROM zone WHERE agencyModeFk = vRefundAgencyMode
LIMIT 1;
INSERT INTO vn.ticket (
clientFk,
shipped,
addressFk,
agencyModeFk,
nickname,
warehouseFk,
companyFk,
landed,
zoneFk
)
SELECT
vCustomer,
CURDATE(),
vAddress,
vRefundAgencyMode,
a.nickname,
vWarehouse,
vCompany,
CURDATE(),
vZoneFk
FROM address a
WHERE a.id = vAddress;
SET vNewTicket = LAST_INSERT_ID();
SET vDone := 0;
OPEN vRsMainTicket ;
FETCH vRsMainTicket INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
WHILE NOT vDone DO
INSERT INTO vn.sale(ticketFk, itemFk, quantity, concept, price, discount)
VALUES( vNewTicket, vItemFk, vQuantity, vConcept, vPrice, vDiscount );
SET vSaleNew = LAST_INSERT_ID();
INSERT INTO vn.saleComponent(saleFk,componentFk,`value`)
SELECT vSaleNew,componentFk,`value`
FROM vn.saleComponent
WHERE saleFk = vSaleMain;
FETCH vRsMainTicket INTO vSaleMain, vItemFk, vQuantity, vConcept, vPrice, vDiscount;
END WHILE;
CLOSE vRsMainTicket;
END;
$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES('InvoiceInDueDay', '*', '*', 'ALLOW', 'ROLE', 'administrative');

View File

@ -0,0 +1,248 @@
DROP PROCEDURE IF EXISTS vn.invoiceInBookingMain;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`invoiceInBookingMain`(vInvoiceInId INT)
BEGIN
DECLARE vTotalAmount,vTotalAmountDivisa DECIMAL(10,2);
DECLARE vBookNumber,vSerialNumber INT;
DECLARE vRate DECIMAL(10,4);
CALL invoiceInBookingCommon(vInvoiceInId,vSerialNumber);
SELECT SUM(iit.taxableBase * IF( i.serial= 'R' AND ti.Iva <> 'HP DEVENGADO 21 ISP', 1 +(ti.PorcentajeIva/100),1)),
SUM(iit.foreignValue * IF( i.serial= 'R', 1 + (ti.PorcentajeIva/100),1)),
iit.taxableBase/iit.foreignValue
INTO vTotalAmount, vTotalAmountDivisa, vRate
FROM newInvoiceIn i
JOIN invoiceInTax iit ON iit.invoiceInFk = i.id
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk;
CALL vn.ledger_next(vBookNumber);
-- Apunte del proveedor
INSERT INTO XDiario(ASIEN,
FECHA,
SUBCTA,
EUROHABER,
CONCEPTO,
CAMBIO,
HABERME,
NFACTICK,
CLAVE,
empresa_id
)
SELECT
vBookNumber,
n.bookEntried,
s.supplierAccount,
vTotalAmount EUROHABER,
n.conceptWithSupplier,
vRate,
vTotalAmountDivisa,
n.invoicesCount,
vInvoiceInId,
n.companyFk
FROM newInvoiceIn n
JOIN newSupplier s;
-- Línea de Gastos
INSERT INTO XDiario ( ASIEN,
FECHA,
SUBCTA,
CONTRA,
EURODEBE,
EUROHABER,
CONCEPTO,
CAMBIO,
DEBEME,
HABERME,
NFACTICK,
empresa_id
)
SELECT vBookNumber ASIEN,
n.bookEntried FECHA,
IF(e.isWithheld , LPAD(RIGHT(s.supplierAccount,5),10,iit.expenceFk),iit.expenceFk) SUBCTA,
s.supplierAccount CONTRA,
IF(e.isWithheld AND iit.taxableBase < 0, NULL, ROUND(SUM(iit.taxableBase),2)) EURODEBE,
IF(e.isWithheld AND iit.taxableBase < 0,ROUND(SUM(-iit.taxableBase),2),NULL) EUROHABER,
n.conceptWithSupplier CONCEPTO,
vRate,
IF(e.isWithheld,NULL,ABS(ROUND(SUM(iit.foreignValue),2))) DEBEME,
IF(e.isWithheld,ABS(ROUND(SUM(iit.foreignValue),2)),NULL) HABERME,
n.invoicesCount NFACTICK,
n.companyFk empresa_id
FROM newInvoiceIn n
JOIN newSupplier s
JOIN invoiceInTax iit ON iit.invoiceInFk = n.id
JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = iit.expenceFk
WHERE e.name != 'Suplidos Transitarios nacionales'
GROUP BY iit.expenceFk;
-- Líneas de IVA
INSERT INTO XDiario( ASIEN,
FECHA,
SUBCTA,
CONTRA,
EURODEBE,
BASEEURO,
CONCEPTO,
FACTURA,
IVA,
AUXILIAR,
SERIE,
TIPOOPE,
FECHA_EX,
FECHA_OP,
NFACTICK,
FACTURAEX,
L340,
LRECT349,
TIPOCLAVE,
TIPOEXENCI,
TIPONOSUJE,
TIPOFACT,
TIPORECTIF,
TERIDNIF,
TERNIF,
TERNOM,
FECREGCON,
empresa_id
)
SELECT vBookNumber ASIEN,
n.bookEntried FECHA,
IF(n.expenceFkDeductible>0, n.expenceFkDeductible, ti.CuentaIvaSoportado) SUBCTA,
s.supplierAccount CONTRA,
SUM(ROUND(ti.PorcentajeIva * it.taxableBase / 100 /* + 0.0001*/ , 2)) EURODEBE,
SUM(it.taxableBase) BASEEURO,
GROUP_CONCAT(DISTINCT e.`name` SEPARATOR ', ') CONCEPTO,
vSerialNumber FACTURA,
ti.PorcentajeIva IVA,
IF(isUeeMember AND eWithheld.id IS NULL,'','*') AUXILIAR,
n.serial SERIE,
ttr.ClaveOperacionDefecto,
n.issued FECHA_EX,
n.operated FECHA_OP,
n.invoicesCount NFACTICK,
n.supplierRef FACTURAEX,
TRUE L340,
(isSameCountry OR NOT isUeeMember) LRECT349,
n.cplusTrascendency472Fk TIPOCLAVE,
n.cplusTaxBreakFk TIPOEXENCI,
n.cplusSubjectOpFk TIPONOSUJE,
n.cplusInvoiceType472Fk TIPOFACT,
n.cplusRectificationTypeFk TIPORECTIF,
iis.cplusTerIdNifFk TERIDNIF,
s.nif AS TERNIF,
s.name AS TERNOM,
n.booked FECREGCON,
n.companyFk
FROM newInvoiceIn n
JOIN newSupplier s
JOIN invoiceInTax it ON n.id = it.invoiceInFk
JOIN sage.TiposIva ti ON ti.CodigoIva = it.taxTypeSageFk
JOIN sage.TiposTransacciones ttr ON ttr.CodigoTransaccion = it.transactionTypeSageFk
JOIN invoiceInSerial iis ON iis.code = n.serial
JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = it.expenceFk
LEFT JOIN (
SELECT eWithheld.id
FROM invoiceInTax hold
JOIN expence eWithheld ON eWithheld.id = hold.expenceFk AND eWithheld.isWithheld
WHERE hold.invoiceInFk = vInvoiceInId LIMIT 1
) eWithheld ON TRUE
WHERE it.taxTypeSageFk IS NOT NULL
AND it.taxTypeSageFk NOT IN (22, 90)
GROUP BY ti.PorcentajeIva, e.id;
-- Línea iva inversor sujeto pasivo
INSERT INTO XDiario( ASIEN,
FECHA,
SUBCTA,
CONTRA,
EUROHABER,
BASEEURO,
CONCEPTO,
FACTURA,
IVA,
AUXILIAR,
SERIE,
TIPOOPE,
FECHA_EX,
FECHA_OP,
NFACTICK,
FACTURAEX,
L340,
LRECT349,
TIPOCLAVE,
TIPOEXENCI,
TIPONOSUJE,
TIPOFACT,
TIPORECTIF,
TERIDNIF,
TERNIF,
TERNOM,
empresa_id
)
SELECT vBookNumber ASIEN,
n.bookEntried FECHA,
ti.CuentaIvaRepercutido SUBCTA,
s.supplierAccount CONTRA,
SUM(ROUND(ti.PorcentajeIva * it.taxableBase / 100,2)) EUROHABER,
ROUND(SUM(it.taxableBase),2) BASEEURO,
GROUP_CONCAT(DISTINCT e.`name` SEPARATOR ', ') CONCEPTO,
vSerialNumber FACTURA,
ti.PorcentajeIva IVA,
'*' AUXILIAR,
n.serial SERIE,
ttr.ClaveOperacionDefecto,
n.issued FECHA_EX,
n.operated FECHA_OP,
n.invoicesCount NFACTICK,
n.supplierRef FACTURAEX,
FALSE L340,
(isSameCountry OR NOT isUeeMember) LRECT349,
1 TIPOCLAVE,
n.cplusTaxBreakFk TIPOEXENCI,
n.cplusSubjectOpFk TIPONOSUJE,
n.cplusInvoiceType472Fk TIPOFACT,
n.cplusRectificationTypeFk TIPORECTIF,
iis.cplusTerIdNifFk TERIDNIF,
s.nif AS TERNIF,
s.name AS TERNOM,
n.companyFk
FROM newInvoiceIn n
JOIN newSupplier s
JOIN invoiceInTax it ON n.id = it.invoiceInFk
JOIN sage.TiposIva ti ON ti.CodigoIva = it.taxTypeSageFk
JOIN sage.TiposTransacciones ttr ON ttr.CodigoTransaccion = it.transactionTypeSageFk
JOIN invoiceInSerial iis ON iis.code = n.serial
JOIN (SELECT * FROM expence e GROUP BY e.id)e ON e.id = it.expenceFk
WHERE ti.Iva = 'HP DEVENGADO 21 ISP' OR MID(s.account, 4, 1) = '1'
GROUP BY ti.PorcentajeIva, e.id;
-- Actualización del registro original
UPDATE invoiceIn ii
JOIN newInvoiceIn ni ON ii.id = ni.id
SET ii.serialNumber = vSerialNumber,
ii.isBooked = TRUE;
-- Problemas derivados de la precisión en los decimales al calcular los impuestos
UPDATE XDiario
SET EURODEBE = EURODEBE -
(SELECT IF(ABS(sub.difference) = 0.01, sub.difference, 0)
FROM(
SELECT SUM(IFNULL(ROUND(EURODEBE, 2),0)) - SUM(IFNULL(ROUND(EUROHABER, 2), 0)) difference
FROM XDiario
WHERE ASIEN = vBookNumber
)sub
)
WHERE ASIEN = vBookNumber
AND EURODEBE <> 0
ORDER BY id DESC
LIMIT 1;
END$$
DELIMITER ;

View File

@ -0,0 +1,38 @@
USE vn;
DELIMITER $$
$$
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `vn`.`saleVolume` AS
select
`s`.`ticketFk` AS `ticketFk`,
`s`.`id` AS `saleFk`,
round(`ic`.`cm3delivery` * `s`.`quantity` / 1000, 0) AS `litros`,
`t`.`routeFk` AS `routeFk`,
`t`.`shipped` AS `shipped`,
`t`.`landed` AS `landed`,
`s`.`quantity` * `ic`.`cm3delivery` / 1000000 AS `volume`,
`s`.`quantity` * `ic`.`grams` / 1000 AS `physicalWeight`,
`s`.`quantity` * `ic`.`cm3delivery` * greatest(`i`.`density`, 167) / 1000000 AS `weight`,
`s`.`quantity` * `ic`.`cm3delivery` / 1000000 AS `physicalVolume`,
`s`.`quantity` * `ic`.`cm3delivery` * ifnull(`t`.`zonePrice`, `z`.`price`) / (`vc`.`standardFlowerBox` * 1000) AS `freight`,
`t`.`zoneFk` AS `zoneFk`,
`t`.`clientFk` AS `clientFk`,
`s`.`isPicked` AS `isPicked`,
`s`.`quantity` * `s`.`price` * (100 - `s`.`discount`) / 100 AS `eurosValue`,
`i`.`itemPackingTypeFk` AS `itemPackingTypeFk`
from
(((((`sale` `s`
join `item` `i` on
(`i`.`id` = `s`.`itemFk`))
join `ticket` `t` on
(`t`.`id` = `s`.`ticketFk`))
join `zone` `z` on
(`z`.`id` = `t`.`zoneFk`))
join `volumeConfig` `vc`)
join `itemCost` `ic` on
(`ic`.`itemFk` = `s`.`itemFk`
and `ic`.`warehouseFk` = `t`.`warehouseFk`))
where
`s`.`quantity` > 0;
$$
DELIMITER ;

View File

@ -0,0 +1,5 @@
ALTER TABLE `vn`.`smsConfig` ADD apiKey varchar(50) NULL;
ALTER TABLE `vn`.`smsConfig` CHANGE `user` user__ varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL;
ALTER TABLE `vn`.`smsConfig` CHANGE password password__ varchar(50) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL;
ALTER TABLE `vn`.`sms` MODIFY COLUMN statusCode smallint(9) DEFAULT 0 NULL;
ALTER TABLE `vn`.`sms` MODIFY COLUMN status varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT 'OK' NULL;

View File

@ -0,0 +1,25 @@
ALTER TABLE `postgresql`.`business` ADD payedHolidays INT NULL;
ALTER TABLE `postgresql`.`business` CHANGE payedHolidays payedHolidays INT NULL AFTER reasonEndFk;
CREATE OR REPLACE
ALGORITHM = UNDEFINED VIEW `vn`.`workerLabour` AS
select
`b`.`business_id` AS `businessFk`,
`p`.`id_trabajador` AS `workerFk`,
`bl`.`workcenter_id` AS `workCenterFk`,
`b`.`date_start` AS `started`,
`b`.`date_end` AS `ended`,
`d`.`id` AS `departmentFk`,
`b`.`payedHolidays` AS `payedHolidays`
from
((((`postgresql`.`person` `p`
join `postgresql`.`profile` `pr` on
((`pr`.`person_id` = `p`.`person_id`)))
join `postgresql`.`business` `b` on
((`b`.`client_id` = `pr`.`profile_id`)))
join `postgresql`.`business_labour` `bl` on
((`b`.`business_id` = `bl`.`business_id`)))
join `vn`.`department` `d` on
((`d`.`id` = `bl`.`department_id`)))
order by
`b`.`date_start` desc

View File

@ -0,0 +1,7 @@
UPDATE `vn`.`smsConfig`
SET `uri` = 'https://api.gateway360.com/api/3.0/sms/send'
WHERE `id` = 1;
UPDATE `vn`.`smsConfig`
SET `apiKey` = '5715476da95b46d686a5a255e6459523'
WHERE `id` = 1;

File diff suppressed because one or more lines are too long

View File

@ -8,7 +8,7 @@ ALTER TABLE `vn`.`ticket` AUTO_INCREMENT = 1;
INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`) INSERT INTO `salix`.`AccessToken` (`id`, `ttl`, `created`, `userId`)
VALUES VALUES
('TOTALLY_SECURE_TOKEN', '1209600', CURDATE(), 66); ('DEFAULT_TOKEN', '1209600', CURDATE(), 66);
INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`) INSERT INTO `vn`.`ticketConfig` (`id`, `scopeDays`)
@ -104,17 +104,17 @@ INSERT INTO `vn`.`currency`(`id`, `code`, `name`, `ratio`)
(3, 'GBP', 'Libra', 1), (3, 'GBP', 'Libra', 1),
(4, 'JPY', 'Yen Japones', 1); (4, 'JPY', 'Yen Japones', 1);
INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`) INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`, `ibanLength`, `continentFk`, `hasDailyInvoice`, `CEE`)
VALUES VALUES
(1, 'España', 1, 'ES', 1, 24, 4), (1, 'España', 1, 'ES', 1, 24, 4, 0, 1),
(2, 'Italia', 1, 'IT', 1, 27, 4), (2, 'Italia', 1, 'IT', 1, 27, 4, 0, 1),
(3, 'Alemania', 1, 'DE', 1, 22, 4), (3, 'Alemania', 1, 'DE', 1, 22, 4, 0, 1),
(4, 'Rumania', 1, 'RO', 1, 24, 4), (4, 'Rumania', 1, 'RO', 1, 24, 4, 0, 1),
(5, 'Holanda', 1, 'NL', 1, 18, 4), (5, 'Holanda', 1, 'NL', 1, 18, 4, 0, 1),
(8, 'Portugal', 1, 'PT', 1, 27, 4), (8, 'Portugal', 1, 'PT', 1, 27, 4, 0, 1),
(13,'Ecuador', 0, 'EC', 1, 24, 2), (13,'Ecuador', 0, 'EC', 1, 24, 2, 1, 2),
(19,'Francia', 1, 'FR', 1, 27, 4), (19,'Francia', 1, 'FR', 1, 27, 4, 0, 1),
(30,'Canarias', 1, 'IC', 1, 24, 4); (30,'Canarias', 1, 'IC', 1, 24, 4, 1, 2);
INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`) INSERT INTO `hedera`.`language` (`code`, `name`, `orgName`, `isActive`)
VALUES VALUES
@ -243,7 +243,7 @@ INSERT INTO `vn`.`province`(`id`, `name`, `countryFk`, `autonomyFk`, `warehouseF
VALUES VALUES
(1, 'Province one', 1, 1, NULL), (1, 'Province one', 1, 1, NULL),
(2, 'Province two', 1, 1, NULL), (2, 'Province two', 1, 1, NULL),
(3, 'Province three', 1, 2, NULL), (3, 'Province three', 30, 2, NULL),
(4, 'Province four', 2, 3, NULL), (4, 'Province four', 2, 3, NULL),
(5, 'Province five', 13, 4, NULL); (5, 'Province five', 13, 4, NULL);
@ -455,7 +455,8 @@ INSERT INTO `vn`.`creditInsurance`(`id`, `creditClassification`, `credit`, `crea
INSERT INTO `vn`.`companyGroup`(`id`, `code`) INSERT INTO `vn`.`companyGroup`(`id`, `code`)
VALUES VALUES
(1, 'Wayne Industries'); (1, 'wayneIndustries'),
(2, 'Verdnatura');
INSERT INTO `vn`.`bankEntity`(`id`, `countryFk`, `name`, `bic`) INSERT INTO `vn`.`bankEntity`(`id`, `countryFk`, `name`, `bic`)
VALUES VALUES
@ -466,13 +467,13 @@ INSERT INTO `vn`.`supplierAccount`(`id`, `supplierFk`, `iban`, `bankEntityFk`)
VALUES VALUES
(241, 442, 'ES111122333344111122221111', 128); (241, 442, 'ES111122333344111122221111', 128);
INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `phytosanitary`) INSERT INTO `vn`.`company`(`id`, `code`, `supplierAccountFk`, `workerManagerFk`, `companyCode`, `sage200Company`, `expired`, `companyGroupFk`, `phytosanitary`)
VALUES VALUES
(69 , 'CCs', NULL, 30, NULL, 0, NULL, NULL), (69 , 'CCs', NULL, 30, NULL, 0, NULL, 1, NULL),
(442 , 'VNL', 241, 30, 2 , 1, NULL, 'VNL Company - Plant passport'), (442 , 'VNL', 241, 30, 2 , 1, NULL, 2, 'VNL Company - Plant passport'),
(567 , 'VNH', NULL, 30, NULL, 4, NULL, 'VNH Company - Plant passport'), (567 , 'VNH', NULL, 30, NULL, 4, NULL, 1, 'VNH Company - Plant passport'),
(791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', NULL), (791 , 'FTH', NULL, 30, NULL, 3, '2015-11-30', 1, NULL),
(1381, 'ORN', NULL, 30, NULL, 7, NULL, 'ORN Company - Plant passport'); (1381, 'ORN', NULL, 30, NULL, 7, NULL, 1, 'ORN Company - Plant passport');
INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`) INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`)
VALUES VALUES
@ -486,7 +487,9 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
('A', 'Global nacional', 1, 'NATIONAL', 0), ('A', 'Global nacional', 1, 'NATIONAL', 0),
('T', 'Española rapida', 1, 'NATIONAL', 0), ('T', 'Española rapida', 1, 'NATIONAL', 0),
('V', 'Intracomunitaria global', 0, 'CEE', 1), ('V', 'Intracomunitaria global', 0, 'CEE', 1),
('M', 'Múltiple nacional', 1, 'NATIONAL', 0); ('M', 'Múltiple nacional', 1, 'NATIONAL', 0),
('E', 'Exportación rápida', 0, 'WORLD', 0);
;
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
@ -606,7 +609,7 @@ INSERT INTO `vn`.`ticket`(`id`, `priority`, `agencyModeFk`,`warehouseFk`,`routeF
(9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, CURDATE()), (9 , NULL, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1104, 'Stark tower', 124, NULL, 0, 3, 5, 1, CURDATE()),
(10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, CURDATE()), (10, 1, 1, 5, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1102, 'Ingram Street', 2, NULL, 0, 1, 5, 1, CURDATE()),
(11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, CURDATE()), (11, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1102, 'NY roofs', 122, NULL, 0, 3, 5, 1, CURDATE()),
(12, 1, 1, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, CURDATE()), (12, 1, 8, 1, 1, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 1, 5, 1, CURDATE()),
(13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, CURDATE()), (13, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1103, 'Phone Box', 123, NULL, 0, 3, 5, 1, CURDATE()),
(14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, CURDATE()), (14, 1, 2, 1, NULL, CURDATE(), CURDATE(), 1104, 'Malibu Point', 4, NULL, 0, 9, 5, 1, CURDATE()),
(15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, CURDATE()), (15, 1, 7, 1, 6, CURDATE(), DATE_ADD(CURDATE(), INTERVAL + 1 DAY), 1105, 'An incredibly long alias for testing purposes', 125, NULL, 0, 3, 5, 1, CURDATE()),
@ -800,25 +803,25 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('VT', 'Sales'); ('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`) `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`, `itemPackingTypeFk`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL, 'V'),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL, 'H'),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL, NULL),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL, NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL), (5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL, NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL), (6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL, NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL), (7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL, NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL), (8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL, NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL), (9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL, NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL), (10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL, NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL), (11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL, NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL), (12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL, NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1, NULL), (13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1, NULL, NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL), (14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL), (15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL), (16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL, NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL); (71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL, NULL);
-- Update the taxClass after insert of the items -- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2 UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1078,11 +1081,15 @@ INSERT INTO `vn`.`itemPlacement`(`id`, `itemFk`, `warehouseFk`, `code`)
(3, 1, 3, 'A33'), (3, 1, 3, 'A33'),
(4, 2, 1, 'A44'); (4, 2, 1, 'A44');
INSERT INTO `vn`.`train`(`id`, `name`)
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`)
VALUES VALUES
(1, 1106, 5, DATE_ADD(CURDATE(),INTERVAL +1 DAY)), (1, 'Train1'),
(2, 1106, 14, CURDATE()); (2, 'Train2');
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES
(1, 1106, 5, DATE_ADD(CURDATE(),INTERVAL +1 DAY), 1),
(2, 1106, 14, CURDATE(), 1);
INSERT INTO `vn`.`ticketCollection`(`id`, `ticketFk`, `collectionFk`) INSERT INTO `vn`.`ticketCollection`(`id`, `ticketFk`, `collectionFk`)
VALUES VALUES
@ -1290,11 +1297,11 @@ INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `pr
(5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'), (5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'),
(6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'); (6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222');
INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`) INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`, `commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`, `isPayMethodChecked`)
VALUES VALUES
(1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1), (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants', 1),
(2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 1, 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1), (2, 'Farmer King', 'The farmer', 4000020002, 1, '87945234L', 0, CURDATE(), 1, 'supplier address 2', 'SILLA', 2, 43022, 1, 2, 10, 93, 2, 8, 18, 'animals', 1),
(442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants', 1); (442, 'Verdnatura Levante SL', 'Verdnatura', 5115000442, 1, '06815934E', 0, CURDATE(), 1, 'supplier address 3', 'SILLA', 1, 43022, 1, 2, 15, 6, 9, 3, 18, 'flowerPlants', 1);
INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`) INSERT INTO `vn`.`supplierContact`(`id`, `supplierFk`, `phone`, `mobile`, `email`, `observation`, `name`)
VALUES VALUES
@ -1904,9 +1911,9 @@ INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`
(1107, 1, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -14 DAY), DATE_ADD(CURDATE(), INTERVAL 9 DAY))), (1107, 1, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -14 DAY), DATE_ADD(CURDATE(), INTERVAL 9 DAY))),
(1107, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -15 DAY), DATE_ADD(CURDATE(), INTERVAL 7 DAY))); (1107, 2, IF(MONTH(CURDATE()) >= 1 AND DAY(CURDATE()) > 20, DATE_ADD(CURDATE(), INTERVAL -15 DAY), DATE_ADD(CURDATE(), INTERVAL 7 DAY)));
INSERT INTO `vn`.`smsConfig` (`id`, `uri`, `title`) INSERT INTO `vn`.`smsConfig` (`id`, `uri`, `title`, `apiKey`)
VALUES VALUES
('1', 'https://websms.xtratelecom.es/api_php/server.wsdl', 'Verdnatura'); ('1', 'https://api.gateway360.com/api/3.0/sms/send', 'Verdnatura', '5715476da95b46d686a5a255e6459523');
INSERT INTO `vn`.`sharingClient`(`id`, `workerFk`, `started`, `ended`, `clientFk`) INSERT INTO `vn`.`sharingClient`(`id`, `workerFk`, `started`, `ended`, `clientFk`)
VALUES VALUES
@ -2348,7 +2355,7 @@ REPLACE INTO `vn`.`invoiceIn`(`id`, `serialNumber`,`serial`, `supplierFk`, `issu
INSERT INTO `vn`.`invoiceInDueDay`(`invoiceInFk`, `dueDated`, `bankFk`, `amount`) INSERT INTO `vn`.`invoiceInDueDay`(`invoiceInFk`, `dueDated`, `bankFk`, `amount`)
VALUES VALUES
(1, CURDATE(), 1, 237), (1, CURDATE(), 1, 336.99),
(1, CURDATE(), 1, 15.25), (1, CURDATE(), 1, 15.25),
(2, CURDATE(), 1, 168), (2, CURDATE(), 1, 168),
(2, CURDATE(), 1, 55.17), (2, CURDATE(), 1, 55.17),

File diff suppressed because it is too large Load Diff

View File

@ -458,7 +458,8 @@ export default {
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5)', firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5)',
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(8)', firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(8)',
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span', invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button', stateButton: 'vn-ticket-summary vn-button-menu > button ',
stateAutocomplete: 'div.filter.ng-scope > vn-textfield > div.container > div.infix > div.control',
descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div' descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div'
}, },
ticketsIndex: { ticketsIndex: {
@ -559,6 +560,7 @@ export default {
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]', moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]', moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
moreMenuPayBack: 'vn-item[name="payBack"]',
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',

View File

@ -206,7 +206,22 @@ describe('Ticket Edit sale path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should log in as salesAssistant and navigate to ticket sales', async() => {
await page.loginAndModule('salesAssistant', 'ticket');
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
});
it('should select the third sale and create a pay back', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuPayBack);
await page.waitForState('ticket.card.sale');
});
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and create a claim of it', async() => {
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim); await page.waitToClick(selectors.ticketSales.moreMenuCreateClaim);

View File

@ -76,8 +76,24 @@ describe('Ticket Summary path', () => {
await page.waitForState('ticket.card.summary'); await page.waitForState('ticket.card.summary');
}); });
it('should click on the SET OK button', async() => { it('should set the ticket state to OK using the top right button', async() => {
await page.waitToClick(selectors.ticketSummary.setOk); const searchValue = 'OK';
await page.waitToClick(selectors.ticketSummary.stateButton);
await page.write(selectors.ticketSummary.stateAutocomplete, searchValue);
try {
await page.waitForFunction(text => {
const element = document.querySelector('li.active');
if (element)
return element.innerText.toLowerCase().includes(text.toLowerCase());
}, {}, searchValue);
} catch (error) {
const state = await page.evaluate(() => {
const stateSelector = 'vn-ticket-summary vn-label-value:nth-child(1) > section > span';
return document.querySelector(stateSelector).value;
});
throw new Error(`${stateSelector} innerText is ${state}! ${error}`);
}
await page.keyboard.press('Enter');
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');

View File

@ -43,4 +43,10 @@
&.disabled.checked > .btn { &.disabled.checked > .btn {
background-color: $color-font-secondary; background-color: $color-font-secondary;
} }
&[triple-state]:not(.indeterminate):not(.checked) {
.btn {
background-color: lighten($color-alert, 5%);
}
}
} }

View File

@ -171,9 +171,10 @@ export default class SmartTable extends Component {
if (field.length === 2) if (field.length === 2)
sortType = field[1]; sortType = field[1];
const priority = this.sortCriteria.length + 1;
const column = this.columns.find(column => column.field == fieldName); const column = this.columns.find(column => column.field == fieldName);
if (column) { if (column) {
this.sortCriteria.push({field: fieldName, sortType: sortType}); this.sortCriteria.push({field: fieldName, sortType: sortType, priority: priority});
const isASC = sortType == 'ASC'; const isASC = sortType == 'ASC';
const isDESC = sortType == 'DESC'; const isDESC = sortType == 'DESC';
@ -187,6 +188,8 @@ export default class SmartTable extends Component {
column.element.classList.remove('desc'); column.element.classList.remove('desc');
column.element.classList.add('asc'); column.element.classList.add('asc');
} }
this.setPriority(column.element, priority);
} }
} }
} }
@ -241,9 +244,13 @@ export default class SmartTable extends Component {
const isDESC = existingCriteria && existingCriteria.sortType == 'DESC'; const isDESC = existingCriteria && existingCriteria.sortType == 'DESC';
if (!existingCriteria) { if (!existingCriteria) {
this.sortCriteria.push({field: field, sortType: 'ASC'}); const priority = this.sortCriteria.length + 1;
this.sortCriteria.push({field: field, sortType: 'ASC', priority: priority});
element.classList.remove('desc'); element.classList.remove('desc');
element.classList.add('asc'); element.classList.add('asc');
this.setPriority(element, priority);
} }
if (isDESC) { if (isDESC) {
@ -252,6 +259,8 @@ export default class SmartTable extends Component {
}), 1); }), 1);
element.classList.remove('desc'); element.classList.remove('desc');
element.classList.remove('asc'); element.classList.remove('asc');
element.querySelector('sort-priority').remove();
} }
if (isASC) { if (isASC) {
@ -260,9 +269,29 @@ export default class SmartTable extends Component {
element.classList.add('desc'); element.classList.add('desc');
} }
let priority = 0;
for (const criteria of this.sortCriteria) {
const column = this.columns.find(column => column.field == criteria.field);
if (column) {
criteria.priority = priority;
priority++;
column.element.querySelector('sort-priority').remove();
this.setPriority(column.element, priority);
}
}
this.applySort(); this.applySort();
} }
setPriority(column, priority) {
const sortPriority = document.createElement('sort-priority');
sortPriority.setAttribute('class', 'sort-priority');
sortPriority.innerHTML = priority;
column.appendChild(sortPriority);
}
displaySearch() { displaySearch() {
const header = this.element.querySelector('thead > tr'); const header = this.element.querySelector('thead > tr');
if (!header) return; if (!header) return;

View File

@ -96,9 +96,10 @@ describe('Component smartTable', () => {
expect(firstSortCriteria.field).toEqual('id'); expect(firstSortCriteria.field).toEqual('id');
expect(firstSortCriteria.sortType).toEqual('ASC'); expect(firstSortCriteria.sortType).toEqual('ASC');
expect(firstSortCriteria.priority).toEqual(1);
}); });
it('should insert two new objects to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => { it('should add new entries to the controller sortCriteria with a sortType values of "ASC" and "DESC"', () => {
const element = document.createElement('div'); const element = document.createElement('div');
controller.model = {order: 'test1, id DESC'}; controller.model = {order: 'test1, id DESC'};
controller.columns = [ controller.columns = [
@ -114,8 +115,11 @@ describe('Component smartTable', () => {
expect(firstSortCriteria.field).toEqual('test1'); expect(firstSortCriteria.field).toEqual('test1');
expect(firstSortCriteria.sortType).toEqual('ASC'); expect(firstSortCriteria.sortType).toEqual('ASC');
expect(firstSortCriteria.priority).toEqual(1);
expect(secondSortCriteria.field).toEqual('id'); expect(secondSortCriteria.field).toEqual('id');
expect(secondSortCriteria.sortType).toEqual('DESC'); expect(secondSortCriteria.sortType).toEqual('DESC');
expect(secondSortCriteria.priority).toEqual(2);
}); });
}); });

View File

@ -9,7 +9,7 @@ smart-table {
} }
th[field][number] { th[field][number] {
& > :before { & > span:before {
vertical-align: middle; vertical-align: middle;
font-family: 'Material Icons'; font-family: 'Material Icons';
content: 'arrow_downward'; content: 'arrow_downward';
@ -19,26 +19,26 @@ smart-table {
} }
&.asc > :before, &.desc > :before { &.asc > span:before, &.desc > span:before {
color: $color-font; color: $color-font;
opacity: 1; opacity: 1;
} }
&.asc > :before { &.asc > span:before {
content: 'arrow_upward'; content: 'arrow_upward';
} }
&.desc > :before { &.desc > span:before {
content: 'arrow_downward'; content: 'arrow_downward';
} }
&:hover > :before { &:hover > span:before {
opacity: 1; opacity: 1;
} }
} }
th[field]:not([number]) { th[field]:not([number]) {
& > :after { & > span:after {
vertical-align: middle; vertical-align: middle;
font-family: 'Material Icons'; font-family: 'Material Icons';
content: 'arrow_downward'; content: 'arrow_downward';
@ -48,20 +48,20 @@ smart-table {
} }
&.asc > :after, &.desc > :after { &.asc > span:after, &.desc > span:after {
color: $color-font; color: $color-font;
opacity: 1; opacity: 1;
} }
&.asc > :after { &.asc > span:after {
content: 'arrow_upward'; content: 'arrow_upward';
} }
&.desc > :after { &.desc > span:after {
content: 'arrow_downward'; content: 'arrow_downward';
} }
&:hover > :after { &:hover > span:after {
opacity: 1; opacity: 1;
} }
} }
@ -143,4 +143,16 @@ smart-table {
flex: initial; flex: initial;
width: 33% width: 33%
} }
}
.sort-priority {
background-color: $color-font-bg-marginal;
border-radius: 50%;
padding: 2px 5px;
display: inline-block;
text-align: center;
width: 7px;
height: 13px;
font-size: 10px;
color: $color-font-bg
} }

View File

@ -23,371 +23,398 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-100:before { .icon-isTooLittle:before {
content: "\e976";
}
.icon-account:before {
content: "\e900";
}
.icon-actions:before {
content: "\e901";
}
.icon-addperson:before {
content: "\e902";
}
.icon-agency:before {
content: "\e903";
}
.icon-albaran:before {
content: "\e904";
}
.icon-anonymous:before {
content: "\e905";
}
.icon-apps:before {
content: "\e906";
}
.icon-artificial:before {
content: "\e907";
}
.icon-attach:before {
content: "\e908";
}
.icon-barcode:before {
content: "\e909";
}
.icon-basket:before {
content: "\e90a";
}
.icon-basketadd:before {
content: "\e90b";
}
.icon-bin:before {
content: "\e90c";
}
.icon-botanical:before {
content: "\e90d";
}
.icon-bucket:before {
content: "\e90e";
}
.icon-buscaman:before {
content: "\e90f";
}
.icon-buyrequest:before {
content: "\e910";
}
.icon-calc_volum .path1:before {
content: "\e911";
color: rgb(0, 0, 0);
}
.icon-calc_volum .path2:before {
content: "\e912";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path3:before {
content: "\e913";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path4:before {
content: "\e914";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path5:before {
content: "\e915";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path6:before {
content: "\e916";
margin-left: -1em;
color: rgb(255, 255, 255);
}
.icon-calendar:before {
content: "\e917";
}
.icon-catalog:before {
content: "\e918";
}
.icon-claims:before {
content: "\e919";
}
.icon-client:before {
content: "\e91a";
}
.icon-clone:before {
content: "\e91b"; content: "\e91b";
} }
.icon-columnadd:before {
content: "\e91c";
}
.icon-columndelete:before {
content: "\e91d";
}
.icon-accessory:before {
content: "\e91e";
}
.icon-components:before {
content: "\e91f";
}
.icon-handmade:before {
content: "\e920";
}
.icon-consignatarios:before {
content: "\e921";
}
.icon-control:before {
content: "\e922";
}
.icon-credit:before {
content: "\e923";
}
.icon-deletedTicketCross:before {
content: "\e924";
}
.icon-deleteline:before {
content: "\e925";
}
.icon-delivery:before {
content: "\e926";
}
.icon-deliveryprices:before {
content: "\e927";
}
.icon-details:before {
content: "\e928";
}
.icon-dfiscales:before {
content: "\e929";
}
.icon-doc:before {
content: "\e92a";
}
.icon-entry:before {
content: "\e92b";
}
.icon-exit:before {
content: "\e92c";
}
.icon-eye:before {
content: "\e92d";
}
.icon-fixedPrice:before {
content: "\e92e";
}
.icon-flower:before {
content: "\e92f";
}
.icon-frozen:before { .icon-frozen:before {
content: "\e930"; content: "\e900";
}
.icon-fruit:before {
content: "\e931";
}
.icon-funeral:before {
content: "\e932";
}
.icon-greuge:before {
content: "\e933";
}
.icon-grid:before {
content: "\e934";
}
.icon-handmadeArtificial:before {
content: "\e935";
}
.icon-headercol:before {
content: "\e936";
}
.icon-history:before {
content: "\e937";
}
.icon-disabled:before {
content: "\e938";
}
.icon-info:before {
content: "\e939";
}
.icon-inventory:before {
content: "\e93a";
}
.icon-invoice:before {
content: "\e93b";
}
.icon-invoice-in:before {
content: "\e93c";
}
.icon-invoice-in-create:before {
content: "\e93d";
}
.icon-invoice-out:before {
content: "\e93e";
}
.icon-item:before {
content: "\e93f";
}
.icon-languaje:before {
content: "\e940";
}
.icon-lines:before {
content: "\e941";
}
.icon-linesprepaired:before {
content: "\e942";
}
.icon-logout:before {
content: "\e943";
}
.icon-mana:before {
content: "\e944";
}
.icon-mandatory:before {
content: "\e945";
}
.icon-net:before {
content: "\e946";
}
.icon-niche:before {
content: "\e947";
}
.icon-no036:before {
content: "\e948";
}
.icon-notes:before {
content: "\e949";
}
.icon-noweb:before {
content: "\e94a";
}
.icon-onlinepayment:before {
content: "\e94b";
}
.icon-package:before {
content: "\e94c";
}
.icon-payment:before {
content: "\e94d";
}
.icon-pbx:before {
content: "\e94e";
} }
.icon-Person:before { .icon-Person:before {
content: "\e94f"; content: "\e901";
} }
.icon-pets:before { .icon-handmadeArtificial:before {
content: "\e950"; content: "\e902";
} }
.icon-photo:before { .icon-fruit:before {
content: "\e951"; content: "\e903";
} }
.icon-plant:before { .icon-funeral:before {
content: "\e952"; content: "\e904";
}
.icon-stowaway:before {
content: "\e953";
}
.icon-preserved:before {
content: "\e954";
}
.icon-recovery:before {
content: "\e955";
}
.icon-regentry:before {
content: "\e956";
}
.icon-reserve:before {
content: "\e957";
}
.icon-revision:before {
content: "\e958";
}
.icon-risk:before {
content: "\e959";
}
.icon-services:before {
content: "\e95a";
}
.icon-settings:before {
content: "\e95b";
}
.icon-shipment-01 .path1:before {
content: "\e95c";
color: rgb(225, 225, 225);
}
.icon-shipment-01 .path2:before {
content: "\e95d";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-sign:before {
content: "\e95e";
}
.icon-sms:before {
content: "\e95f";
}
.icon-solclaim:before {
content: "\e960";
}
.icon-solunion:before {
content: "\e961";
}
.icon-splitline:before {
content: "\e962";
}
.icon-splur:before {
content: "\e963";
}
.icon-supplier:before {
content: "\e965";
}
.icon-supplierfalse:before {
content: "\e966";
}
.icon-tags:before {
content: "\e967";
}
.icon-tax:before {
content: "\e968";
}
.icon-thermometer:before {
content: "\e969";
}
.icon-ticket:before {
content: "\e96a";
}
.icon-traceability:before {
content: "\e96b";
}
.icon-transaction:before {
content: "\e96c";
} }
.icon-treatments:before { .icon-treatments:before {
content: "\e96d"; content: "\e905";
} }
.icon-unavailable:before { .icon-preserved:before {
content: "\e96e"; content: "\e906";
} }
.icon-greenery:before { .icon-greenery:before {
content: "\e96f"; content: "\e907";
} }
.icon-volume:before { .icon-plant:before {
content: "\e970"; content: "\e908";
} }
.icon-wand:before { .icon-handmade:before {
content: "\e971"; content: "\e909";
} }
.icon-web:before { .icon-accessory:before {
content: "\e972"; content: "\e90a";
} }
.icon-wiki:before { .icon-artificial:before {
content: "\e973"; content: "\e90b";
} }
.icon-worker:before { .icon-flower:before {
content: "\e974"; content: "\e90c";
}
.icon-fixedPrice:before {
content: "\e90d";
}
.icon-addperson:before {
content: "\e90e";
}
.icon-supplierfalse:before {
content: "\e90f";
}
.icon-invoice-out:before {
content: "\e910";
}
.icon-invoice-in:before {
content: "\e911";
}
.icon-invoice-in-create:before {
content: "\e912";
}
.icon-basketadd:before {
content: "\e913";
}
.icon-basket:before {
content: "\e914";
}
.icon-calc_volum .path1:before {
content: "\e915";
}
.icon-calc_volum .path2:before {
content: "\e916";
margin-left: -1em;
}
.icon-calc_volum .path3:before {
content: "\e917";
margin-left: -1em;
}
.icon-calc_volum .path4:before {
content: "\e918";
margin-left: -1em;
}
.icon-calc_volum .path5:before {
content: "\e919";
margin-left: -1em;
}
.icon-calc_volum .path6:before {
content: "\e91a";
margin-left: -1em;
}
.icon-deliveryprices:before {
content: "\e91c";
}
.icon-onlinepayment:before {
content: "\e91d";
}
.icon-risk:before {
content: "\e91e";
}
.icon-noweb:before {
content: "\e91f";
}
.icon-no036:before {
content: "\e920";
}
.icon-inactive:before {
content: "\e921";
}
.icon-unavailable:before {
content: "\e922";
}
.icon-invoice-01:before {
content: "\e923";
}
.icon-invoice:before {
content: "\e924";
}
.icon-supplier:before {
content: "\e925";
}
.icon-client2:before {
content: "\e926";
}
.icon-supplier2:before {
content: "\e927";
}
.icon-client:before {
content: "\e928";
}
.icon-shipment-01:before {
content: "\e929";
}
.icon-inventory:before {
content: "\e92b";
} }
.icon-zone:before { .icon-zone:before {
content: "\e92c";
}
.icon-wiki:before {
content: "\e92d";
}
.icon-attach:before {
content: "\e92e";
}
.icon-zone2:before {
content: "\e92f";
}
.icon-anonymous:before {
content: "\e930";
}
.icon-net:before {
content: "\e931";
}
.icon-buyrequest:before {
content: "\e932";
}
.icon-thermometer:before {
content: "\e933";
}
.icon-entry:before {
content: "\e934";
}
.icon-deletedTicket:before {
content: "\e935";
}
.icon-deliveryprices-01:before {
content: "\e936";
}
.icon-catalog:before {
content: "\e937";
}
.icon-agency:before {
content: "\e938";
}
.icon-delivery:before {
content: "\e939";
}
.icon-wand:before {
content: "\e93a";
}
.icon-buscaman:before {
content: "\e93b";
}
.icon-pbx:before {
content: "\e93c";
}
.icon-calendar:before {
content: "\e93d";
}
.icon-splitline:before {
content: "\e93e";
}
.icon-consignatarios:before {
content: "\e93f";
}
.icon-tax:before {
content: "\e940";
}
.icon-notes:before {
content: "\e941";
}
.icon-lines:before {
content: "\e942";
}
.icon-languaje:before {
content: "\e943";
}
.icon-greuge:before {
content: "\e944";
}
.icon-credit:before {
content: "\e945";
}
.icon-components:before {
content: "\e946";
}
.icon-pets:before {
content: "\e947";
}
.icon-linesprepaired:before {
content: "\e948";
}
.icon-control:before {
content: "\e949";
}
.icon-revision:before {
content: "\e94a";
}
.icon-newinvoices:before {
content: "\e94b";
}
.icon-services:before {
content: "\e94c";
}
.icon-newalbaran:before {
content: "\e94d";
}
.icon-solunion:before {
content: "\e94e";
}
.icon-stowaway:before {
content: "\e94f";
}
.icon-exit:before {
content: "\e950";
}
.icon-apps:before {
content: "\e951";
}
.icon-info:before {
content: "\e952";
}
.icon-columndelete:before {
content: "\e953";
}
.icon-columnadd:before {
content: "\e954";
}
.icon-deleteline:before {
content: "\e955";
}
.icon-item:before {
content: "\e956";
}
.icon-worker:before {
content: "\e957";
}
.icon-headercol:before {
content: "\e958";
}
.icon-reserva:before {
content: "\e959";
}
.icon-100:before {
content: "\e95a";
}
.icon-noweb1:before {
content: "\e95b";
}
.icon-settings1:before {
content: "\e95c";
}
.icon-sign:before {
content: "\e95d";
}
.icon-polizon:before {
content: "\e95e";
}
.icon-solclaim:before {
content: "\e95f";
}
.icon-actions:before {
content: "\e960";
}
.icon-details:before {
content: "\e961";
}
.icon-traceability:before {
content: "\e962";
}
.icon-claims:before {
content: "\e963";
}
.icon-regentry:before {
content: "\e964";
}
.icon-regentry-1:before {
content: "\e965";
}
.icon-transaction:before {
content: "\e966";
}
.icon-history:before {
content: "\e968";
}
.icon-entry:before {
content: "\e969";
}
.icon-mana:before {
content: "\e96a";
}
.icon-ticket:before {
content: "\e96b";
}
.icon-niche:before {
content: "\e96c";
}
.icon-tags:before {
content: "\e96d";
}
.icon-volume:before {
content: "\e96e";
}
.icon-bin:before {
content: "\e96f";
}
.icon-splur:before {
content: "\e970";
}
.icon-barcode:before {
content: "\e971";
}
.icon-botanical:before {
content: "\e972";
}
.icon-clone:before {
content: "\e973";
}
.icon-photo:before {
content: "\e974";
}
.icon-sms:before {
content: "\e975"; content: "\e975";
} }
.icon-eye:before {
content: "\e976";
}
.icon-doc:before {
content: "\e977";
}
.icon-package:before {
content: "\e978";
}
.icon-settings:before {
content: "\e979";
}
.icon-bucket:before {
content: "\e97a";
}
.icon-mandatory:before {
content: "\e97b";
}
.icon-recovery:before {
content: "\e97c";
}
.icon-payment:before {
content: "\e97e";
}
.icon-invoices:before {
content: "\e97f";
}
.icon-grid:before {
content: "\e980";
}
.icon-logout:before {
content: "\e981";
}
.icon-web:before {
content: "\e982";
}
.icon-albaran:before {
content: "\e983";
}
.icon-dfiscales:before {
content: "\e984";
}

Binary file not shown.

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 162 KiB

After

Width:  |  Height:  |  Size: 162 KiB

View File

@ -51,7 +51,7 @@ h1, h2, h3, h4, h5, h6 {
color: $color-main; color: $color-main;
} }
.text-secondary { .text-secondary {
color: $color-font-secondary; color: $color-font-light;
} }
/* Helpers */ /* Helpers */

133
front/package-lock.json generated Normal file
View File

@ -0,0 +1,133 @@
{
"name": "salix-front",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"@uirouter/angularjs": {
"version": "1.0.29",
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.29.tgz",
"integrity": "sha512-RImWnBarNixkMto0o8stEaGwZmvhv5cnuOLXyMU2pY8MP2rgEF74ZNJTLeJCW14LR7XDUxVH8Mk8bPI6lxedmQ==",
"requires": {
"@uirouter/core": "6.0.7"
}
},
"@uirouter/core": {
"version": "6.0.7",
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.7.tgz",
"integrity": "sha512-KUTJxL+6q0PiBnFx4/Z+Hsyg0pSGiaW5yZQeJmUxknecjpTbnXkLU8H2EqRn9N2B+qDRa7Jg8RcgeNDPY72O1w=="
},
"angular": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.2.tgz",
"integrity": "sha512-IauMOej2xEe7/7Ennahkbb5qd/HFADiNuLSESz9Q27inmi32zB0lnAsFeLEWcox3Gd1F6YhNd1CP7/9IukJ0Gw=="
},
"angular-animate": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
},
"angular-moment": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
"requires": {
"moment": ">=2.8.0 <3.0.0"
}
},
"angular-translate": {
"version": "2.18.4",
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.18.4.tgz",
"integrity": "sha512-KohNrkH6J9PK+VW0L/nsRTcg5Fw70Ajwwe3Jbfm54Pf9u9Fd+wuingoKv+h45mKf38eT+Ouu51FPua8VmZNoCw==",
"requires": {
"angular": "^1.8.0"
}
},
"angular-translate-loader-partial": {
"version": "2.18.4",
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.18.4.tgz",
"integrity": "sha512-bsjR+FbB0sdA2528E/ugwKdlPPQhA1looxLxI3otayBTFXBpED33besfSZhYAISLgNMSL038vSssfRUen9qD8w==",
"requires": {
"angular-translate": "~2.18.4"
}
},
"argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"requires": {
"sprintf-js": "~1.0.2"
}
},
"croppie": {
"version": "2.6.5",
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
},
"esprima": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
},
"js-yaml": {
"version": "3.14.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
"requires": {
"argparse": "^1.0.7",
"esprima": "^4.0.0"
}
},
"mg-crud": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
"integrity": "sha1-p6AWGzWSPK7/8ZpIBpS2V1vDggw=",
"requires": {
"angular": "^1.6.1"
}
},
"moment": {
"version": "2.29.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz",
"integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ=="
},
"oclazyload": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
"integrity": "sha1-Kjirv/QJDAihEBZxkZRbWfLoJ5w="
},
"require-yaml": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
"integrity": "sha1-LhsY2RPDuqcqWk03O28Tjd0sMr0=",
"requires": {
"js-yaml": "^4.1.0"
},
"dependencies": {
"argparse": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
"requires": {
"argparse": "^2.0.1"
}
}
}
},
"sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw="
},
"validator": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
"integrity": "sha1-R84j7Y1Ord+p1LjvAHG2zxB418g="
}
}
}

View File

@ -120,5 +120,6 @@
"This item is not available": "This item is not available", "This item is not available": "This item is not available",
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}", "Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}",
"The type of business must be filled in basic data": "The type of business must be filled in basic data", "The type of business must be filled in basic data": "The type of business must be filled in basic data",
"isWithoutNegatives": "isWithoutNegatives" "isWithoutNegatives": "isWithoutNegatives",
alexm marked this conversation as resolved
Review

this should be translated or removed?

this should be translated or removed?
"The worker has hours recorded that day": "The worker has hours recorded that day"
} }

View File

@ -96,13 +96,13 @@
"This postcode already exists": "Este código postal ya existe", "This postcode already exists": "Este código postal ya existe",
"Concept cannot be blank": "El concepto no puede quedar en blanco", "Concept cannot be blank": "El concepto no puede quedar en blanco",
"File doesn't exists": "El archivo no existe", "File doesn't exists": "El archivo no existe",
"You don't have privileges to change the zone": "No tienes permisos para cambiar la zona", "You don't have privileges to change the zone or for these parameters there are more than one shipping options, talk to agencies": "No tienes permisos para cambiar la zona o para esos parámetros hay más de una opción de envío, hable con las agencias",
"This ticket is already on weekly tickets": "Este ticket ya está en tickets programados", "This ticket is already on weekly tickets": "Este ticket ya está en tickets programados",
"Ticket id cannot be blank": "El id de ticket no puede quedar en blanco", "Ticket id cannot be blank": "El id de ticket no puede quedar en blanco",
"Weekday cannot be blank": "El día de la semana no puede quedar en blanco", "Weekday cannot be blank": "El día de la semana no puede quedar en blanco",
"You can't delete a confirmed order": "No puedes borrar un pedido confirmado", "You can't delete a confirmed order": "No puedes borrar un pedido confirmado",
"Can't create stowaway for this ticket": "No se puede crear un polizon para este ticket", "Can't create stowaway for this ticket": "No se puede crear un polizon para este ticket",
"The socialName has an invalid format": "El nombre fiscal tiene un formato incorrecto", "The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto",
"Invalid quantity": "Cantidad invalida", "Invalid quantity": "Cantidad invalida",
"This postal code is not valid": "This postal code is not valid", "This postal code is not valid": "This postal code is not valid",
"is invalid": "is invalid", "is invalid": "is invalid",
@ -210,11 +210,14 @@
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio", "Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito", "You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente", "You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"Amounts do not match": "Las cantidades no coinciden",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", "The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días", "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días",
"The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día", "The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día",
"The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día", "The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día",
"isWithoutNegatives": "Tiene Negativos", "isWithoutNegatives": "Tiene Negativos",
"You can not modify is pay method checked": "No se puede modificar el campo método de pago validado" "You can not modify is pay method checked": "No se puede modificar el campo método de pago validado",
"Can't transfer claimed sales": "No puedes transferir lineas reclamadas",
"You don't have privileges to create pay back": "No tienes permisos para crear un abono"
alexm marked this conversation as resolved Outdated
Outdated
Review

this should be translated or removed?

this should be translated or removed?
} }

View File

@ -59,11 +59,12 @@ module.exports = Self => {
const landedPlusWeek = new Date(ticket.landed); const landedPlusWeek = new Date(ticket.landed);
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7); landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
const hasClaimManagerRole = await models.Account.hasRole(userId, 'claimManager', myOptions);
const isClaimable = landedPlusWeek >= new Date(); const isClaimable = landedPlusWeek >= new Date();
if (ticket.isDeleted) if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`); throw new UserError(`You can't create a claim for a removed ticket`);
if (!isClaimable) if (!isClaimable && !hasClaimManagerRole)
throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`); throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`);
const newClaim = await Self.create({ const newClaim = await Self.create({

View File

@ -46,9 +46,40 @@ describe('Claim createFromSales()', () => {
} }
}); });
it('should be able to create a claim for a ticket delivered more than seven days ago as claimManager', async() => {
const tx = await models.Claim.beginTransaction({});
const claimManagerId = 72;
activeCtx.accessToken.userId = claimManagerId;
try {
const options = {transaction: tx};
const todayMinusEightDays = new Date();
todayMinusEightDays.setDate(todayMinusEightDays.getDate() - 8);
const ticket = await models.Ticket.findById(ticketId, options);
await ticket.updateAttribute('landed', todayMinusEightDays, options);
const claim = await models.Claim.createFromSales(ctx, ticketId, newSale, options);
expect(claim.ticketFk).toEqual(ticketId);
const claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should not be able to create a claim for a ticket delivered more than seven days ago', async() => { it('should not be able to create a claim for a ticket delivered more than seven days ago', async() => {
const tx = await models.Claim.beginTransaction({}); const tx = await models.Claim.beginTransaction({});
activeCtx.accessToken.userId = 1;
let error; let error;
try { try {

View File

@ -1,5 +1,4 @@
const soap = require('soap'); const got = require('got');
const xmlParser = require('xml2js').parseString;
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
@ -35,57 +34,49 @@ module.exports = Self => {
Self.send = async(ctx, destinationFk, destination, message) => { Self.send = async(ctx, destinationFk, destination, message) => {
const userId = ctx.req.accessToken.userId; const userId = ctx.req.accessToken.userId;
const smsConfig = await Self.app.models.SmsConfig.findOne(); const smsConfig = await Self.app.models.SmsConfig.findOne();
const soapClient = await soap.createClientAsync(smsConfig.uri);
if (destination.length == 9) {
const spainPrefix = '0034';
destination = spainPrefix + destination;
}
const params = { const params = {
user: smsConfig.user, api_key: smsConfig.apiKey,
pass: smsConfig.password, messages: [{
src: smsConfig.title, from: smsConfig.title,
dst: destination, to: destination,
msg: message text: message
}]
}; };
let xmlResponse; let response;
let xmlResult;
let xmlParsed;
let status;
try { try {
if (process.env.NODE_ENV !== 'production') { if (process.env.NODE_ENV !== 'production')
status = { response = {result: [{status: 'ok'}]};
codigo: [200], else {
descripcion: ['Fake response'] const jsonTest = {
json: params
}; };
} else { response = await got.post(smsConfig.uri, jsonTest).json();
[xmlResponse] = await soapClient.sendSMSAsync(params);
xmlResult = xmlResponse.result.$value;
xmlParsed = await new Promise((resolve, reject) => {
xmlParser(xmlResult, (err, result) => {
if (err)
reject(err);
resolve(result);
});
});
[status] = xmlParsed['xtratelecom-sms-response'].sms;
} }
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
const statusCode = status.codigo[0]; const [result] = response.result;
const statusDescription = status.descripcion[0]; const error = result.error_id;
const newSms = { const newSms = {
senderFk: userId, senderFk: userId,
destinationFk: destinationFk || null, destinationFk: destinationFk || null,
destination: destination, destination: destination,
message: message, message: message,
statusCode: statusCode, status: error
status: statusDescription
}; };
const sms = await Self.create(newSms); const sms = await Self.create(newSms);
if (statusCode != 200) if (error)
throw new UserError(`We weren't able to send this SMS`); throw new UserError(`We weren't able to send this SMS`);
return sms; return sms;

View File

@ -1,14 +1,10 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const soap = require('soap');
describe('sms send()', () => { describe('sms send()', () => {
it('should return the expected message and status code', async() => { it('should not return status error', async() => {
const code = 200; const ctx = {req: {accessToken: {userId: 1}}};
spyOn(soap, 'createClientAsync').and.returnValue('a so fake client'); const result = await app.models.Sms.send(ctx, 1105, '123456789', 'My SMS Body');
let ctx = {req: {accessToken: {userId: 1}}};
let result = await app.models.Sms.send(ctx, 1105, 'destination', 'My SMS Body');
expect(result.statusCode).toEqual(code); expect(result.status).toBeUndefined();
expect(result.status).toContain('Fake response');
}); });
}); });

View File

@ -55,15 +55,6 @@ module.exports = Self => {
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/ with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
}); });
Self.validate('businessTypeFk', hasBusinessType, {
message: `The type of business must be filled in basic data`
});
function hasBusinessType(err) {
if (!this.businessTypeFk)
err();
}
Self.validatesLengthOf('postcode', { Self.validatesLengthOf('postcode', {
allowNull: true, allowNull: true,
allowBlank: true, allowBlank: true,
@ -189,6 +180,32 @@ module.exports = Self => {
return regexp.test(value); return regexp.test(value);
} }
Self.observe('before save', async ctx => {
const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance;
const businessTypeFk = changes && changes.businessTypeFk || orgData && orgData.businessTypeFk;
const isTaxDataChecked = changes && changes.isTaxDataChecked || orgData && orgData.isTaxDataChecked;
let invalidBusinessType = false;
if (!ctx.isNewInstance) {
const isWorker = await Self.app.models.UserAccount.findById(orgData.id);
const changedFields = Object.keys(changes);
const hasChangedOtherFields = changedFields.some(key => key !== 'businessTypeFk');
if (!businessTypeFk && !isTaxDataChecked && !isWorker && !hasChangedOtherFields)
invalidBusinessType = true;
}
if (ctx.isNewInstance) {
if (!businessTypeFk && !isTaxDataChecked)
invalidBusinessType = true;
}
if (invalidBusinessType)
throw new UserError(`The type of business must be filled in basic data`);
});
Self.observe('before save', async function(ctx) { Self.observe('before save', async function(ctx) {
const changes = ctx.data || ctx.instance; const changes = ctx.data || ctx.instance;
const orgData = ctx.currentInstance; const orgData = ctx.currentInstance;
@ -206,7 +223,7 @@ module.exports = Self => {
&& orgData.isTaxDataChecked != isTaxDataChecked; && orgData.isTaxDataChecked != isTaxDataChecked;
if ((socialNameChanged || dataCheckedChanged) && !isAlpha(socialName)) if ((socialNameChanged || dataCheckedChanged) && !isAlpha(socialName))
throw new UserError('The socialName has an invalid format'); throw new UserError(`The social name has an invalid format`);
if (changes.salesPerson === null) { if (changes.salesPerson === null) {
changes.credit = 0; changes.credit = 0;
@ -267,7 +284,7 @@ module.exports = Self => {
replyTo: worker.email replyTo: worker.email
}; };
await got.get(`${origin}/api/email/payment-update`, { await got.get(`${origin}/api/email/payment-update`, {
query: params searchParams: params
}); });
} }

View File

@ -16,10 +16,7 @@
"uri": { "uri": {
"type": "String" "type": "String"
}, },
"user": { "apiKey": {
"type": "String"
},
"password": {
"type": "String" "type": "String"
}, },
"title": { "title": {

View File

@ -26,8 +26,7 @@
"required": true "required": true
}, },
"statusCode": { "statusCode": {
"type": "Number", "type": "Number"
"required": true
}, },
"status": { "status": {
"type": "String" "type": "String"

View File

@ -82,7 +82,7 @@ class Controller extends Dialog {
} }
set amountToReturn(value) { set amountToReturn(value) {
if (!value) return; if (isNaN(value)) return;
value = value.toFixed(2); value = value.toFixed(2);

View File

@ -35,6 +35,13 @@
{{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) &#x2192; {{::agencyModeName}} - {{::warehouseInName}} ({{::shipped | date: 'dd/MM/yyyy'}}) &#x2192;
{{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}}) {{::warehouseOutName}} ({{::landed | date: 'dd/MM/yyyy'}})
</tpl-item> </tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog($ctrl.entry.travelFk)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete> </vn-autocomplete>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
@ -121,4 +128,94 @@
ng-click="watcher.loadOriginalData()"> ng-click="watcher.loadOriginalData()">
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>
<!-- Filter travel dialog -->
<vn-dialog
vn-id="filterDialog"
message="Filter travel">
<tpl-body class="travelFilter">
<vn-horizontal>
<vn-autocomplete
label="Agency"
ng-model="$ctrl.travelFilterParams.agencyFk"
url="AgencyModes"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Warehouse Out"
ng-model="$ctrl.travelFilterParams.warehouseOutFk"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Warehouse In"
ng-model="$ctrl.travelFilterParams.warehouseInFk"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-date-picker
label="Shipped"
ng-model="$ctrl.travelFilterParams.shipped">
</vn-date-picker>
<vn-date-picker
label="Landed"
ng-model="$ctrl.travelFilterParams.landed">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal class="vn-mb-md">
<vn-button vn-none
label="Search"
ng-click="$ctrl.filter()">
</vn-button>
</vn-horizontal>
<vn-crud-model
vn-id="travelsModel"
url="Travels"
filter="$ctrl.travelFilter"
data="travels"
limit="10">
</vn-crud-model>
<vn-data-viewer
model="travelsModel"
class="vn-w-lg">
<vn-table class="scrollable">
<vn-thead>
<vn-tr>
<vn-th shrink>ID</vn-th>
<vn-th expand>Agency</vn-th>
<vn-th expand>Warehouse Out</vn-th>
<vn-th expand>Warehouse In</vn-th>
<vn-th expand>Shipped</vn-th>
<vn-th expand>Landed</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="travel in travels"
class="clickable vn-tr search-result"
ng-click="$ctrl.selectTravel(travel.id)">
<vn-td shrink>
<span
vn-click-stop="travelDescriptor.show($event, travel.id)"
class="link">
{{::travel.id}}
</span>
</vn-td>
<vn-td expand>{{::travel.agency.name}}</vn-td>
<vn-td expand>{{::travel.warehouseOut.name}}</vn-td>
<vn-td expand>{{::travel.warehouseIn.name}}</vn-td>
<vn-td expand>{{::travel.shipped | date: 'dd/MM/yyyy'}}</vn-td>
<vn-td expand>{{::travel.landed | date: 'dd/MM/yyyy'}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-travel-descriptor-popover
vn-id="travel-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-travel-descriptor-popover>
</tpl-body>
</vn-dialog>

View File

@ -1,10 +1,68 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
showFilterDialog(travel) {
this.activeTravel = travel;
this.travelFilterParams = {};
this.travelFilter = {
include: [
{
relation: 'agency',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseIn',
scope: {
fields: ['name']
}
},
{
relation: 'warehouseOut',
scope: {
fields: ['name']
}
}
]
};
this.$.filterDialog.show();
}
selectTravel(id) {
this.entry.travelFk = id;
this.$.filterDialog.hide();
}
filter() {
const filter = this.travelFilter;
const params = this.travelFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'agencyFk':
case 'warehouseInFk':
case 'warehouseOutFk':
case 'shipped':
case 'landed':
where[key] = value;
}
}
filter.where = where;
this.$.travelsModel.applyFilter(filter);
}
}
ngModule.vnComponent('vnEntryBasicData', { ngModule.vnComponent('vnEntryBasicData', {
template: require('./index.html'), template: require('./index.html'),
controller: Section,
bindings: { bindings: {
entry: '<' entry: '<'
} },
controller: Controller
}); });

View File

@ -0,0 +1,3 @@
.travelFilter{
width: 950px;
}

View File

@ -179,7 +179,7 @@
ng-click="$ctrl.selectItem(item.id)"> ng-click="$ctrl.selectItem(item.id)">
<vn-td shrink> <vn-td shrink>
<span <span
ng-click="itemDescriptor.show($event, item.id)" vn-click-stop="itemDescriptor.show($event, item.id)"
class="link"> class="link">
{{::item.id}} {{::item.id}}
</span> </span>

View File

@ -0,0 +1,43 @@
module.exports = Self => {
Self.remoteMethod('getTotals', {
description: 'Return totals for an invoiceIn',
accessType: 'READ',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'invoiceIn id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: '/:id/getTotals',
verb: 'GET'
}
});
Self.getTotals = async(id, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [result] = await Self.rawSql(`
SELECT iit.*,
SUM(iidd.amount) totalDueDay
FROM vn.invoiceIn ii
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) totalVat
FROM vn.invoiceInTax iit
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE iit.invoiceInFk = ?) iit ON TRUE
LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id
WHERE
ii.id = ?`, [id, id]);
return result;
};
};

View File

@ -0,0 +1,21 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceIn getTotals()', () => {
it('should check that returns invoiceIn totals', async() => {
const invoiceInId = 1;
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
const invoiceIntotals = await models.InvoiceIn.getTotals(invoiceInId, options);
expect(typeof invoiceIntotals.totalTaxableBase).toBe('number');
expect(invoiceIntotals.totalTaxableBase).toEqual(invoiceIntotals.totalDueDay);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,34 @@
const models = require('vn-loopback/server/server').models;
describe('invoiceIn toBook()', () => {
it('should check that invoiceIn is booked', async() => {
const userId = 1;
const ctx = {
req: {
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
};
const invoiceInId = 1;
const tx = await models.InvoiceIn.beginTransaction({});
const options = {transaction: tx};
try {
const invoiceInBefore = await models.InvoiceIn.findById(invoiceInId, null, options);
expect(invoiceInBefore.isBooked).toEqual(false);
await models.InvoiceIn.toBook(ctx, invoiceInId, options);
const invoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options);
expect(invoiceIn.isBooked).toEqual(true);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -20,6 +20,7 @@ module.exports = Self => {
}); });
Self.summary = async(id, options) => { Self.summary = async(id, options) => {
const models = Self.app.models;
const myOptions = {}; const myOptions = {};
if (typeof options == 'object') if (typeof options == 'object')
@ -85,25 +86,9 @@ module.exports = Self => {
} }
] ]
}; };
let summaryObj = await models.InvoiceIn.findById(id, filter, myOptions);
let summaryObj = await Self.app.models.InvoiceIn.findById(id, filter, myOptions); summaryObj.totals = await models.InvoiceIn.getTotals(id, myOptions);
summaryObj.totals = await getTotals(id);
return summaryObj; return summaryObj;
}; };
async function getTotals(invoiceInFk) {
return (await Self.rawSql(`
SELECT iit.*,
SUM(iidd.amount) totalDueDay
FROM vn.invoiceIn ii
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) totalVat
FROM vn.invoiceInTax iit
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE iit.invoiceInFk = ?) iit ON TRUE
LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id
WHERE
ii.id = ?`, [invoiceInFk, invoiceInFk]))[0];
}
}; };

View File

@ -0,0 +1,42 @@
module.exports = Self => {
Self.remoteMethodCtx('toBook', {
description: 'To book the invoiceIn',
accessType: 'WRITE',
accepts: {
arg: 'id',
type: 'number',
required: true,
description: 'The invoiceIn id',
http: {source: 'path'}
},
returns: {
type: 'object',
root: true
},
http: {
path: '/:id/toBook',
verb: 'POST'
}
});
Self.toBook = async(ctx, id, options) => {
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
await Self.rawSql(`CALL vn.invoiceInBookingMain(?)`, [id], myOptions);
if (tx) await tx.commit();
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -2,4 +2,6 @@ module.exports = Self => {
require('../methods/invoice-in/filter')(Self); require('../methods/invoice-in/filter')(Self);
require('../methods/invoice-in/summary')(Self); require('../methods/invoice-in/summary')(Self);
require('../methods/invoice-in/clone')(Self); require('../methods/invoice-in/clone')(Self);
require('../methods/invoice-in/toBook')(Self);
require('../methods/invoice-in/getTotals')(Self);
}; };

View File

@ -1,8 +1,16 @@
<vn-descriptor-content module="invoiceIn" description="$ctrl.invoiceIn.supplierRef"> <vn-descriptor-content module="invoiceIn" description="$ctrl.invoiceIn.supplierRef">
<slot-menu> <slot-menu>
<vn-item
ng-click="$ctrl.checkToBook()"
vn-acl="administrative"
ng-hide="$ctrl.invoiceIn.isBooked == true"
translate>
To book
</vn-item>
<vn-item <vn-item
ng-click="deleteConfirmation.show()" ng-click="deleteConfirmation.show()"
vn-acl="invoicing" vn-acl="administrative"
vn-acl-action="remove" vn-acl-action="remove"
name="deleteInvoice" name="deleteInvoice"
translate> translate>
@ -10,7 +18,7 @@
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="cloneConfirmation.show()" ng-click="cloneConfirmation.show()"
vn-acl="invoicing" vn-acl="administrative"
name="cloneInvoice" name="cloneInvoice"
translate> translate>
Clone Invoice Clone Invoice
@ -26,7 +34,7 @@
</vn-label-value> </vn-label-value>
<vn-label-value label="Supplier"> <vn-label-value label="Supplier">
<span ng-click="supplierDescriptor.show($event, $ctrl.invoiceIn.supplier.id)" class="link"> <span ng-click="supplierDescriptor.show($event, $ctrl.invoiceIn.supplier.id)" class="link">
{{$ctrl.invoiceIn.supplier.nickname}} {{$ctrl.invoiceIn.supplier.nickname}}
</span> </span>
</vn-label-value> </vn-label-value>
</div> </div>
@ -46,7 +54,9 @@
icon="icon-invoice-in"> icon="icon-invoice-in">
</vn-quick-link> </vn-quick-link>
</div> </div>
</div> </div>
</slot-body> </slot-body>
</vn-descriptor-content> </vn-descriptor-content>
<vn-confirm <vn-confirm
@ -62,3 +72,8 @@
<vn-supplier-descriptor-popover <vn-supplier-descriptor-popover
vn-id="supplierDescriptor"> vn-id="supplierDescriptor">
</vn-supplier-descriptor-popover> </vn-supplier-descriptor-popover>
<vn-confirm
vn-id="confirm-toBookAnyway"
message="Are you sure you want to book this invoice?"
on-accept="$ctrl.onAcceptToBook()">
</vn-confirm>

View File

@ -51,6 +51,43 @@ class Controller extends Descriptor {
return this.getData(`InvoiceIns/${this.id}`, {filter}) return this.getData(`InvoiceIns/${this.id}`, {filter})
.then(res => this.entity = res.data); .then(res => this.entity = res.data);
} }
checkToBook() {
let message = '';
const id = this.invoiceIn.id;
this.$q.all([
this.$http.get(`InvoiceIns/${this.id}/getTotals`)
.then(res => {
const taxableBaseNotEqualDueDay = res.data.totalDueDay != res.data.totalTaxableBase;
const vatNotEqualDueDay = res.data.totalDueDay != res.data.totalVat;
if (taxableBaseNotEqualDueDay && vatNotEqualDueDay)
message += 'amountsDoNotMatch';
}),
this.$http.get('InvoiceInDueDays/count', {
filter: {
where: {
invoiceInFk: id,
dueDated: {gte: new Date()}
}
}})
.then(res => {
if (res.data)
message += 'future payments';
})
]).finally(() => {
if (message.length)
this.$.confirmToBookAnyway.show();
else
this.onAcceptToBook();
});
}
onAcceptToBook() {
this.$http.post(`InvoiceIns/${this.id}/toBook`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn booked')));
}
} }
ngModule.vnComponent('vnInvoiceInDescriptor', { ngModule.vnComponent('vnInvoiceInDescriptor', {

View File

@ -8,19 +8,70 @@ describe('vnInvoiceInDescriptor', () => {
beforeEach(inject(($componentController, _$httpBackend_) => { beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
controller = $componentController('vnInvoiceInDescriptor', {$element: null}); const $element = angular.element('<vn-invoice-in-descriptor></vn-invoice-in-descriptor>');
controller = $componentController('vnInvoiceInDescriptor', {$element});
controller.invoiceIn = {id: 1};
$httpBackend.when('GET', `InvoiceIns/${controller.invoiceIn.id}`).respond({id: 1});
})); }));
describe('loadData()', () => { describe('loadData()', () => {
it(`should perform a get query to store the invoice in data into the controller`, () => { it(`should perform a get query to store the invoice in data into the controller`, () => {
const id = 1; expect(controller.invoiceIn).toEqual({id: 1});
const response = {id: 1}; });
});
$httpBackend.expectGET(`InvoiceIns/${id}`).respond(response); describe('onAcceptToBook()', () => {
controller.id = id; it(`should perform a post query to book the invoice`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$state.reload = jest.fn();
const id = 1;
$httpBackend.expectPOST(`InvoiceIns/${id}/toBook`).respond();
controller.onAcceptToBook();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.invoiceIn).toEqual(response); expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('InvoiceIn booked');
});
});
describe('checkToBook()', () => {
it(`should show a warning before book`, () => {
controller.$.confirmToBookAnyway = {show: () => {}};
jest.spyOn(controller.$.confirmToBookAnyway, 'show');
const invoceInId = 1;
const data = {
totalDueDay: 'an amount',
totalTaxableBase: 'distinct amount'
};
$httpBackend.expectGET(`InvoiceIns/${invoceInId}/getTotals`).respond(data);
$httpBackend.expectGET(`InvoiceInDueDays/count`).respond();
controller.checkToBook();
$httpBackend.flush();
expect(controller.$.confirmToBookAnyway.show).toHaveBeenCalledWith();
});
it(`should call onAcceptToBook`, () => {
controller.onAcceptToBook = jest.fn();
const invoceInId = 1;
const data = {
totalDueDay: 'same amount',
totalTaxableBase: 'same amount'
};
$httpBackend.expectGET(`InvoiceIns/${invoceInId}/getTotals`).respond(data);
$httpBackend.expectGET(`InvoiceInDueDays/count`).respond();
controller.checkToBook();
$httpBackend.flush();
expect(controller.onAcceptToBook).toHaveBeenCalledWith();
}); });
}); });
}); });

View File

@ -1,13 +1,16 @@
InvoiceIn: Facturas recibidas
Search invoices in by reference: Buscar facturas recibidas por referencia
Entries list: Listado de entradas
InvoiceIn deleted: Factura eliminada
Remove tax: Quitar iva
Add tax: Añadir iva Add tax: Añadir iva
Amounts do not match: La BI no coincide con el vencimiento ni con el total
Due day: Vencimiento
Entries list: Listado de entradas
Foreign value: Divisa
InvoiceIn: Facturas recibidas
InvoiceIn cloned: Factura clonada
InvoiceIn deleted: Factura eliminada
Invoice list: Listado de facturas recibidas
InvoiceIn booked: Factura contabilizada
Remove tax: Quitar iva
Sage tax: Sage iva Sage tax: Sage iva
Sage transaction: Sage transaccion Sage transaction: Sage transaccion
Foreign value: Divisa Search invoices in by reference: Buscar facturas recibidas por referencia
Due day: Vencimiento To book: Contabilizar
Invoice list: Listado de facturas recibidas
InvoiceIn cloned: Factura clonada

View File

@ -21,6 +21,20 @@
ng-model="filter.fi"> ng-model="filter.fi">
</vn-textfield> </vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one ng-model="filter.supplierFk"
url="Suppliers"
show-field="nickname"
search-function="{or: [{id: $search}, {nickname: {like: '%'+ $search +'%'}}]}"
value-field="id"
order="nickname"
label="Supplier">
<tpl-item>
{{::id}} - {{::nickname}}
</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-textfield <vn-textfield
vn-one vn-one

View File

@ -1,7 +1,7 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra'); const fs = require('fs-extra');
const got = require('got');
const path = require('path'); const path = require('path');
const axios = require('axios');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('createPdf', { Self.remoteMethodCtx('createPdf', {
@ -57,39 +57,37 @@ module.exports = Self => {
hasPdf: true hasPdf: true
}, myOptions); }, myOptions);
const response = got.stream(`${origin}/api/report/invoice`, { return axios.get(`${origin}/api/report/invoice`, {
query: { responseType: 'stream',
params: {
authorization: auth.id, authorization: auth.id,
invoiceId: id invoiceId: id
} }
}); }).then(async response => {
const issued = invoiceOut.issued;
const year = issued.getFullYear().toString();
const month = (issued.getMonth() + 1).toString();
const day = issued.getDate().toString();
const issued = invoiceOut.issued; const container = await models.InvoiceContainer.container(year);
const year = issued.getFullYear().toString(); const rootPath = container.client.root;
const month = (issued.getMonth() + 1).toString(); const fileName = `${year}${invoiceOut.ref}.pdf`;
const day = issued.getDate().toString(); const src = path.join(rootPath, year, month, day);
fileSrc = path.join(src, fileName);
const container = await models.InvoiceContainer.container(year); await fs.mkdir(src, {recursive: true});
const rootPath = container.client.root;
const fileName = `${year}${invoiceOut.ref}.pdf`;
const src = path.join(rootPath, year, month, day);
fileSrc = path.join(src, fileName);
await fs.mkdir(src, {recursive: true}); if (tx) await tx.commit();
if (tx) await tx.commit(); response.data.pipe(fs.createWriteStream(fileSrc));
}).catch(async e => {
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
const writeStream = fs.createWriteStream(fileSrc); throw e;
writeStream.on('open', () => response.pipe(writeStream));
writeStream.on('finish', () => writeStream.end());
return new Promise(resolve => {
writeStream.on('close', () => resolve(invoiceOut));
}); });
} catch (e) { } catch (e) {
if (tx) await tx.rollback(); if (tx) await tx.rollback();
if (fs.existsSync(fileSrc))
await fs.unlink(fileSrc);
throw e; throw e;
} }
}; };

View File

@ -1,24 +1,28 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const got = require('got'); const LoopBackContext = require('loopback-context');
const fs = require('fs-extra'); const fs = require('fs-extra');
const axios = require('axios');
describe('InvoiceOut createPdf()', () => { describe('InvoiceOut createPdf()', () => {
const userId = 1; const userId = 1;
const ctx = { const activeCtx = {
req: { accessToken: {userId: userId, id: 'DEFAULT_TOKEN'},
headers: {origin: 'http://localhost:5000'}
accessToken: {userId: userId},
headers: {origin: 'http://localhost:5000'},
}
}; };
const ctx = {req: activeCtx};
it('should create a new PDF file and set true the hasPdf property', async() => { it('should create a new PDF file and set true the hasPdf property', async() => {
const invoiceId = 1; const invoiceId = 1;
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
const response = { const response = {
pipe: () => {}, data: {
on: () => {}, pipe: () => {},
on: () => {},
}
}; };
spyOn(got, 'stream').and.returnValue(response); spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(response)));
spyOn(models.InvoiceContainer, 'container').and.returnValue({ spyOn(models.InvoiceContainer, 'container').and.returnValue({
client: {root: '/path'} client: {root: '/path'}
}); });
@ -32,9 +36,10 @@ describe('InvoiceOut createPdf()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
try { try {
const result = await models.InvoiceOut.createPdf(ctx, invoiceId, options); await models.InvoiceOut.createPdf(ctx, invoiceId, options);
const invoiceOut = await models.InvoiceOut.findById(invoiceId, null, options);
expect(result.hasPdf).toBe(true); expect(invoiceOut.hasPdf).toBe(true);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -23,6 +23,6 @@ describe('InvoiceOut download()', () => {
const result = await models.InvoiceOut.download(ctx, invoiceId); const result = await models.InvoiceOut.download(ctx, invoiceId);
expect(result[1]).toEqual('application/pdf'); expect(result[1]).toEqual('application/pdf');
expect(result[2]).toEqual('filename="2021T1111111.pdf"'); expect(result[2]).toMatch(/filename="\d{4}T1111111.pdf"/);
}); });
}); });

View File

@ -19,6 +19,10 @@ class Controller extends Section {
this.id = value.id; this.id = value.id;
} }
get hasInvoicing() {
return this.aclService.hasAny(['invoicing']);
}
loadData() { loadData() {
const filter = { const filter = {
include: [ include: [
@ -51,8 +55,13 @@ class Controller extends Section {
deleteInvoiceOut() { deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.id}/delete`) return this.$http.post(`InvoiceOuts/${this.invoiceOut.id}/delete`)
.then(() => this.$state.go('invoiceOut.index')) .then(() => {
.then(() => this.$state.reload()) const isInsideInvoiceOut = this.$state.current.name.startsWith('invoiceOut');
if (isInsideInvoiceOut)
this.$state.go('invoiceOut.index');
else
this.$state.reload();
})
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted'))); .then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
} }

View File

@ -50,6 +50,35 @@ describe('vnInvoiceOutDescriptorMenu', () => {
}); });
}); });
describe('deleteInvoiceOut()', () => {
it(`should make a query and call showSuccess()`, () => {
controller.invoiceOut = invoiceOut;
controller.$state.reload = jest.fn();
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond();
controller.deleteInvoiceOut();
$httpBackend.flush();
expect(controller.$state.reload).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
it(`should make a query and call showSuccess() after state.go if the state wasn't in invoiceOut module`, () => {
controller.invoiceOut = invoiceOut;
jest.spyOn(controller.$state, 'go').mockReturnValue('ok');
jest.spyOn(controller.vnApp, 'showSuccess');
controller.$state.current.name = 'invoiceOut.card.something';
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond();
controller.deleteInvoiceOut();
$httpBackend.flush();
expect(controller.$state.go).toHaveBeenCalledWith('invoiceOut.index');
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('sendPdfInvoice()', () => { describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => { it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage'); jest.spyOn(controller.vnApp, 'showMessage');

View File

@ -14,4 +14,4 @@ InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura? Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate PDF invoice: Regenerar PDF factura Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
The email can't be empty: El correo no puede estar vacío The email can't be empty: El correo no puede estar vacío

View File

@ -112,7 +112,10 @@ module.exports = Self => {
case 'search': case 'search':
return /^\d+$/.test(value) return /^\d+$/.test(value)
? {or: [{'i.id': value}, codeWhere]} ? {or: [{'i.id': value}, codeWhere]}
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]}; : {or: [
{'i.name': {like: `%${value}%`}},
{'i.longName': {like: `%${value}%`}},
codeWhere]};
case 'id': case 'id':
case 'isActive': case 'isActive':
case 'typeFk': case 'typeFk':

View File

@ -296,7 +296,7 @@
ng-click="$ctrl.selectItem(item.id)"> ng-click="$ctrl.selectItem(item.id)">
<vn-td shrink> <vn-td shrink>
<span <span
ng-click="itemDescriptor.show($event, item.id)" vn-click-stop="itemDescriptor.show($event, item.id)"
class="link"> class="link">
{{::item.id}} {{::item.id}}
</span> </span>

View File

@ -5,6 +5,7 @@ Search tickets: Buscar tickets
Delete selected elements: Eliminar los elementos seleccionados 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? 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 Component lack: Faltan componentes
Ticket too little: Ticket demasiado pequeño
Minimize/Maximize: Minimizar/Maximizar Minimize/Maximize: Minimizar/Maximizar
Problems: Problemas Problems: Problemas
Theoretical: Teórica Theoretical: Teórica

View File

@ -1,8 +1,8 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="SalesMonitors/salesFilter" url="SalesMonitors/salesFilter"
limit="20" auto-load="false"
order="shipped DESC, theoreticalHour, id"> limit="20">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
<vn-searchbar <vn-searchbar
@ -122,6 +122,12 @@
class="bright" class="bright"
icon="icon-components"> icon="icon-components">
</vn-icon> </vn-icon>
<vn-icon
ng-show="::ticket.isTooLittle"
translate-attr="{title: 'Ticket too little'}"
class="bright"
icon="icon-isTooLittle">
</vn-icon>
</td> </td>
<td> <td>
<span <span

View File

@ -10,7 +10,12 @@
], ],
"card": [] "card": []
}, },
"keybindings": [], "keybindings": [
{
"key": "m",
"state": "monitor.index"
}
],
"routes": [ "routes": [
{ {
"url": "/monitor", "url": "/monitor",

View File

@ -15,6 +15,12 @@
{"state": "order.card.line", "icon": "icon-lines"} {"state": "order.card.line", "icon": "icon-lines"}
] ]
}, },
"keybindings": [
{
"key": "o",
"state": "order.index"
}
],
"routes": [ "routes": [
{ {
"url": "/order", "url": "/order",

View File

@ -38,7 +38,6 @@ module.exports = Self => {
'payDemFk', 'payDemFk',
'payDay', 'payDay',
'account', 'account',
'isFarmer',
'sageTaxTypeFk', 'sageTaxTypeFk',
'sageTransactionTypeFk', 'sageTransactionTypeFk',
'sageWithholdingFk', 'sageWithholdingFk',
@ -102,6 +101,11 @@ module.exports = Self => {
] ]
}; };
let supplier = await Self.app.models.Supplier.findOne(filter); let supplier = await Self.app.models.Supplier.findOne(filter);
const farmerCode = 2;
if (supplier.sageWithholdingFk == farmerCode)
supplier.isFarmer = true;
return supplier; return supplier;
}; };
}; };

View File

@ -25,4 +25,12 @@ describe('Supplier getSummary()', () => {
expect(payMethod.name).toEqual('PayMethod one'); expect(payMethod.name).toEqual('PayMethod one');
}); });
it(`should get if supplier is farmer by sageWithholdingFk`, async() => {
const supplier = await app.models.Supplier.findById(2);
const supplierSummary = await app.models.Supplier.getSummary(2);
expect(supplier.isFarmer).toBeUndefined();
expect(supplierSummary.isFarmer).toEqual(true);
});
}); });

View File

@ -7,12 +7,14 @@ module.exports = Self => {
}); });
async function ibanValidation(err, done) { async function ibanValidation(err, done) {
let filter = { const supplier = await Self.app.models.Supplier.findById(this.supplierFk);
const filter = {
fields: ['code'], fields: ['code'],
where: {id: this.countryFk} where: {id: supplier.countryFk}
}; };
let country = await Self.app.models.Country.findOne(filter);
let code = country ? country.code.toLowerCase() : null; const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
if (code != 'es') if (code != 'es')
return done(); return done();

View File

@ -114,6 +114,6 @@ module.exports = Self => {
&& orgData.socialName != socialName; && orgData.socialName != socialName;
if ((socialNameChanged) && !isAlpha(socialName)) if ((socialNameChanged) && !isAlpha(socialName))
throw new UserError('The socialName has an invalid format'); throw new UserError('The social name has an invalid format');
}); });
}; };

View File

@ -27,9 +27,6 @@
"nif": { "nif": {
"type": "string" "type": "string"
}, },
"isFarmer": {
"type": "boolean"
},
"phone": { "phone": {
"type": "number" "type": "number"
}, },

View File

@ -0,0 +1,81 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('payBack', {
description: 'Create ticket with the selected lines changing the sign to the quantites',
accessType: 'WRITE',
accepts: [{
arg: 'sales',
description: 'The sales',
type: ['object'],
required: true
},
{
arg: 'ticketId',
type: 'number',
required: true,
description: 'The ticket id'
}],
returns: {
type: 'number',
root: true
},
http: {
path: `/payBack`,
verb: 'post'
}
});
Self.payBack = async(ctx, sales, ticketId, options) => {
const myOptions = {};
let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const salesIds = [];
const params = [];
const userId = ctx.req.accessToken.userId;
const isClaimManager = await Self.app.models.Account.hasRole(userId, 'claimManager');
const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant');
const hasValidRole = isClaimManager || isSalesAssistant;
if (!hasValidRole)
throw new UserError(`You don't have privileges to create pay back`);
sales.forEach(sale => {
salesIds.push(sale.id);
params.push('?');
});
const paramsString = params.join();
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
SELECT s.id, s.itemFk, - s.quantity, s.concept, s.price, s.discount
FROM sale s
WHERE s.id IN (${paramsString});
CALL vn.ticket_doRefund(${ticketId}, @newTicket);
DROP TEMPORARY TABLE tmp.sale;`;
await Self.rawSql(query, salesIds, myOptions);
const [newTicket] = await Self.rawSql('SELECT @newTicket id', null, myOptions);
ticketId = newTicket.id;
if (tx) await tx.commit();
return ticketId;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,54 @@
const models = require('vn-loopback/server/server').models;
describe('sale payBack()', () => {
it('should create ticket with the selected lines changing the sign to the quantites', async() => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 9}}};
const ticketId = 11;
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
try {
const options = {transaction: tx};
const response = await models.Sale.payBack(ctx, sales, ticketId, options);
const [newTicketId] = await models.Sale.rawSql('SELECT MAX(t.id) id FROM vn.ticket t;', null, options);
expect(response).toEqual(newTicketId.id);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it(`should throw an error if the user doesn't have privileges to create a pay back`, async() => {
const tx = await models.Sale.beginTransaction({});
const ctx = {req: {accessToken: {userId: 1}}};
const ticketId = 11;
const sales = [
{id: 7, ticketFk: 11}
];
let error;
try {
const options = {transaction: tx};
await models.Sale.payBack(ctx, sales, ticketId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toBeDefined();
expect(error.message).toEqual(`You don't have privileges to create pay back`);
});
});

View File

@ -118,10 +118,19 @@ module.exports = Self => {
const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions); const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions);
if (!isProductionBoss) { if (!isProductionBoss) {
const zoneShipped = await models.Agency.getShipped(args.landed, args.addressFk, args.agencyModeFk, args.warehouseFk, myOptions); const zoneShipped = await models.Agency.getShipped(
args.landed,
args.addressFk,
args.agencyModeFk,
args.warehouseFk,
myOptions);
if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) if (!zoneShipped || zoneShipped.zoneFk != args.zoneFk) {
throw new UserError(`You don't have privileges to change the zone`); const error = `You don't have privileges to change the zone or
for these parameters there are more than one shipping options, talk to agencies`;
throw new UserError(error);
}
} }
if (args.isWithoutNegatives) { if (args.isWithoutNegatives) {
const query = `CALL ticket_getVisibleAvailable(?,?)`; const query = `CALL ticket_getVisibleAvailable(?,?)`;

View File

@ -9,10 +9,14 @@ module.exports = Self => {
description: 'ticket id', description: 'ticket id',
http: {source: 'path'} http: {source: 'path'}
}], }],
returns: { returns: [{
type: ['Object'], arg: 'saleVolume',
root: true type: ['object']
}, },
{
arg: 'packingTypeVolume',
type: ['object']
}],
http: { http: {
path: `/:id/getVolume`, path: `/:id/getVolume`,
verb: 'GET' verb: 'GET'
@ -25,7 +29,21 @@ module.exports = Self => {
if (typeof options == 'object') if (typeof options == 'object')
Object.assign(myOptions, options); Object.assign(myOptions, options);
return Self.rawSql(`SELECT * FROM vn.saleVolume const saleVolume = await Self.rawSql(`
WHERE ticketFk = ?`, [ticketFk], myOptions); SELECT saleFk, volume
FROM vn.saleVolume
WHERE ticketFk = ?`, [ticketFk], myOptions);
const packingTypeVolume = await Self.rawSql(`
SELECT s.itemPackingTypeFk code,
i.description,
SUM(s.volume) volume
FROM vn.saleVolume s
LEFT JOIN vn.itemPackingType i
ON i.code = s.itemPackingTypeFk
WHERE s.ticketFk = ?
GROUP BY s.itemPackingTypeFk`, [ticketFk], myOptions);
return [saleVolume, packingTypeVolume];
}; };
}; };

View File

@ -87,8 +87,12 @@ module.exports = Self => {
args.warehouseId, args.warehouseId,
myOptions); myOptions);
if (!zoneShipped || zoneShipped.zoneFk != args.zoneId) if (!zoneShipped || zoneShipped.zoneFk != args.zoneId) {
throw new UserError(`You don't have privileges to change the zone`); const error = `You don't have privileges to change the zone or
for these parameters there are more than one shipping options, talk to agencies`;
throw new UserError(error);
}
} }
const items = await models.Sale.find({ const items = await models.Sale.find({

View File

@ -8,9 +8,15 @@ describe('ticket getVolume()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ticketId = 1; const ticketId = 1;
const result = await models.Ticket.getVolume(ticketId, options); const expectedSaleVolume = 1.09;
const expectedPackingTypeVolume = 0.028;
expect(result[0].volume).toEqual(1.09); const result = await models.Ticket.getVolume(ticketId, options);
const [saleVolume] = result[0];
const [packingTypeVolume] = result[1];
expect(saleVolume.volume).toEqual(expectedSaleVolume);
expect(packingTypeVolume.volume).toEqual(expectedPackingTypeVolume);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -58,13 +58,63 @@ describe('sale transferSales()', () => {
expect(error.message).toEqual(`The sales of the receiver ticket can't be modified`); expect(error.message).toEqual(`The sales of the receiver ticket can't be modified`);
}); });
it('should transfer the sales from one ticket to a new one then send them back and delete the created ticket', async() => { it('should throw an error if any of the sales has a claim', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const ticketId = 11;
const receiverTicketId = null;
const sales = await models.Ticket.getSales(ticketId, options);
await models.Ticket.transferSales(ctx, ticketId, receiverTicketId, sales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`Can't transfer claimed sales`);
});
it('should be able to transfer claimed sales if the role is claimManager', async() => {
const tx = await models.Ticket.beginTransaction({});
let error;
try {
const options = {transaction: tx};
const claimManagerId = 72;
const myActiveCtx = {
accessToken: {userId: claimManagerId},
};
const myCtx = {req: myActiveCtx};
const ticketId = 11;
const receiverTicketId = null;
const sales = await models.Ticket.getSales(ticketId, options);
await models.Ticket.transferSales(myCtx, ticketId, receiverTicketId, sales, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error).toBeUndefined();
});
it('should transfer the sales from a ticket to a new one', async() => {
const tx = await models.Ticket.beginTransaction({}); const tx = await models.Ticket.beginTransaction({});
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const formerTicketId = 11; const formerTicketId = 22;
let createdTicketId = undefined; let createdTicketId = undefined;
let formerTicketSales = await models.Ticket.getSales(formerTicketId, options); let formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
@ -77,23 +127,11 @@ describe('sale transferSales()', () => {
createdTicketId = createdTicket.id; createdTicketId = createdTicket.id;
formerTicketSales = await models.Ticket.getSales(formerTicketId, options); formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
createdTicketSales = await models.Ticket.getSales(createdTicketId, options); let createdTicketSales = await models.Ticket.getSales(createdTicketId, options);
expect(formerTicketSales.length).toEqual(0); expect(formerTicketSales.length).toEqual(0);
expect(createdTicketSales.length).toEqual(2); expect(createdTicketSales.length).toEqual(2);
await models.Ticket.transferSales(
ctx, createdTicketId, formerTicketId, createdTicketSales, options);
formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
createdTicketSales = await models.Ticket.getSales(createdTicketId, options);
createdTicket = await models.Ticket.findById(createdTicketId, null, options);
expect(createdTicket.isDeleted).toBeTruthy();
expect(formerTicketSales.length).toEqual(2);
expect(createdTicketSales.length).toEqual(0);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
await tx.rollback(); await tx.rollback();
@ -109,10 +147,9 @@ describe('sale transferSales()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const currentTicket = await models.Ticket.findById(11, null, options); const currentTicketId = 22;
const currentTicketSales = await models.Ticket.getSales(currentTicket.id, options); const currentTicketSales = await models.Ticket.getSales(currentTicketId, options);
const currentTicketId = currentTicket.id;
const receiverTicketId = undefined; const receiverTicketId = undefined;
currentTicketSales[0].quantity = 99; currentTicketSales[0].quantity = 99;
@ -135,7 +172,7 @@ describe('sale transferSales()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const formerTicketId = 11; const formerTicketId = 22;
let createdTicketId = undefined; let createdTicketId = undefined;
let formerTicketSales = await models.Ticket.getSales(formerTicketId, options); let formerTicketSales = await models.Ticket.getSales(formerTicketId, options);
@ -143,7 +180,7 @@ describe('sale transferSales()', () => {
const completeSaleId = formerTicketSales[1].id; const completeSaleId = formerTicketSales[1].id;
let partialSaleTotalQuantity = formerTicketSales[0].quantity; let partialSaleTotalQuantity = formerTicketSales[0].quantity;
expect(partialSaleTotalQuantity).toEqual(15); expect(partialSaleTotalQuantity).toEqual(30);
formerTicketSales[0].quantity = 1; formerTicketSales[0].quantity = 1;

View File

@ -75,6 +75,13 @@ module.exports = Self => {
for (const sale of originalSales) for (const sale of originalSales)
map.set(sale.id, sale); map.set(sale.id, sale);
const saleIds = sales.map(sale => sale.id);
const isClaimManager = await models.Account.hasRole(userId, 'claimManager');
const hasClaimedSales = await models.ClaimBeginning.findOne({where: {saleFk: {inq: saleIds}}});
if (hasClaimedSales && !isClaimManager)
throw new UserError(`Can't transfer claimed sales`);
for (const sale of sales) { for (const sale of sales) {
const originalSale = map.get(sale.id); const originalSale = map.get(sale.id);
const originalSaleData = { // <-- Loopback modifies original instance on save const originalSaleData = { // <-- Loopback modifies original instance on save

View File

@ -6,6 +6,7 @@ module.exports = Self => {
require('../methods/sale/updateQuantity')(Self); require('../methods/sale/updateQuantity')(Self);
require('../methods/sale/updateConcept')(Self); require('../methods/sale/updateConcept')(Self);
require('../methods/sale/recalculatePrice')(Self); require('../methods/sale/recalculatePrice')(Self);
require('../methods/sale/payBack')(Self);
require('../methods/sale/canEdit')(Self); require('../methods/sale/canEdit')(Self);
Self.validatesPresenceOf('concept', { Self.validatesPresenceOf('concept', {

View File

@ -149,7 +149,10 @@ class Controller extends Section {
return this.$http.post(`Tickets/${this.id}/setDeleted`) return this.$http.post(`Tickets/${this.id}/setDeleted`)
.then(() => this.reload()) .then(() => this.reload())
.then(() => { .then(() => {
this.$state.go('ticket.index'); const isInsideTicket = this.$state.current.name.startsWith('ticket');
if (isInsideTicket)
this.$state.go('ticket.index');
this.vnApp.showSuccess(this.$t('Ticket deleted. You can undo this action within the first hour')); this.vnApp.showSuccess(this.$t('Ticket deleted. You can undo this action within the first hour'));
}); });
} }

View File

@ -78,9 +78,22 @@ describe('Ticket Component vnTicketDescriptorMenu', () => {
describe('deleteTicket()', () => { describe('deleteTicket()', () => {
it('should make a query and call vnApp.showSuccess()', () => { it('should make a query and call vnApp.showSuccess()', () => {
jest.spyOn(controller, 'reload').mockReturnThis();
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expectPOST(`Tickets/${ticket.id}/setDeleted`).respond();
controller.deleteTicket();
$httpBackend.flush();
expect(controller.reload).toHaveBeenCalled();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
it(`should make a query and call showSuccess() after state.go if the state wasn't inside ticket module`, () => {
jest.spyOn(controller, 'reload').mockReturnThis(); jest.spyOn(controller, 'reload').mockReturnThis();
jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); jest.spyOn(controller.$state, 'go').mockReturnValue('ok');
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');
controller.$state.current.name = 'ticket.card.something';
$httpBackend.expectPOST(`Tickets/${ticket.id}/setDeleted`).respond(); $httpBackend.expectPOST(`Tickets/${ticket.id}/setDeleted`).respond();
controller.deleteTicket(); controller.deleteTicket();

View File

@ -37,6 +37,7 @@ Observation type: Tipo de observación
Original: Original Original: Original
Package size: Bultos Package size: Bultos
Package type: Tipo de porte Package type: Tipo de porte
Packing type: Encajado
Phone: Teléfono Phone: Teléfono
PPU: Ud. PPU: Ud.
Price: Precio Price: Precio

View File

@ -490,4 +490,11 @@
ng-if="$ctrl.isEditable && $ctrl.hasReserves()"> ng-if="$ctrl.isEditable && $ctrl.hasReserves()">
Unmark as reserved Unmark as reserved
</vn-item> </vn-item>
<vn-item translate
name="payBack"
ng-click="$ctrl.createPayBack()"
vn-acl="claimManager, salesAssistant"
vn-acl-action="remove">
Pay Back
</vn-item>
</vn-menu> </vn-menu>

View File

@ -40,7 +40,9 @@ class Controller extends Section {
const landedPlusWeek = new Date(this.ticket.landed); const landedPlusWeek = new Date(this.ticket.landed);
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7); landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
return landedPlusWeek >= new Date(); const hasClaimManagerRole = this.aclService.hasAny(['claimManager']);
return landedPlusWeek >= new Date() || hasClaimManagerRole;
} }
return false; return false;
} }
@ -460,6 +462,18 @@ class Controller extends Section {
}); });
} }
createPayBack() {
const sales = this.selectedValidSales();
if (!sales) return;
const params = {sales: sales, ticketId: this.ticket.id};
const query = `Sales/payBack`;
this.resetChanges();
this.$http.post(query, params).then(res => {
this.$state.go('ticket.card.sale', {id: res.data});
});
}
itemSearchFunc($search) { itemSearchFunc($search) {
return /^\d+$/.test($search) return /^\d+$/.test($search)
? {id: $search} ? {id: $search}

View File

@ -701,6 +701,22 @@ describe('Ticket', () => {
}); });
}); });
describe('createPayBack()', () => {
it('should make an HTTP POST query and then call to the $state go() method', () => {
jest.spyOn(controller, 'selectedValidSales').mockReturnValue(controller.sales);
jest.spyOn(controller, 'resetChanges');
jest.spyOn(controller.$state, 'go');
const expectedId = 9999;
$httpBackend.expect('POST', `Sales/payBack`).respond(200, expectedId);
controller.createPayBack();
$httpBackend.flush();
expect(controller.resetChanges).toHaveBeenCalledWith();
expect(controller.$state.go).toHaveBeenCalledWith('ticket.card.sale', {id: expectedId});
});
});
describe('itemSearchFunc()', () => { describe('itemSearchFunc()', () => {
it('should return the filter by id property for an input of a number', () => { it('should return the filter by id property for an input of a number', () => {
const itemId = 1; const itemId = 1;

View File

@ -35,4 +35,5 @@ Address: Dirección
Warehouse: Almacen Warehouse: Almacen
Agency: Agencia Agency: Agencia
Shipped: F. envio Shipped: F. envio
Packaging: Encajado Packaging: Encajado
Pay Back: Abono

View File

@ -11,14 +11,14 @@
Ticket #{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} Ticket #{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}}
({{$ctrl.summary.client.id}}) - {{$ctrl.summary.nickname}} ({{$ctrl.summary.client.id}}) - {{$ctrl.summary.nickname}}
</span> </span>
<vn-button <vn-button-menu
disabled="!$ctrl.isEditable" disabled="!$ctrl.isEditable"
class="flat" class="message"
style="color: inherit;" label="Change state"
label="SET OK" value-field="code"
ng-click="$ctrl.setOkState()" url="States/editableStates"
vn-tooltip="Change ticket state to 'Ok'"> on-change="$ctrl.changeState(value)">
</vn-button> </vn-button-menu>
<vn-ticket-descriptor-menu <vn-ticket-descriptor-menu
ng-if="!$ctrl.isTicketModule" ng-if="!$ctrl.isTicketModule"
ticket-id="$ctrl.summary.id" ticket-id="$ctrl.summary.id"

View File

@ -59,10 +59,11 @@ class Controller extends Summary {
this.$.invoiceOutDescriptor.show(event.target, this.summary.invoiceOut.id); this.$.invoiceOutDescriptor.show(event.target, this.summary.invoiceOut.id);
} }
setOkState() { changeState(value) {
const params = {ticketFk: 'id' in this.ticket ? this.ticket.id : this.$params.id}; const params = {
ticketFk: 'id' in this.ticket ? this.ticket.id : this.$params.id,
params.code = 'OK'; code: value
};
this.$http.post(`TicketTrackings/changeState`, params) this.$http.post(`TicketTrackings/changeState`, params)
.then(() => { .then(() => {

View File

@ -42,5 +42,20 @@ describe('Ticket', () => {
expect(controller.formattedAddress).toEqual('1007 Mountain Drive - 46060 - Gotham (Gotham)'); expect(controller.formattedAddress).toEqual('1007 Mountain Drive - 46060 - Gotham (Gotham)');
}); });
}); });
describe('changeState()', () => {
it('should change the state', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const value = 'myTicketState';
let res = {id: 1, nickname: 'myNickname'};
$httpBackend.when('GET', `Tickets/1/summary`).respond(200, res);
$httpBackend.expectPOST(`TicketTrackings/changeState`).respond(200, 'ok');
controller.changeState(value);
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
});
});
}); });
}); });

View File

@ -2,4 +2,5 @@ Address phone: Tel. consignatario
Address mobile: Móv. consignatario Address mobile: Móv. consignatario
Client phone: Tel. cliente Client phone: Tel. cliente
Client mobile: Móv. cliente Client mobile: Móv. cliente
Go to the ticket: Ir al ticket Go to the ticket: Ir al ticket
Change state: Cambiar estado

View File

@ -6,22 +6,17 @@
data="$ctrl.sales" data="$ctrl.sales"
limit="20"> limit="20">
</vn-crud-model> </vn-crud-model>
<vn-crud-model auto-load="true"
url="tickets/{{$ctrl.$params.id}}/getVolume"
data="$ctrl.volumes">
</vn-crud-model>
<mg-ajax path="tickets/{{$ctrl.$params.id}}/getTotalVolume" options="mgEdit"></mg-ajax>
<vn-vertical> <vn-vertical>
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
<div class="totalBox"> <div class="totalBox" ng-repeat="packingType in $ctrl.packingTypeVolume">
<vn-label-value label="Total" <vn-label-value label="Tipo"
value="{{::edit.model.totalVolume}}"> value="{{::packingType.description}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Cajas" <vn-label-value label="Volumen"
value="{{::edit.model.totalBoxes}}"> value="{{::packingType.volume}}">
</vn-label-value> </vn-label-value>
</div> </div>
</vn-horizontal> </vn-horizontal>
<vn-vertical> <vn-vertical>
<vn-table model="model"> <vn-table model="model">
@ -29,6 +24,7 @@
<vn-tr> <vn-tr>
<vn-th field="itemFk" number>Item</vn-th> <vn-th field="itemFk" number>Item</vn-th>
<vn-th field="concept" default-order="ASC">Description</vn-th> <vn-th field="concept" default-order="ASC">Description</vn-th>
<vn-th field="itemPackingTypeFk" number>Packing type</vn-th>
<vn-th field="quantity" number>Quantity</vn-th> <vn-th field="quantity" number>Quantity</vn-th>
<vn-th number>m³ per quantity</vn-th> <vn-th number>m³ per quantity</vn-th>
</vn-tr> </vn-tr>
@ -55,6 +51,7 @@
tabindex="-1"> tabindex="-1">
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td number>{{::sale.item.itemPackingTypeFk}}</vn-td>
<vn-td number>{{::sale.quantity}}</vn-td> <vn-td number>{{::sale.quantity}}</vn-td>
<vn-td number>{{::sale.saleVolume.volume | number:3}}</vn-td> <vn-td number>{{::sale.saleVolume.volume | number:3}}</vn-td>
</vn-tr> </vn-tr>

View File

@ -23,24 +23,19 @@ class Controller extends Section {
if (value) this.applyVolumes(); if (value) this.applyVolumes();
} }
get volumes() {
return this._volumes;
}
set volumes(value) {
this._volumes = value;
if (value) this.applyVolumes();
}
applyVolumes() { applyVolumes() {
if (!this.sales || !this.volumes) return; const ticket = this.sales[0].ticketFk;
this.$http.get(`Tickets/${ticket}/getVolume`).then(res => {
const saleVolume = res.data.saleVolume;
this.sales.forEach(sale => { const volumes = new Map();
this.volumes.forEach(volume => { for (const volume of saleVolume)
if (sale.id === volume.saleFk) volumes.set(volume.saleFk, volume);
sale.saleVolume = volume;
}); for (const sale of this.sales)
sale.saleVolume = volumes.get(sale.id);
this.packingTypeVolume = res.data.packingTypeVolume;
}); });
} }
} }

Some files were not shown because too many files have changed in this diff Show More