Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5056-gestion-vagones
gitea/salix/pipeline/head This commit looks good
Details
gitea/salix/pipeline/head This commit looks good
Details
This commit is contained in:
commit
a8f92b151d
|
@ -4,5 +4,6 @@
|
||||||
"files.eol": "\n",
|
"files.eol": "\n",
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll.eslint": true
|
"source.fixAll.eslint": true
|
||||||
}
|
},
|
||||||
|
"search.useIgnoreFiles": false
|
||||||
}
|
}
|
||||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -5,16 +5,24 @@ All notable changes to this project will be documented in this file.
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||||
|
|
||||||
|
## [2312.01] - 2023-04-06
|
||||||
|
|
||||||
|
### Added
|
||||||
|
-
|
||||||
|
|
||||||
|
### Changed
|
||||||
|
-
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
-
|
||||||
|
|
||||||
## [2310.01] - 2023-03-23
|
## [2310.01] - 2023-03-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
-
|
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
|
||||||
|
|
||||||
### Changed
|
|
||||||
-
|
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
|
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
|
||||||
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
|
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
|
||||||
|
|
||||||
## [2308.01] - 2023-03-09
|
## [2308.01] - 2023-03-09
|
||||||
|
|
|
@ -10,6 +10,7 @@ RUN apt-get update \
|
||||||
curl \
|
curl \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
gnupg2 \
|
gnupg2 \
|
||||||
|
graphicsmagick \
|
||||||
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
||||||
&& apt-get install -y --no-install-recommends nodejs \
|
&& apt-get install -y --no-install-recommends nodejs \
|
||||||
&& npm install -g npm@8.19.2
|
&& npm install -g npm@8.19.2
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('previousLabel', {
|
Self.remoteMethodCtx('previousLabel', {
|
||||||
description: 'Returns the previa label pdf',
|
description: 'Returns the previa label pdf',
|
||||||
|
@ -33,17 +31,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.previousLabel = async(ctx, id) => {
|
Self.previousLabel = (ctx, id) => Self.printReport(ctx, id, 'previa-label');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('previa-label', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="previa-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -131,7 +131,7 @@ module.exports = Self => {
|
||||||
WHERE u.id = ?`, [userId], options);
|
WHERE u.id = ?`, [userId], options);
|
||||||
|
|
||||||
let roles = [];
|
let roles = [];
|
||||||
for (role of result)
|
for (const role of result)
|
||||||
roles.push(role.name);
|
roles.push(role.name);
|
||||||
|
|
||||||
return roles;
|
return roles;
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
ALTER TABLE vn.invoiceOutSerial
|
||||||
|
ADD `type` ENUM('global', 'quick') DEFAULT NULL NULL;
|
||||||
|
|
||||||
|
UPDATE vn.invoiceOutSerial
|
||||||
|
SET type = 'global'
|
||||||
|
WHERE code IN ('A','V');
|
|
@ -2,13 +2,15 @@ DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getWeight`;
|
||||||
|
|
||||||
DELIMITER $$
|
DELIMITER $$
|
||||||
$$
|
$$
|
||||||
CREATE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getWeight`(vInvoice VARCHAR(15)) RETURNS decimal(10,2)
|
CREATE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getWeight`(
|
||||||
|
vInvoiceRef VARCHAR(15)
|
||||||
|
)RETURNS decimal(10,2)
|
||||||
READS SQL DATA
|
READS SQL DATA
|
||||||
BEGIN
|
BEGIN
|
||||||
/**
|
/**
|
||||||
* Calcula el peso de una factura emitida
|
* Calcula el peso de una factura emitida
|
||||||
*
|
*
|
||||||
* @param vInvoice Id de la factura
|
* @param vInvoiceRef referencia de la factura
|
||||||
* @return vTotalWeight peso de la factura
|
* @return vTotalWeight peso de la factura
|
||||||
*/
|
*/
|
||||||
DECLARE vTotalWeight DECIMAL(10,2);
|
DECLARE vTotalWeight DECIMAL(10,2);
|
||||||
|
@ -22,7 +24,7 @@ BEGIN
|
||||||
JOIN item i ON i.id = s.itemFk
|
JOIN item i ON i.id = s.itemFk
|
||||||
JOIN itemCost ic ON ic.itemFk = i.id
|
JOIN itemCost ic ON ic.itemFk = i.id
|
||||||
AND ic.warehouseFk = t.warehouseFk
|
AND ic.warehouseFk = t.warehouseFk
|
||||||
WHERE t.refFk = vInvoice
|
WHERE t.refFk = vInvoiceRef
|
||||||
AND i.intrastatFk;
|
AND i.intrastatFk;
|
||||||
|
|
||||||
RETURN vTotalWeight;
|
RETURN vTotalWeight;
|
|
@ -0,0 +1,6 @@
|
||||||
|
UPDATE `vn`.`report`
|
||||||
|
SET `method`='InvoiceOuts/{refFk}/invoice-out-pdf'
|
||||||
|
WHERE name='invoice';
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`printQueue` MODIFY COLUMN printerFk tinyint(3) unsigned DEFAULT 82 NOT NULL;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getMaxIssued`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getMaxIssued`(
|
||||||
|
vSerial VARCHAR(2),
|
||||||
|
vCompanyFk INT,
|
||||||
|
vYear INT
|
||||||
|
) RETURNS DATE
|
||||||
|
READS SQL DATA
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Retorna la fecha a partir de la cual es válido emitir una factura
|
||||||
|
*
|
||||||
|
* @param vSerial Serie de facturación
|
||||||
|
* @param vCompanyFk Empresa factura emitida
|
||||||
|
* @param vYear Año contable
|
||||||
|
* @return vInvoiceOutIssued fecha factura válida
|
||||||
|
*/
|
||||||
|
DECLARE vInvoiceOutIssued DATE;
|
||||||
|
DECLARE vFirstDayOfYear DATE;
|
||||||
|
|
||||||
|
SET vFirstDayOfYear := MAKEDATE(vYear, 1);
|
||||||
|
|
||||||
|
SELECT IFNULL(MAX(io.issued), vFirstDayOfYear) INTO vInvoiceOutIssued
|
||||||
|
FROM invoiceOut io
|
||||||
|
WHERE io.serial = vSerial
|
||||||
|
AND io.companyFk = vCompanyFk
|
||||||
|
AND io.issued BETWEEN vFirstDayOfYear
|
||||||
|
AND util.lastDayOfYear(vFirstDayOfYear);
|
||||||
|
|
||||||
|
RETURN vInvoiceOutIssued;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,258 @@
|
||||||
|
DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`(
|
||||||
|
vSerial VARCHAR(255),
|
||||||
|
vInvoiceDate DATE,
|
||||||
|
vTaxArea VARCHAR(25),
|
||||||
|
OUT vNewInvoiceId INT)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Creación de facturas emitidas.
|
||||||
|
* requiere previamente tabla ticketToInvoice(id).
|
||||||
|
*
|
||||||
|
* @param vSerial serie a la cual se hace la factura
|
||||||
|
* @param vInvoiceDate fecha de la factura
|
||||||
|
* @param vTaxArea tipo de iva en relacion a la empresa y al cliente
|
||||||
|
* @param vNewInvoiceId id de la factura que se acaba de generar
|
||||||
|
* @return vNewInvoiceId
|
||||||
|
*/
|
||||||
|
DECLARE vIsAnySaleToInvoice BOOL;
|
||||||
|
DECLARE vIsAnyServiceToInvoice BOOL;
|
||||||
|
DECLARE vNewRef VARCHAR(255);
|
||||||
|
DECLARE vWorker INT DEFAULT account.myUser_getId();
|
||||||
|
DECLARE vCompanyFk INT;
|
||||||
|
DECLARE vInterCompanyFk INT;
|
||||||
|
DECLARE vClientFk INT;
|
||||||
|
DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1;
|
||||||
|
DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6;
|
||||||
|
DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2;
|
||||||
|
DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R';
|
||||||
|
DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S';
|
||||||
|
DECLARE vNewInvoiceInFk INT;
|
||||||
|
DECLARE vIsInterCompany BOOL DEFAULT FALSE;
|
||||||
|
DECLARE vIsCEESerial BOOL DEFAULT FALSE;
|
||||||
|
DECLARE vIsCorrectInvoiceDate BOOL;
|
||||||
|
DECLARE vMaxShipped DATE;
|
||||||
|
|
||||||
|
SET vInvoiceDate = IFNULL(vInvoiceDate, util.CURDATE());
|
||||||
|
|
||||||
|
SELECT t.clientFk,
|
||||||
|
t.companyFk,
|
||||||
|
MAX(DATE(t.shipped)),
|
||||||
|
DATE(vInvoiceDate) >= invoiceOut_getMaxIssued(
|
||||||
|
vSerial,
|
||||||
|
t.companyFk,
|
||||||
|
YEAR(vInvoiceDate))
|
||||||
|
INTO vClientFk,
|
||||||
|
vCompanyFk,
|
||||||
|
vMaxShipped,
|
||||||
|
vIsCorrectInvoiceDate
|
||||||
|
FROM ticketToInvoice tt
|
||||||
|
JOIN ticket t ON t.id = tt.id;
|
||||||
|
|
||||||
|
IF(vMaxShipped > vInvoiceDate) THEN
|
||||||
|
CALL util.throw("Invoice date can't be less than max date");
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF NOT vIsCorrectInvoiceDate THEN
|
||||||
|
CALL util.throw('Exists an invoice with a previous date');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Eliminem de ticketToInvoice els tickets que no han de ser facturats
|
||||||
|
DELETE ti.*
|
||||||
|
FROM ticketToInvoice ti
|
||||||
|
JOIN ticket t ON t.id = ti.id
|
||||||
|
JOIN sale s ON s.ticketFk = t.id
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
JOIN supplier su ON su.id = t.companyFk
|
||||||
|
JOIN client c ON c.id = t.clientFk
|
||||||
|
LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk
|
||||||
|
WHERE (YEAR(t.shipped) < 2001 AND t.isDeleted)
|
||||||
|
OR c.isTaxDataChecked = FALSE
|
||||||
|
OR t.isDeleted
|
||||||
|
OR c.hasToInvoice = FALSE
|
||||||
|
OR itc.id IS NULL;
|
||||||
|
|
||||||
|
SELECT SUM(s.quantity * s.price * (100 - s.discount)/100) <> 0
|
||||||
|
INTO vIsAnySaleToInvoice
|
||||||
|
FROM ticketToInvoice t
|
||||||
|
JOIN sale s ON s.ticketFk = t.id;
|
||||||
|
|
||||||
|
SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice
|
||||||
|
FROM ticketToInvoice t
|
||||||
|
JOIN ticketService ts ON ts.ticketFk = t.id;
|
||||||
|
|
||||||
|
IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice)
|
||||||
|
AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase())
|
||||||
|
THEN
|
||||||
|
|
||||||
|
-- el trigger añade el siguiente Id_Factura correspondiente a la vSerial
|
||||||
|
INSERT INTO invoiceOut(
|
||||||
|
ref,
|
||||||
|
serial,
|
||||||
|
issued,
|
||||||
|
clientFk,
|
||||||
|
dued,
|
||||||
|
companyFk,
|
||||||
|
cplusInvoiceType477Fk
|
||||||
|
)
|
||||||
|
SELECT
|
||||||
|
1,
|
||||||
|
vSerial,
|
||||||
|
vInvoiceDate,
|
||||||
|
vClientFk,
|
||||||
|
getDueDate(vInvoiceDate, dueDay),
|
||||||
|
vCompanyFk,
|
||||||
|
IF(vSerial = vCorrectingSerial,
|
||||||
|
vCplusCorrectingInvoiceTypeFk,
|
||||||
|
IF(vSerial = vSimplifiedSerial,
|
||||||
|
vCplusSimplifiedInvoiceTypeFk,
|
||||||
|
vCplusStandardInvoiceTypeFk))
|
||||||
|
FROM client
|
||||||
|
WHERE id = vClientFk;
|
||||||
|
|
||||||
|
SET vNewInvoiceId = LAST_INSERT_ID();
|
||||||
|
|
||||||
|
SELECT `ref`
|
||||||
|
INTO vNewRef
|
||||||
|
FROM invoiceOut
|
||||||
|
WHERE id = vNewInvoiceId;
|
||||||
|
|
||||||
|
UPDATE ticket t
|
||||||
|
JOIN ticketToInvoice ti ON ti.id = t.id
|
||||||
|
SET t.refFk = vNewRef;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.updateInter;
|
||||||
|
CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY
|
||||||
|
SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador
|
||||||
|
FROM ticketToInvoice ti
|
||||||
|
LEFT JOIN ticketState ts ON ti.id = ts.ticket
|
||||||
|
JOIN state s
|
||||||
|
WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
|
||||||
|
|
||||||
|
INSERT INTO ticketTracking(stateFk,ticketFk,workerFk)
|
||||||
|
SELECT * FROM tmp.updateInter;
|
||||||
|
|
||||||
|
INSERT INTO ticketLog (action, userFk, originFk, description)
|
||||||
|
SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef)
|
||||||
|
FROM ticketToInvoice ti;
|
||||||
|
|
||||||
|
CALL invoiceExpenceMake(vNewInvoiceId);
|
||||||
|
CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
|
||||||
|
|
||||||
|
UPDATE invoiceOut io
|
||||||
|
JOIN (
|
||||||
|
SELECT SUM(amount) total
|
||||||
|
FROM invoiceOutExpence
|
||||||
|
WHERE invoiceOutFk = vNewInvoiceId
|
||||||
|
) base
|
||||||
|
JOIN (
|
||||||
|
SELECT SUM(vat) total
|
||||||
|
FROM invoiceOutTax
|
||||||
|
WHERE invoiceOutFk = vNewInvoiceId
|
||||||
|
) vat
|
||||||
|
SET io.amount = base.total + vat.total
|
||||||
|
WHERE io.id = vNewInvoiceId;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.updateInter;
|
||||||
|
|
||||||
|
SELECT COUNT(*), id
|
||||||
|
INTO vIsInterCompany, vInterCompanyFk
|
||||||
|
FROM company
|
||||||
|
WHERE clientFk = vClientFk;
|
||||||
|
|
||||||
|
IF (vIsInterCompany) THEN
|
||||||
|
|
||||||
|
INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk)
|
||||||
|
SELECT vCompanyFk, vNewRef, vInvoiceDate, vInterCompanyFk;
|
||||||
|
|
||||||
|
SET vNewInvoiceInFk = LAST_INSERT_ID();
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.ticket;
|
||||||
|
CREATE TEMPORARY TABLE tmp.ticket
|
||||||
|
(KEY (ticketFk))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT id ticketFk
|
||||||
|
FROM ticketToInvoice;
|
||||||
|
|
||||||
|
CALL `ticket_getTax`('NATIONAL');
|
||||||
|
|
||||||
|
SET @vTaxableBaseServices := 0.00;
|
||||||
|
SET @vTaxCodeGeneral := NULL;
|
||||||
|
|
||||||
|
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
|
||||||
|
SELECT vNewInvoiceInFk,
|
||||||
|
@vTaxableBaseServices,
|
||||||
|
sub.expenceFk,
|
||||||
|
sub.taxTypeSageFk,
|
||||||
|
sub.transactionTypeSageFk
|
||||||
|
FROM (
|
||||||
|
SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
|
||||||
|
i.expenceFk,
|
||||||
|
i.taxTypeSageFk,
|
||||||
|
i.transactionTypeSageFk,
|
||||||
|
@vTaxCodeGeneral := i.taxClassCodeFk
|
||||||
|
FROM tmp.ticketServiceTax tst
|
||||||
|
JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code
|
||||||
|
WHERE i.isService
|
||||||
|
HAVING taxableBase
|
||||||
|
) sub;
|
||||||
|
|
||||||
|
INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk)
|
||||||
|
SELECT vNewInvoiceInFk,
|
||||||
|
SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
|
||||||
|
@vTaxableBaseServices, 0) taxableBase,
|
||||||
|
i.expenceFk,
|
||||||
|
i.taxTypeSageFk ,
|
||||||
|
i.transactionTypeSageFk
|
||||||
|
FROM tmp.ticketTax tt
|
||||||
|
JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code
|
||||||
|
WHERE !i.isService
|
||||||
|
GROUP BY tt.pgcFk
|
||||||
|
HAVING taxableBase
|
||||||
|
ORDER BY tt.priority;
|
||||||
|
|
||||||
|
CALL invoiceInDueDay_calculate(vNewInvoiceInFk);
|
||||||
|
|
||||||
|
SELECT COUNT(*) INTO vIsCEESerial
|
||||||
|
FROM invoiceOutSerial
|
||||||
|
WHERE code = vSerial;
|
||||||
|
|
||||||
|
IF vIsCEESerial THEN
|
||||||
|
|
||||||
|
INSERT INTO invoiceInIntrastat (
|
||||||
|
invoiceInFk,
|
||||||
|
intrastatFk,
|
||||||
|
amount,
|
||||||
|
stems,
|
||||||
|
countryFk,
|
||||||
|
net)
|
||||||
|
SELECT
|
||||||
|
vNewInvoiceInFk,
|
||||||
|
i.intrastatFk,
|
||||||
|
SUM(CAST((s.quantity * s.price * (100 - s.discount) / 100 ) AS DECIMAL(10, 2))),
|
||||||
|
SUM(CAST(IFNULL(i.stems, 1) * s.quantity AS DECIMAL(10, 2))),
|
||||||
|
su.countryFk,
|
||||||
|
CAST(SUM(IFNULL(i.stems, 1)
|
||||||
|
* s.quantity
|
||||||
|
* IF(ic.grams, ic.grams, IFNULL(i.weightByPiece, 0)) / 1000) AS DECIMAL(10, 2))
|
||||||
|
FROM sale s
|
||||||
|
JOIN ticket t ON s.ticketFk = t.id
|
||||||
|
JOIN supplier su ON su.id = t.companyFk
|
||||||
|
JOIN item i ON i.id = s.itemFk
|
||||||
|
LEFT JOIN itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk
|
||||||
|
WHERE t.refFk = vNewRef
|
||||||
|
GROUP BY i.intrastatFk;
|
||||||
|
|
||||||
|
END IF;
|
||||||
|
DROP TEMPORARY TABLE tmp.ticket;
|
||||||
|
DROP TEMPORARY TABLE tmp.ticketAmount;
|
||||||
|
DROP TEMPORARY TABLE tmp.ticketTax;
|
||||||
|
DROP TEMPORARY TABLE tmp.ticketServiceTax;
|
||||||
|
END IF;
|
||||||
|
END IF;
|
||||||
|
DROP TEMPORARY TABLE `ticketToInvoice`;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,141 @@
|
||||||
|
DROP PROCEDURE IF EXISTS `vn`.`ticketPackaging_add`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketPackaging_add`(
|
||||||
|
vClientFk INT,
|
||||||
|
vDated DATE,
|
||||||
|
vCompanyFk INT,
|
||||||
|
vWithoutPeriodGrace BOOLEAN)
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Genera nuevos tickets de embalajes para los clientes no han los han retornado
|
||||||
|
* y actualiza los valores para la tabla ticketPackaging
|
||||||
|
*
|
||||||
|
* @param vClientFk Cliente en caso de NULL todos los clientes
|
||||||
|
* @param vDated Fecha hasta la cual se revisan los embalajes
|
||||||
|
* @param vCompanyFk Empresa de la cual se comprobaran sus clientes
|
||||||
|
* @param vWithoutPeriodGrace si no se aplica el periodo de gracia de un mes
|
||||||
|
*/
|
||||||
|
DECLARE vNewTicket INT;
|
||||||
|
DECLARE vDateStart DATE;
|
||||||
|
DECLARE vDateEnd DATE;
|
||||||
|
DECLARE vGraceDate DATE DEFAULT vDated;
|
||||||
|
DECLARE vWarehouseInventory INT;
|
||||||
|
DECLARE vComponentCost INT;
|
||||||
|
DECLARE vDone INT DEFAULT FALSE;
|
||||||
|
DECLARE vClientId INT;
|
||||||
|
|
||||||
|
DECLARE vCursor CURSOR FOR
|
||||||
|
SELECT DISTINCT clientFk
|
||||||
|
FROM (
|
||||||
|
SELECT clientFk, SUM(quantity) totalQuantity
|
||||||
|
FROM tmp.packagingToInvoice
|
||||||
|
GROUP BY itemFk, clientFk
|
||||||
|
HAVING totalQuantity > 0)sub;
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||||
|
|
||||||
|
DECLARE EXIT HANDLER FOR SQLEXCEPTION
|
||||||
|
BEGIN
|
||||||
|
ROLLBACK;
|
||||||
|
RESIGNAL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
SELECT id INTO vWarehouseInventory
|
||||||
|
FROM warehouse
|
||||||
|
WHERE `code`= 'inv';
|
||||||
|
|
||||||
|
SELECT id INTO vComponentCost
|
||||||
|
FROM component
|
||||||
|
WHERE `code`= 'purchaseValue';
|
||||||
|
|
||||||
|
SELECT packagingInvoicingDated INTO vDateStart
|
||||||
|
FROM ticketConfig;
|
||||||
|
|
||||||
|
IF vWarehouseInventory IS NULL THEN
|
||||||
|
CALL util.throw('Warehouse inventory not set');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
IF vComponentCost IS NULL THEN
|
||||||
|
CALL util.throw('Component cost not set');
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
SET vDateEnd = vDated + INTERVAL 1 DAY;
|
||||||
|
|
||||||
|
IF NOT vWithoutPeriodGrace THEN
|
||||||
|
SET vGraceDate = vGraceDate -INTERVAL 1 MONTH;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.packagingToInvoice;
|
||||||
|
CREATE TEMPORARY TABLE tmp.packagingToInvoice
|
||||||
|
(INDEX (clientFk))
|
||||||
|
ENGINE = MEMORY
|
||||||
|
SELECT p.itemFk,
|
||||||
|
tp.packagingFk,
|
||||||
|
tp.quantity,
|
||||||
|
tp.ticketFk,
|
||||||
|
p.price,
|
||||||
|
t.clientFk
|
||||||
|
FROM ticketPackaging tp
|
||||||
|
JOIN packaging p ON p.id = tp.packagingFk
|
||||||
|
JOIN ticket t ON t.id = tp.ticketFk
|
||||||
|
JOIN client c ON c.id = t.clientFk
|
||||||
|
WHERE c.isActive
|
||||||
|
AND (vClientFk IS NULL OR t.clientFk = vClientFk)
|
||||||
|
AND t.shipped BETWEEN vDateStart AND vDateEnd
|
||||||
|
AND (tp.quantity < 0 OR (tp.quantity > 0 AND t.shipped < vGraceDate))
|
||||||
|
AND tp.quantity
|
||||||
|
AND p.itemFk;
|
||||||
|
|
||||||
|
OPEN vCursor;
|
||||||
|
l: LOOP
|
||||||
|
|
||||||
|
FETCH vCursor INTO vClientId;
|
||||||
|
|
||||||
|
IF vDone THEN
|
||||||
|
LEAVE l;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
START TRANSACTION;
|
||||||
|
|
||||||
|
CALL ticket_add(
|
||||||
|
vClientId,
|
||||||
|
vDateEnd,
|
||||||
|
vWarehouseInventory,
|
||||||
|
vCompanyFk,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
vDateEnd,
|
||||||
|
account.myUser_getId(),
|
||||||
|
TRUE,
|
||||||
|
vNewTicket);
|
||||||
|
|
||||||
|
INSERT INTO ticketPackaging(ticketFk, packagingFk, quantity, pvp)
|
||||||
|
SELECT vNewTicket, packagingFk, - SUM(quantity) totalQuantity, price
|
||||||
|
FROM tmp.packagingToInvoice
|
||||||
|
WHERE clientFk = vClientId
|
||||||
|
GROUP BY packagingFk
|
||||||
|
HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity < 0);
|
||||||
|
|
||||||
|
INSERT INTO sale(ticketFk, itemFk, concept, quantity, price)
|
||||||
|
SELECT vNewTicket, pti.itemFk, i.name, SUM(pti.quantity) totalQuantity, pti.price
|
||||||
|
FROM tmp.packagingToInvoice pti
|
||||||
|
JOIN item i ON i.id = pti.itemFk
|
||||||
|
WHERE pti.clientFk = vClientId
|
||||||
|
GROUP BY pti.itemFk
|
||||||
|
HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity > 0);
|
||||||
|
|
||||||
|
INSERT INTO saleComponent(saleFk, componentFk, value)
|
||||||
|
SELECT id, vComponentCost, price
|
||||||
|
FROM sale
|
||||||
|
WHERE ticketFk = vNewTicket;
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
END LOOP;
|
||||||
|
CLOSE vCursor;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.packagingToInvoice;
|
||||||
|
END$$
|
||||||
|
DELIMITER ;
|
|
@ -164,7 +164,7 @@ INSERT INTO `vn`.`warehouse`(`id`, `name`, `code`, `isComparative`, `isInventory
|
||||||
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
(3, 'Warehouse Three', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
||||||
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
(4, 'Warehouse Four', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
||||||
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
(5, 'Warehouse Five', NULL, 1, 1, 1, 1, 0, 0, 2, 1, 1),
|
||||||
(13, 'Inventory', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0),
|
(13, 'Inventory', 'inv', 1, 1, 1, 0, 0, 0, 2, 1, 0),
|
||||||
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0);
|
(60, 'Algemesi', NULL, 1, 1, 1, 0, 0, 0, 2, 1, 0);
|
||||||
|
|
||||||
|
|
||||||
|
@ -173,10 +173,11 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
|
||||||
(1, 'First sector', 1, 1, 'FIRST'),
|
(1, 'First sector', 1, 1, 'FIRST'),
|
||||||
(2, 'Second sector', 2, 0, 'SECOND');
|
(2, 'Second sector', 2, 0, 'SECOND');
|
||||||
|
|
||||||
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`)
|
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'printer1', 'path1', 0, 1),
|
(1, 'printer1', 'path1', 0, 1 , NULL),
|
||||||
(2, 'printer2', 'path2', 1, 1);
|
(2, 'printer2', 'path2', 1, 1 , NULL),
|
||||||
|
(4, 'printer4', 'path4', 0, NULL, '10.1.10.4');
|
||||||
|
|
||||||
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
|
INSERT INTO `vn`.`worker`(`id`, `code`, `firstName`, `lastName`, `userFk`,`bossFk`, `phone`, `sectorFk`, `labelerFk`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -571,14 +572,13 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion`
|
||||||
('NATIONAL', 0, 1),
|
('NATIONAL', 0, 1),
|
||||||
('WORLD', 2, 15);
|
('WORLD', 2, 15);
|
||||||
|
|
||||||
INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`)
|
INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`)
|
||||||
VALUES
|
VALUES
|
||||||
('A', 'Global nacional', 1, 'NATIONAL', 0),
|
('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'),
|
||||||
('T', 'Española rapida', 1, 'NATIONAL', 0),
|
('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'),
|
||||||
('V', 'Intracomunitaria global', 0, 'CEE', 1),
|
('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'),
|
||||||
('M', 'Múltiple nacional', 1, 'NATIONAL', 0),
|
('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'),
|
||||||
('E', 'Exportación rápida', 0, 'WORLD', 0);
|
('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick');
|
||||||
;
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -2367,11 +2367,11 @@ INSERT INTO `vn`.`device` (`sn`, `model`, `userFk`)
|
||||||
VALUES
|
VALUES
|
||||||
('aaa', 'android', '9');
|
('aaa', 'android', '9');
|
||||||
|
|
||||||
INSERT INTO `vn`.`queuePriority`(`id`, `priority`)
|
INSERT INTO `vn`.`queuePriority`(`id`, `priority`, `code`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'Alta'),
|
(1, 'Alta', 'high'),
|
||||||
(2, 'Normal'),
|
(2, 'Normal', 'normal'),
|
||||||
(3, 'Baja');
|
(3, 'Baja', 'low');
|
||||||
|
|
||||||
INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`)
|
INSERT INTO `vn`.`workerTimeControlParams` (`id`, `dayBreak`, `weekBreak`, `weekScope`, `dayWorkMax`, `dayStayMax`, `weekMaxBreak`, `weekMaxScope`, `askInOut`)
|
||||||
VALUES
|
VALUES
|
||||||
|
@ -2783,6 +2783,10 @@ INSERT INTO `salix`.`url` (`appName`, `environment`, `url`)
|
||||||
('lilium', 'dev', 'http://localhost:8080/#/'),
|
('lilium', 'dev', 'http://localhost:8080/#/'),
|
||||||
('salix', 'dev', 'http://localhost:5000/#!/');
|
('salix', 'dev', 'http://localhost:5000/#!/');
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`report` (`id`, `name`, `paperSizeFk`, `method`)
|
||||||
|
VALUES
|
||||||
|
(3, 'invoice', NULL, 'InvoiceOuts/{refFk}/invoice-out-pdf');
|
||||||
|
|
||||||
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
|
INSERT INTO `vn`.`payDemDetail` (`id`, `detail`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1);
|
(1, 1);
|
||||||
|
@ -2821,12 +2825,17 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
|
||||||
(1, 1, util.VN_NOW()),
|
(1, 1, util.VN_NOW()),
|
||||||
(3, 3, util.VN_NOW());
|
(3, 3, util.VN_NOW());
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`)
|
||||||
|
VALUES
|
||||||
|
(1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'),
|
||||||
|
(2, 9, 2000, 50, 'SENDED', util.VN_NOW(), 1, NULL),
|
||||||
|
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
|
||||||
|
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);
|
||||||
|
|
||||||
INSERT INTO `vn`.`wagonConfig` (`id`, `width`, `height`, `maxWagonHeight`, `minHeightBetweenTrays`, `maxTrays`)
|
INSERT INTO `vn`.`wagonConfig` (`id`, `width`, `height`, `maxWagonHeight`, `minHeightBetweenTrays`, `maxTrays`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 1350, 1900, 200, 50, 6);
|
(1, 1350, 1900, 200, 50, 6);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
INSERT INTO `vn`.`wagonTypeColor` (`id`, `name`, `rgb`)
|
INSERT INTO `vn`.`wagonTypeColor` (`id`, `name`, `rgb`)
|
||||||
VALUES
|
VALUES
|
||||||
(1, 'red', '#ff0000'),
|
(1, 'red', '#ff0000'),
|
||||||
|
|
|
@ -19742,6 +19742,102 @@ DELIMITER ;
|
||||||
/*!50003 SET character_set_client = @saved_cs_client */ ;
|
/*!50003 SET character_set_client = @saved_cs_client */ ;
|
||||||
/*!50003 SET character_set_results = @saved_cs_results */ ;
|
/*!50003 SET character_set_results = @saved_cs_results */ ;
|
||||||
/*!50003 SET collation_connection = @saved_col_connection */ ;
|
/*!50003 SET collation_connection = @saved_col_connection */ ;
|
||||||
|
/*!50003 DROP FUNCTION IF EXISTS `CURDATE` */;
|
||||||
|
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
||||||
|
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
|
||||||
|
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
|
||||||
|
/*!50003 SET character_set_client = utf8mb4 */ ;
|
||||||
|
/*!50003 SET character_set_results = utf8mb4 */ ;
|
||||||
|
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
|
||||||
|
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
|
||||||
|
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
|
||||||
|
DELIMITER ;;
|
||||||
|
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`CURDATE`() RETURNS date
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* @return The mock date
|
||||||
|
**/
|
||||||
|
|
||||||
|
RETURN DATE(mockTime());
|
||||||
|
END ;;
|
||||||
|
DELIMITER ;
|
||||||
|
/*!50003 SET sql_mode = @saved_sql_mode */ ;
|
||||||
|
/*!50003 SET character_set_client = @saved_cs_client */ ;
|
||||||
|
/*!50003 SET character_set_results = @saved_cs_results */ ;
|
||||||
|
/*!50003 SET collation_connection = @saved_col_connection */ ;
|
||||||
|
/*!50003 DROP FUNCTION IF EXISTS `mockTime` */;
|
||||||
|
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
||||||
|
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
|
||||||
|
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
|
||||||
|
/*!50003 SET character_set_client = utf8mb4 */ ;
|
||||||
|
/*!50003 SET character_set_results = utf8mb4 */ ;
|
||||||
|
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
|
||||||
|
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
|
||||||
|
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
|
||||||
|
DELIMITER ;;
|
||||||
|
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTime`() RETURNS datetime
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Returns the mockTime with predefined timezone or current dateTime
|
||||||
|
* depending of config.mockEnabled
|
||||||
|
*
|
||||||
|
* @return formatted datetime
|
||||||
|
*/
|
||||||
|
DECLARE vMockEnabled BOOL;
|
||||||
|
|
||||||
|
SELECT mockEnabled INTO vMockEnabled FROM config LIMIT 1;
|
||||||
|
|
||||||
|
IF vMockEnabled THEN
|
||||||
|
RETURN mockTimeBase(FALSE);
|
||||||
|
ELSE
|
||||||
|
RETURN NOW();
|
||||||
|
END IF;
|
||||||
|
END ;;
|
||||||
|
DELIMITER ;
|
||||||
|
/*!50003 SET sql_mode = @saved_sql_mode */ ;
|
||||||
|
/*!50003 SET character_set_client = @saved_cs_client */ ;
|
||||||
|
/*!50003 SET character_set_results = @saved_cs_results */ ;
|
||||||
|
/*!50003 SET collation_connection = @saved_col_connection */ ;
|
||||||
|
/*!50003 DROP FUNCTION IF EXISTS `mockTimeBase` */;
|
||||||
|
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
||||||
|
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
|
||||||
|
/*!50003 SET @saved_col_connection = @@collation_connection */ ;
|
||||||
|
/*!50003 SET character_set_client = utf8mb4 */ ;
|
||||||
|
/*!50003 SET character_set_results = utf8mb4 */ ;
|
||||||
|
/*!50003 SET collation_connection = utf8mb4_unicode_ci */ ;
|
||||||
|
/*!50003 SET @saved_sql_mode = @@sql_mode */ ;
|
||||||
|
/*!50003 SET sql_mode = 'IGNORE_SPACE,NO_ENGINE_SUBSTITUTION' */ ;
|
||||||
|
DELIMITER ;;
|
||||||
|
CREATE DEFINER=`root`@`localhost` FUNCTION `util`.`mockTimeBase`(vIsUtc BOOL) RETURNS datetime
|
||||||
|
DETERMINISTIC
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Returns the date formatted to utc if vIsUtc or config.mocTz if not
|
||||||
|
*
|
||||||
|
* @param vIsUtc If date must be returned as UTC format
|
||||||
|
* @return The formatted mock time
|
||||||
|
*/
|
||||||
|
DECLARE vMockUtcTime DATETIME;
|
||||||
|
DECLARE vMockTz VARCHAR(255);
|
||||||
|
|
||||||
|
SELECT mockUtcTime, mockTz
|
||||||
|
INTO vMockUtcTime, vMockTz
|
||||||
|
FROM config
|
||||||
|
LIMIT 1;
|
||||||
|
|
||||||
|
IF vIsUtc OR vMockTz IS NULL THEN
|
||||||
|
RETURN vMockUtcTime;
|
||||||
|
ELSE
|
||||||
|
RETURN CONVERT_TZ(vMockUtcTime, '+00:00', vMockTz);
|
||||||
|
END IF;
|
||||||
|
END ;;
|
||||||
|
DELIMITER ;
|
||||||
|
/*!50003 SET sql_mode = @saved_sql_mode */ ;
|
||||||
|
/*!50003 SET character_set_client = @saved_cs_client */ ;
|
||||||
|
/*!50003 SET character_set_results = @saved_cs_results */ ;
|
||||||
|
/*!50003 SET collation_connection = @saved_col_connection */ ;
|
||||||
/*!50003 DROP FUNCTION IF EXISTS `firstDayOfYear` */;
|
/*!50003 DROP FUNCTION IF EXISTS `firstDayOfYear` */;
|
||||||
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
/*!50003 SET @saved_cs_client = @@character_set_client */ ;
|
||||||
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
|
/*!50003 SET @saved_cs_results = @@character_set_results */ ;
|
||||||
|
@ -32632,7 +32728,7 @@ CREATE TABLE `printQueue` (
|
||||||
CONSTRAINT `printQueue_printerFk` FOREIGN KEY (`printerFk`) REFERENCES `printer` (`id`) ON UPDATE CASCADE,
|
CONSTRAINT `printQueue_printerFk` FOREIGN KEY (`printerFk`) REFERENCES `printer` (`id`) ON UPDATE CASCADE,
|
||||||
CONSTRAINT `printQueue_priorityFk` FOREIGN KEY (`priorityFk`) REFERENCES `queuePriority` (`id`) ON UPDATE CASCADE,
|
CONSTRAINT `printQueue_priorityFk` FOREIGN KEY (`priorityFk`) REFERENCES `queuePriority` (`id`) ON UPDATE CASCADE,
|
||||||
CONSTRAINT `printQueue_report` FOREIGN KEY (`reportFk`) REFERENCES `report` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
CONSTRAINT `printQueue_report` FOREIGN KEY (`reportFk`) REFERENCES `report` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||||
) ENGINE=InnoDBDEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
|
||||||
/*!40101 SET character_set_client = @saved_cs_client */;
|
/*!40101 SET character_set_client = @saved_cs_client */;
|
||||||
|
|
||||||
--
|
--
|
||||||
|
@ -68286,11 +68382,11 @@ BEGIN
|
||||||
FROM ticketConfig;
|
FROM ticketConfig;
|
||||||
|
|
||||||
IF vWarehouseInventory IS NULL THEN
|
IF vWarehouseInventory IS NULL THEN
|
||||||
CALL util.throw('Warehouse inventory not seted');
|
CALL util.throw('Warehouse inventory not set');
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
IF vComponentCost IS NULL THEN
|
IF vComponentCost IS NULL THEN
|
||||||
CALL util.throw('Component cost not seted');
|
CALL util.throw('Component cost not set');
|
||||||
END IF;
|
END IF;
|
||||||
|
|
||||||
SET vDateEnd = vDated + INTERVAL 1 DAY;
|
SET vDateEnd = vDated + INTERVAL 1 DAY;
|
||||||
|
@ -81124,3 +81220,4 @@ USE `vn`;
|
||||||
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
|
||||||
|
|
||||||
-- Dump completed on 2023-02-21 8:14:30
|
-- Dump completed on 2023-02-21 8:14:30
|
||||||
|
|
||||||
|
|
|
@ -524,7 +524,7 @@ export default {
|
||||||
},
|
},
|
||||||
itemLog: {
|
itemLog: {
|
||||||
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
|
anyLineCreated: 'vn-item-log > vn-log vn-tbody > vn-tr',
|
||||||
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(2) td.after',
|
fifthLineCreatedProperty: 'vn-item-log > vn-log vn-tbody > vn-tr:nth-child(5) table tr:nth-child(4) td.after',
|
||||||
},
|
},
|
||||||
ticketSummary: {
|
ticketSummary: {
|
||||||
header: 'vn-ticket-summary > vn-card > h5',
|
header: 'vn-ticket-summary > vn-card > h5',
|
||||||
|
@ -1052,20 +1052,22 @@ export default {
|
||||||
invoiceOutIndex: {
|
invoiceOutIndex: {
|
||||||
topbarSearch: 'vn-searchbar',
|
topbarSearch: 'vn-searchbar',
|
||||||
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
|
searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
|
||||||
createInvoice: 'vn-invoice-out-index > div > vn-vertical > vn-button > button vn-icon[icon="add"]',
|
createInvoice: 'vn-invoice-out-index > div > vn-button > button vn-icon[icon="add"]',
|
||||||
createManualInvoice: 'vn-item[name="manualInvoice"]',
|
|
||||||
createGlobalInvoice: 'vn-item[name="globalInvoice"]',
|
|
||||||
manualInvoiceForm: '.vn-invoice-out-manual',
|
manualInvoiceForm: '.vn-invoice-out-manual',
|
||||||
manualInvoiceTicket: 'vn-autocomplete[ng-model="$ctrl.invoice.ticketFk"]',
|
manualInvoiceTicket: 'vn-autocomplete[ng-model="$ctrl.invoice.ticketFk"]',
|
||||||
manualInvoiceClient: 'vn-autocomplete[ng-model="$ctrl.invoice.clientFk"]',
|
manualInvoiceClient: 'vn-autocomplete[ng-model="$ctrl.invoice.clientFk"]',
|
||||||
manualInvoiceSerial: 'vn-autocomplete[ng-model="$ctrl.invoice.serial"]',
|
manualInvoiceSerial: 'vn-autocomplete[ng-model="$ctrl.invoice.serial"]',
|
||||||
manualInvoiceTaxArea: 'vn-autocomplete[ng-model="$ctrl.invoice.taxArea"]',
|
manualInvoiceTaxArea: 'vn-autocomplete[ng-model="$ctrl.invoice.taxArea"]',
|
||||||
saveInvoice: 'button[response="accept"]',
|
saveInvoice: 'button[response="accept"]'
|
||||||
globalInvoiceForm: '.vn-invoice-out-global-invoicing',
|
},
|
||||||
globalInvoiceClientsRange: 'vn-radio[val="clientsRange"]',
|
invoiceOutGlobalInvoicing: {
|
||||||
globalInvoiceDate: '[ng-model="$ctrl.invoice.invoiceDate"]',
|
oneClient: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-vertical > vn-radio[val="one"]',
|
||||||
globalInvoiceFromClient: '[ng-model="$ctrl.invoice.fromClientId"]',
|
allClients: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-vertical > vn-radio[val="all"]',
|
||||||
globalInvoiceToClient: '[ng-model="$ctrl.invoice.toClientId"]',
|
clientId: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-autocomplete[ng-model="$ctrl.clientId"]',
|
||||||
|
printer: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-autocomplete[ng-model="$ctrl.printerFk"]',
|
||||||
|
makeInvoice: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-submit',
|
||||||
|
invoiceDate: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-date-picker[ng-model="$ctrl.invoiceDate"]',
|
||||||
|
maxShipped: 'vn-invoice-out-global-invoicing vn-side-menu form > vn-vertical > vn-date-picker[ng-model="$ctrl.maxShipped"]'
|
||||||
},
|
},
|
||||||
invoiceOutDescriptor: {
|
invoiceOutDescriptor: {
|
||||||
moreMenu: 'vn-invoice-out-descriptor vn-icon-button[icon=more_vert]',
|
moreMenu: 'vn-invoice-out-descriptor vn-icon-button[icon=more_vert]',
|
||||||
|
|
|
@ -59,6 +59,6 @@ describe('Item log path', () => {
|
||||||
const fifthLineCreatedProperty = await page
|
const fifthLineCreatedProperty = await page
|
||||||
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
|
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
|
||||||
|
|
||||||
expect(fifthLineCreatedProperty).toEqual('Coral y materiales similares');
|
expect(fifthLineCreatedProperty).toEqual('05080000');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -17,7 +17,6 @@ describe('InvoiceOut manual invoice path', () => {
|
||||||
|
|
||||||
it('should open the manual invoice form', async() => {
|
it('should open the manual invoice form', async() => {
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
|
|
||||||
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -45,7 +44,6 @@ describe('InvoiceOut manual invoice path', () => {
|
||||||
|
|
||||||
it('should now open the manual invoice form', async() => {
|
it('should now open the manual invoice form', async() => {
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createManualInvoice);
|
|
||||||
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -17,47 +17,27 @@ describe('InvoiceOut global invoice path', () => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
let invoicesBefore;
|
let invoicesBeforeOneClient;
|
||||||
|
let invoicesBeforeAllClients;
|
||||||
|
let now = Date.vnNew();
|
||||||
|
|
||||||
it('should count the amount of invoices listed before globla invoces are made', async() => {
|
it('should count the amount of invoices listed before globla invoces are made', async() => {
|
||||||
invoicesBefore = await page.countElement(selectors.invoiceOutIndex.searchResult);
|
invoicesBeforeOneClient = await page.countElement(selectors.invoiceOutIndex.searchResult);
|
||||||
|
|
||||||
expect(invoicesBefore).toBeGreaterThanOrEqual(4);
|
expect(invoicesBeforeOneClient).toBeGreaterThanOrEqual(4);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should open the global invoice form', async() => {
|
it('should open the global invoice form', async() => {
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
await page.accessToSection('invoiceOut.global-invoicing');
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createGlobalInvoice);
|
|
||||||
await page.waitForSelector(selectors.invoiceOutIndex.globalInvoiceForm);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a global invoice for charles xavier today', async() => {
|
it('should create a global invoice for charles xavier today', async() => {
|
||||||
await page.pickDate(selectors.invoiceOutIndex.globalInvoiceDate);
|
await page.waitToClick(selectors.invoiceOutGlobalInvoicing.oneClient);
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.globalInvoiceClientsRange);
|
await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.clientId, '1108');
|
||||||
await page.autocompleteSearch(selectors.invoiceOutIndex.globalInvoiceFromClient, 'Petter Parker');
|
await page.pickDate(selectors.invoiceOutGlobalInvoicing.invoiceDate, now);
|
||||||
await page.autocompleteSearch(selectors.invoiceOutIndex.globalInvoiceToClient, 'Petter Parker');
|
await page.pickDate(selectors.invoiceOutGlobalInvoicing.maxShipped, now);
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice);
|
await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.printer, '1');
|
||||||
const message = await page.waitForSnackbar();
|
await page.waitToClick(selectors.invoiceOutGlobalInvoicing.makeInvoice);
|
||||||
|
await page.waitForTimeout(1000);
|
||||||
expect(message.text).toContain('Data saved!');
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should count the amount of invoices listed after globla invocing', async() => {
|
|
||||||
await page.waitToClick('[icon="search"]');
|
|
||||||
await page.waitForTimeout(1000); // index search needs time to return results
|
|
||||||
const currentInvoices = await page.countElement(selectors.invoiceOutIndex.searchResult);
|
|
||||||
|
|
||||||
expect(currentInvoices).toBeGreaterThan(invoicesBefore);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create a global invoice for all clients today', async() => {
|
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createInvoice);
|
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.createGlobalInvoice);
|
|
||||||
await page.waitForSelector(selectors.invoiceOutIndex.globalInvoiceForm);
|
|
||||||
await page.pickDate(selectors.invoiceOutIndex.globalInvoiceDate);
|
|
||||||
await page.waitToClick(selectors.invoiceOutIndex.saveInvoice);
|
|
||||||
const message = await page.waitForSnackbar();
|
|
||||||
|
|
||||||
expect(message.text).toContain('Data saved!');
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,6 @@ describe('Zone descriptor path', () => {
|
||||||
await page.accessToSection('ticket.card.log');
|
await page.accessToSection('ticket.card.log');
|
||||||
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
|
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
|
||||||
|
|
||||||
expect(lastChanges).toContain('Arreglar');
|
expect(lastChanges).toContain('1');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="weekdays">
|
<div class="weekdays">
|
||||||
<section
|
<section
|
||||||
ng-repeat="day in ::$ctrl.weekDays"
|
ng-repeat="day in ::$ctrl.weekDays"
|
||||||
translate-attr="::{title: day.name}"
|
translate-attr="::{title: day.name}"
|
||||||
ng-click="$ctrl.selectWeekDay($event, day.index)">
|
ng-click="$ctrl.selectWeekDay($event, day.index)">
|
||||||
<span>{{::day.localeChar}}</span>
|
<span>{{::day.localeChar}}</span>
|
||||||
</section>
|
</section>
|
||||||
|
@ -57,4 +57,4 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,9 +15,9 @@ export default class Calendar extends FormInput {
|
||||||
constructor($element, $scope, vnWeekDays, moment) {
|
constructor($element, $scope, vnWeekDays, moment) {
|
||||||
super($element, $scope);
|
super($element, $scope);
|
||||||
this.weekDays = vnWeekDays.locales;
|
this.weekDays = vnWeekDays.locales;
|
||||||
this.defaultDate = Date.vnNew();
|
|
||||||
this.displayControls = true;
|
this.displayControls = true;
|
||||||
this.moment = moment;
|
this.moment = moment;
|
||||||
|
this.defaultDate = Date.vnNew();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -207,14 +207,23 @@ export default class Calendar extends FormInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
repeatLast() {
|
repeatLast() {
|
||||||
if (!this.formatDay) return;
|
if (this.formatDay) {
|
||||||
|
const days = this.element.querySelectorAll('.days > .day');
|
||||||
|
for (let i = 0; i < days.length; i++) {
|
||||||
|
this.formatDay({
|
||||||
|
$day: this.days[i],
|
||||||
|
$element: days[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let days = this.element.querySelectorAll('.days > .day');
|
if (this.formatWeek) {
|
||||||
for (let i = 0; i < days.length; i++) {
|
const weeks = this.element.querySelectorAll('.weeks > .day');
|
||||||
this.formatDay({
|
for (const week of weeks) {
|
||||||
$day: this.days[i],
|
this.formatWeek({
|
||||||
$element: days[i]
|
$element: week
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +237,7 @@ ngModule.vnComponent('vnCalendar', {
|
||||||
hasEvents: '&?',
|
hasEvents: '&?',
|
||||||
getClass: '&?',
|
getClass: '&?',
|
||||||
formatDay: '&?',
|
formatDay: '&?',
|
||||||
|
formatWeek: '&?',
|
||||||
displayControls: '<?',
|
displayControls: '<?',
|
||||||
hideYear: '<?',
|
hideYear: '<?',
|
||||||
hideContiguous: '<?',
|
hideContiguous: '<?',
|
||||||
|
|
|
@ -25,8 +25,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/@uirouter/angularjs": {
|
"node_modules/@uirouter/angularjs": {
|
||||||
"version": "1.0.30",
|
"version": "1.0.30",
|
||||||
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uirouter/core": "6.0.8"
|
"@uirouter/core": "6.0.8"
|
||||||
},
|
},
|
||||||
|
@ -39,28 +38,22 @@
|
||||||
},
|
},
|
||||||
"node_modules/@uirouter/core": {
|
"node_modules/@uirouter/core": {
|
||||||
"version": "6.0.8",
|
"version": "6.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.0.0"
|
"node": ">=4.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/angular": {
|
"node_modules/angular": {
|
||||||
"version": "1.8.3",
|
"version": "1.8.3",
|
||||||
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw==",
|
|
||||||
"deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
|
|
||||||
},
|
},
|
||||||
"node_modules/angular-animate": {
|
"node_modules/angular-animate": {
|
||||||
"version": "1.8.2",
|
"version": "1.8.2",
|
||||||
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA==",
|
|
||||||
"deprecated": "For the actively supported Angular, see https://www.npmjs.com/package/@angular/core. AngularJS support has officially ended. For extended AngularJS support options, see https://goo.gle/angularjs-path-forward."
|
|
||||||
},
|
},
|
||||||
"node_modules/angular-moment": {
|
"node_modules/angular-moment": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"moment": ">=2.8.0 <3.0.0"
|
"moment": ">=2.8.0 <3.0.0"
|
||||||
},
|
},
|
||||||
|
@ -70,8 +63,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/angular-translate": {
|
"node_modules/angular-translate": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "^1.8.0"
|
"angular": "^1.8.0"
|
||||||
},
|
},
|
||||||
|
@ -81,29 +73,25 @@
|
||||||
},
|
},
|
||||||
"node_modules/angular-translate-loader-partial": {
|
"node_modules/angular-translate-loader-partial": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular-translate": "~2.19.0"
|
"angular-translate": "~2.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/argparse": {
|
"node_modules/argparse": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/croppie": {
|
"node_modules/croppie": {
|
||||||
"version": "2.6.5",
|
"version": "2.6.5",
|
||||||
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
|
||||||
},
|
},
|
||||||
"node_modules/esprima": {
|
"node_modules/esprima": {
|
||||||
"version": "4.0.1",
|
"version": "4.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
"license": "BSD-2-Clause",
|
||||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
|
|
||||||
"bin": {
|
"bin": {
|
||||||
"esparse": "bin/esparse.js",
|
"esparse": "bin/esparse.js",
|
||||||
"esvalidate": "bin/esvalidate.js"
|
"esvalidate": "bin/esvalidate.js"
|
||||||
|
@ -114,8 +102,7 @@
|
||||||
},
|
},
|
||||||
"node_modules/js-yaml": {
|
"node_modules/js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^1.0.7",
|
"argparse": "^1.0.7",
|
||||||
"esprima": "^4.0.0"
|
"esprima": "^4.0.0"
|
||||||
|
@ -126,42 +113,36 @@
|
||||||
},
|
},
|
||||||
"node_modules/mg-crud": {
|
"node_modules/mg-crud": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"angular": "^1.6.1"
|
"angular": "^1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/moment": {
|
"node_modules/moment": {
|
||||||
"version": "2.29.4",
|
"version": "2.29.4",
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/oclazyload": {
|
"node_modules/oclazyload": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
|
"license": "MIT"
|
||||||
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
|
|
||||||
},
|
},
|
||||||
"node_modules/require-yaml": {
|
"node_modules/require-yaml": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
|
"license": "BSD",
|
||||||
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"js-yaml": ""
|
"js-yaml": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/require-yaml/node_modules/argparse": {
|
"node_modules/require-yaml/node_modules/argparse": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
"license": "Python-2.0"
|
||||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
|
||||||
},
|
},
|
||||||
"node_modules/require-yaml/node_modules/js-yaml": {
|
"node_modules/require-yaml/node_modules/js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
},
|
},
|
||||||
|
@ -171,13 +152,11 @@
|
||||||
},
|
},
|
||||||
"node_modules/sprintf-js": {
|
"node_modules/sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
"license": "BSD-3-Clause"
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
|
||||||
},
|
},
|
||||||
"node_modules/validator": {
|
"node_modules/validator": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
|
"license": "MIT",
|
||||||
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q==",
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 0.10"
|
"node": ">= 0.10"
|
||||||
}
|
}
|
||||||
|
@ -186,73 +165,51 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@uirouter/angularjs": {
|
"@uirouter/angularjs": {
|
||||||
"version": "1.0.30",
|
"version": "1.0.30",
|
||||||
"resolved": "https://registry.npmjs.org/@uirouter/angularjs/-/angularjs-1.0.30.tgz",
|
|
||||||
"integrity": "sha512-qkc3RFZc91S5K0gc/QVAXc9LGDPXjR04vDgG/11j8+yyZEuQojXxKxdLhKIepiPzqLmGRVqzBmBc27gtqaEeZg==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"@uirouter/core": "6.0.8"
|
"@uirouter/core": "6.0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"@uirouter/core": {
|
"@uirouter/core": {
|
||||||
"version": "6.0.8",
|
"version": "6.0.8"
|
||||||
"resolved": "https://registry.npmjs.org/@uirouter/core/-/core-6.0.8.tgz",
|
|
||||||
"integrity": "sha512-Gc/BAW47i4L54p8dqYCJJZuv2s3tqlXQ0fvl6Zp2xrblELPVfxmjnc0eurx3XwfQdaqm3T6uls6tQKkof/4QMw=="
|
|
||||||
},
|
},
|
||||||
"angular": {
|
"angular": {
|
||||||
"version": "1.8.3",
|
"version": "1.8.3"
|
||||||
"resolved": "https://registry.npmjs.org/angular/-/angular-1.8.3.tgz",
|
|
||||||
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
|
|
||||||
},
|
},
|
||||||
"angular-animate": {
|
"angular-animate": {
|
||||||
"version": "1.8.2",
|
"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": {
|
"angular-moment": {
|
||||||
"version": "1.3.0",
|
"version": "1.3.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
|
|
||||||
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"moment": ">=2.8.0 <3.0.0"
|
"moment": ">=2.8.0 <3.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"angular-translate": {
|
"angular-translate": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-translate/-/angular-translate-2.19.0.tgz",
|
|
||||||
"integrity": "sha512-Z/Fip5uUT2N85dPQ0sMEe1JdF5AehcDe4tg/9mWXNDVU531emHCg53ZND9Oe0dyNiGX5rWcJKmsL1Fujus1vGQ==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"angular": "^1.8.0"
|
"angular": "^1.8.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"angular-translate-loader-partial": {
|
"angular-translate-loader-partial": {
|
||||||
"version": "2.19.0",
|
"version": "2.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/angular-translate-loader-partial/-/angular-translate-loader-partial-2.19.0.tgz",
|
|
||||||
"integrity": "sha512-NnMw13LMV4bPQmJK7/pZOZAnPxe0M5OtUHchADs5Gye7V7feonuEnrZ8e1CKhBlv9a7IQyWoqcBa4Lnhg8gk5w==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"angular-translate": "~2.19.0"
|
"angular-translate": "~2.19.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"argparse": {
|
"argparse": {
|
||||||
"version": "1.0.10",
|
"version": "1.0.10",
|
||||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
|
||||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"sprintf-js": "~1.0.2"
|
"sprintf-js": "~1.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"croppie": {
|
"croppie": {
|
||||||
"version": "2.6.5",
|
"version": "2.6.5"
|
||||||
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
|
||||||
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
|
||||||
},
|
},
|
||||||
"esprima": {
|
"esprima": {
|
||||||
"version": "4.0.1",
|
"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": {
|
"js-yaml": {
|
||||||
"version": "3.14.1",
|
"version": "3.14.1",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
|
||||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^1.0.7",
|
"argparse": "^1.0.7",
|
||||||
"esprima": "^4.0.0"
|
"esprima": "^4.0.0"
|
||||||
|
@ -260,39 +217,27 @@
|
||||||
},
|
},
|
||||||
"mg-crud": {
|
"mg-crud": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
|
|
||||||
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"angular": "^1.6.1"
|
"angular": "^1.6.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"moment": {
|
"moment": {
|
||||||
"version": "2.29.4",
|
"version": "2.29.4"
|
||||||
"resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
|
|
||||||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
|
||||||
},
|
},
|
||||||
"oclazyload": {
|
"oclazyload": {
|
||||||
"version": "0.6.3",
|
"version": "0.6.3"
|
||||||
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
|
|
||||||
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
|
|
||||||
},
|
},
|
||||||
"require-yaml": {
|
"require-yaml": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
|
|
||||||
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"js-yaml": ""
|
"js-yaml": ""
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"argparse": {
|
"argparse": {
|
||||||
"version": "2.0.1",
|
"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": {
|
"js-yaml": {
|
||||||
"version": "4.1.0",
|
"version": "4.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
|
||||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
|
||||||
"requires": {
|
"requires": {
|
||||||
"argparse": "^2.0.1"
|
"argparse": "^2.0.1"
|
||||||
}
|
}
|
||||||
|
@ -300,14 +245,10 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"sprintf-js": {
|
"sprintf-js": {
|
||||||
"version": "1.0.3",
|
"version": "1.0.3"
|
||||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
|
||||||
},
|
},
|
||||||
"validator": {
|
"validator": {
|
||||||
"version": "6.3.0",
|
"version": "6.3.0"
|
||||||
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
|
|
||||||
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q=="
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
const {Report, Email} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.printReport = async function(ctx, id, reportName) {
|
||||||
|
const args = Object.assign({}, ctx.args);
|
||||||
|
const params = {lang: ctx.req.getLocale()};
|
||||||
|
|
||||||
|
delete args.ctx;
|
||||||
|
for (const param in args)
|
||||||
|
params[param] = args[param];
|
||||||
|
|
||||||
|
const report = new Report(reportName, params);
|
||||||
|
const stream = await report.toPdfStream();
|
||||||
|
|
||||||
|
let fileName = `${reportName}`;
|
||||||
|
if (id) fileName += `-${id}`;
|
||||||
|
|
||||||
|
return [stream, 'application/pdf', `filename="${fileName}.pdf"`];
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.printEmail = async function(ctx, id, templateName) {
|
||||||
|
const {accessToken} = ctx.req;
|
||||||
|
const args = Object.assign({}, ctx.args);
|
||||||
|
const params = {lang: ctx.req.getLocale()};
|
||||||
|
|
||||||
|
delete args.ctx;
|
||||||
|
for (const param in args)
|
||||||
|
params[param] = args[param];
|
||||||
|
|
||||||
|
params.isPreview = true;
|
||||||
|
params.access_token = accessToken.id;
|
||||||
|
|
||||||
|
const report = new Email(templateName, params);
|
||||||
|
const html = await report.render();
|
||||||
|
|
||||||
|
let fileName = `${templateName}`;
|
||||||
|
if (id) fileName += `-${id}`;
|
||||||
|
|
||||||
|
return [html, 'text/html', `filename=${fileName}.pdf"`];
|
||||||
|
};
|
||||||
|
|
||||||
|
Self.sendTemplate = async function(ctx, templateName) {
|
||||||
|
const args = Object.assign({}, ctx.args);
|
||||||
|
const params = {
|
||||||
|
recipient: args.recipient,
|
||||||
|
lang: ctx.req.getLocale()
|
||||||
|
};
|
||||||
|
|
||||||
|
delete args.ctx;
|
||||||
|
for (const param in args)
|
||||||
|
params[param] = args[param];
|
||||||
|
|
||||||
|
const email = new Email(templateName, params);
|
||||||
|
|
||||||
|
return email.send();
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,4 +1,3 @@
|
||||||
const pick = require('object.pick');
|
|
||||||
const LoopBackContext = require('loopback-context');
|
const LoopBackContext = require('loopback-context');
|
||||||
|
|
||||||
module.exports = function(Self) {
|
module.exports = function(Self) {
|
||||||
|
@ -6,344 +5,11 @@ module.exports = function(Self) {
|
||||||
Self.super_.setup.call(this);
|
Self.super_.setup.call(this);
|
||||||
};
|
};
|
||||||
|
|
||||||
Self.observe('after save', async function(ctx) {
|
|
||||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
|
||||||
await logInModel(ctx, loopBackContext);
|
|
||||||
});
|
|
||||||
|
|
||||||
Self.observe('before save', async function(ctx) {
|
Self.observe('before save', async function(ctx) {
|
||||||
const appModels = ctx.Model.app.models;
|
ctx.options.httpCtx = LoopBackContext.getCurrentContext();
|
||||||
const definition = ctx.Model.definition;
|
|
||||||
const options = {};
|
|
||||||
|
|
||||||
// Check for transactions
|
|
||||||
if (ctx.options && ctx.options.transaction)
|
|
||||||
options.transaction = ctx.options.transaction;
|
|
||||||
|
|
||||||
let oldInstance;
|
|
||||||
let newInstance;
|
|
||||||
|
|
||||||
if (ctx.data) {
|
|
||||||
const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
|
|
||||||
newInstance = ctx.data;
|
|
||||||
oldInstance = changes;
|
|
||||||
|
|
||||||
if (ctx.where && !ctx.currentInstance) {
|
|
||||||
const fields = Object.keys(ctx.data);
|
|
||||||
const modelName = definition.name;
|
|
||||||
|
|
||||||
ctx.oldInstances = await appModels[modelName].find({
|
|
||||||
where: ctx.where,
|
|
||||||
fields: fields
|
|
||||||
}, options);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get changes from created instance
|
|
||||||
if (ctx.isNewInstance)
|
|
||||||
newInstance = ctx.instance.__data;
|
|
||||||
|
|
||||||
ctx.hookState.oldInstance = oldInstance;
|
|
||||||
ctx.hookState.newInstance = newInstance;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.observe('before delete', async function(ctx) {
|
Self.observe('before delete', async function(ctx) {
|
||||||
const appModels = ctx.Model.app.models;
|
ctx.options.httpCtx = LoopBackContext.getCurrentContext();
|
||||||
const definition = ctx.Model.definition;
|
|
||||||
const relations = ctx.Model.relations;
|
|
||||||
|
|
||||||
let options = {};
|
|
||||||
if (ctx.options && ctx.options.transaction)
|
|
||||||
options.transaction = ctx.options.transaction;
|
|
||||||
|
|
||||||
if (ctx.where) {
|
|
||||||
let affectedModel = definition.name;
|
|
||||||
let deletedInstances = await appModels[affectedModel].find({
|
|
||||||
where: ctx.where
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
let relation = definition.settings.log.relation;
|
|
||||||
|
|
||||||
if (relation) {
|
|
||||||
let primaryKey = relations[relation].keyFrom;
|
|
||||||
|
|
||||||
let arrangedDeletedInstances = [];
|
|
||||||
for (let i = 0; i < deletedInstances.length; i++) {
|
|
||||||
if (primaryKey)
|
|
||||||
deletedInstances[i].originFk = deletedInstances[i][primaryKey];
|
|
||||||
let arrangedInstance = await fkToValue(deletedInstances[i], ctx);
|
|
||||||
arrangedDeletedInstances[i] = arrangedInstance;
|
|
||||||
}
|
|
||||||
ctx.hookState.oldInstance = arrangedDeletedInstances;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.observe('after delete', async function(ctx) {
|
|
||||||
const loopBackContext = LoopBackContext.getCurrentContext();
|
|
||||||
if (ctx.hookState.oldInstance)
|
|
||||||
logDeletedInstances(ctx, loopBackContext);
|
|
||||||
});
|
|
||||||
|
|
||||||
async function logDeletedInstances(ctx, loopBackContext) {
|
|
||||||
const appModels = ctx.Model.app.models;
|
|
||||||
const definition = ctx.Model.definition;
|
|
||||||
let options = {};
|
|
||||||
if (ctx.options && ctx.options.transaction)
|
|
||||||
options.transaction = ctx.options.transaction;
|
|
||||||
|
|
||||||
ctx.hookState.oldInstance.forEach(async instance => {
|
|
||||||
let userFk;
|
|
||||||
if (loopBackContext)
|
|
||||||
userFk = loopBackContext.active.accessToken.userId;
|
|
||||||
|
|
||||||
let changedModelValue = definition.settings.log.changedModelValue;
|
|
||||||
let logRecord = {
|
|
||||||
originFk: instance.originFk,
|
|
||||||
userFk: userFk,
|
|
||||||
action: 'delete',
|
|
||||||
changedModel: definition.name,
|
|
||||||
changedModelId: instance.id,
|
|
||||||
changedModelValue: instance[changedModelValue],
|
|
||||||
oldInstance: instance,
|
|
||||||
newInstance: {}
|
|
||||||
};
|
|
||||||
|
|
||||||
delete instance.originFk;
|
|
||||||
|
|
||||||
let logModel = definition.settings.log.model;
|
|
||||||
await appModels[logModel].create(logRecord, options);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get log values from a foreign key
|
|
||||||
async function fkToValue(instance, ctx) {
|
|
||||||
const appModels = ctx.Model.app.models;
|
|
||||||
const relations = ctx.Model.relations;
|
|
||||||
let options = {};
|
|
||||||
|
|
||||||
// Check for transactions
|
|
||||||
if (ctx.options && ctx.options.transaction)
|
|
||||||
options.transaction = ctx.options.transaction;
|
|
||||||
|
|
||||||
const instanceCopy = JSON.parse(JSON.stringify(instance));
|
|
||||||
const result = {};
|
|
||||||
for (const key in instanceCopy) {
|
|
||||||
let value = instanceCopy[key];
|
|
||||||
|
|
||||||
if (value instanceof Object)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (value === undefined) continue;
|
|
||||||
|
|
||||||
if (value) {
|
|
||||||
for (let relationName in relations) {
|
|
||||||
const relation = relations[relationName];
|
|
||||||
if (relation.keyFrom == key && key != 'id') {
|
|
||||||
const model = relation.modelTo;
|
|
||||||
const modelName = relation.modelTo.modelName;
|
|
||||||
const properties = model && model.definition.properties;
|
|
||||||
const settings = model && model.definition.settings;
|
|
||||||
|
|
||||||
const recordSet = await appModels[modelName].findById(value, null, options);
|
|
||||||
|
|
||||||
const hasShowField = settings.log && settings.log.showField;
|
|
||||||
let showField = hasShowField && recordSet
|
|
||||||
&& recordSet[settings.log.showField];
|
|
||||||
|
|
||||||
if (!showField) {
|
|
||||||
const showFieldNames = [
|
|
||||||
'name',
|
|
||||||
'description',
|
|
||||||
'code',
|
|
||||||
'nickname'
|
|
||||||
];
|
|
||||||
for (field of showFieldNames) {
|
|
||||||
const propField = properties && properties[field];
|
|
||||||
const recordField = recordSet && recordSet[field];
|
|
||||||
|
|
||||||
if (propField && recordField) {
|
|
||||||
showField = field;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showField && recordSet && recordSet[showField]) {
|
|
||||||
value = recordSet[showField];
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
value = recordSet && recordSet.id || value;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
result[key] = value;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function logInModel(ctx, loopBackContext) {
|
|
||||||
const appModels = ctx.Model.app.models;
|
|
||||||
const definition = ctx.Model.definition;
|
|
||||||
const defSettings = ctx.Model.definition.settings;
|
|
||||||
const relations = ctx.Model.relations;
|
|
||||||
|
|
||||||
const options = {};
|
|
||||||
if (ctx.options && ctx.options.transaction)
|
|
||||||
options.transaction = ctx.options.transaction;
|
|
||||||
|
|
||||||
let primaryKey;
|
|
||||||
for (let property in definition.properties) {
|
|
||||||
if (definition.properties[property].id) {
|
|
||||||
primaryKey = property;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!primaryKey) throw new Error('Primary key not found');
|
|
||||||
let originId;
|
|
||||||
|
|
||||||
// RELATIONS LOG
|
|
||||||
let changedModelId;
|
|
||||||
|
|
||||||
if (ctx.instance && !defSettings.log.relation) {
|
|
||||||
originId = ctx.instance.id;
|
|
||||||
changedModelId = ctx.instance.id;
|
|
||||||
} else if (defSettings.log.relation) {
|
|
||||||
primaryKey = relations[defSettings.log.relation].keyFrom;
|
|
||||||
|
|
||||||
if (ctx.where && ctx.where[primaryKey])
|
|
||||||
originId = ctx.where[primaryKey];
|
|
||||||
else if (ctx.instance) {
|
|
||||||
originId = ctx.instance[primaryKey];
|
|
||||||
changedModelId = ctx.instance.id;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
originId = ctx.currentInstance.id;
|
|
||||||
changedModelId = ctx.currentInstance.id;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sets the changedModelValue to save and the instances changed in case its an updateAll
|
|
||||||
let showField = defSettings.log.showField;
|
|
||||||
let where;
|
|
||||||
if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) {
|
|
||||||
changedModelId = [];
|
|
||||||
where = [];
|
|
||||||
let changedInstances = await appModels[definition.name].find({
|
|
||||||
where: ctx.where,
|
|
||||||
fields: ['id', showField, primaryKey]
|
|
||||||
}, options);
|
|
||||||
|
|
||||||
changedInstances.forEach(element => {
|
|
||||||
where.push(element[showField]);
|
|
||||||
changedModelId.push(element.id);
|
|
||||||
originId = element[primaryKey];
|
|
||||||
});
|
|
||||||
} else if (ctx.hookState.oldInstance)
|
|
||||||
where = ctx.instance[showField];
|
|
||||||
|
|
||||||
// Set oldInstance, newInstance, userFk and action
|
|
||||||
let oldInstance = {};
|
|
||||||
if (ctx.hookState.oldInstance)
|
|
||||||
Object.assign(oldInstance, ctx.hookState.oldInstance);
|
|
||||||
|
|
||||||
let newInstance = {};
|
|
||||||
if (ctx.hookState.newInstance)
|
|
||||||
Object.assign(newInstance, ctx.hookState.newInstance);
|
|
||||||
let userFk;
|
|
||||||
if (loopBackContext)
|
|
||||||
userFk = loopBackContext.active.accessToken.userId;
|
|
||||||
|
|
||||||
let action = setActionType(ctx);
|
|
||||||
|
|
||||||
removeUnloggable(definition, oldInstance);
|
|
||||||
removeUnloggable(definition, newInstance);
|
|
||||||
|
|
||||||
oldInstance = await fkToValue(oldInstance, ctx);
|
|
||||||
newInstance = await fkToValue(newInstance, ctx);
|
|
||||||
|
|
||||||
// Prevent log with no new changes
|
|
||||||
const hasNewChanges = Object.keys(newInstance).length;
|
|
||||||
if (!hasNewChanges) return;
|
|
||||||
|
|
||||||
let logRecord = {
|
|
||||||
originFk: originId,
|
|
||||||
userFk: userFk,
|
|
||||||
action: action,
|
|
||||||
changedModel: definition.name,
|
|
||||||
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
|
|
||||||
changedModelValue: where,
|
|
||||||
oldInstance: oldInstance,
|
|
||||||
newInstance: newInstance
|
|
||||||
};
|
|
||||||
|
|
||||||
let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx);
|
|
||||||
let logModel = defSettings.log.model;
|
|
||||||
|
|
||||||
await appModels[logModel].create(logsToSave, options);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes unwanted properties
|
|
||||||
* @param {*} definition Model definition
|
|
||||||
* @param {*} properties Modified object properties
|
|
||||||
*/
|
|
||||||
function removeUnloggable(definition, properties) {
|
|
||||||
const objectCopy = Object.assign({}, properties);
|
|
||||||
const propList = Object.keys(objectCopy);
|
|
||||||
const propDefs = new Map();
|
|
||||||
|
|
||||||
for (let property in definition.properties) {
|
|
||||||
const propertyDef = definition.properties[property];
|
|
||||||
|
|
||||||
propDefs.set(property, propertyDef);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let property of propList) {
|
|
||||||
const propertyDef = propDefs.get(property);
|
|
||||||
const firstChar = property.substring(0, 1);
|
|
||||||
const isPrivate = firstChar == '$';
|
|
||||||
|
|
||||||
if (isPrivate || !propertyDef)
|
|
||||||
delete properties[property];
|
|
||||||
|
|
||||||
if (!propertyDef) continue;
|
|
||||||
|
|
||||||
if (propertyDef.log === false || isPrivate)
|
|
||||||
delete properties[property];
|
|
||||||
else if (propertyDef.logValue === false)
|
|
||||||
properties[property] = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// this function retuns all the instances changed in case this is an updateAll
|
|
||||||
function setLogsToSave(changedInstances, changedInstancesIds, logRecord, ctx) {
|
|
||||||
let promises = [];
|
|
||||||
if (changedInstances && typeof changedInstances == 'object') {
|
|
||||||
for (let i = 0; i < changedInstances.length; i++) {
|
|
||||||
logRecord.changedModelId = changedInstancesIds[i];
|
|
||||||
logRecord.changedModelValue = changedInstances[i];
|
|
||||||
if (ctx.oldInstances)
|
|
||||||
logRecord.oldInstance = ctx.oldInstances[i];
|
|
||||||
promises.push(JSON.parse(JSON.stringify(logRecord)));
|
|
||||||
}
|
|
||||||
} else
|
|
||||||
return logRecord;
|
|
||||||
|
|
||||||
return promises;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setActionType(ctx) {
|
|
||||||
let oldInstance = ctx.hookState.oldInstance;
|
|
||||||
let newInstance = ctx.hookState.newInstance;
|
|
||||||
|
|
||||||
if (oldInstance && newInstance)
|
|
||||||
return 'update';
|
|
||||||
else if (!oldInstance && newInstance)
|
|
||||||
return 'insert';
|
|
||||||
|
|
||||||
return 'delete';
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -7,6 +7,7 @@ module.exports = function(Self) {
|
||||||
|
|
||||||
require('../methods/vn-model/getSetValues')(Self);
|
require('../methods/vn-model/getSetValues')(Self);
|
||||||
require('../methods/vn-model/getEnumValues')(Self);
|
require('../methods/vn-model/getEnumValues')(Self);
|
||||||
|
require('../methods/vn-model/printService')(Self);
|
||||||
|
|
||||||
Object.assign(Self, {
|
Object.assign(Self, {
|
||||||
setup() {
|
setup() {
|
||||||
|
|
|
@ -152,5 +152,7 @@
|
||||||
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
|
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
|
||||||
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
|
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
|
||||||
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
|
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
|
||||||
|
"Warehouse inventory not set": "Almacén inventario no está establecido",
|
||||||
|
"Component cost not set": "Componente coste no está estabecido",
|
||||||
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2"
|
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2"
|
||||||
}
|
}
|
||||||
|
|
|
@ -251,6 +251,7 @@
|
||||||
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
"Receipt's bank was not found": "No se encontró el banco del recibo",
|
||||||
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
"This receipt was not compensated": "Este recibo no ha sido compensado",
|
||||||
"Client's email was not found": "No se encontró el email del cliente",
|
"Client's email was not found": "No se encontró el email del cliente",
|
||||||
|
"Negative basis": "Base negativa",
|
||||||
"This worker code already exists": "Este codigo de trabajador ya existe",
|
"This worker code already exists": "Este codigo de trabajador ya existe",
|
||||||
"This personal mail already exists": "Este correo personal ya existe",
|
"This personal mail already exists": "Este correo personal ya existe",
|
||||||
"This worker already exists": "Este trabajador ya existe",
|
"This worker already exists": "Este trabajador ya existe",
|
||||||
|
@ -264,6 +265,9 @@
|
||||||
"It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas",
|
"It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas",
|
||||||
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.",
|
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.",
|
||||||
"There is no assigned email for this client": "No hay correo asignado para este cliente",
|
"There is no assigned email for this client": "No hay correo asignado para este cliente",
|
||||||
|
"Exists an invoice with a previous date": "Existe una factura con fecha anterior",
|
||||||
|
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
|
||||||
|
"Warehouse inventory not set": "El almacén inventario no está establecido",
|
||||||
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
|
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
|
||||||
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
|
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
|
||||||
"Not exist this branch": "La rama no existe"
|
"Not exist this branch": "La rama no existe"
|
||||||
|
|
|
@ -2,8 +2,41 @@ const mysql = require('mysql');
|
||||||
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
const MySQL = require('loopback-connector-mysql').MySQL;
|
const MySQL = require('loopback-connector-mysql').MySQL;
|
||||||
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
|
const EnumFactory = require('loopback-connector-mysql').EnumFactory;
|
||||||
|
const Transaction = require('loopback-connector').Transaction;
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
const limitSet = new Set([
|
||||||
|
'save',
|
||||||
|
'updateOrCreate',
|
||||||
|
'replaceOrCreate',
|
||||||
|
'replaceById',
|
||||||
|
'update'
|
||||||
|
]);
|
||||||
|
|
||||||
|
const opOpts = {
|
||||||
|
update: [
|
||||||
|
'update',
|
||||||
|
'replaceById',
|
||||||
|
// |insert
|
||||||
|
'save',
|
||||||
|
'updateOrCreate',
|
||||||
|
'replaceOrCreate'
|
||||||
|
],
|
||||||
|
delete: [
|
||||||
|
'destroy',
|
||||||
|
'destroyAll'
|
||||||
|
],
|
||||||
|
insert: [
|
||||||
|
'create'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const opMap = new Map();
|
||||||
|
for (const op in opOpts) {
|
||||||
|
for (const met of opOpts[op])
|
||||||
|
opMap.set(met, op);
|
||||||
|
}
|
||||||
|
|
||||||
class VnMySQL extends MySQL {
|
class VnMySQL extends MySQL {
|
||||||
/**
|
/**
|
||||||
* Promisified version of execute().
|
* Promisified version of execute().
|
||||||
|
@ -219,6 +252,277 @@ class VnMySQL extends MySQL {
|
||||||
this.makePagination(filter)
|
this.makePagination(filter)
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
create(model, data, opts, cb) {
|
||||||
|
const ctx = {data};
|
||||||
|
this.invokeMethod('create',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
createAll(model, data, opts, cb) {
|
||||||
|
const ctx = {data};
|
||||||
|
this.invokeMethod('createAll',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
save(model, data, opts, cb) {
|
||||||
|
const ctx = {data};
|
||||||
|
this.invokeMethod('save',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOrCreate(model, data, opts, cb) {
|
||||||
|
const ctx = {data};
|
||||||
|
this.invokeMethod('updateOrCreate',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceOrCreate(model, data, opts, cb) {
|
||||||
|
const ctx = {data};
|
||||||
|
this.invokeMethod('replaceOrCreate',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
destroyAll(model, where, opts, cb) {
|
||||||
|
const ctx = {where};
|
||||||
|
this.invokeMethod('destroyAll',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
update(model, where, data, opts, cb) {
|
||||||
|
const ctx = {where, data};
|
||||||
|
this.invokeMethod('update',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
replaceById(model, id, data, opts, cb) {
|
||||||
|
const ctx = {id, data};
|
||||||
|
this.invokeMethod('replaceById',
|
||||||
|
arguments, model, ctx, opts, cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoggable(model) {
|
||||||
|
const Model = this.getModelDefinition(model).model;
|
||||||
|
const settings = Model.definition.settings;
|
||||||
|
return settings.base && settings.base === 'Loggable';
|
||||||
|
}
|
||||||
|
|
||||||
|
invokeMethod(method, args, model, ctx, opts, cb) {
|
||||||
|
if (!this.isLoggable(model))
|
||||||
|
return super[method].apply(this, args);
|
||||||
|
|
||||||
|
this.invokeMethodP(method, [...args], model, ctx, opts)
|
||||||
|
.then(res => cb(...res), cb);
|
||||||
|
}
|
||||||
|
|
||||||
|
async invokeMethodP(method, args, model, ctx, opts) {
|
||||||
|
const Model = this.getModelDefinition(model).model;
|
||||||
|
const settings = Model.definition.settings;
|
||||||
|
let tx;
|
||||||
|
if (!opts.transaction) {
|
||||||
|
tx = await Transaction.begin(this, {});
|
||||||
|
opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Fetch old values (update|delete) or login
|
||||||
|
let where, id, data, idName, limit, op, oldInstances, newInstances;
|
||||||
|
const hasGrabUser = settings.log && settings.log.grabUser;
|
||||||
|
if(hasGrabUser){
|
||||||
|
const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
|
||||||
|
const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts);
|
||||||
|
await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
where = ctx.where;
|
||||||
|
id = ctx.id;
|
||||||
|
data = ctx.data;
|
||||||
|
idName = this.idName(model);
|
||||||
|
|
||||||
|
limit = limitSet.has(method);
|
||||||
|
|
||||||
|
op = opMap.get(method);
|
||||||
|
|
||||||
|
if (!where) {
|
||||||
|
if (id) where = {[idName]: id};
|
||||||
|
else where = {[idName]: data[idName]};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch old values
|
||||||
|
switch (op) {
|
||||||
|
case 'update':
|
||||||
|
case 'delete':
|
||||||
|
// Single entity operation
|
||||||
|
const stmt = this.buildSelectStmt(op, data, idName, model, where, limit);
|
||||||
|
stmt.merge(`FOR UPDATE`);
|
||||||
|
oldInstances = await this.executeStmt(stmt, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await new Promise(resolve => {
|
||||||
|
const fnArgs = args.slice(0, -2);
|
||||||
|
fnArgs.push(opts, (...args) => resolve(args));
|
||||||
|
super[method].apply(this, fnArgs);
|
||||||
|
});
|
||||||
|
|
||||||
|
if(hasGrabUser)
|
||||||
|
await this.executeP(`CALL account.myUser_logout()`, null, opts);
|
||||||
|
else {
|
||||||
|
// Fetch new values
|
||||||
|
const ids = [];
|
||||||
|
|
||||||
|
switch (op) {
|
||||||
|
case 'insert':
|
||||||
|
case 'update': {
|
||||||
|
switch (method) {
|
||||||
|
case 'createAll':
|
||||||
|
for (const row of res[1])
|
||||||
|
ids.push(row[idName]);
|
||||||
|
break;
|
||||||
|
case 'create':
|
||||||
|
ids.push(res[1]);
|
||||||
|
break;
|
||||||
|
case 'update':
|
||||||
|
if (data[idName] != null)
|
||||||
|
ids.push(data[idName]);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newWhere = ids.length ? {[idName]: ids} : where;
|
||||||
|
|
||||||
|
const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit);
|
||||||
|
newInstances = await this.executeStmt(stmt, opts);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.createLogRecord(oldInstances, newInstances, model, opts);
|
||||||
|
}
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
return res;
|
||||||
|
} catch (err) {
|
||||||
|
if (tx) tx.rollback();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
buildSelectStmt(op, data, idName, model, where, limit) {
|
||||||
|
const Model = this.getModelDefinition(model).model;
|
||||||
|
const properties = Object.keys(Model.definition.properties);
|
||||||
|
|
||||||
|
const fields = data ? Object.keys(data) : [];
|
||||||
|
if (op == 'delete')
|
||||||
|
properties.forEach(property => fields.push(property));
|
||||||
|
else {
|
||||||
|
const log = Model.definition.settings.log;
|
||||||
|
fields.push(idName);
|
||||||
|
if (log.relation) fields.push(Model.relations[log.relation].keyFrom);
|
||||||
|
if (log.showField) fields.push(log.showField);
|
||||||
|
else {
|
||||||
|
const showFieldNames = ['name', 'description', 'code', 'nickname'];
|
||||||
|
for (const field of showFieldNames) {
|
||||||
|
if (properties.includes(field)) {
|
||||||
|
log.showField = field;
|
||||||
|
fields.push(field);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const stmt = new ParameterizedSQL(
|
||||||
|
'SELECT ' +
|
||||||
|
this.buildColumnNames(model, {fields}) +
|
||||||
|
' FROM ' +
|
||||||
|
this.tableEscaped(model)
|
||||||
|
);
|
||||||
|
stmt.merge(this.buildWhere(model, where));
|
||||||
|
if (limit) stmt.merge(`LIMIT 1`);
|
||||||
|
|
||||||
|
return stmt;
|
||||||
|
}
|
||||||
|
|
||||||
|
async createLogRecord(oldInstances, newInstances, model, opts) {
|
||||||
|
function setActionType() {
|
||||||
|
if (oldInstances && newInstances)
|
||||||
|
return 'update';
|
||||||
|
else if (!oldInstances && newInstances)
|
||||||
|
return 'insert';
|
||||||
|
return 'delete';
|
||||||
|
}
|
||||||
|
|
||||||
|
const action = setActionType();
|
||||||
|
if (!newInstances && action != 'delete') return;
|
||||||
|
|
||||||
|
const Model = this.getModelDefinition(model).model;
|
||||||
|
const models = Model.app.models;
|
||||||
|
const definition = Model.definition;
|
||||||
|
const log = definition.settings.log;
|
||||||
|
|
||||||
|
const primaryKey = this.idName(model);
|
||||||
|
const originRelation = log.relation;
|
||||||
|
const originFkField = originRelation
|
||||||
|
? Model.relations[originRelation].keyFrom
|
||||||
|
: primaryKey;
|
||||||
|
|
||||||
|
// Prevent adding logs when deleting a principal entity (Client, Zone...)
|
||||||
|
if (action == 'delete' && !originRelation) return;
|
||||||
|
|
||||||
|
function map(instances) {
|
||||||
|
const map = new Map();
|
||||||
|
if (!instances) return;
|
||||||
|
for (const instance of instances)
|
||||||
|
map.set(instance[primaryKey], instance);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
const changedModel = definition.name;
|
||||||
|
const userFk = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
|
||||||
|
const oldMap = map(oldInstances);
|
||||||
|
const newMap = map(newInstances);
|
||||||
|
const ids = (oldMap || newMap).keys();
|
||||||
|
|
||||||
|
const logEntries = [];
|
||||||
|
|
||||||
|
function insertValuesLogEntry(logEntry, instance) {
|
||||||
|
logEntry.originFk = instance[originFkField];
|
||||||
|
logEntry.changedModelId = instance[primaryKey];
|
||||||
|
if (log.showField) logEntry.changedModelValue = instance[log.showField];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const id of ids) {
|
||||||
|
const oldI = oldMap && oldMap.get(id);
|
||||||
|
const newI = newMap && newMap.get(id);
|
||||||
|
|
||||||
|
const logEntry = {
|
||||||
|
action,
|
||||||
|
userFk,
|
||||||
|
changedModel,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (newI) {
|
||||||
|
insertValuesLogEntry(logEntry, newI);
|
||||||
|
// Delete unchanged properties
|
||||||
|
if (oldI) {
|
||||||
|
Object.keys(oldI).forEach(prop => {
|
||||||
|
const hasChanges = oldI[prop] instanceof Date ?
|
||||||
|
oldI[prop]?.getTime() != newI[prop]?.getTime() :
|
||||||
|
oldI[prop] != newI[prop];
|
||||||
|
|
||||||
|
if (!hasChanges) {
|
||||||
|
delete oldI[prop];
|
||||||
|
delete newI[prop];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
insertValuesLogEntry(logEntry, oldI);
|
||||||
|
|
||||||
|
logEntry.oldInstance = oldI;
|
||||||
|
logEntry.newInstance = newI;
|
||||||
|
logEntries.push(logEntry);
|
||||||
|
}
|
||||||
|
await models[log.model].create(logEntries, opts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
exports.VnMySQL = VnMySQL;
|
exports.VnMySQL = VnMySQL;
|
||||||
|
|
|
@ -91,7 +91,11 @@ exports.getChanges = (original, changes) => {
|
||||||
const isPrivate = firstChar == '$';
|
const isPrivate = firstChar == '$';
|
||||||
if (isPrivate) return;
|
if (isPrivate) return;
|
||||||
|
|
||||||
if (changes[property] != original[property]) {
|
const hasChanges = original[property] instanceof Date ?
|
||||||
|
changes[property]?.getTime() != original[property]?.getTime() :
|
||||||
|
changes[property] != original[property];
|
||||||
|
|
||||||
|
if (hasChanges) {
|
||||||
newChanges[property] = changes[property];
|
newChanges[property] = changes[property];
|
||||||
|
|
||||||
if (original[property] != undefined)
|
if (original[property] != undefined)
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const { Report } = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('claimPickupPdf', {
|
Self.remoteMethodCtx('claimPickupPdf', {
|
||||||
description: 'Returns the claim pickup order pdf',
|
description: 'Returns the claim pickup order pdf',
|
||||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.claimPickupPdf = async(ctx, id) => {
|
Self.claimPickupPdf = (ctx, id) => Self.printReport(ctx, id, 'claim-pickup-order');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('claim-pickup-order', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -86,7 +86,20 @@
|
||||||
icon="icon-ticket">
|
icon="icon-ticket">
|
||||||
</vn-quick-link>
|
</vn-quick-link>
|
||||||
</div>
|
</div>
|
||||||
<div ng-transclude="btnThree"></div>
|
<div ng-transclude="btnThree">
|
||||||
|
<vn-quick-link
|
||||||
|
tooltip="Sale tracking"
|
||||||
|
state="['ticket.card.saleTracking', {id: $ctrl.claim.ticketFk}]"
|
||||||
|
icon="assignment">
|
||||||
|
</vn-quick-link>
|
||||||
|
</div>
|
||||||
|
<div ng-transclude="btnFour">
|
||||||
|
<vn-quick-link
|
||||||
|
tooltip="Ticket tracking"
|
||||||
|
state="['ticket.card.tracking.index', {id: $ctrl.claim.ticketFk}]"
|
||||||
|
icon="icon-eye">
|
||||||
|
</vn-quick-link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</slot-body>
|
</slot-body>
|
||||||
</vn-descriptor-content>
|
</vn-descriptor-content>
|
||||||
|
|
|
@ -18,3 +18,5 @@ Claim deleted!: Reclamación eliminada!
|
||||||
claim: reclamación
|
claim: reclamación
|
||||||
Photos: Fotos
|
Photos: Fotos
|
||||||
Go to the claim: Ir a la reclamación
|
Go to the claim: Ir a la reclamación
|
||||||
|
Sale tracking: Líneas preparadas
|
||||||
|
Ticket tracking: Estados del ticket
|
||||||
|
|
|
@ -51,19 +51,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.campaignMetricsEmail = async ctx => {
|
Self.campaignMetricsEmail = ctx => Self.sendTemplate(ctx, 'campaign-metrics');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('campaign-metrics', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('campaignMetricsPdf', {
|
Self.remoteMethodCtx('campaignMetricsPdf', {
|
||||||
description: 'Returns the campaign metrics pdf',
|
description: 'Returns the campaign metrics pdf',
|
||||||
|
@ -50,17 +48,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.campaignMetricsPdf = async(ctx, id) => {
|
Self.campaignMetricsPdf = (ctx, id) => Self.printReport(ctx, id, 'campaign-metrics');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('campaign-metrics', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -46,19 +46,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientDebtStatementEmail = async ctx => {
|
Self.clientDebtStatementEmail = ctx => Self.sendTemplate(ctx, 'client-debt-statement');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('client-debt-statement', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clientDebtStatementHtml', {
|
Self.remoteMethodCtx('clientDebtStatementHtml', {
|
||||||
description: 'Returns the client debt statement email preview',
|
description: 'Returns the client debt statement email preview',
|
||||||
|
@ -45,21 +43,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientDebtStatementHtml = async(ctx, id) => {
|
Self.clientDebtStatementHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-debt-statement');
|
||||||
const {accessToken} = ctx.req;
|
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
params.access_token = accessToken.id;
|
|
||||||
|
|
||||||
const report = new Email('client-debt-statement', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clientDebtStatementPdf', {
|
Self.remoteMethodCtx('clientDebtStatementPdf', {
|
||||||
description: 'Returns the client debt statement pdf',
|
description: 'Returns the client debt statement pdf',
|
||||||
|
@ -45,17 +43,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientDebtStatementPdf = async(ctx, id) => {
|
Self.clientDebtStatementPdf = (ctx, id) => Self.printReport(ctx, id, 'client-debt-statement');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('client-debt-statement', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clientWelcomeEmail', {
|
Self.remoteMethodCtx('clientWelcomeEmail', {
|
||||||
description: 'Sends the client welcome email with an attached PDF',
|
description: 'Sends the client welcome email with an attached PDF',
|
||||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientWelcomeEmail = async ctx => {
|
Self.clientWelcomeEmail = ctx => Self.sendTemplate(ctx, 'client-welcome');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('client-welcome', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clientWelcomeHtml', {
|
Self.remoteMethodCtx('clientWelcomeHtml', {
|
||||||
description: 'Returns the client welcome email preview',
|
description: 'Returns the client welcome email preview',
|
||||||
|
@ -40,19 +38,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientWelcomeHtml = async(ctx, id) => {
|
Self.clientWelcomeHtml = (ctx, id) => Self.printEmail(ctx, id, 'client-welcome');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
|
|
||||||
const report = new Email('client-welcome', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('clientCreditEmail', {
|
Self.remoteMethodCtx('clientCreditEmail', {
|
||||||
description: 'Sends the credit request email with an attached PDF',
|
description: 'Sends the credit request email with an attached PDF',
|
||||||
|
@ -10,7 +8,7 @@ module.exports = Self => {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The client id',
|
description: 'The client id',
|
||||||
http: {source: 'path'}
|
http: {source: 'path'},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'recipient',
|
arg: 'recipient',
|
||||||
|
@ -22,38 +20,25 @@ module.exports = Self => {
|
||||||
arg: 'replyTo',
|
arg: 'replyTo',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
description: 'The sender email to reply to',
|
description: 'The sender email to reply to',
|
||||||
required: false
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'recipientId',
|
arg: 'recipientId',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'The recipient id to send to the recipient preferred language',
|
description:
|
||||||
required: false
|
'The recipient id to send to the recipient preferred language',
|
||||||
}
|
required: false,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['object'],
|
type: ['object'],
|
||||||
root: true
|
root: true,
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
path: '/:id/credit-request-email',
|
path: '/:id/credit-request-email',
|
||||||
verb: 'POST'
|
verb: 'POST',
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientCreditEmail = async ctx => {
|
Self.clientCreditEmail = ctx => Self.sendTemplate(ctx, 'credit-request');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('credit-request', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('creditRequestHtml', {
|
Self.remoteMethodCtx('creditRequestHtml', {
|
||||||
description: 'Returns the credit request email preview',
|
description: 'Returns the credit request email preview',
|
||||||
|
@ -40,21 +38,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.creditRequestHtml = async(ctx, id) => {
|
Self.creditRequestHtml = (ctx, id) => Self.printEmail(ctx, id, 'credit-request');
|
||||||
const {accessToken} = ctx.req;
|
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
params.access_token = accessToken.id;
|
|
||||||
|
|
||||||
const report = new Email('credit-request', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('creditRequestPdf', {
|
Self.remoteMethodCtx('creditRequestPdf', {
|
||||||
description: 'Returns the credit request pdf',
|
description: 'Returns the credit request pdf',
|
||||||
|
@ -40,17 +38,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.creditRequestPdf = async(ctx, id) => {
|
Self.creditRequestPdf = (ctx, id) => Self.printReport(ctx, id, 'credit-request');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('credit-request', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -97,7 +97,7 @@ module.exports = Self => {
|
||||||
|
|
||||||
const stmts = [];
|
const stmts = [];
|
||||||
const stmt = new ParameterizedSQL(
|
const stmt = new ParameterizedSQL(
|
||||||
`SELECT
|
`SELECT
|
||||||
c.id,
|
c.id,
|
||||||
c.name,
|
c.name,
|
||||||
c.socialName,
|
c.socialName,
|
||||||
|
|
|
@ -80,7 +80,7 @@ module.exports = function(Self) {
|
||||||
const data = await Self.rawSql(query, [id, date], myOptions);
|
const data = await Self.rawSql(query, [id, date], myOptions);
|
||||||
|
|
||||||
client.debt = data[0].debt;
|
client.debt = data[0].debt;
|
||||||
client.unpaid = await Self.app.models.ClientUnpaid.findOne({id}, myOptions);
|
client.unpaid = await Self.app.models.ClientUnpaid.findById(id, null, myOptions);
|
||||||
|
|
||||||
return client;
|
return client;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('incotermsAuthorizationEmail', {
|
Self.remoteMethodCtx('incotermsAuthorizationEmail', {
|
||||||
description: 'Sends the incoterms authorization email with an attached PDF',
|
description: 'Sends the incoterms authorization email with an attached PDF',
|
||||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.incotermsAuthorizationEmail = async ctx => {
|
Self.incotermsAuthorizationEmail = ctx => Self.sendTemplate(ctx, 'incoterms-authorization');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('incoterms-authorization', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('incotermsAuthorizationHtml', {
|
Self.remoteMethodCtx('incotermsAuthorizationHtml', {
|
||||||
description: 'Returns the incoterms authorization email preview',
|
description: 'Returns the incoterms authorization email preview',
|
||||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.incotermsAuthorizationHtml = async(ctx, id) => {
|
Self.incotermsAuthorizationHtml = (ctx, id) => Self.printEmail(ctx, id, 'incoterms-authorization');
|
||||||
const {accessToken} = ctx.req;
|
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
params.access_token = accessToken.id;
|
|
||||||
|
|
||||||
const report = new Email('incoterms-authorization', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('incotermsAuthorizationPdf', {
|
Self.remoteMethodCtx('incotermsAuthorizationPdf', {
|
||||||
description: 'Returns the incoterms authorization pdf',
|
description: 'Returns the incoterms authorization pdf',
|
||||||
|
@ -46,17 +44,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.incotermsAuthorizationPdf = async(ctx, id) => {
|
Self.incotermsAuthorizationPdf = (ctx, id) => Self.printReport(ctx, id, 'incoterms-authorization');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('incoterms-authorization', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('letterDebtorNdEmail', {
|
Self.remoteMethodCtx('letterDebtorNdEmail', {
|
||||||
description: 'Sends the second debtor letter email with an attached PDF',
|
description: 'Sends the second debtor letter email with an attached PDF',
|
||||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.letterDebtorNdEmail = async ctx => {
|
Self.letterDebtorNdEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-nd');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('letter-debtor-nd', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('letterDebtorNdHtml', {
|
Self.remoteMethodCtx('letterDebtorNdHtml', {
|
||||||
description: 'Returns the second letter debtor email preview',
|
description: 'Returns the second letter debtor email preview',
|
||||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.letterDebtorNdHtml = async(ctx, id) => {
|
Self.letterDebtorNdHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-nd');
|
||||||
const {accessToken} = ctx.req;
|
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
params.access_token = accessToken.id;
|
|
||||||
|
|
||||||
const report = new Email('letter-debtor-nd', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('letterDebtorPdf', {
|
Self.remoteMethodCtx('letterDebtorPdf', {
|
||||||
description: 'Returns the letter debtor pdf',
|
description: 'Returns the letter debtor pdf',
|
||||||
|
@ -46,17 +44,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.letterDebtorPdf = async(ctx, id) => {
|
Self.letterDebtorPdf = (ctx, id) => Self.printReport(ctx, id, 'letter-debtor');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('letter-debtor', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('letterDebtorStEmail', {
|
Self.remoteMethodCtx('letterDebtorStEmail', {
|
||||||
description: 'Sends the printer setup email with an attached PDF',
|
description: 'Sends the printer setup email with an attached PDF',
|
||||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.letterDebtorStEmail = async ctx => {
|
Self.letterDebtorStEmail = ctx => Self.sendTemplate(ctx, 'letter-debtor-st');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('letter-debtor-st', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('letterDebtorStHtml', {
|
Self.remoteMethodCtx('letterDebtorStHtml', {
|
||||||
description: 'Returns the letter debtor email preview',
|
description: 'Returns the letter debtor email preview',
|
||||||
|
@ -46,21 +44,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.letterDebtorStHtml = async(ctx, id) => {
|
Self.letterDebtorStHtml = (ctx, id) => Self.printEmail(ctx, id, 'letter-debtor-st');
|
||||||
const {accessToken} = ctx.req;
|
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
params.access_token = accessToken.id;
|
|
||||||
|
|
||||||
const report = new Email('letter-debtor-st', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('printerSetupEmail', {
|
Self.remoteMethodCtx('printerSetupEmail', {
|
||||||
description: 'Sends the printer setup email with an attached PDF',
|
description: 'Sends the printer setup email with an attached PDF',
|
||||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.printerSetupEmail = async ctx => {
|
Self.printerSetupEmail = ctx => Self.sendTemplate(ctx, 'printer-setup');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('printer-setup', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('printerSetupHtml', {
|
Self.remoteMethodCtx('printerSetupHtml', {
|
||||||
description: 'Returns the printer setup email preview',
|
description: 'Returns the printer setup email preview',
|
||||||
|
@ -40,19 +38,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.printerSetupHtml = async(ctx, id) => {
|
Self.printerSetupHtml = (ctx, id) => Self.printEmail(ctx, id, 'printer-setup');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
params.isPreview = true;
|
|
||||||
|
|
||||||
const report = new Email('printer-setup', params);
|
|
||||||
const html = await report.render();
|
|
||||||
|
|
||||||
return [html, 'text/html', `filename="mail-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('sepaCoreEmail', {
|
Self.remoteMethodCtx('sepaCoreEmail', {
|
||||||
description: 'Sends the campaign metrics email with an attached PDF',
|
description: 'Sends the campaign metrics email with an attached PDF',
|
||||||
|
@ -47,19 +45,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.sepaCoreEmail = async ctx => {
|
Self.sepaCoreEmail = ctx => Self.sendTemplate(ctx, 'sepa-core');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('sepa-core', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const { Report } = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('balanceCompensationPdf', {
|
Self.remoteMethodCtx('balanceCompensationPdf', {
|
||||||
description: 'Returns the the debit balances compensation pdf',
|
description: 'Returns the the debit balances compensation pdf',
|
||||||
|
@ -10,7 +8,7 @@ module.exports = Self => {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The receipt id',
|
description: 'The receipt id',
|
||||||
http: { source: 'path' }
|
http: {source: 'path'}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: [
|
returns: [
|
||||||
|
@ -34,17 +32,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.balanceCompensationPdf = async(ctx, id) => {
|
Self.balanceCompensationPdf = (ctx, id) => Self.printReport(ctx, id, 'balance-compensation');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('balance-compensation', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('receiptPdf', {
|
Self.remoteMethodCtx('receiptPdf', {
|
||||||
description: 'Returns the receipt pdf',
|
description: 'Returns the receipt pdf',
|
||||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.receiptPdf = async(ctx, id) => {
|
Self.receiptPdf = (ctx, id) => Self.printReport(ctx, id, 'receipt');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('receipt', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -279,6 +279,18 @@ module.exports = Self => {
|
||||||
// Credit changes
|
// Credit changes
|
||||||
if (changes.credit !== undefined)
|
if (changes.credit !== undefined)
|
||||||
await Self.changeCredit(ctx, finalState, changes);
|
await Self.changeCredit(ctx, finalState, changes);
|
||||||
|
|
||||||
|
const oldInstance = {};
|
||||||
|
if (!ctx.isNewInstance) {
|
||||||
|
const newProps = Object.keys(changes);
|
||||||
|
Object.keys(orgData.__data).forEach(prop => {
|
||||||
|
if (newProps.includes(prop))
|
||||||
|
oldInstance[prop] = orgData[prop];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.hookState.oldInstance = oldInstance;
|
||||||
|
ctx.hookState.newInstance = changes;
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.observe('after save', async ctx => {
|
Self.observe('after save', async ctx => {
|
||||||
|
|
|
@ -154,6 +154,11 @@
|
||||||
"model": "Account",
|
"model": "Account",
|
||||||
"foreignKey": "id"
|
"foreignKey": "id"
|
||||||
},
|
},
|
||||||
|
"user": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Account",
|
||||||
|
"foreignKey": "id"
|
||||||
|
},
|
||||||
"payMethod": {
|
"payMethod": {
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "PayMethod",
|
"model": "PayMethod",
|
||||||
|
|
|
@ -62,6 +62,11 @@
|
||||||
"type": "belongsTo",
|
"type": "belongsTo",
|
||||||
"model": "Bank",
|
"model": "Bank",
|
||||||
"foreignKey": "bankFk"
|
"foreignKey": "bankFk"
|
||||||
|
},
|
||||||
|
"supplier": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Supplier",
|
||||||
|
"foreignKey": "companyFk"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -70,11 +70,12 @@
|
||||||
icon="icon-no036"
|
icon="icon-no036"
|
||||||
ng-if="$ctrl.client.isTaxDataChecked == false">
|
ng-if="$ctrl.client.isTaxDataChecked == false">
|
||||||
</vn-icon>
|
</vn-icon>
|
||||||
<vn-icon
|
<vn-icon-button
|
||||||
vn-tooltip="{{$ctrl.clientUnpaid()}}"
|
vn-tooltip="{{$ctrl.clientUnpaid()}}"
|
||||||
icon="icon-clientUnpaid"
|
icon="icon-clientUnpaid"
|
||||||
|
ui-sref="client.card.unpaid"
|
||||||
ng-if="$ctrl.client.unpaid">
|
ng-if="$ctrl.client.unpaid">
|
||||||
</vn-icon>
|
</vn-icon-button>
|
||||||
</div>
|
</div>
|
||||||
<div class="quicklinks">
|
<div class="quicklinks">
|
||||||
<div ng-transclude="btnOne">
|
<div ng-transclude="btnOne">
|
||||||
|
|
|
@ -46,8 +46,9 @@ class Controller extends Descriptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
clientUnpaid() {
|
clientUnpaid() {
|
||||||
return this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) +
|
return this.$t(`Unpaid`) + '<br/>'
|
||||||
'<br/>' + this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount});
|
+ this.$t(`Unpaid Dated`, {dated: this.client.unpaid.dated}) + '<br/>'
|
||||||
|
+ this.$t(`Unpaid Amount`, {amount: this.client.unpaid.amount});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,12 +9,12 @@
|
||||||
</vn-watcher>
|
</vn-watcher>
|
||||||
<form
|
<form
|
||||||
name="form"
|
name="form"
|
||||||
ng-submit="watcher.submit()"
|
ng-submit="$ctrl.onSubmit()"
|
||||||
class="vn-w-md">
|
class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-vertical>
|
<vn-vertical>
|
||||||
<vn-check
|
<vn-check
|
||||||
label="Unpaid client"
|
label="Unpaid client"
|
||||||
ng-model="watcher.hasData"
|
ng-model="watcher.hasData"
|
||||||
on-change="$ctrl.setDefaultDate(watcher.hasData)">
|
on-change="$ctrl.setDefaultDate(watcher.hasData)">
|
||||||
</vn-check>
|
</vn-check>
|
||||||
|
@ -48,4 +48,4 @@
|
||||||
</vn-button>
|
</vn-button>
|
||||||
</vn-button-bar>
|
</vn-button-bar>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -6,9 +6,17 @@ export default class Controller extends Section {
|
||||||
if (hasData && !this.clientUnpaid.dated)
|
if (hasData && !this.clientUnpaid.dated)
|
||||||
this.clientUnpaid.dated = Date.vnNew();
|
this.clientUnpaid.dated = Date.vnNew();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onSubmit() {
|
||||||
|
this.$.watcher.submit()
|
||||||
|
.then(() => this.card.reload());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnClientUnpaid', {
|
ngModule.vnComponent('vnClientUnpaid', {
|
||||||
template: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller
|
controller: Controller,
|
||||||
|
require: {
|
||||||
|
card: '^vnClientCard'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('entryOrderPdf', {
|
Self.remoteMethodCtx('entryOrderPdf', {
|
||||||
description: 'Returns the entry order pdf',
|
description: 'Returns the entry order pdf',
|
||||||
|
@ -38,17 +36,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.entryOrderPdf = async(ctx, id) => {
|
Self.entryOrderPdf = (ctx, id) => Self.printReport(ctx, id, 'entry-order');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('entry-order', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('invoiceInEmail', {
|
Self.remoteMethodCtx('invoiceInEmail', {
|
||||||
description: 'Sends the invoice in email with an attached PDF',
|
description: 'Sends the invoice in email with an attached PDF',
|
||||||
|
@ -35,19 +33,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.invoiceInEmail = async ctx => {
|
Self.invoiceInEmail = ctx => Self.sendTemplate(ctx, 'invoiceIn');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('invoiceIn', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('invoiceInPdf', {
|
Self.remoteMethodCtx('invoiceInPdf', {
|
||||||
description: 'Returns the invoiceIn pdf',
|
description: 'Returns the invoiceIn pdf',
|
||||||
|
@ -34,17 +32,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.invoiceInPdf = async(ctx, id) => {
|
Self.invoiceInPdf = (ctx, id) => Self.printReport(ctx, id, 'invoiceIn');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
delete args.ctx;
|
|
||||||
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('invoiceIn', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,47 +4,37 @@ module.exports = Self => {
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [
|
accepts: [
|
||||||
{
|
{
|
||||||
|
arg: 'clientId',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The client id'
|
||||||
|
}, {
|
||||||
arg: 'invoiceDate',
|
arg: 'invoiceDate',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: 'The invoice date'
|
description: 'The invoice date',
|
||||||
},
|
required: true
|
||||||
{
|
}, {
|
||||||
arg: 'maxShipped',
|
arg: 'maxShipped',
|
||||||
type: 'date',
|
type: 'date',
|
||||||
description: 'The maximum shipped date'
|
description: 'The maximum shipped date',
|
||||||
},
|
required: true
|
||||||
{
|
}, {
|
||||||
arg: 'fromClientId',
|
|
||||||
type: 'number',
|
|
||||||
description: 'The minimum client id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'toClientId',
|
|
||||||
type: 'number',
|
|
||||||
description: 'The maximum client id'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
arg: 'companyFk',
|
arg: 'companyFk',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'The company id to invoice'
|
description: 'The company id to invoice',
|
||||||
}
|
required: true
|
||||||
|
},
|
||||||
],
|
],
|
||||||
returns: [{
|
returns: {
|
||||||
arg: 'clientsAndAddresses',
|
type: 'Object',
|
||||||
type: ['object']
|
root: true
|
||||||
},
|
},
|
||||||
{
|
|
||||||
arg: 'invoice',
|
|
||||||
type: 'object'
|
|
||||||
}],
|
|
||||||
http: {
|
http: {
|
||||||
path: '/clientsToInvoice',
|
path: '/clientsToInvoice',
|
||||||
verb: 'POST'
|
verb: 'POST'
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.clientsToInvoice = async(ctx, options) => {
|
Self.clientsToInvoice = async(ctx, clientId, invoiceDate, maxShipped, companyFk, options) => {
|
||||||
const args = ctx.args;
|
|
||||||
let tx;
|
let tx;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
|
|
||||||
|
@ -56,134 +46,52 @@ module.exports = Self => {
|
||||||
myOptions.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
let query;
|
|
||||||
try {
|
try {
|
||||||
query = `
|
|
||||||
SELECT MAX(issued) issued
|
|
||||||
FROM vn.invoiceOut io
|
|
||||||
JOIN vn.time t ON t.dated = io.issued
|
|
||||||
WHERE io.serial = 'A'
|
|
||||||
AND t.year = YEAR(?)
|
|
||||||
AND io.companyFk = ?`;
|
|
||||||
const [maxIssued] = await Self.rawSql(query, [
|
|
||||||
args.invoiceDate,
|
|
||||||
args.companyFk
|
|
||||||
], myOptions);
|
|
||||||
|
|
||||||
const maxSerialDate = maxIssued.issued || args.invoiceDate;
|
|
||||||
if (args.invoiceDate < maxSerialDate)
|
|
||||||
args.invoiceDate = maxSerialDate;
|
|
||||||
|
|
||||||
if (args.invoiceDate < args.maxShipped)
|
|
||||||
args.maxShipped = args.invoiceDate;
|
|
||||||
|
|
||||||
const minShipped = Date.vnNew();
|
|
||||||
minShipped.setFullYear(minShipped.getFullYear() - 1);
|
|
||||||
minShipped.setMonth(1);
|
|
||||||
minShipped.setDate(1);
|
|
||||||
minShipped.setHours(0, 0, 0, 0);
|
|
||||||
|
|
||||||
// Packaging liquidation
|
// Packaging liquidation
|
||||||
const vIsAllInvoiceable = false;
|
const vIsAllInvoiceable = false;
|
||||||
const clientsWithPackaging = await getClientsWithPackaging(ctx, myOptions);
|
await Self.rawSql('CALL ticketPackaging_add(?, ?, ?, ?)', [
|
||||||
for (let client of clientsWithPackaging) {
|
clientId,
|
||||||
await Self.rawSql('CALL packageInvoicing(?, ?, ?, ?, @newTicket)', [
|
invoiceDate,
|
||||||
client.id,
|
companyFk,
|
||||||
args.invoiceDate,
|
vIsAllInvoiceable
|
||||||
args.companyFk,
|
], myOptions);
|
||||||
vIsAllInvoiceable
|
|
||||||
], myOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
const invoiceableClients = await getInvoiceableClients(ctx, myOptions);
|
const minShipped = Date.vnNew();
|
||||||
|
minShipped.setFullYear(maxShipped.getFullYear() - 1);
|
||||||
|
|
||||||
if (!invoiceableClients) return;
|
const query = `
|
||||||
|
SELECT c.id clientId,
|
||||||
|
c.name clientName,
|
||||||
|
a.id,
|
||||||
|
a.nickname
|
||||||
|
FROM ticket t
|
||||||
|
JOIN address a ON a.id = t.addressFk
|
||||||
|
JOIN client c ON c.id = t.clientFk
|
||||||
|
WHERE t.refFk IS NULL
|
||||||
|
AND t.shipped BETWEEN ? AND util.dayEnd(?)
|
||||||
|
AND (t.clientFk = ? OR ? IS NULL )
|
||||||
|
AND t.companyFk = ?
|
||||||
|
AND c.hasToInvoice
|
||||||
|
AND c.isTaxDataChecked
|
||||||
|
AND c.isActive
|
||||||
|
AND NOT t.isDeleted
|
||||||
|
GROUP BY c.id, IF(c.hasToInvoiceByAddress, a.id, TRUE)
|
||||||
|
HAVING SUM(t.totalWithVat) > 0;`;
|
||||||
|
|
||||||
const clientsAndAddresses = invoiceableClients.map(invoiceableClient => {
|
const addresses = await Self.rawSql(query, [
|
||||||
return {
|
minShipped,
|
||||||
clientId: invoiceableClient.id,
|
maxShipped,
|
||||||
addressId: invoiceableClient.addressFk
|
clientId,
|
||||||
|
clientId,
|
||||||
};
|
companyFk
|
||||||
}
|
], myOptions);
|
||||||
);
|
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return [
|
return addresses;
|
||||||
clientsAndAddresses,
|
|
||||||
{
|
|
||||||
invoiceDate: args.invoiceDate,
|
|
||||||
maxShipped: args.maxShipped,
|
|
||||||
fromClientId: args.fromClientId,
|
|
||||||
toClientId: args.toClientId,
|
|
||||||
companyFk: args.companyFk,
|
|
||||||
minShipped: minShipped
|
|
||||||
}
|
|
||||||
];
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (tx) await tx.rollback();
|
if (tx) await tx.rollback();
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getClientsWithPackaging(ctx, options) {
|
|
||||||
const models = Self.app.models;
|
|
||||||
const args = ctx.args;
|
|
||||||
const query = `SELECT DISTINCT clientFk AS id
|
|
||||||
FROM ticket t
|
|
||||||
JOIN ticketPackaging tp ON t.id = tp.ticketFk
|
|
||||||
JOIN client c ON c.id = t.clientFk
|
|
||||||
WHERE t.shipped BETWEEN '2017-11-21' AND ?
|
|
||||||
AND t.clientFk >= ?
|
|
||||||
AND (t.clientFk <= ? OR ? IS NULL)
|
|
||||||
AND c.isActive`;
|
|
||||||
return models.InvoiceOut.rawSql(query, [
|
|
||||||
args.maxShipped,
|
|
||||||
args.fromClientId,
|
|
||||||
args.toClientId,
|
|
||||||
args.toClientId
|
|
||||||
], options);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getInvoiceableClients(ctx, options) {
|
|
||||||
const models = Self.app.models;
|
|
||||||
const args = ctx.args;
|
|
||||||
const minShipped = Date.vnNew();
|
|
||||||
minShipped.setFullYear(minShipped.getFullYear() - 1);
|
|
||||||
|
|
||||||
const query = `SELECT
|
|
||||||
c.id,
|
|
||||||
SUM(IFNULL
|
|
||||||
(
|
|
||||||
s.quantity *
|
|
||||||
s.price * (100-s.discount)/100,
|
|
||||||
0)
|
|
||||||
+ IFNULL(ts.quantity * ts.price,0)
|
|
||||||
) AS sumAmount,
|
|
||||||
c.hasToInvoiceByAddress,
|
|
||||||
c.email,
|
|
||||||
c.isToBeMailed,
|
|
||||||
a.id addressFk
|
|
||||||
FROM ticket t
|
|
||||||
LEFT JOIN sale s ON s.ticketFk = t.id
|
|
||||||
LEFT JOIN ticketService ts ON ts.ticketFk = t.id
|
|
||||||
JOIN address a ON a.id = t.addressFk
|
|
||||||
JOIN client c ON c.id = t.clientFk
|
|
||||||
WHERE ISNULL(t.refFk) AND c.id >= ?
|
|
||||||
AND (t.clientFk <= ? OR ? IS NULL)
|
|
||||||
AND t.shipped BETWEEN ? AND util.dayEnd(?)
|
|
||||||
AND t.companyFk = ? AND c.hasToInvoice
|
|
||||||
AND c.isTaxDataChecked AND c.isActive
|
|
||||||
GROUP BY c.id, IF(c.hasToInvoiceByAddress,a.id,TRUE) HAVING sumAmount > 0`;
|
|
||||||
|
|
||||||
return models.InvoiceOut.rawSql(query, [
|
|
||||||
args.fromClientId,
|
|
||||||
args.toClientId,
|
|
||||||
args.toClientId,
|
|
||||||
minShipped,
|
|
||||||
args.maxShipped,
|
|
||||||
args.companyFk
|
|
||||||
], options);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -44,7 +44,7 @@ module.exports = Self => {
|
||||||
try {
|
try {
|
||||||
const invoiceOut = await Self.findById(id, null, myOptions);
|
const invoiceOut = await Self.findById(id, null, myOptions);
|
||||||
const hasInvoicing = await models.Account.hasRole(userId, 'invoicing', myOptions);
|
const hasInvoicing = await models.Account.hasRole(userId, 'invoicing', myOptions);
|
||||||
console.log(invoiceOut, !hasInvoicing);
|
|
||||||
if (invoiceOut.hasPdf && !hasInvoicing)
|
if (invoiceOut.hasPdf && !hasInvoicing)
|
||||||
throw new UserError(`You don't have enough privileges`);
|
throw new UserError(`You don't have enough privileges`);
|
||||||
|
|
||||||
|
|
|
@ -65,7 +65,7 @@ module.exports = Self => {
|
||||||
try {
|
try {
|
||||||
await fs.access(file.path);
|
await fs.access(file.path);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await Self.createPdf(ctx, id);
|
await Self.createPdf(ctx, id, myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
const stream = fs.createReadStream(file.path);
|
const stream = fs.createReadStream(file.path);
|
||||||
|
|
|
@ -47,7 +47,6 @@ module.exports = Self => {
|
||||||
ids = ids.split(',');
|
ids = ids.split(',');
|
||||||
|
|
||||||
for (let id of ids) {
|
for (let id of ids) {
|
||||||
console.log(zipConfig, totalSize, zipConfig ? zipConfig.maxSize : null);
|
|
||||||
if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large');
|
if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large');
|
||||||
const invoiceOutPdf = await models.InvoiceOut.download(ctx, id, myOptions);
|
const invoiceOutPdf = await models.InvoiceOut.download(ctx, id, myOptions);
|
||||||
const fileName = extractFileName(invoiceOutPdf[2]);
|
const fileName = extractFileName(invoiceOutPdf[2]);
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('exportationPdf', {
|
Self.remoteMethodCtx('exportationPdf', {
|
||||||
description: 'Returns the exportation pdf',
|
description: 'Returns the exportation pdf',
|
||||||
|
@ -39,17 +37,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.exportationPdf = async(ctx, reference) => {
|
Self.exportationPdf = (ctx, reference) => Self.printReport(ctx, reference, 'exportation');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('exportation', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="doc-${reference}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,35 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('getInvoiceDate', {
|
||||||
|
description: 'Returns default Invoice Date',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'companyFk',
|
||||||
|
type: 'number',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/getInvoiceDate`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getInvoiceDate = async companyFk => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const [invoiceDate] = await models.InvoiceOut.rawSql(
|
||||||
|
`SELECT MAX(io.issued) issued
|
||||||
|
FROM invoiceOut io
|
||||||
|
JOIN invoiceOutSerial ios ON ios.code = io.serial
|
||||||
|
WHERE ios.type = 'global'
|
||||||
|
AND io.issued
|
||||||
|
AND io.companyFk = ?`,
|
||||||
|
[companyFk]
|
||||||
|
);
|
||||||
|
return invoiceDate;
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,43 +1,42 @@
|
||||||
|
const UserError = require('vn-loopback/util/user-error');
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('invoiceClient', {
|
Self.remoteMethodCtx('invoiceClient', {
|
||||||
description: 'Make a invoice of a client',
|
description: 'Make a invoice of a client',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [
|
||||||
arg: 'clientId',
|
{
|
||||||
type: 'number',
|
arg: 'clientId',
|
||||||
description: 'The client id to invoice',
|
type: 'number',
|
||||||
required: true
|
description: 'The client id to invoice',
|
||||||
},
|
required: true
|
||||||
{
|
}, {
|
||||||
arg: 'addressId',
|
arg: 'addressId',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
description: 'The address id to invoice',
|
description: 'The address id to invoice',
|
||||||
required: true
|
required: true
|
||||||
},
|
}, {
|
||||||
{
|
arg: 'invoiceDate',
|
||||||
arg: 'invoiceDate',
|
type: 'date',
|
||||||
type: 'date',
|
description: 'The invoice date',
|
||||||
description: 'The invoice date',
|
required: true
|
||||||
required: true
|
}, {
|
||||||
},
|
arg: 'maxShipped',
|
||||||
{
|
type: 'date',
|
||||||
arg: 'maxShipped',
|
description: 'The maximum shipped date',
|
||||||
type: 'date',
|
required: true
|
||||||
description: 'The maximum shipped date',
|
}, {
|
||||||
required: true
|
arg: 'companyFk',
|
||||||
},
|
type: 'number',
|
||||||
{
|
description: 'The company id to invoice',
|
||||||
arg: 'companyFk',
|
required: true
|
||||||
type: 'number',
|
}, {
|
||||||
description: 'The company id to invoice',
|
arg: 'printerFk',
|
||||||
required: true
|
type: 'number',
|
||||||
},
|
description: 'The printer to print',
|
||||||
{
|
required: true
|
||||||
arg: 'minShipped',
|
}
|
||||||
type: 'date',
|
],
|
||||||
description: 'The minium shupped date',
|
|
||||||
required: true
|
|
||||||
}],
|
|
||||||
returns: {
|
returns: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
|
@ -62,69 +61,65 @@ module.exports = Self => {
|
||||||
myOptions.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const minShipped = Date.vnNew();
|
||||||
|
minShipped.setFullYear(args.maxShipped.getFullYear() - 1);
|
||||||
|
|
||||||
let invoiceId;
|
let invoiceId;
|
||||||
let invoiceOut;
|
let invoiceOut;
|
||||||
try {
|
try {
|
||||||
const client = await models.Client.findById(args.clientId, {
|
const client = await models.Client.findById(args.clientId, {
|
||||||
fields: ['id', 'hasToInvoiceByAddress']
|
fields: ['id', 'hasToInvoiceByAddress']
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
try {
|
|
||||||
if (client.hasToInvoiceByAddress) {
|
|
||||||
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
|
||||||
args.minShipped,
|
|
||||||
args.maxShipped,
|
|
||||||
args.addressId,
|
|
||||||
args.companyFk
|
|
||||||
], myOptions);
|
|
||||||
} else {
|
|
||||||
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
|
||||||
args.maxShipped,
|
|
||||||
client.id,
|
|
||||||
args.companyFk
|
|
||||||
], myOptions);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make invoice
|
if (client.hasToInvoiceByAddress) {
|
||||||
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
||||||
|
minShipped,
|
||||||
// Validates ticket nagative base
|
args.maxShipped,
|
||||||
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
args.addressId,
|
||||||
if (hasAnyNegativeBase && isSpanishCompany)
|
args.companyFk
|
||||||
return tx.rollback();
|
], myOptions);
|
||||||
|
} else {
|
||||||
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
||||||
const [invoiceSerial] = await Self.rawSql(query, [
|
args.maxShipped,
|
||||||
client.id,
|
client.id,
|
||||||
args.companyFk,
|
args.companyFk
|
||||||
'G'
|
|
||||||
], myOptions);
|
], myOptions);
|
||||||
const serialLetter = invoiceSerial.serial;
|
|
||||||
|
|
||||||
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
|
||||||
await Self.rawSql(query, [
|
|
||||||
serialLetter,
|
|
||||||
args.invoiceDate
|
|
||||||
], myOptions);
|
|
||||||
|
|
||||||
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
|
||||||
if (newInvoice.id) {
|
|
||||||
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
|
||||||
|
|
||||||
invoiceId = newInvoice.id;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
const failedClient = {
|
|
||||||
id: client.id,
|
|
||||||
stacktrace: e
|
|
||||||
};
|
|
||||||
await notifyFailures(ctx, failedClient, myOptions);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
invoiceOut = await models.InvoiceOut.findById(invoiceId, {
|
// Make invoice
|
||||||
include: {
|
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
||||||
relation: 'client'
|
|
||||||
}
|
// Validates ticket nagative base
|
||||||
}, myOptions);
|
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
||||||
|
if (hasAnyNegativeBase && isSpanishCompany)
|
||||||
|
throw new UserError('Negative basis');
|
||||||
|
|
||||||
|
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
||||||
|
const [invoiceSerial] = await Self.rawSql(query, [
|
||||||
|
client.id,
|
||||||
|
args.companyFk,
|
||||||
|
'G'
|
||||||
|
], myOptions);
|
||||||
|
const serialLetter = invoiceSerial.serial;
|
||||||
|
|
||||||
|
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
||||||
|
await Self.rawSql(query, [
|
||||||
|
serialLetter,
|
||||||
|
args.invoiceDate
|
||||||
|
], myOptions);
|
||||||
|
|
||||||
|
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
||||||
|
if (newInvoice.id) {
|
||||||
|
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
||||||
|
|
||||||
|
invoiceOut = await models.InvoiceOut.findById(newInvoice.id, {
|
||||||
|
include: {
|
||||||
|
relation: 'client'
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
invoiceId = newInvoice.id;
|
||||||
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -132,15 +127,26 @@ module.exports = Self => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.args = {
|
if (invoiceId) {
|
||||||
reference: invoiceOut.ref,
|
if (!invoiceOut.client().isToBeMailed) {
|
||||||
recipientId: invoiceOut.clientFk,
|
const query = `
|
||||||
recipient: invoiceOut.client().email
|
CALL vn.report_print(
|
||||||
};
|
'invoice',
|
||||||
try {
|
?,
|
||||||
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
account.myUser_getId(),
|
||||||
} catch (err) {}
|
JSON_OBJECT('refFk', ?),
|
||||||
|
'normal'
|
||||||
|
);`;
|
||||||
|
await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]);
|
||||||
|
} else {
|
||||||
|
ctx.args = {
|
||||||
|
reference: invoiceOut.ref,
|
||||||
|
recipientId: invoiceOut.clientFk,
|
||||||
|
recipient: invoiceOut.client().email
|
||||||
|
};
|
||||||
|
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
||||||
|
}
|
||||||
|
}
|
||||||
return invoiceId;
|
return invoiceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,13 +154,12 @@ module.exports = Self => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const query = 'SELECT hasAnyNegativeBase() AS base';
|
const query = 'SELECT hasAnyNegativeBase() AS base';
|
||||||
const [result] = await models.InvoiceOut.rawSql(query, null, options);
|
const [result] = await models.InvoiceOut.rawSql(query, null, options);
|
||||||
|
|
||||||
return result && result.base;
|
return result && result.base;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getIsSpanishCompany(companyId, options) {
|
async function getIsSpanishCompany(companyId, options) {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const query = `SELECT COUNT(*) AS total
|
const query = `SELECT COUNT(*) isSpanishCompany
|
||||||
FROM supplier s
|
FROM supplier s
|
||||||
JOIN country c ON c.id = s.countryFk
|
JOIN country c ON c.id = s.countryFk
|
||||||
AND c.code = 'ES'
|
AND c.code = 'ES'
|
||||||
|
@ -163,28 +168,6 @@ module.exports = Self => {
|
||||||
companyId
|
companyId
|
||||||
], options);
|
], options);
|
||||||
|
|
||||||
return supplierCompany && supplierCompany.total;
|
return supplierCompany && supplierCompany.isSpanishCompany;
|
||||||
}
|
|
||||||
|
|
||||||
async function notifyFailures(ctx, failedClient, options) {
|
|
||||||
const models = Self.app.models;
|
|
||||||
const userId = ctx.req.accessToken.userId;
|
|
||||||
const $t = ctx.req.__; // $translate
|
|
||||||
|
|
||||||
const worker = await models.EmailUser.findById(userId, null, options);
|
|
||||||
const subject = $t('Global invoicing failed');
|
|
||||||
let body = $t(`Wasn't able to invoice the following clients`) + ':<br/><br/>';
|
|
||||||
|
|
||||||
body += `ID: <strong>${failedClient.id}</strong>
|
|
||||||
<br/> <strong>${failedClient.stacktrace}</strong><br/><br/>`;
|
|
||||||
|
|
||||||
await Self.rawSql(`
|
|
||||||
INSERT INTO vn.mail (sender, replyTo, sent, subject, body)
|
|
||||||
VALUES (?, ?, FALSE, ?, ?)`, [
|
|
||||||
worker.email,
|
|
||||||
worker.email,
|
|
||||||
subject,
|
|
||||||
body
|
|
||||||
], options);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,50 @@
|
||||||
|
const {Report} = require('vn-print');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('invoiceOutPdf', {
|
||||||
|
description: 'Returns the invoice pdf',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'reference',
|
||||||
|
type: 'string',
|
||||||
|
required: true,
|
||||||
|
description: 'The invoice reference',
|
||||||
|
http: {source: 'path'}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: [
|
||||||
|
{
|
||||||
|
arg: 'body',
|
||||||
|
type: 'file',
|
||||||
|
root: true
|
||||||
|
}, {
|
||||||
|
arg: 'Content-Type',
|
||||||
|
type: 'String',
|
||||||
|
http: {target: 'header'}
|
||||||
|
}, {
|
||||||
|
arg: 'Content-Disposition',
|
||||||
|
type: 'String',
|
||||||
|
http: {target: 'header'}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
http: {
|
||||||
|
path: '/:reference/invoice-out-pdf',
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.invoiceOutPdf = async(ctx, reference) => {
|
||||||
|
const args = Object.assign({}, ctx.args);
|
||||||
|
const params = {lang: ctx.req.getLocale()};
|
||||||
|
|
||||||
|
delete args.ctx;
|
||||||
|
for (const param in args)
|
||||||
|
params[param] = args[param];
|
||||||
|
|
||||||
|
const report = new Report('invoice', params);
|
||||||
|
const stream = await report.toPdfStream();
|
||||||
|
|
||||||
|
return [stream, 'application/pdf', `filename="doc-${reference}.pdf"`];
|
||||||
|
};
|
||||||
|
};
|
|
@ -1,53 +0,0 @@
|
||||||
const models = require('vn-loopback/server/server').models;
|
|
||||||
const UserError = require('vn-loopback/util/user-error');
|
|
||||||
|
|
||||||
describe('InvoiceOut downloadZip()', () => {
|
|
||||||
const userId = 9;
|
|
||||||
const invoiceIds = '1,2';
|
|
||||||
const ctx = {
|
|
||||||
req: {
|
|
||||||
|
|
||||||
accessToken: {userId: userId},
|
|
||||||
headers: {origin: 'http://localhost:5000'},
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
it('should return part of link to dowloand the zip', async() => {
|
|
||||||
const tx = await models.InvoiceOut.beginTransaction({});
|
|
||||||
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
|
|
||||||
const result = await models.InvoiceOut.downloadZip(ctx, invoiceIds, options);
|
|
||||||
|
|
||||||
expect(result).toBeDefined();
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
throw e;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should return an error if the size of the files is too large', async() => {
|
|
||||||
const tx = await models.InvoiceOut.beginTransaction({});
|
|
||||||
|
|
||||||
let error;
|
|
||||||
try {
|
|
||||||
const options = {transaction: tx};
|
|
||||||
const zipConfig = {
|
|
||||||
maxSize: 0
|
|
||||||
};
|
|
||||||
await models.ZipConfig.create(zipConfig, options);
|
|
||||||
|
|
||||||
await models.InvoiceOut.downloadZip(ctx, invoiceIds, options);
|
|
||||||
|
|
||||||
await tx.rollback();
|
|
||||||
} catch (e) {
|
|
||||||
await tx.rollback();
|
|
||||||
error = e;
|
|
||||||
}
|
|
||||||
|
|
||||||
expect(error).toEqual(new UserError(`Files are too large`));
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -65,7 +65,6 @@ describe('InvoiceOut filter()', () => {
|
||||||
await invoiceOut.updateAttribute('hasPdf', true, options);
|
await invoiceOut.updateAttribute('hasPdf', true, options);
|
||||||
|
|
||||||
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
|
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
|
||||||
console.log(result);
|
|
||||||
|
|
||||||
expect(result.length).toEqual(1);
|
expect(result.length).toEqual(1);
|
||||||
|
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
"InvoiceContainer": {
|
"InvoiceContainer": {
|
||||||
"dataSource": "invoiceStorage"
|
"dataSource": "invoiceStorage"
|
||||||
},
|
},
|
||||||
|
"Printer": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"TaxArea": {
|
"TaxArea": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -15,4 +15,6 @@ module.exports = Self => {
|
||||||
require('../methods/invoiceOut/exportationPdf')(Self);
|
require('../methods/invoiceOut/exportationPdf')(Self);
|
||||||
require('../methods/invoiceOut/invoiceCsv')(Self);
|
require('../methods/invoiceOut/invoiceCsv')(Self);
|
||||||
require('../methods/invoiceOut/invoiceCsvEmail')(Self);
|
require('../methods/invoiceOut/invoiceCsvEmail')(Self);
|
||||||
|
require('../methods/invoiceOut/invoiceOutPdf')(Self);
|
||||||
|
require('../methods/invoiceOut/getInvoiceDate')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
{
|
||||||
|
"name": "Printer",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "printer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isLabeler": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"acls": [{
|
||||||
|
"accessType": "READ",
|
||||||
|
"principalType": "ROLE",
|
||||||
|
"principalId": "$everyone",
|
||||||
|
"permission": "ALLOW"
|
||||||
|
}]
|
||||||
|
}
|
|
@ -0,0 +1,144 @@
|
||||||
|
<vn-card
|
||||||
|
ng-if="$ctrl.status"
|
||||||
|
class="vn-w-lg vn-pa-md"
|
||||||
|
style="height: 80px; display: flex; align-items: center; justify-content: center; gap: 20px;">
|
||||||
|
<vn-spinner
|
||||||
|
enable="$ctrl.status != 'done'">
|
||||||
|
</vn-spinner>
|
||||||
|
<div>
|
||||||
|
<div ng-switch="$ctrl.status">
|
||||||
|
<span ng-switch-when="packageInvoicing" translate>
|
||||||
|
Build packaging tickets
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="invoicing">
|
||||||
|
{{'Invoicing client' | translate}} {{$ctrl.currentAddress.clientId}}
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="stopping" translate>
|
||||||
|
Stopping process
|
||||||
|
</span>
|
||||||
|
<span ng-switch-when="done" translate>
|
||||||
|
Ended process
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div ng-if="$ctrl.nAddresses" class="text-caption text-secondary">
|
||||||
|
{{$ctrl.percentage | percentage: 0}} ({{$ctrl.addressNumber}} {{'of' | translate}} {{$ctrl.nAddresses}})
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</vn-card>
|
||||||
|
<vn-card class="vn-w-lg vn-mt-md" ng-if="$ctrl.errors.length">
|
||||||
|
<vn-table>
|
||||||
|
<vn-thead>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-th number>Id</vn-th>
|
||||||
|
<vn-th>Client</vn-th>
|
||||||
|
<vn-th number>Address id</vn-th>
|
||||||
|
<vn-th>Street</vn-th>
|
||||||
|
<vn-th>Error</vn-th>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-thead>
|
||||||
|
<vn-tbody>
|
||||||
|
<vn-tr ng-repeat="error in $ctrl.errors">
|
||||||
|
<vn-td number>
|
||||||
|
<span
|
||||||
|
vn-click-stop="clientDescriptor.show($event, error.address.clientId)"
|
||||||
|
class="link">
|
||||||
|
{{::error.address.clientId}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
{{::error.address.clientName}}
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>
|
||||||
|
{{::error.address.id}}
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
{{::error.address.nickname}}
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
<span class="chip alert">{{::error.message}}</span>
|
||||||
|
</vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-tbody>
|
||||||
|
</vn-table>
|
||||||
|
</vn-card>
|
||||||
|
<vn-side-menu side="right">
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="InvoiceOutSerials"
|
||||||
|
data="invoiceOutSerials"
|
||||||
|
order="code">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-crud-model
|
||||||
|
auto-load="true"
|
||||||
|
url="Companies"
|
||||||
|
data="companies"
|
||||||
|
order="code">
|
||||||
|
</vn-crud-model>
|
||||||
|
<form class="vn-pa-md">
|
||||||
|
<vn-vertical>
|
||||||
|
<vn-vertical class="vn-mb-sm">
|
||||||
|
<vn-radio
|
||||||
|
label="All clients"
|
||||||
|
val="all"
|
||||||
|
ng-model="$ctrl.clientsToInvoice">
|
||||||
|
</vn-radio>
|
||||||
|
<vn-radio
|
||||||
|
label="One client"
|
||||||
|
val="one"
|
||||||
|
ng-model="$ctrl.clientsToInvoice">
|
||||||
|
</vn-radio>
|
||||||
|
</vn-vertical>
|
||||||
|
<vn-autocomplete
|
||||||
|
url="Clients"
|
||||||
|
label="Client"
|
||||||
|
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
||||||
|
order="id"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
ng-model="$ctrl.clientId"
|
||||||
|
required="true"
|
||||||
|
ng-if="$ctrl.clientsToInvoice == 'one'">
|
||||||
|
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="Invoice date"
|
||||||
|
ng-model="$ctrl.invoiceDate">
|
||||||
|
</vn-date-picker>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="Max date"
|
||||||
|
ng-model="$ctrl.maxShipped">
|
||||||
|
</vn-date-picker>
|
||||||
|
<vn-autocomplete
|
||||||
|
url="Companies"
|
||||||
|
label="Company"
|
||||||
|
show-field="code"
|
||||||
|
value-field="id"
|
||||||
|
ng-model="$ctrl.companyFk">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
url="Printers"
|
||||||
|
label="Printer"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id"
|
||||||
|
where="{isLabeler: false}"
|
||||||
|
ng-model="$ctrl.printerFk">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-submit
|
||||||
|
ng-if="!$ctrl.invoicing"
|
||||||
|
ng-click="$ctrl.makeInvoice()"
|
||||||
|
label="Invoice out">
|
||||||
|
</vn-submit>
|
||||||
|
<vn-submit
|
||||||
|
ng-if="$ctrl.invoicing"
|
||||||
|
label="Stop"
|
||||||
|
ng-click="$ctrl.stopInvoicing()">
|
||||||
|
</vn-submit>
|
||||||
|
</vn-vertical>
|
||||||
|
</form>
|
||||||
|
</vn-side-menu>
|
||||||
|
|
||||||
|
<vn-client-descriptor-popover
|
||||||
|
vn-id="clientDescriptor">
|
||||||
|
</vn-client-descriptor-popover>
|
|
@ -0,0 +1,133 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
import UserError from 'core/lib/user-error';
|
||||||
|
import './style.scss';
|
||||||
|
|
||||||
|
class Controller extends Section {
|
||||||
|
$onInit() {
|
||||||
|
const date = Date.vnNew();
|
||||||
|
Object.assign(this, {
|
||||||
|
maxShipped: new Date(date.getFullYear(), date.getMonth(), 0),
|
||||||
|
clientsToInvoice: 'all',
|
||||||
|
});
|
||||||
|
|
||||||
|
this.$http.get('UserConfigs/getUserConfig')
|
||||||
|
.then(res => {
|
||||||
|
this.companyFk = res.data.companyFk;
|
||||||
|
const params = {
|
||||||
|
companyFk: this.companyFk
|
||||||
|
};
|
||||||
|
return this.$http.get('InvoiceOuts/getInvoiceDate', {params});
|
||||||
|
})
|
||||||
|
.then(res => {
|
||||||
|
this.minInvoicingDate = res.data.issued ? new Date(res.data.issued) : null;
|
||||||
|
this.invoiceDate = this.minInvoicingDate;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
stopInvoicing() {
|
||||||
|
this.status = 'stopping';
|
||||||
|
}
|
||||||
|
|
||||||
|
makeInvoice() {
|
||||||
|
this.invoicing = true;
|
||||||
|
this.status = 'packageInvoicing';
|
||||||
|
this.errors = [];
|
||||||
|
this.addresses = null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (this.clientsToInvoice == 'one' && !this.clientId)
|
||||||
|
throw new UserError('Choose a valid client');
|
||||||
|
if (!this.invoiceDate || !this.maxShipped)
|
||||||
|
throw new UserError('Invoice date and the max date should be filled');
|
||||||
|
if (this.invoiceDate < this.maxShipped)
|
||||||
|
throw new UserError('Invoice date can\'t be less than max date');
|
||||||
|
if (this.invoiceDate.getTime() < this.minInvoicingDate.getTime())
|
||||||
|
throw new UserError('Exists an invoice with a previous date');
|
||||||
|
if (!this.companyFk)
|
||||||
|
throw new UserError('Choose a valid company');
|
||||||
|
if (!this.printerFk)
|
||||||
|
throw new UserError('Choose a valid printer');
|
||||||
|
|
||||||
|
if (this.clientsToInvoice == 'all')
|
||||||
|
this.clientId = undefined;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
invoiceDate: this.invoiceDate,
|
||||||
|
maxShipped: this.maxShipped,
|
||||||
|
clientId: this.clientId,
|
||||||
|
companyFk: this.companyFk
|
||||||
|
};
|
||||||
|
this.$http.post(`InvoiceOuts/clientsToInvoice`, params)
|
||||||
|
.then(res => {
|
||||||
|
this.addresses = res.data;
|
||||||
|
if (!this.addresses.length)
|
||||||
|
throw new UserError(`There aren't tickets to invoice`);
|
||||||
|
|
||||||
|
this.addressIndex = 0;
|
||||||
|
return this.invoiceOut();
|
||||||
|
})
|
||||||
|
.catch(err => this.handleError(err));
|
||||||
|
} catch (err) {
|
||||||
|
this.handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleError(err) {
|
||||||
|
this.invoicing = false;
|
||||||
|
this.status = null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
|
||||||
|
invoiceOut() {
|
||||||
|
if (this.addressIndex == this.addresses.length || this.status == 'stopping') {
|
||||||
|
this.invoicing = false;
|
||||||
|
this.status = 'done';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.status = 'invoicing';
|
||||||
|
const address = this.addresses[this.addressIndex];
|
||||||
|
this.currentAddress = address;
|
||||||
|
|
||||||
|
const params = {
|
||||||
|
clientId: address.clientId,
|
||||||
|
addressId: address.id,
|
||||||
|
invoiceDate: this.invoiceDate,
|
||||||
|
maxShipped: this.maxShipped,
|
||||||
|
companyFk: this.companyFk,
|
||||||
|
printerFk: this.printerFk,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$http.post(`InvoiceOuts/invoiceClient`, params)
|
||||||
|
.catch(res => {
|
||||||
|
this.errors.unshift({
|
||||||
|
address,
|
||||||
|
message: res.data.error.message
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
this.addressIndex++;
|
||||||
|
this.invoiceOut();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
get nAddresses() {
|
||||||
|
if (!this.addresses) return 0;
|
||||||
|
return this.addresses.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
get addressNumber() {
|
||||||
|
return Math.min(this.addressIndex + 1, this.nAddresses);
|
||||||
|
}
|
||||||
|
|
||||||
|
get percentage() {
|
||||||
|
const len = this.nAddresses;
|
||||||
|
return Math.min(this.addressIndex, len) / len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,74 @@
|
||||||
|
import './index';
|
||||||
|
|
||||||
|
describe('InvoiceOut', () => {
|
||||||
|
describe('Component vnInvoiceOutGlobalInvoicing', () => {
|
||||||
|
let controller;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
|
beforeEach(ngModule('invoiceOut'));
|
||||||
|
|
||||||
|
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
const $scope = $rootScope.$new();
|
||||||
|
const $element = angular.element('<vn-invoice-out-global-invoicing></vn-invoice-out-global-invoicing>');
|
||||||
|
|
||||||
|
controller = $componentController('vnInvoiceOutGlobalInvoicing', {$element, $scope});
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('makeInvoice()', () => {
|
||||||
|
it('should throw an error when invoiceDate or maxShipped properties are not filled in', () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
|
controller.clientsToInvoice = 'all';
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
controller.makeInvoice();
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedError = 'Invoice date and the max date should be filled';
|
||||||
|
|
||||||
|
expect(error).toBe(expectedError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an error when select one client and clientId is not filled in', () => {
|
||||||
|
jest.spyOn(controller.vnApp, 'showError');
|
||||||
|
controller.clientsToInvoice = 'one';
|
||||||
|
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
controller.makeInvoice();
|
||||||
|
} catch (e) {
|
||||||
|
error = e.message;
|
||||||
|
}
|
||||||
|
|
||||||
|
const expectedError = 'Choose a valid client';
|
||||||
|
|
||||||
|
expect(error).toBe(expectedError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should make an http POST query and then call to the showSuccess() method', () => {
|
||||||
|
const date = Date.vnNew();
|
||||||
|
Object.assign(controller, {
|
||||||
|
invoiceDate: date,
|
||||||
|
maxShipped: date,
|
||||||
|
minInvoicingDate: date,
|
||||||
|
clientsToInvoice: 'one',
|
||||||
|
clientId: 1101,
|
||||||
|
companyFk: 442,
|
||||||
|
printerFk: 1
|
||||||
|
});
|
||||||
|
$httpBackend.expectPOST(`InvoiceOuts/clientsToInvoice`).respond([{
|
||||||
|
clientId: 1101,
|
||||||
|
id: 121
|
||||||
|
}]);
|
||||||
|
$httpBackend.expectPOST(`InvoiceOuts/invoiceClient`).respond();
|
||||||
|
controller.makeInvoice();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.status).toEqual('done');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,20 @@
|
||||||
|
There aren't tickets to invoice: No existen tickets para facturar
|
||||||
|
Max date: Fecha límite
|
||||||
|
Invoice date: Fecha de factura
|
||||||
|
Invoice date can't be less than max date: La fecha de factura no puede ser inferior a la fecha límite
|
||||||
|
Invoice date and the max date should be filled: La fecha de factura y la fecha límite deben rellenarse
|
||||||
|
Choose a valid company: Selecciona un empresa válida
|
||||||
|
Choose a valid printer: Selecciona una impresora válida
|
||||||
|
All clients: Todos los clientes
|
||||||
|
Build packaging tickets: Generando tickets de embalajes
|
||||||
|
Address id: Id dirección
|
||||||
|
Printer: Impresora
|
||||||
|
of: de
|
||||||
|
Client: Cliente
|
||||||
|
Current client id: Id cliente actual
|
||||||
|
Invoicing client: Facturando cliente
|
||||||
|
Ended process: Proceso finalizado
|
||||||
|
Invoice out: Facturar
|
||||||
|
One client: Un solo cliente
|
||||||
|
Choose a valid client: Selecciona un cliente válido
|
||||||
|
Stop: Parar
|
|
@ -0,0 +1,17 @@
|
||||||
|
@import "variables";
|
||||||
|
|
||||||
|
vn-invoice-out-global-invoicing{
|
||||||
|
|
||||||
|
h5{
|
||||||
|
color: $color-primary;
|
||||||
|
}
|
||||||
|
|
||||||
|
#error {
|
||||||
|
line-break: normal;
|
||||||
|
overflow-wrap: break-word;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,4 +9,4 @@ import './descriptor';
|
||||||
import './descriptor-popover';
|
import './descriptor-popover';
|
||||||
import './descriptor-menu';
|
import './descriptor-menu';
|
||||||
import './index/manual';
|
import './index/manual';
|
||||||
import './index/global-invoicing';
|
import './global-invoicing';
|
||||||
|
|
|
@ -1,96 +0,0 @@
|
||||||
<tpl-title translate>
|
|
||||||
Create global invoice
|
|
||||||
</tpl-title>
|
|
||||||
<tpl-body id="manifold-form">
|
|
||||||
<vn-crud-model
|
|
||||||
auto-load="true"
|
|
||||||
url="InvoiceOutSerials"
|
|
||||||
data="invoiceOutSerials"
|
|
||||||
order="code">
|
|
||||||
</vn-crud-model>
|
|
||||||
<vn-crud-model
|
|
||||||
auto-load="true"
|
|
||||||
url="Companies"
|
|
||||||
data="companies"
|
|
||||||
order="code">
|
|
||||||
</vn-crud-model>
|
|
||||||
<div
|
|
||||||
class="progress vn-my-md"
|
|
||||||
ng-if="$ctrl.packageInvoicing">
|
|
||||||
<vn-horizontal>
|
|
||||||
<div>
|
|
||||||
{{'Calculating packages to invoice...' | translate}}
|
|
||||||
</div>
|
|
||||||
</vn-horizontal>
|
|
||||||
</div>
|
|
||||||
<div
|
|
||||||
class="progress vn-my-md"
|
|
||||||
ng-if="$ctrl.lastClientId">
|
|
||||||
<vn-horizontal>
|
|
||||||
<div>
|
|
||||||
{{'Id Client' | translate}}: {{$ctrl.currentClientId}}
|
|
||||||
{{'of' | translate}} {{::$ctrl.lastClientId}}
|
|
||||||
</div>
|
|
||||||
</vn-horizontal>
|
|
||||||
</div>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-one
|
|
||||||
label="Invoice date"
|
|
||||||
ng-model="$ctrl.invoice.invoiceDate">
|
|
||||||
</vn-date-picker>
|
|
||||||
<vn-date-picker
|
|
||||||
vn-one
|
|
||||||
label="Max date"
|
|
||||||
ng-model="$ctrl.invoice.maxShipped">
|
|
||||||
</vn-date-picker>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-radio
|
|
||||||
label="All clients"
|
|
||||||
val="allClients"
|
|
||||||
ng-model="$ctrl.clientsNumber"
|
|
||||||
ng-click="$ctrl.$onInit()">
|
|
||||||
</vn-radio>
|
|
||||||
<vn-radio
|
|
||||||
label="Clients range"
|
|
||||||
val="clientsRange"
|
|
||||||
ng-model="$ctrl.clientsNumber">
|
|
||||||
</vn-radio>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal ng-show="$ctrl.clientsNumber == 'clientsRange'">
|
|
||||||
<vn-autocomplete
|
|
||||||
url="Clients"
|
|
||||||
label="From client"
|
|
||||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
|
||||||
order="id"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.invoice.fromClientId">
|
|
||||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
<vn-autocomplete
|
|
||||||
url="Clients"
|
|
||||||
label="To client"
|
|
||||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
|
||||||
order="id"
|
|
||||||
show-field="name"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.invoice.toClientId">
|
|
||||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-autocomplete
|
|
||||||
url="Companies"
|
|
||||||
label="Company"
|
|
||||||
show-field="code"
|
|
||||||
value-field="id"
|
|
||||||
ng-model="$ctrl.invoice.companyFk">
|
|
||||||
</vn-autocomplete>
|
|
||||||
</vn-horizontal>
|
|
||||||
</tpl-body>
|
|
||||||
<tpl-buttons>
|
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
|
||||||
<button vn-id="invoiceButton" response="accept" translate>Invoice</button>{{$ctrl.isInvoicing}}
|
|
||||||
</tpl-buttons>
|
|
|
@ -1,129 +0,0 @@
|
||||||
import ngModule from '../../module';
|
|
||||||
import Dialog from 'core/components/dialog';
|
|
||||||
import './style.scss';
|
|
||||||
|
|
||||||
class Controller extends Dialog {
|
|
||||||
constructor($element, $, $transclude) {
|
|
||||||
super($element, $, $transclude);
|
|
||||||
this.invoice = {
|
|
||||||
maxShipped: Date.vnNew()
|
|
||||||
};
|
|
||||||
this.clientsNumber = 'allClients';
|
|
||||||
}
|
|
||||||
|
|
||||||
$onInit() {
|
|
||||||
this.getMinClientId();
|
|
||||||
this.getMaxClientId();
|
|
||||||
}
|
|
||||||
|
|
||||||
getMinClientId() {
|
|
||||||
this.getClientId('min')
|
|
||||||
.then(res => this.invoice.fromClientId = res.data.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
getMaxClientId() {
|
|
||||||
this.getClientId('max')
|
|
||||||
.then(res => this.invoice.toClientId = res.data.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
getClientId(func) {
|
|
||||||
const order = func == 'min' ? 'ASC' : 'DESC';
|
|
||||||
const params = {
|
|
||||||
filter: {
|
|
||||||
order: 'id ' + order,
|
|
||||||
limit: 1
|
|
||||||
}
|
|
||||||
};
|
|
||||||
return this.$http.get('Clients/findOne', {params});
|
|
||||||
}
|
|
||||||
|
|
||||||
get companyFk() {
|
|
||||||
return this.invoice.companyFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
set companyFk(value) {
|
|
||||||
this.invoice.companyFk = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
restartValues() {
|
|
||||||
this.lastClientId = null;
|
|
||||||
this.$.invoiceButton.disabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
cancelRequest() {
|
|
||||||
this.canceler = this.$q.defer();
|
|
||||||
return {timeout: this.canceler.promise};
|
|
||||||
}
|
|
||||||
|
|
||||||
invoiceOut(invoice, clientsAndAddresses) {
|
|
||||||
const [clientAndAddress] = clientsAndAddresses;
|
|
||||||
if (!clientAndAddress) return;
|
|
||||||
this.currentClientId = clientAndAddress.clientId;
|
|
||||||
const params = {
|
|
||||||
clientId: clientAndAddress.clientId,
|
|
||||||
addressId: clientAndAddress.addressId,
|
|
||||||
invoiceDate: invoice.invoiceDate,
|
|
||||||
maxShipped: invoice.maxShipped,
|
|
||||||
companyFk: invoice.companyFk,
|
|
||||||
minShipped: invoice.minShipped,
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
const options = this.cancelRequest();
|
|
||||||
|
|
||||||
return this.$http.post(`InvoiceOuts/invoiceClient`, params, options)
|
|
||||||
.then(() => {
|
|
||||||
clientsAndAddresses.shift();
|
|
||||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
responseHandler(response) {
|
|
||||||
try {
|
|
||||||
if (response !== 'accept')
|
|
||||||
return super.responseHandler(response);
|
|
||||||
|
|
||||||
if (!this.invoice.invoiceDate || !this.invoice.maxShipped)
|
|
||||||
throw new Error('Invoice date and the max date should be filled');
|
|
||||||
|
|
||||||
if (!this.invoice.fromClientId || !this.invoice.toClientId)
|
|
||||||
throw new Error('Choose a valid clients range');
|
|
||||||
|
|
||||||
this.on('close', () => {
|
|
||||||
if (this.canceler) this.canceler.resolve();
|
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$.invoiceButton.disabled = true;
|
|
||||||
this.packageInvoicing = true;
|
|
||||||
const options = this.cancelRequest();
|
|
||||||
|
|
||||||
this.$http.post(`InvoiceOuts/clientsToInvoice`, this.invoice, options)
|
|
||||||
.then(res => {
|
|
||||||
this.packageInvoicing = false;
|
|
||||||
const invoice = res.data.invoice;
|
|
||||||
const clientsAndAddresses = res.data.clientsAndAddresses;
|
|
||||||
if (!clientsAndAddresses.length) return super.responseHandler(response);
|
|
||||||
this.lastClientId = clientsAndAddresses[clientsAndAddresses.length - 1].clientId;
|
|
||||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
|
||||||
})
|
|
||||||
.then(() => super.responseHandler(response))
|
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
|
||||||
.finally(() => this.restartValues());
|
|
||||||
} catch (e) {
|
|
||||||
this.vnApp.showError(this.$t(e.message));
|
|
||||||
this.restartValues();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope', '$transclude'];
|
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', {
|
|
||||||
slotTemplate: require('./index.html'),
|
|
||||||
controller: Controller,
|
|
||||||
bindings: {
|
|
||||||
companyFk: '<?'
|
|
||||||
}
|
|
||||||
});
|
|
|
@ -1,118 +0,0 @@
|
||||||
import './index';
|
|
||||||
|
|
||||||
describe('InvoiceOut', () => {
|
|
||||||
describe('Component vnInvoiceOutGlobalInvoicing', () => {
|
|
||||||
let controller;
|
|
||||||
let $httpBackend;
|
|
||||||
let $httpParamSerializer;
|
|
||||||
|
|
||||||
beforeEach(ngModule('invoiceOut'));
|
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
|
|
||||||
$httpBackend = _$httpBackend_;
|
|
||||||
$httpParamSerializer = _$httpParamSerializer_;
|
|
||||||
let $scope = $rootScope.$new();
|
|
||||||
const $element = angular.element('<vn-invoice-out-global-invoicing></vn-invoice-out-global-invoicing>');
|
|
||||||
const $transclude = {
|
|
||||||
$$boundTransclude: {
|
|
||||||
$$slots: []
|
|
||||||
}
|
|
||||||
};
|
|
||||||
controller = $componentController('vnInvoiceOutGlobalInvoicing', {$element, $scope, $transclude});
|
|
||||||
controller.$.invoiceButton = {disabled: false};
|
|
||||||
}));
|
|
||||||
|
|
||||||
describe('getMinClientId()', () => {
|
|
||||||
it('should set the invoice fromClientId property', () => {
|
|
||||||
const filter = {
|
|
||||||
order: 'id ASC',
|
|
||||||
limit: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const serializedParams = $httpParamSerializer({filter});
|
|
||||||
$httpBackend.expectGET(`Clients/findOne?${serializedParams}`).respond(200, {id: 1101});
|
|
||||||
|
|
||||||
controller.getMinClientId();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.invoice.fromClientId).toEqual(1101);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getMaxClientId()', () => {
|
|
||||||
it('should set the invoice toClientId property', () => {
|
|
||||||
const filter = {
|
|
||||||
order: 'id DESC',
|
|
||||||
limit: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
const serializedParams = $httpParamSerializer({filter});
|
|
||||||
$httpBackend.expectGET(`Clients/findOne?${serializedParams}`).respond(200, {id: 1112});
|
|
||||||
|
|
||||||
controller.getMaxClientId();
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.invoice.toClientId).toEqual(1112);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('responseHandler()', () => {
|
|
||||||
it('should throw an error when invoiceDate or maxShipped properties are not filled in', () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
|
||||||
|
|
||||||
controller.invoice = {
|
|
||||||
fromClientId: 1101,
|
|
||||||
toClientId: 1101
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.responseHandler('accept');
|
|
||||||
|
|
||||||
const expectedError = 'Invoice date and the max date should be filled';
|
|
||||||
|
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith(expectedError);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw an error when fromClientId or toClientId properties are not filled in', () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showError');
|
|
||||||
|
|
||||||
controller.invoice = {
|
|
||||||
invoiceDate: Date.vnNew(),
|
|
||||||
maxShipped: Date.vnNew()
|
|
||||||
};
|
|
||||||
|
|
||||||
controller.responseHandler('accept');
|
|
||||||
|
|
||||||
expect(controller.vnApp.showError).toHaveBeenCalledWith(`Choose a valid clients range`);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should make an http POST query and then call to the showSuccess() method', () => {
|
|
||||||
jest.spyOn(controller.vnApp, 'showSuccess');
|
|
||||||
|
|
||||||
const minShipped = Date.vnNew();
|
|
||||||
minShipped.setFullYear(minShipped.getFullYear() - 1);
|
|
||||||
minShipped.setMonth(1);
|
|
||||||
minShipped.setDate(1);
|
|
||||||
minShipped.setHours(0, 0, 0, 0);
|
|
||||||
controller.invoice = {
|
|
||||||
invoiceDate: Date.vnNew(),
|
|
||||||
maxShipped: Date.vnNew(),
|
|
||||||
fromClientId: 1101,
|
|
||||||
toClientId: 1101,
|
|
||||||
companyFk: 442,
|
|
||||||
minShipped: minShipped
|
|
||||||
};
|
|
||||||
const response = {
|
|
||||||
clientsAndAddresses: [{clientId: 1101, addressId: 121}],
|
|
||||||
invoice: controller.invoice
|
|
||||||
};
|
|
||||||
|
|
||||||
$httpBackend.expect('POST', `InvoiceOuts/clientsToInvoice`).respond(response);
|
|
||||||
$httpBackend.expect('POST', `InvoiceOuts/invoiceClient`).respond({id: 1});
|
|
||||||
controller.responseHandler('accept');
|
|
||||||
$httpBackend.flush();
|
|
||||||
|
|
||||||
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,14 +0,0 @@
|
||||||
Create global invoice: Crear factura global
|
|
||||||
Some fields are required: Algunos campos son obligatorios
|
|
||||||
Max date: Fecha límite
|
|
||||||
Adding invoices to queue...: Añadiendo facturas a la cola...
|
|
||||||
Invoice date: Fecha de factura
|
|
||||||
From client: Desde el cliente
|
|
||||||
To client: Hasta el cliente
|
|
||||||
Invoice date and the max date should be filled: La fecha de factura y la fecha límite deben rellenarse
|
|
||||||
Choose a valid clients range: Selecciona un rango válido de clientes
|
|
||||||
of: de
|
|
||||||
Id Client: Id Cliente
|
|
||||||
All clients: Todos los clientes
|
|
||||||
Clients range: Rango de clientes
|
|
||||||
Calculating packages to invoice...: Calculando paquetes a factura...
|
|
|
@ -1,17 +0,0 @@
|
||||||
@import "variables";
|
|
||||||
|
|
||||||
.vn-invoice-out-global-invoicing {
|
|
||||||
tpl-body {
|
|
||||||
width: 500px;
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
font-weight: bold;
|
|
||||||
text-align: center;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
color: $color-primary;
|
|
||||||
vn-horizontal {
|
|
||||||
justify-content: center
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -18,7 +18,7 @@
|
||||||
<vn-thead>
|
<vn-thead>
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-th shrink>
|
<vn-th shrink>
|
||||||
<vn-multi-check
|
<vn-multi-check
|
||||||
model="model">
|
model="model">
|
||||||
</vn-multi-check>
|
</vn-multi-check>
|
||||||
</vn-th>
|
</vn-th>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
||||||
<vn-td>
|
<vn-td>
|
||||||
<vn-check
|
<vn-check
|
||||||
ng-model="invoiceOut.checked"
|
ng-model="invoiceOut.checked"
|
||||||
vn-click-stop>
|
vn-click-stop>
|
||||||
</vn-check>
|
</vn-check>
|
||||||
|
@ -68,29 +68,13 @@
|
||||||
</vn-card>
|
</vn-card>
|
||||||
</vn-data-viewer>
|
</vn-data-viewer>
|
||||||
<div fixed-bottom-right>
|
<div fixed-bottom-right>
|
||||||
<vn-vertical style="align-items: center;">
|
<vn-button class="round sm vn-mb-sm"
|
||||||
<vn-button class="round sm vn-mb-sm"
|
icon="add"
|
||||||
icon="add"
|
ng-click="manualInvoicing.show()"
|
||||||
ng-click="invoicingOptions.show($event)"
|
vn-tooltip="Make invoice..."
|
||||||
vn-tooltip="Make invoice..."
|
vn-acl="invoicing"
|
||||||
tooltip-position="left"
|
vn-acl-action="remove">
|
||||||
vn-acl="invoicing"
|
</vn-button>
|
||||||
vn-acl-action="remove">
|
|
||||||
</vn-button>
|
|
||||||
|
|
||||||
<vn-menu vn-id="invoicingOptions">
|
|
||||||
<vn-item translate
|
|
||||||
name="manualInvoice"
|
|
||||||
ng-click="manualInvoicing.show()">
|
|
||||||
Manual invoicing
|
|
||||||
</vn-item>
|
|
||||||
<vn-item translate
|
|
||||||
name="globalInvoice"
|
|
||||||
ng-click="globalInvoicing.show()">
|
|
||||||
Global invoicing
|
|
||||||
</vn-item>
|
|
||||||
</vn-menu>
|
|
||||||
</vn-vertical>
|
|
||||||
</div>
|
</div>
|
||||||
<vn-popup vn-id="summary">
|
<vn-popup vn-id="summary">
|
||||||
<vn-invoice-out-summary
|
<vn-invoice-out-summary
|
||||||
|
@ -103,7 +87,3 @@
|
||||||
<vn-invoice-out-manual
|
<vn-invoice-out-manual
|
||||||
vn-id="manual-invoicing">
|
vn-id="manual-invoicing">
|
||||||
</vn-invoice-out-manual>
|
</vn-invoice-out-manual>
|
||||||
<vn-invoice-out-global-invoicing
|
|
||||||
vn-id="global-invoicing"
|
|
||||||
company-fk="$ctrl.vnConfig.companyFk">
|
|
||||||
</vn-invoice-out-global-invoicing>
|
|
|
@ -14,11 +14,11 @@
|
||||||
data="taxAreas"
|
data="taxAreas"
|
||||||
order="code">
|
order="code">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<div
|
<div
|
||||||
class="progress vn-my-md"
|
class="progress vn-my-md"
|
||||||
ng-if="$ctrl.isInvoicing">
|
ng-if="$ctrl.isInvoicing">
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-icon vn-none icon="warning"></vn-icon>
|
<vn-icon vn-none icon="warning"></vn-icon>
|
||||||
<span vn-none translate>Invoicing in progress...</span>
|
<span vn-none translate>Invoicing in progress...</span>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</div>
|
</div>
|
||||||
|
@ -80,4 +80,4 @@
|
||||||
<tpl-buttons>
|
<tpl-buttons>
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
<button response="accept" translate vn-focus>Make invoice</button>
|
<button response="accept" translate vn-focus>Make invoice</button>
|
||||||
</tpl-buttons>
|
</tpl-buttons>
|
||||||
|
|
|
@ -14,4 +14,4 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,9 @@
|
||||||
"dependencies": ["worker", "client", "ticket"],
|
"dependencies": ["worker", "client", "ticket"],
|
||||||
"menus": {
|
"menus": {
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "invoiceOut.index", "icon": "icon-invoice-out"}
|
{"state": "invoiceOut.index", "icon": "icon-invoice-out"},
|
||||||
|
{"state": "invoiceOut.global-invoicing", "icon": "contact_support"}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -24,6 +26,12 @@
|
||||||
"component": "vn-invoice-out-index",
|
"component": "vn-invoice-out-index",
|
||||||
"description": "InvoiceOut"
|
"description": "InvoiceOut"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "/global-invoicing?q",
|
||||||
|
"state": "invoiceOut.global-invoicing",
|
||||||
|
"component": "vn-invoice-out-global-invoicing",
|
||||||
|
"description": "Global invoicing"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "/summary",
|
"url": "/summary",
|
||||||
"state": "invoiceOut.card.summary",
|
"state": "invoiceOut.card.summary",
|
||||||
|
@ -40,4 +48,4 @@
|
||||||
"component": "vn-invoice-out-card"
|
"component": "vn-invoice-out-card"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,191 @@
|
||||||
|
const axios = require('axios');
|
||||||
|
const uuid = require('uuid');
|
||||||
|
const fs = require('fs/promises');
|
||||||
|
const { createWriteStream } = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const gm = require('gm');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethod('download', {
|
||||||
|
description: 'Processes the image download queue',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
http: {
|
||||||
|
path: `/download`,
|
||||||
|
verb: 'POST',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.download = async () => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const tempContainer = await models.TempContainer.container(
|
||||||
|
'salix-image'
|
||||||
|
);
|
||||||
|
const tempPath = path.join(
|
||||||
|
tempContainer.client.root,
|
||||||
|
tempContainer.name
|
||||||
|
);
|
||||||
|
const maxAttempts = 3;
|
||||||
|
const collectionName = 'catalog';
|
||||||
|
|
||||||
|
const tx = await Self.beginTransaction({});
|
||||||
|
|
||||||
|
let tempFilePath;
|
||||||
|
let queueRow;
|
||||||
|
try {
|
||||||
|
const myOptions = { transaction: tx };
|
||||||
|
|
||||||
|
queueRow = await Self.findOne(
|
||||||
|
{
|
||||||
|
fields: ['id', 'itemFk', 'url', 'attempts'],
|
||||||
|
where: {
|
||||||
|
url: { neq: null },
|
||||||
|
attempts: {
|
||||||
|
lt: maxAttempts,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: 'priority, attempts, updated',
|
||||||
|
},
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!queueRow) return;
|
||||||
|
|
||||||
|
const collection = await models.ImageCollection.findOne(
|
||||||
|
{
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'maxWidth',
|
||||||
|
'maxHeight',
|
||||||
|
'model',
|
||||||
|
'property',
|
||||||
|
],
|
||||||
|
where: { name: collectionName },
|
||||||
|
include: {
|
||||||
|
relation: 'sizes',
|
||||||
|
scope: {
|
||||||
|
fields: ['width', 'height', 'crop'],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
const fileName = `${uuid.v4()}.png`;
|
||||||
|
tempFilePath = path.join(tempPath, fileName);
|
||||||
|
|
||||||
|
// Insert image row
|
||||||
|
await models.Image.create(
|
||||||
|
{
|
||||||
|
name: fileName,
|
||||||
|
collectionFk: collectionName,
|
||||||
|
updated: Date.vnNow(),
|
||||||
|
},
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
// Update item
|
||||||
|
const model = models[collection.model];
|
||||||
|
if (!model) throw new Error('No matching model found');
|
||||||
|
|
||||||
|
const item = await model.findById(queueRow.itemFk, null, myOptions);
|
||||||
|
if (item) {
|
||||||
|
await item.updateAttribute(
|
||||||
|
collection.property,
|
||||||
|
fileName,
|
||||||
|
myOptions
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download remote image
|
||||||
|
const response = await axios.get(queueRow.url, {
|
||||||
|
responseType: 'stream',
|
||||||
|
});
|
||||||
|
|
||||||
|
const writeStream = createWriteStream(tempFilePath);
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
writeStream.on('open', () => response.data.pipe(writeStream));
|
||||||
|
writeStream.on('finish', () => resolve());
|
||||||
|
writeStream.on('error', error => reject(error));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Resize
|
||||||
|
const container = await models.ImageContainer.container(
|
||||||
|
collectionName
|
||||||
|
);
|
||||||
|
const rootPath = container.client.root;
|
||||||
|
const collectionDir = path.join(rootPath, collectionName);
|
||||||
|
|
||||||
|
// To max size
|
||||||
|
const { maxWidth, maxHeight } = collection;
|
||||||
|
const fullSizePath = path.join(collectionDir, 'full');
|
||||||
|
const toFullSizePath = `${fullSizePath}/${fileName}`;
|
||||||
|
|
||||||
|
await fs.mkdir(fullSizePath, { recursive: true });
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
gm(tempFilePath)
|
||||||
|
.resize(maxWidth, maxHeight, '>')
|
||||||
|
.setFormat('png')
|
||||||
|
.write(toFullSizePath, function (err) {
|
||||||
|
if (err) reject(err);
|
||||||
|
if (!err) resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// To collection sizes
|
||||||
|
for (const size of collection.sizes()) {
|
||||||
|
const { width, height } = size;
|
||||||
|
|
||||||
|
const sizePath = path.join(collectionDir, `${width}x${height}`);
|
||||||
|
const toSizePath = `${sizePath}/${fileName}`;
|
||||||
|
|
||||||
|
await fs.mkdir(sizePath, { recursive: true });
|
||||||
|
await new Promise((resolve, reject) => {
|
||||||
|
const gmInstance = gm(tempFilePath);
|
||||||
|
|
||||||
|
if (size.crop) {
|
||||||
|
gmInstance
|
||||||
|
.resize(width, height, '^')
|
||||||
|
.gravity('Center')
|
||||||
|
.crop(width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!size.crop) gmInstance.resize(width, height, '>');
|
||||||
|
|
||||||
|
gmInstance
|
||||||
|
.setFormat('png')
|
||||||
|
.write(toSizePath, function (err) {
|
||||||
|
if (err) reject(err);
|
||||||
|
if (!err) resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.unlink(tempFilePath);
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
await queueRow.destroy(myOptions);
|
||||||
|
|
||||||
|
// Restart queue
|
||||||
|
Self.download();
|
||||||
|
|
||||||
|
await tx.commit();
|
||||||
|
} catch (error) {
|
||||||
|
await tx.rollback();
|
||||||
|
|
||||||
|
if (queueRow.attempts < maxAttempts) {
|
||||||
|
await queueRow.updateAttributes({
|
||||||
|
error: error,
|
||||||
|
attempts: queueRow.attempts + 1,
|
||||||
|
updated: Date.vnNew(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await fs.unlink(tempFilePath);
|
||||||
|
} catch (error) { }
|
||||||
|
|
||||||
|
Self.download();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -62,7 +62,7 @@ module.exports = Self => {
|
||||||
writeStream.on('open', () => response.pipe(writeStream));
|
writeStream.on('open', () => response.pipe(writeStream));
|
||||||
writeStream.on('error', async error =>
|
writeStream.on('error', async error =>
|
||||||
await errorHandler(image.itemFk, error, filePath));
|
await errorHandler(image.itemFk, error, filePath));
|
||||||
writeStream.on('finish', writeStream.end());
|
writeStream.on('finish', () => writeStream.end());
|
||||||
|
|
||||||
writeStream.on('close', async function() {
|
writeStream.on('close', async function() {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Report} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('labelPdf', {
|
Self.remoteMethodCtx('labelPdf', {
|
||||||
description: 'Returns the item label pdf',
|
description: 'Returns the item label pdf',
|
||||||
|
@ -56,17 +54,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.labelPdf = async(ctx, id) => {
|
Self.labelPdf = (ctx, id) => Self.printReport(ctx, id, 'item-label');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {lang: ctx.req.getLocale()};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const report = new Report('item-label', params);
|
|
||||||
const stream = await report.toPdfStream();
|
|
||||||
|
|
||||||
return [stream, 'application/pdf', `filename="item-${id}.pdf"`];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/item-image-queue/downloadImages')(Self);
|
require('../methods/item-image-queue/download')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
const {Email} = require('vn-print');
|
|
||||||
|
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('driverRouteEmail', {
|
Self.remoteMethodCtx('driverRouteEmail', {
|
||||||
description: 'Sends the driver route email with an attached PDF',
|
description: 'Sends the driver route email with an attached PDF',
|
||||||
|
@ -41,19 +39,5 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.driverRouteEmail = async ctx => {
|
Self.driverRouteEmail = ctx => Self.sendTemplate(ctx, 'driver-route');
|
||||||
const args = Object.assign({}, ctx.args);
|
|
||||||
const params = {
|
|
||||||
recipient: args.recipient,
|
|
||||||
lang: ctx.req.getLocale()
|
|
||||||
};
|
|
||||||
|
|
||||||
delete args.ctx;
|
|
||||||
for (const param in args)
|
|
||||||
params[param] = args[param];
|
|
||||||
|
|
||||||
const email = new Email('driver-route', params);
|
|
||||||
|
|
||||||
return email.send();
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue