diff --git a/back/models/dms-type.json b/back/models/dms-type.json
index 8d7195132..d3e96a986 100644
--- a/back/models/dms-type.json
+++ b/back/models/dms-type.json
@@ -17,10 +17,6 @@
"type": "string",
"required": true
},
- "path": {
- "type": "string",
- "required": true
- },
"code": {
"type": "string",
"required": true
diff --git a/back/models/vn-user.js b/back/models/vn-user.js
index b1d09f0c0..39e7008ca 100644
--- a/back/models/vn-user.js
+++ b/back/models/vn-user.js
@@ -258,18 +258,20 @@ module.exports = function(Self) {
class Mailer {
async send(verifyOptions, cb) {
- const url = new URL(verifyOptions.verifyHref);
- if (process.env.NODE_ENV) url.port = '';
+ try {
+ const url = new URL(verifyOptions.verifyHref);
+ if (process.env.NODE_ENV) url.port = '';
- const params = {
- url: url.href,
- recipient: verifyOptions.to
- };
+ const email = new Email('email-verify', {
+ url: url.href,
+ recipient: verifyOptions.to
+ });
+ await email.send();
- const email = new Email('email-verify', params);
- email.send();
-
- cb(null, verifyOptions.to);
+ cb(null, verifyOptions.to);
+ } catch (err) {
+ cb(err);
+ }
}
}
diff --git a/back/tests.js b/back/tests.js
index efade4d7d..2678f6744 100644
--- a/back/tests.js
+++ b/back/tests.js
@@ -59,8 +59,8 @@ async function test() {
const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
- jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
jasmine.exitOnCompletion = true;
+ jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 900000;
}
const backSpecs = [
diff --git a/db/Dockerfile b/db/Dockerfile
index 8eeed35e5..0020e8950 100644
--- a/db/Dockerfile
+++ b/db/Dockerfile
@@ -1,4 +1,4 @@
-FROM mariadb:10.7.7
+FROM mariadb:10.11.6
ENV MYSQL_ROOT_PASSWORD root
ENV TZ Europe/Madrid
diff --git a/db/changes/240201/01-functions.sql b/db/changes/240201/01-functions.sql
new file mode 100644
index 000000000..7bbe1f442
--- /dev/null
+++ b/db/changes/240201/01-functions.sql
@@ -0,0 +1,81 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`ticketPositionInPath`(vTicketId INT)
+ RETURNS varchar(10) CHARSET utf8mb3 COLLATE utf8mb3_general_ci
+ DETERMINISTIC
+BEGIN
+
+ DECLARE vRestTicketsMaxOrder INT;
+ DECLARE vRestTicketsMinOrder INT;
+ DECLARE vRestTicketsPacking INT;
+ DECLARE vMyProductionOrder INT;
+ DECLARE vPosition VARCHAR(10) DEFAULT 'MID';
+ DECLARE vMyPath INT;
+ DECLARE vMyWarehouse INT;
+ DECLARE PACKING_ORDER INT;
+ DECLARE vExpeditionsCount INT;
+ DECLARE vIsValenciaPath BOOLEAN DEFAULT FALSE;
+
+
+
+SELECT `order`
+ INTO PACKING_ORDER
+ FROM state
+ WHERE code = 'PACKING';
+
+SELECT t.routeFk, t.warehouseFk, IFNULL(ts.productionOrder,0)
+ INTO vMyPath, vMyWarehouse, vMyProductionOrder
+ FROM ticket t
+ LEFT JOIN ticketState ts on ts.ticketFk = t.id
+ WHERE t.id = vTicketId;
+
+SELECT (ag.`name` = 'VN_VALENCIA')
+ INTO vIsValenciaPath
+ FROM vn2008.Rutas r
+ JOIN vn2008.Agencias a on a.Id_Agencia = r.Id_Agencia
+ JOIN vn2008.agency ag on ag.agency_id = a.agency_id
+ WHERE r.Id_Ruta = vMyPath;
+
+IF vIsValenciaPath THEN -- Rutas Valencia
+
+ SELECT COUNT(*)
+ INTO vExpeditionsCount
+ FROM expedition e
+ JOIN ticket t ON t.id = e.ticketFk
+ WHERE t.routeFk = vMyPath;
+
+ SELECT MAX(ts.productionOrder), MIN(ts.productionOrder)
+ INTO vRestTicketsMaxOrder, vRestTicketsMinOrder
+ FROM ticket t
+ LEFT JOIN ticketState ts on t.id = ts.ticketFk
+ WHERE t.routeFk = vMyPath
+ AND t.warehouseFk = vMyWarehouse
+ AND t.id != vTicketid;
+
+ SELECT COUNT(*)
+ INTO vRestTicketsPacking
+ FROM ticket t
+ LEFT JOIN ticketState ts on t.id = ts.ticketFk
+ WHERE ts.productionOrder = PACKING_ORDER
+ AND t.routeFk = vMyPath
+ AND t.warehouseFk = vMyWarehouse
+ AND t.id != vTicketid;
+
+ IF vExpeditionsCount = 1 THEN
+ SET vPosition = 'FIRST';
+ ELSEIF vRestTicketsMinOrder > PACKING_ORDER THEN
+ SET vPosition = 'LAST';
+ ELSEIF vRestTicketsPacking THEN
+ SET vPosition = 'SHARED';
+ ELSE
+ SET vPosition = 'MID';
+ END IF;
+
+ELSE
+ SET vPosition = 'MID';
+
+END IF;
+
+RETURN vPosition;
+
+END$$
+DELIMITER ;
diff --git a/db/changes/240201/01-procedures.sql b/db/changes/240201/01-procedures.sql
new file mode 100644
index 000000000..ab52dbd1b
--- /dev/null
+++ b/db/changes/240201/01-procedures.sql
@@ -0,0 +1,1788 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`item_getVisible`(
+ vWarehouse TINYINT,
+ vDate DATE,
+ vType INT,
+ vPrefix VARCHAR(255))
+BEGIN
+
+/**
+ * Gets visible items of the specified type at specified date.
+ *
+ * @param vWarehouse The warehouse id
+ * @param vDate The visible date
+ * @param vType The type id
+ * @param vPrefix The article prefix to filter or %NULL for all
+ * @return tmp.itemVisible Visible items
+ */
+ DECLARE vPrefixLen SMALLINT;
+ DECLARE vFilter VARCHAR(255) DEFAULT NULL;
+ DECLARE vDateInv DATE DEFAULT vn.getInventoryDate();
+ DECLARE EXIT HANDLER FOR 1114
+ BEGIN
+ GET DIAGNOSTICS CONDITION 1
+ @message = MESSAGE_TEXT;
+ CALL vn.mail_insert(
+ 'cau@verdnatura.es',
+ NULL,
+ CONCAT('hedera.item_getVisible error: ', @message),
+ CONCAT(
+ 'warehouse: ', IFNULL(vWarehouse, ''),
+ ', Fecha:', IFNULL(vDate, ''),
+ ', tipo: ', IFNULL(vType,''),
+ ', prefijo: ', IFNULL(vPrefix,'')));
+ RESIGNAL;
+ END;
+ SET vPrefixLen = IFNULL(LENGTH(vPrefix), 0) + 1;
+
+ IF vPrefixLen > 1 THEN
+ SET vFilter = CONCAT(vPrefix, '%');
+ END IF;
+
+ DROP TEMPORARY TABLE IF EXISTS `filter`;
+ CREATE TEMPORARY TABLE `filter`
+ (INDEX (itemFk))
+ ENGINE = MEMORY
+ SELECT id itemFk FROM vn.item
+ WHERE typeFk = vType
+ AND (vFilter IS NULL OR `name` LIKE vFilter);
+
+ DROP TEMPORARY TABLE IF EXISTS currentStock;
+ CREATE TEMPORARY TABLE currentStock
+ (INDEX (itemFk))
+ ENGINE = MEMORY
+ SELECT itemFk, SUM(quantity) quantity
+ FROM (
+ SELECT b.itemFk, b.quantity
+ FROM vn.buy b
+ JOIN vn.entry e ON e.id = b.entryFk
+ JOIN vn.travel t ON t.id = e.travelFk
+ WHERE t.landed BETWEEN vDateInv AND vDate
+ AND t.warehouseInFk = vWarehouse
+ AND NOT e.isRaid
+ UNION ALL
+ SELECT b.itemFk, -b.quantity
+ FROM vn.buy b
+ JOIN vn.entry e ON e.id = b.entryFk
+ JOIN vn.travel t ON t.id = e.travelFk
+ WHERE t.shipped BETWEEN vDateInv AND util.VN_CURDATE()
+ AND t.warehouseOutFk = vWarehouse
+ AND NOT e.isRaid
+ AND t.isDelivered
+ UNION ALL
+ SELECT m.itemFk, -m.quantity
+ FROM vn.sale m
+ JOIN vn.ticket t ON t.id = m.ticketFk
+ JOIN vn.ticketState s ON s.ticketFk = t.id
+ WHERE t.shipped BETWEEN vDateInv AND util.VN_CURDATE()
+ AND t.warehouseFk = vWarehouse
+ AND s.alertLevel = 3
+ ) t
+ GROUP BY itemFk
+ HAVING quantity > 0;
+
+ DROP TEMPORARY TABLE IF EXISTS tmp;
+ CREATE TEMPORARY TABLE tmp
+ (INDEX (itemFk))
+ ENGINE = MEMORY
+ SELECT *
+ FROM (
+ SELECT b.itemFk, b.packagingFk, b.packing
+ FROM vn.buy b
+ JOIN vn.entry e ON e.id = b.entryFk
+ JOIN vn.travel t ON t.id = e.travelFk
+ WHERE t.landed BETWEEN vDateInv AND vDate
+ AND NOT b.isIgnored
+ AND b.price2 >= 0
+ AND b.packagingFk IS NOT NULL
+ ORDER BY t.warehouseInFk = vWarehouse DESC, t.landed DESC
+ LIMIT 10000000000000000000
+ ) t GROUP BY itemFk;
+
+ DROP TEMPORARY TABLE IF EXISTS tmp.itemVisible;
+ CREATE TEMPORARY TABLE tmp.itemVisible
+ ENGINE = MEMORY
+ SELECT i.id Id_Article,
+ SUBSTRING(i.`name`, vPrefixLen) Article,
+ t.packing, p.id Id_Cubo,
+ IF(p.depth > 0, p.depth, 0) depth, p.width, p.height,
+ CEIL(s.quantity / t.packing) etiquetas
+ FROM vn.item i
+ JOIN `filter` f ON f.itemFk = i.id
+ JOIN currentStock s ON s.itemFk = i.id
+ LEFT JOIN tmp t ON t.itemFk = i.id
+ LEFT JOIN vn.packaging p ON p.id = t.packagingFk
+ WHERE CEIL(s.quantity / t.packing) > 0
+ -- FIXME: Column Cubos.box not included in view vn.packaging
+ /* AND p.box */ ;
+
+ DROP TEMPORARY TABLE
+ `filter`,
+ currentStock,
+ tmp;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `hedera`.`order_confirmWithUser`(vSelf INT, vUserId INT)
+BEGIN
+/**
+ * Confirms an order, creating each of its tickets on the corresponding
+ * date, store and user.
+ *
+ * @param vSelf The order identifier
+ * @param vUser The user identifier
+ */
+ DECLARE vOk BOOL;
+ DECLARE vDone BOOL DEFAULT FALSE;
+ DECLARE vWarehouse INT;
+ DECLARE vShipment DATE;
+ DECLARE vTicket INT;
+ DECLARE vNotes VARCHAR(255);
+ DECLARE vItem INT;
+ DECLARE vConcept VARCHAR(30);
+ DECLARE vAmount INT;
+ DECLARE vPrice DECIMAL(10,2);
+ DECLARE vSale INT;
+ DECLARE vRate INT;
+ DECLARE vRowId INT;
+ DECLARE vPriceFixed DECIMAL(10,2);
+ DECLARE vDelivery DATE;
+ DECLARE vAddress INT;
+ DECLARE vIsConfirmed BOOL;
+ DECLARE vClientId INT;
+ DECLARE vCompanyId INT;
+ DECLARE vAgencyModeId INT;
+ DECLARE TICKET_FREE INT DEFAULT 2;
+ DECLARE vCalc INT;
+ DECLARE vIsLogifloraItem BOOL;
+ DECLARE vOldQuantity INT;
+ DECLARE vNewQuantity INT;
+ DECLARE vIsTaxDataChecked BOOL;
+
+ DECLARE cDates CURSOR FOR
+ SELECT zgs.shipped, r.warehouse_id
+ FROM `order` o
+ JOIN order_row r ON r.order_id = o.id
+ LEFT JOIN tmp.zoneGetShipped zgs ON zgs.warehouseFk = r.warehouse_id
+ WHERE o.id = vSelf AND r.amount != 0
+ GROUP BY r.warehouse_id;
+
+ DECLARE cRows CURSOR FOR
+ SELECT r.id, r.item_id, i.name, r.amount, r.price, r.rate, i.isFloramondo
+ FROM order_row r
+ JOIN vn.item i ON i.id = r.item_id
+ WHERE r.amount != 0
+ AND r.warehouse_id = vWarehouse
+ AND r.order_id = vSelf
+ ORDER BY r.rate DESC;
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND
+ SET vDone = TRUE;
+
+ DECLARE EXIT HANDLER FOR SQLEXCEPTION
+ BEGIN
+ ROLLBACK;
+ RESIGNAL;
+ END;
+
+ -- Carga los datos del pedido
+ SELECT o.date_send, o.address_id, o.note, a.clientFk,
+ o.company_id, o.agency_id, c.isTaxDataChecked
+ INTO vDelivery, vAddress, vNotes, vClientId,
+ vCompanyId, vAgencyModeId, vIsTaxDataChecked
+ FROM hedera.`order` o
+ JOIN vn.address a ON a.id = o.address_id
+ JOIN vn.client c ON c.id = a.clientFk
+ WHERE o.id = vSelf;
+
+ -- Verifica si el cliente tiene los datos comprobados
+ IF NOT vIsTaxDataChecked THEN
+ CALL util.throw ('clientNotVerified');
+ END IF;
+
+ -- Carga las fechas de salida de cada almacen
+ CALL vn.zone_getShipped (vDelivery, vAddress, vAgencyModeId, FALSE);
+
+ -- Trabajador que realiza la accion
+ IF vUserId IS NULL THEN
+ SELECT employeeFk INTO vUserId FROM orderConfig;
+ END IF;
+
+ START TRANSACTION;
+
+ CALL order_checkEditable(vSelf);
+
+ -- Check order is not empty
+
+ SELECT COUNT(*) > 0 INTO vOk
+ FROM order_row WHERE order_id = vSelf AND amount > 0;
+
+ IF NOT vOk THEN
+ CALL util.throw ('ORDER_EMPTY');
+ END IF;
+
+ -- Crea los tickets del pedido
+
+ OPEN cDates;
+
+ lDates:
+ LOOP
+ SET vTicket = NULL;
+ SET vDone = FALSE;
+ FETCH cDates INTO vShipment, vWarehouse;
+
+ IF vDone THEN
+ LEAVE lDates;
+ END IF;
+
+ -- Busca un ticket existente que coincida con los parametros
+ WITH tPrevia AS
+ (SELECT DISTINCT s.ticketFk
+ FROM vn.sale s
+ JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id
+ JOIN vn.ticket t ON t.id = s.ticketFk
+ WHERE t.shipped BETWEEN vShipment AND util.dayend(vShipment)
+ )
+ SELECT t.id INTO vTicket
+ FROM vn.ticket t
+ LEFT JOIN tPrevia tp ON tp.ticketFk = t.id
+ LEFT JOIN vn.ticketState tls on tls.ticketFk = t.id
+ JOIN hedera.`order` o
+ ON o.address_id = t.addressFk
+ AND vWarehouse = t.warehouseFk
+ AND o.date_send = t.landed
+ AND DATE(t.shipped) = vShipment
+ WHERE o.id = vSelf
+ AND t.refFk IS NULL
+ AND tp.ticketFk IS NULL
+ AND IFNULL(tls.alertLevel,0) = 0
+ LIMIT 1;
+
+ -- Crea el ticket en el caso de no existir uno adecuado
+ IF vTicket IS NULL
+ THEN
+
+ SET vShipment = IFNULL(vShipment, util.VN_CURDATE());
+
+ CALL vn.ticket_add(
+ vClientId,
+ vShipment,
+ vWarehouse,
+ vCompanyId,
+ vAddress,
+ vAgencyModeId,
+ NULL,
+ vDelivery,
+ vUserId,
+ TRUE,
+ vTicket
+ );
+ ELSE
+ INSERT INTO vn.ticketTracking
+ SET ticketFk = vTicket,
+ workerFk = vUserId,
+ stateFk = TICKET_FREE;
+ END IF;
+
+ INSERT IGNORE INTO vn.orderTicket
+ SET orderFk = vSelf,
+ ticketFk = vTicket;
+
+ -- Añade las notas
+
+ IF vNotes IS NOT NULL AND vNotes != ''
+ THEN
+ INSERT INTO vn.ticketObservation SET
+ ticketFk = vTicket,
+ observationTypeFk = 4 /* salesperson */,
+ `description` = vNotes
+ ON DUPLICATE KEY UPDATE
+ `description` = CONCAT(VALUES(`description`),'. ', `description`);
+ END IF;
+
+ -- Añade los movimientos y sus componentes
+
+ OPEN cRows;
+
+ lRows: LOOP
+ SET vDone = FALSE;
+ FETCH cRows INTO vRowId, vItem, vConcept, vAmount, vPrice, vRate, vIsLogifloraItem;
+
+ IF vDone THEN
+ LEAVE lRows;
+ END IF;
+
+ SET vSale = NULL;
+
+ SELECT s.id, s.quantity INTO vSale, vOldQuantity
+ FROM vn.sale s
+ WHERE ticketFk = vTicket
+ AND price = vPrice
+ AND itemFk = vItem
+ AND discount = 0
+ LIMIT 1;
+
+ IF vSale THEN
+ UPDATE vn.sale
+ SET quantity = quantity + vAmount,
+ originalQuantity = quantity
+ WHERE id = vSale;
+
+ SELECT s.quantity INTO vNewQuantity
+ FROM vn.sale s
+ WHERE id = vSale;
+ ELSE
+ -- Obtiene el coste
+ SELECT SUM(rc.`price`) valueSum INTO vPriceFixed
+ FROM orderRowComponent rc
+ JOIN vn.component c ON c.id = rc.componentFk
+ JOIN vn.componentType ct ON ct.id = c.typeFk AND ct.isBase
+ WHERE rc.rowFk = vRowId;
+
+ INSERT INTO vn.sale
+ SET itemFk = vItem,
+ ticketFk = vTicket,
+ concept = vConcept,
+ quantity = vAmount,
+ price = vPrice,
+ priceFixed = vPriceFixed,
+ isPriceFixed = TRUE;
+
+ SET vSale = LAST_INSERT_ID();
+
+ INSERT INTO vn.saleComponent
+ (saleFk, componentFk, `value`)
+ SELECT vSale, rc.componentFk, rc.price
+ FROM orderRowComponent rc
+ JOIN vn.component c ON c.id = rc.componentFk
+ WHERE rc.rowFk = vRowId
+ GROUP BY vSale, rc.componentFk;
+ END IF;
+
+ UPDATE order_row SET Id_Movimiento = vSale
+ WHERE id = vRowId;
+
+ -- Inserta en putOrder si la compra es de Floramondo
+ IF vIsLogifloraItem THEN
+ CALL cache.availableNoRaids_refresh(vCalc,FALSE,vWarehouse,vShipment);
+
+ SET @available := 0;
+
+ SELECT GREATEST(0,available) INTO @available
+ FROM cache.availableNoRaids
+ WHERE calc_id = vCalc
+ AND item_id = vItem;
+
+ UPDATE cache.availableNoRaids
+ SET available = GREATEST(0,available - vAmount)
+ WHERE item_id = vItem
+ AND calc_id = vCalc;
+
+ INSERT INTO edi.putOrder (
+ deliveryInformationID,
+ supplyResponseId,
+ quantity ,
+ EndUserPartyId,
+ EndUserPartyGLN,
+ FHAdminNumber,
+ saleFk
+ )
+ SELECT di.ID,
+ i.supplyResponseFk,
+ CEIL((vAmount - @available)/ sr.NumberOfItemsPerCask),
+ o.address_id ,
+ vClientId,
+ IFNULL(ca.fhAdminNumber, fhc.defaultAdminNumber),
+ vSale
+ FROM edi.deliveryInformation di
+ JOIN vn.item i ON i.supplyResponseFk = di.supplyResponseID
+ JOIN edi.supplyResponse sr ON sr.ID = i.supplyResponseFk
+ LEFT JOIN edi.clientFHAdminNumber ca ON ca.clientFk = vClientId
+ JOIN edi.floraHollandConfig fhc
+ JOIN hedera.`order` o ON o.id = vSelf
+ WHERE i.id = vItem
+ AND di.LatestOrderDateTime > util.VN_NOW()
+ AND vAmount > @available
+ LIMIT 1;
+ END IF;
+ END LOOP;
+
+ CLOSE cRows;
+ END LOOP;
+
+ CLOSE cDates;
+
+ UPDATE `order` SET confirmed = TRUE, confirm_date = util.VN_NOW()
+ WHERE id = vSelf;
+
+ COMMIT;
+END$$
+DELIMITER ;
+
+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 tmp.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;
+ DECLARE vDone BOOL;
+ DECLARE vTicketFk INT;
+ DECLARE vCursor CURSOR FOR
+ SELECT id
+ FROM tmp.ticketToInvoice;
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+
+ SET vInvoiceDate = IFNULL(vInvoiceDate, util.VN_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 tmp.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 tmp.ticketToInvoice els tickets que no han de ser facturats
+ DELETE ti.*
+ FROM tmp.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 tmp.ticketToInvoice t
+ JOIN sale s ON s.ticketFk = t.id;
+
+ SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice
+ FROM tmp.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,
+ siiTypeInvoiceOutFk
+ )
+ 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;
+
+ OPEN vCursor;
+ l: LOOP
+ SET vDone = FALSE;
+ FETCH vCursor INTO vTicketFk;
+
+ IF vDone THEN
+ LEAVE l;
+ END IF;
+
+ CALL ticket_recalc(vTicketFk, vTaxArea);
+
+ END LOOP;
+ CLOSE vCursor;
+
+ UPDATE ticket t
+ JOIN tmp.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 tmp.ticketToInvoice ti
+ LEFT JOIN ticketState ts ON ti.id = ts.ticketFk
+ JOIN state s
+ WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
+
+ INSERT INTO ticketTracking(stateFk,ticketFk,userFk)
+ SELECT * FROM tmp.updateInter;
+
+ CALL invoiceExpenseMake(vNewInvoiceId);
+ CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
+
+ UPDATE invoiceOut io
+ JOIN (
+ SELECT SUM(amount) total
+ FROM invoiceOutExpense
+ 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 tmp.ticketToInvoice;
+
+ CALL `ticket_getTax`('NATIONAL');
+
+ SET @vTaxableBaseServices := 0.00;
+ SET @vTaxCodeGeneral := NULL;
+
+ INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInFk,
+ @vTaxableBaseServices,
+ sub.expenseFk,
+ sub.taxTypeSageFk,
+ sub.transactionTypeSageFk
+ FROM (
+ SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
+ i.expenseFk,
+ 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, expenseFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInFk,
+ SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
+ @vTaxableBaseServices, 0) taxableBase,
+ i.expenseFk,
+ 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 `tmp`.`ticketToInvoice`;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`itemShelvingRadar`(vSectorFk INT)
+proc:BEGIN
+
+ DECLARE vCalcVisibleFk INT;
+ DECLARE vCalcAvailableFk INT;
+ DECLARE hasFatherSector BOOLEAN;
+ DECLARE vBuyerFk INT DEFAULT 0;
+ DECLARE vWarehouseFk INT DEFAULT 0;
+ DECLARE vSonSectorFk INT;
+ DECLARE vWorkerFk INT;
+
+ SELECT s.workerFk
+ INTO vWorkerFk
+ FROM vn.sector s
+ WHERE s.id = vSectorFk;
+
+ SELECT w.id, s.warehouseFk INTO vBuyerFk, vWarehouseFk
+ FROM vn.worker w
+ JOIN vn.sector s ON s.code = w.code
+ WHERE s.id = vSectorFk;
+
+ SELECT s.id INTO vSectorFk
+ FROM vn.sector s
+ WHERE s.warehouseFk = vWarehouseFk
+ AND s.isMain;
+
+ SELECT COUNT(*) INTO hasFatherSector
+ FROM vn.sector
+ WHERE sonFk = vSectorFk;
+
+ SELECT warehouseFk, sonFk INTO vWarehouseFk, vSonSectorFk
+ FROM vn.sector
+ WHERE id = vSectorFk;
+
+ CALL cache.visible_refresh(vCalcVisibleFk, TRUE, vWarehouseFk);
+ CALL cache.available_refresh(vCalcAvailableFk, FALSE, vWarehouseFk, util.VN_CURDATE());
+
+ DROP TEMPORARY TABLE IF EXISTS tmp.itemShelvingRadar;
+
+ IF hasFatherSector THEN
+ CREATE TEMPORARY TABLE tmp.itemShelvingRadar
+ (PRIMARY KEY (itemFk))
+ ENGINE = MEMORY
+ SELECT *
+ FROM (
+ SELECT iss.itemFk,
+ i.longName,
+ i.size,
+ i.subName producer,
+ IFNULL(a.available,0) available,
+ SUM(IF(s.sonFk = vSectorFk, IFNULL(iss.visible,0), 0)) upstairs,
+ SUM(IF(iss.sectorFk = vSectorFk, IFNULL(iss.visible,0), 0)) downstairs,
+ IF(it.isPackaging, NULL, IFNULL(v.visible,0)) as visible,
+ vSectorFk sectorFk
+ FROM vn.itemShelvingStock iss
+ JOIN vn.sector s ON s.id = iss.sectorFk
+ JOIN vn.item i on i.id = iss.itemFk
+ JOIN vn.itemType it ON it.id = i.typeFk AND vBuyerFk IN (0,it.workerFk)
+ LEFT JOIN cache.available a ON a.item_id = iss.itemFk AND a.calc_id = vCalcAvailableFk
+ LEFT JOIN cache.visible v ON v.item_id = iss.itemFk AND v.calc_id = vCalcVisibleFk
+ WHERE vSectorFk IN (iss.sectorFk, s.sonFk)
+ GROUP BY iss.itemFk
+
+ UNION ALL
+
+ SELECT v.item_id,
+ i.longName,
+ i.size,
+ i.subName producer,
+ IFNULL(a.available,0) as available,
+ 0 upstairs,
+ 0 downstairs,
+ IF(it.isPackaging, NULL, v.visible) visible,
+ vSectorFk as sectorFk
+ FROM cache.visible v
+ JOIN vn.item i on i.id = v.item_id
+ JOIN vn.itemType it ON it.id = i.typeFk AND vBuyerFk IN (0,it.workerFk)
+ LEFT JOIN vn.itemShelvingStock iss ON iss.itemFk = v.item_id AND iss.warehouseFk = vWarehouseFk
+ LEFT JOIN cache.available a ON a.item_id = v.item_id AND a.calc_id = vCalcAvailableFk
+ WHERE v.calc_id = vCalcVisibleFk
+ AND iss.itemFk IS NULL
+ AND it.isInventory
+ ) sub GROUP BY itemFk;
+
+ SELECT ishr.*,
+ CAST(visible - upstairs - downstairs AS DECIMAL(10,0)) AS nicho,
+ CAST(downstairs - IFNULL(notPickedYed,0) AS DECIMAL(10,0)) as pendiente
+ FROM tmp.itemShelvingRadar ishr
+ JOIN vn.item i ON i.id = ishr.itemFk
+ LEFT JOIN (SELECT s.itemFk, sum(s.quantity) as notPickedYed
+ FROM vn.ticket t
+ JOIN vn.ticketStateToday tst ON tst.ticketFk = t.id
+ JOIN vn.sale s ON s.ticketFk = t.id
+ WHERE t.warehouseFk = vWarehouseFk
+ AND tst.alertLevel = 0
+ GROUP BY s.itemFk
+ ) sub ON sub.itemFk = ishr.itemFk
+ ORDER BY i.typeFk, i.longName;
+ ELSE
+ CREATE TEMPORARY TABLE tmp.itemShelvingRadar
+ (PRIMARY KEY (itemFk))
+ ENGINE = MEMORY
+ SELECT iss.itemFk,
+ 0 `hour`,
+ 0 `minute`,
+ '--' itemPlacementCode,
+ i.longName,
+ i.size,
+ i.subName producer,
+ i.upToDown,
+ IFNULL(a.available,0) available,
+ IFNULL(v.visible - iss.visible,0) dayEndVisible,
+ IFNULL(v.visible - iss.visible,0) firstNegative,
+ IFNULL(v.visible - iss.visible,0) itemPlacementVisible,
+ IFNULL(i.minimum * b.packing,0) itemPlacementSize,
+ ips.onTheWay,
+ iss.visible itemShelvingStock,
+ IFNULL(v.visible,0) visible,
+ b.isPickedOff,
+ iss.sectorFk
+ FROM vn.itemShelvingStock iss
+ JOIN vn.item i on i.id = iss.itemFk
+ LEFT JOIN cache.last_buy lb ON lb.item_id = iss.itemFk AND lb.warehouse_id = vWarehouseFk
+ LEFT JOIN vn.buy b ON b.id = lb.buy_id
+ LEFT JOIN cache.available a ON a.item_id = iss.itemFk AND a.calc_id = vCalcAvailableFk
+ LEFT JOIN cache.visible v ON v.item_id = iss.itemFk AND v.calc_id = vCalcVisibleFk
+ LEFT JOIN (SELECT itemFk, sum(saldo) as onTheWay
+ FROM vn.itemPlacementSupplyList
+ WHERE saldo > 0
+ GROUP BY itemFk
+ ) ips ON ips.itemFk = i.id
+ WHERE IFNULL(iss.sectorFk,0) IN (0, vSectorFk)
+ OR iss.sectorFk = vSectorFk;
+
+ DROP TEMPORARY TABLE IF EXISTS tmp.itemOutTime;
+ CREATE TEMPORARY TABLE tmp.itemOutTime
+ SELECT *,SUM(amount) quantity
+ FROM
+ (SELECT item_id itemFk,
+ amount,
+ IF(HOUR(t.shipped), HOUR(t.shipped), HOUR(z.`hour`)) as hours,
+ IF(MINUTE(t.shipped), MINUTE(t.shipped), MINUTE(z.`hour`)) as minutes
+ FROM vn2008.item_out io
+ JOIN tmp.itemShelvingRadar isr ON isr.itemFk = io.item_id
+ JOIN vn.ticket t on t.id= io.ticketFk
+ JOIN vn.ticketState ts on ts.ticketFk = io.ticketFk
+ JOIN vn.state s ON s.id = ts.stateFk
+ LEFT JOIN vn.zone z ON z.id = t.zoneFk
+ LEFT JOIN (SELECT DISTINCT saleFk
+ FROM vn.saleTracking st
+ WHERE st.created > util.VN_CURDATE()
+ AND st.isChecked
+ ) stPrevious ON `stPrevious`.`saleFk` = io.saleFk
+ WHERE t.warehouseFk = vWarehouseFk
+ AND s.isPicked = 0
+ AND NOT io.Reservado
+ AND stPrevious.saleFk IS NULL
+ AND io.dat >= util.VN_CURDATE()
+ AND io.dat < util.VN_CURDATE() + INTERVAL 1 DAY
+ ) sub
+ GROUP BY itemFk, hours, minutes;
+
+ INSERT INTO tmp.itemShelvingRadar (itemFk)
+ SELECT itemFk FROM tmp.itemOutTime
+ ON DUPLICATE KEY UPDATE dayEndVisible = dayEndVisible + quantity,
+ firstNegative = if (firstNegative < 0, firstNegative, firstNegative + quantity),
+ `hour` = ifnull(if (firstNegative > 0 , `hour`, hours),0),
+ `minute` = ifnull(if (firstNegative > 0, `minute`, minutes),0);
+
+ UPDATE tmp.itemShelvingRadar isr
+ JOIN (SELECT s.itemFk, sum(s.quantity) amount
+ FROM sale s
+ JOIN ticket t ON t.id = s.ticketFk
+ JOIN ticketLastState tls ON tls.ticketFk = t.id
+ WHERE t.shipped BETWEEN util.VN_CURDATE() AND util.dayend(util.VN_CURDATE())
+ AND tls.name = 'Prep Camara'
+ GROUP BY s.itemFk) sub ON sub.itemFk = isr.itemFk
+ SET isr.dayEndVisible = dayEndVisible + sub.amount,
+ firstNegative = firstNegative + sub.amount;
+
+ SELECT * FROM tmp.itemShelvingRadar;
+ END IF;
+
+ DROP TEMPORARY TABLE tmp.itemShelvingRadar;
+
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`item_getBalance`(
+ vItemFk INT,
+ vWarehouseFk INT,
+ vDate DATETIME
+)
+BEGIN
+/**
+ * @vItemFk item a buscar
+ * @vWarehouseFk almacen donde buscar
+ * @vDate Si la fecha es null, muestra el histórico desde el inventario.
+ * Si la fecha no es null, muestra histórico desde la fecha pasada.
+ */
+ DECLARE vDateInventory DATETIME;
+
+ IF vDate IS NULL THEN
+ SELECT inventoried INTO vDateInventory
+ FROM config;
+ ELSE
+ SELECT mockUtcTime INTO vDateInventory
+ FROM util.config;
+ END IF;
+
+ CREATE OR REPLACE TEMPORARY TABLE tItemDiary(
+ shipped DATE,
+ `in` INT(11),
+ `out` INT(11),
+ alertLevel INT(11),
+ stateName VARCHAR(20),
+ `name` VARCHAR(50),
+ reference VARCHAR(50),
+ origin INT(11),
+ clientFk INT(11),
+ isPicked INT(11),
+ isTicket TINYINT(1),
+ lineFk INT(11),
+ `order` TINYINT(3) UNSIGNED,
+ clientType VARCHAR(20),
+ claimFk INT(10) UNSIGNED,
+ inventorySupplierFk INT(10)
+ );
+
+ INSERT INTO tItemDiary
+ SELECT tr.landed shipped,
+ b.quantity `in`,
+ NULL `out`,
+ st.alertLevel ,
+ st.name stateName,
+ s.name `name`,
+ e.invoiceNumber reference,
+ e.id origin,
+ s.id clientFk,
+ IF(st.`code` = 'DELIVERED', TRUE, FALSE) isPicked,
+ FALSE isTicket,
+ b.id lineFk,
+ NULL `order`,
+ NULL clientType,
+ NULL claimFk,
+ ec.inventorySupplierFk
+ FROM buy b
+ JOIN entry e ON e.id = b.entryFk
+ JOIN travel tr ON tr.id = e.travelFk
+ JOIN supplier s ON s.id = e.supplierFk
+ JOIN state st ON st.`code` = IF( tr.landed < util.VN_CURDATE()
+ OR (util.VN_CURDATE() AND tr.isReceived),
+ 'DELIVERED',
+ 'FREE')
+ JOIN entryConfig ec
+ WHERE tr.landed >= vDateInventory
+ AND vWarehouseFk = tr.warehouseInFk
+ AND (s.id <> ec.inventorySupplierFk OR vDate IS NULL)
+ AND b.itemFk = vItemFk
+ AND e.isExcludedFromAvailable = FALSE
+ AND e.isRaid = FALSE
+ UNION ALL
+ SELECT tr.shipped,
+ NULL,
+ b.quantity,
+ st.alertLevel,
+ st.name,
+ s.name,
+ e.invoiceNumber,
+ e.id,
+ s.id,
+ IF(st.`code` = 'DELIVERED' , TRUE, FALSE),
+ FALSE,
+ b.id,
+ NULL,
+ NULL,
+ NULL,
+ ec.inventorySupplierFk
+ FROM buy b
+ JOIN entry e ON e.id = b.entryFk
+ JOIN travel tr ON tr.id = e.travelFk
+ JOIN warehouse w ON w.id = tr.warehouseOutFk
+ JOIN supplier s ON s.id = e.supplierFk
+ JOIN state st ON st.`code` = IF(tr.shipped < util.VN_CURDATE()
+ OR (tr.shipped = util.VN_CURDATE() AND tr.isReceived),
+ 'DELIVERED',
+ 'FREE')
+ JOIN entryConfig ec
+ WHERE tr.shipped >= vDateInventory
+ AND vWarehouseFk = tr.warehouseOutFk
+ AND (s.id <> ec.inventorySupplierFk OR vDate IS NULL)
+ AND b.itemFk = vItemFk
+ AND e.isExcludedFromAvailable = FALSE
+ AND w.isFeedStock = FALSE
+ AND e.isRaid = FALSE
+ UNION ALL
+ SELECT DATE(t.shipped),
+ NULL,
+ s.quantity,
+ st2.alertLevel,
+ st2.name,
+ t.nickname,
+ t.refFk,
+ t.id,
+ t.clientFk,
+ stk.id,
+ TRUE,
+ s.id,
+ st.`order`,
+ ct.`code`,
+ cb.claimFk,
+ NULL
+ FROM sale s
+ JOIN ticket t ON t.id = s.ticketFk
+ LEFT JOIN ticketState ts ON ts.ticketFk = t.id
+ LEFT JOIN state st ON st.`code` = ts.`code`
+ JOIN client c ON c.id = t.clientFk
+ JOIN clientType ct ON ct.id = c.clientTypeFk
+ JOIN state st2 ON st2.`code` = IF(t.shipped < util.VN_CURDATE(),
+ 'DELIVERED',
+ IF (t.shipped > util.dayEnd(util.VN_CURDATE()),
+ 'FREE',
+ IFNULL(ts.code, 'FREE')))
+ LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
+ LEFT JOIN saleTracking stk ON stk.saleFk = s.id
+ AND stk.stateFk = stPrep.id
+ LEFT JOIN claimBeginning cb ON s.id = cb.saleFk
+ WHERE t.shipped >= vDateInventory
+ AND s.itemFk = vItemFk
+ AND vWarehouseFk =t.warehouseFk
+ ORDER BY shipped,
+ (inventorySupplierFk = clientFk) DESC,
+ alertLevel DESC,
+ isTicket,
+ `order` DESC,
+ isPicked DESC,
+ `in` DESC,
+ `out` DESC;
+
+ IF vDate IS NULL THEN
+
+ SET @a := 0;
+ SET @currentLineFk := 0;
+ SET @shipped := '';
+
+ SELECT DATE(@shipped:= shipped) shipped,
+ alertLevel,
+ stateName,
+ origin,
+ reference,
+ clientFk,
+ name,
+ `in` invalue,
+ `out`,
+ @a := @a + IFNULL(`in`, 0) - IFNULL(`out`, 0) balance,
+ @currentLineFk := IF (@shipped < util.VN_CURDATE()
+ OR (@shipped = util.VN_CURDATE() AND (isPicked OR a.`code` >= 'ON_PREPARATION')),
+ lineFk,
+ @currentLineFk) lastPreparedLineFk,
+ isTicket,
+ lineFk,
+ isPicked,
+ clientType,
+ claimFk
+ FROM tItemDiary
+ LEFT JOIN alertLevel a ON a.id = tItemDiary.alertLevel;
+
+ ELSE
+ SELECT SUM(`in`) - SUM(`out`) INTO @a
+ FROM tItemDiary
+ WHERE shipped < vDate;
+
+ SELECT vDate shipped,
+ 0 alertLevel,
+ 0 stateName,
+ 0 origin,
+ '' reference,
+ 0 clientFk,
+ 'Inventario calculado',
+ @a invalue,
+ NULL `out`,
+ @a balance,
+ 0 lastPreparedLineFk,
+ 0 isTicket,
+ 0 lineFk,
+ 0 isPicked,
+ 0 clientType,
+ 0 claimFk
+ UNION ALL
+ SELECT shipped,
+ alertlevel,
+ stateName,
+ origin,
+ reference,
+ clientFk,
+ name, `in`,
+ `out`,
+ @a := @a + IFNULL(`in`, 0) - IFNULL(`out`, 0),
+ 0,
+ isTicket,
+ lineFk,
+ isPicked,
+ clientType,
+ claimFk
+ FROM tItemDiary
+ WHERE shipped >= vDate;
+ END IF;
+
+ DROP TEMPORARY TABLE tItemDiary;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`productionControl`(
+ vWarehouseFk INT,
+ vScopeDays INT
+)
+proc: BEGIN
+/**
+ * Devuelve un listado de tickets con parámetros relativos a la producción de los días en rango.
+ *
+ * @param vWarehouseFk Identificador de warehouse
+ * @param vScopeDays Número de días desde hoy en adelante que entran en el cálculo.
+ *
+ * @return Table tmp.productionBuffer
+ */
+ DECLARE vEndingDate DATETIME;
+ DECLARE vIsTodayRelative BOOLEAN;
+
+ SELECT util.dayEnd(util.VN_CURDATE()) + INTERVAL LEAST(vScopeDays, maxProductionScopeDays) DAY
+ INTO vEndingDate
+ FROM productionConfig;
+
+ SELECT isTodayRelative INTO vIsTodayRelative
+ FROM worker
+ WHERE id = getUser(); -- Cambiar por account.myUser_getId(), falta dar permisos
+
+ CALL prepareTicketList(util.yesterday(), vEndingDate);
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.ticket
+ SELECT * FROM tmp.productionTicket;
+
+ CALL prepareClientList();
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.sale_getProblems
+ (INDEX (ticketFk)) ENGINE = MEMORY
+ SELECT tt.ticketFk, tt.clientFk, t.warehouseFk, t.shipped
+ FROM tmp.productionTicket tt
+ JOIN ticket t ON t.id = tt.ticketFk;
+
+ CALL ticket_getProblems(vIsTodayRelative);
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.productionBuffer
+ (PRIMARY KEY(ticketFk), previaParking VARCHAR(255))
+ ENGINE = MEMORY
+ SELECT tt.ticketFk,
+ tt.clientFk,
+ t.warehouseFk,
+ t.nickname,
+ t.packages,
+ IF(HOUR(t.shipped), HOUR(t.shipped), COALESCE(HOUR(zc.hour),HOUR(z.hour))) HH,
+ COALESCE(HOUR(zc.hour), HOUR(z.hour)) Departure,
+ COALESCE(MINUTE(t.shipped), MINUTE(zc.hour), MINUTE(z.hour)) mm,
+ t.routeFk,
+ IF(dm.code = 'DELIVERY', z.`id`, 0) zona,
+ t.nickname addressNickname,
+ a.postalCode,
+ a.city,
+ p.name province,
+ CONCAT(z.`name`,' ',IFNULL(RIGHT(t.routeFk,3),'')) agency,
+ am.id agencyModeFk,
+ 0 `lines`,
+ CAST( 0 AS DECIMAL(5,2)) m3,
+ CAST( 0 AS DECIMAL(5,2)) preparationRate,
+ "" problem,
+ IFNULL(tls.state,2) state,
+ w.code workerCode,
+ DATE(t.shipped) shipped,
+ wk.code salesPersonCode,
+ p.id provinceFk,
+ tls.productionOrder,
+ IFNULL(tls.alertLevel, 0) alertLevel,
+ t.isBoxed palletized,
+ IF(rm.isPickingAllowed, rm.bufferFk, NULL) ubicacion,
+ tlu.lastUpdated,
+ IFNULL(st.graphCategory, 0) graphCategory,
+ pk.code parking,
+ 0 H,
+ 0 V,
+ 0 N,
+ st.isOk,
+ ag.isOwn,
+ rm.bufferFk
+ FROM tmp.productionTicket tt
+ JOIN ticket t ON tt.ticketFk = t.id
+ LEFT JOIN ticketStateToday tst ON tst.ticket = t.id
+ LEFT JOIN state st ON st.id = tst.state
+ LEFT JOIN client c ON c.id = t.clientFk
+ LEFT JOIN worker wk ON wk.id = c.salesPersonFk
+ JOIN address a ON a.id = t.addressFk
+ LEFT JOIN province p ON p.id = a.provinceFk
+ JOIN agencyMode am ON am.id = t.agencyModeFk
+ JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
+ JOIN agency ag ON ag.id = am.agencyFk
+ LEFT JOIN ticketState tls ON tls.ticketFk = tt.ticketFk
+ LEFT JOIN ticketLastUpdated tlu ON tlu.ticketFk = tt.ticketFk
+ LEFT JOIN worker w ON w.id = tls.userFk
+ LEFT JOIN routesMonitor rm ON rm.routeFk = t.routeFk
+ LEFT JOIN `zone` z ON z.id = t.zoneFk
+ LEFT JOIN zoneClosure zc ON zc.zoneFk = t.zoneFk
+ AND DATE(t.shipped) = zc.dated
+ LEFT JOIN ticketParking tp ON tp.ticketFk = t.id
+ LEFT JOIN parking pk ON pk.id = tp.parkingFk
+ WHERE t.warehouseFk = vWarehouseFk
+ AND dm.code IN ('AGENCY', 'DELIVERY', 'PICKUP');
+
+ UPDATE tmp.productionBuffer pb
+ JOIN (
+ SELECT pb.ticketFk, GROUP_CONCAT(p.code) previaParking
+ FROM tmp.productionBuffer pb
+ JOIN sale s ON s.ticketFk = pb.ticketFk
+ JOIN saleGroupDetail sgd ON sgd.saleFk = s.id
+ JOIN saleGroup sg ON sg.id = sgd.saleGroupFk
+ JOIN parking p ON p.id = sg.parkingFk
+ GROUP BY pb.ticketFk
+ ) t ON t.ticketFk = pb.ticketFk
+ SET pb.previaParking = t.previaParking;
+
+ -- Problemas por ticket
+ ALTER TABLE tmp.productionBuffer
+ CHANGE COLUMN `problem` `problem` VARCHAR(255),
+ ADD COLUMN `collectionH` INT,
+ ADD COLUMN `collectionV` INT,
+ ADD COLUMN `collectionN` INT;
+
+ UPDATE tmp.productionBuffer pb
+ JOIN tmp.ticket_problems tp ON tp.ticketFk = pb.ticketFk
+ SET pb.problem = TRIM(CAST(CONCAT( IFNULL(tp.itemShortage, ''),
+ IFNULL(tp.itemDelay, ''),
+ IFNULL(tp.itemLost, ''),
+ IF(tp.isFreezed, ' CONGELADO',''),
+ IF(tp.hasHighRisk, ' RIESGO',''),
+ IF(tp.hasTicketRequest, ' COD 100',''),
+ IF(tp.isTaxDataChecked, '',' FICHA INCOMPLETA'),
+ IF(tp.hasComponentLack, ' COMPONENTES', ''),
+ IF(HOUR(util.VN_NOW()) < pb.HH AND tp.isTooLittle, ' PEQUEÑO', '')
+ ) AS char(255)));
+
+ -- Clientes Nuevos o Recuperados
+ UPDATE tmp.productionBuffer pb
+ LEFT JOIN bs.clientNewBorn cnb ON cnb.clientFk = pb.clientFk
+ JOIN productionConfig pc
+ SET pb.problem = TRIM(CAST(CONCAT('NUEVO ', pb.problem) AS CHAR(255)))
+ WHERE (cnb.clientFk IS NULL OR cnb.isRookie)
+ AND pc.rookieDays;
+
+ -- Líneas y volumen por ticket
+ UPDATE tmp.productionBuffer pb
+ JOIN (
+ SELECT tt.ticketFk,
+ COUNT(*) `lines`,
+ SUM(sv.volume) m3,
+ IFNULL(SUM(IF(sv.isPicked, sv.volume, 0)) / SUM(sv.volume), 0) rate
+ FROM tmp.productionTicket tt
+ JOIN saleVolume sv ON sv.ticketFk = tt.ticketFk
+ GROUP BY tt.ticketFk
+ ) m ON m.ticketFk = pb.ticketFk
+ SET pb.`lines` = m.`lines`,
+ pb.m3 = m.m3,
+ pb.preparationRate = m.rate;
+
+ DELETE FROM tmp.productionBuffer
+ WHERE NOT `lines`;
+
+ -- Lineas por linea de encajado
+ UPDATE tmp.productionBuffer pb
+ JOIN (
+ SELECT ticketFk,
+ SUM(sub.H) H,
+ SUM(sub.V) V,
+ SUM(sub.N) N
+ FROM (
+ SELECT t.ticketFk,
+ SUM(i.itemPackingTypeFk = 'H') H,
+ SUM(i.itemPackingTypeFk = 'V') V,
+ SUM(i.itemPackingTypeFk IS NULL) N
+ FROM tmp.productionTicket t
+ JOIN sale s ON s.ticketFk = t.ticketFk
+ JOIN item i ON i.id = s.itemFk
+ GROUP BY t.ticketFk, i.itemPackingTypeFk
+ ) sub
+ GROUP BY ticketFk
+ ) sub2 ON sub2.ticketFk = pb.ticketFk
+ SET pb.H = sub2.H,
+ pb.V = sub2.V,
+ pb.N = sub2.N;
+
+ -- Colecciones segun tipo de encajado
+ UPDATE tmp.productionBuffer pb
+ JOIN ticketCollection tc ON pb.ticketFk = tc.ticketFk
+ SET pb.collectionH = IF(pb.H, tc.collectionFk, NULL),
+ pb.collectionV = IF(pb.V, tc.collectionFk, NULL),
+ pb.collectionN = IF(pb.N, tc.collectionFk, NULL);
+
+ -- Previa pendiente
+ ALTER TABLE tmp.productionBuffer
+ ADD previousWithoutParking BOOL DEFAULT FALSE;
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.ticketWithPrevia
+ (ticketFk INT PRIMARY KEY,
+ salesCount INT DEFAULT 0,
+ salesInParkingCount INT DEFAULT 0)
+ ENGINE = MEMORY;
+
+ -- Insertamos todos los tickets que tienen productos parkineados
+ -- en sectores de previa, segun el sector
+ CREATE OR REPLACE TEMPORARY TABLE tItemShelvingStock
+ (PRIMARY KEY(itemFk, sectorFk))
+ ENGINE = MEMORY
+ SELECT ish.itemFk,
+ p.sectorFk,
+ sc.isPreviousPrepared,
+ sc.itemPackingTypeFk
+ FROM itemShelving ish
+ JOIN shelving sh ON sh.code = ish.shelvingFk
+ JOIN parking p ON p.id = sh.parkingFk
+ JOIN sector sc ON sc.id = p.sectorFk
+ WHERE p.sectorFk
+ AND ish.visible
+ GROUP BY ish.itemFk, p.sectorFk;
+
+ INSERT INTO tmp.ticketWithPrevia(ticketFk, salesCount)
+ SELECT pb.ticketFk, COUNT(DISTINCT s.id)
+ FROM tmp.productionBuffer pb
+ JOIN sale s ON s.ticketFk = pb.ticketFk
+ JOIN tItemShelvingStock iss ON iss.itemFk = s.itemFk
+ JOIN sector sc ON sc.id = iss.sectorFk
+ JOIN item i ON i.id = iss.itemFk
+ WHERE iss.isPreviousPrepared
+ AND (sc.itemPackingTypeFk IS NULL
+ OR (i.itemPackingTypeFk IS NULL AND NOT pb.V)
+ OR sc.itemPackingTypeFk = i.itemPackingTypeFk)
+ AND s.quantity > 0
+ GROUP BY pb.ticketFk;
+
+ -- Se calcula la cantidad de productos que estan ya preparados porque su saleGroup está aparcado
+ UPDATE tmp.ticketWithPrevia twp
+ JOIN (
+ SELECT pb.ticketFk, COUNT(DISTINCT s.id) salesInParkingCount
+ FROM tmp.productionBuffer pb
+ JOIN sale s ON s.ticketFk = pb.ticketFk
+ JOIN saleGroupDetail sgd ON sgd.saleFk = s.id
+ JOIN saleGroup sg ON sg.id = sgd.saleGroupFk
+ WHERE sg.parkingFk IS NOT NULL
+ AND s.quantity > 0
+ GROUP BY pb.ticketFk
+ ) sub ON twp.ticketFk = sub.ticketFk
+ SET twp.salesInParkingCount = sub.salesInParkingCount;
+
+ -- Marcamos como pendientes aquellos que no coinciden las cantidades
+ UPDATE tmp.productionBuffer pb
+ JOIN tmp.ticketWithPrevia twp ON twp.ticketFk = pb.ticketFk
+ SET pb.previousWithoutParking = TRUE
+ WHERE twp.salesCount > twp.salesInParkingCount;
+
+ DROP TEMPORARY TABLE
+ tmp.productionTicket,
+ tmp.ticket,
+ tmp.risk,
+ tmp.ticket_problems,
+ tmp.ticketWithPrevia,
+ tItemShelvingStock;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`expedition_beforeInsert`
+ BEFORE INSERT ON `expedition`
+ FOR EACH ROW
+BEGIN
+ DECLARE intcounter INT;
+ DECLARE vShipFk INT;
+
+ SET NEW.editorFk = account.myUser_getId();
+
+ IF NEW.freightItemFk IS NOT NULL THEN
+
+ UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk;
+
+ SELECT IFNULL(MAX(counter),0) +1 INTO intcounter
+ FROM expedition e
+ INNER JOIN ticket t1 ON e.ticketFk = t1.id
+ LEFT JOIN ticketState ts ON ts.ticketFk = t1.id
+ INNER JOIN ticket t2 ON t2.addressFk = t1.addressFk AND DATE(t2.shipped) = DATE(t1.shipped)
+ AND t1.warehouseFk = t2.warehouseFk
+ WHERE t2.id = NEW.ticketFk AND ts.alertLevel < 3 AND t1.companyFk = t2.companyFk
+ AND t1.agencyModeFk = t2.agencyModeFk;
+
+ SET NEW.`counter` = intcounter;
+ END IF;
+END$$
+DELIMITER ;
+
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT)
+proc: BEGIN
+/**
+ * Este procedimiento recalcula los componentes de un conjunto de sales,
+ * eliminando los componentes existentes e insertandolos de nuevo
+ *
+ * @param vOption si no se quiere forzar llamar con NULL
+ * @table tmp.recalculateSales (id)
+ */
+ DECLARE vShipped DATE;
+ DECLARE vWarehouseFk SMALLINT;
+ DECLARE vAgencyModeFk INT;
+ DECLARE vAddressFk INT;
+ DECLARE vTicketFk INT;
+ DECLARE vLanded DATE;
+ DECLARE vIsEditable BOOLEAN;
+ DECLARE vZoneFk INTEGER;
+ DECLARE vDone BOOL DEFAULT FALSE;
+
+ DECLARE vCur CURSOR FOR
+ SELECT DISTINCT s.ticketFk
+ FROM tmp.recalculateSales rs
+ JOIN vn.sale s ON s.id = rs.id;
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+
+ OPEN vCur;
+
+ l: LOOP
+ SET vDone = FALSE;
+ FETCH vCur INTO vTicketFk;
+
+ IF vDone THEN
+ LEAVE l;
+ END IF;
+
+ SELECT (hasToRecalcPrice OR ts.alertLevel IS NULL) AND t.refFk IS NULL,
+ t.zoneFk,
+ t.warehouseFk,
+ t.shipped,
+ t.addressFk,
+ t.agencyModeFk,
+ t.landed
+ INTO vIsEditable,
+ vZoneFk,
+ vWarehouseFk,
+ vShipped,
+ vAddressFk,
+ vAgencyModeFk,
+ vLanded
+ FROM ticket t
+ LEFT JOIN ticketState ts ON t.id = ts.ticketFk
+ LEFT JOIN alertLevel al ON al.id = ts.alertLevel
+ WHERE t.id = vTicketFk;
+
+ CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE);
+
+ IF NOT EXISTS (SELECT TRUE FROM tmp.zoneGetLanded LIMIT 1) THEN
+ CALL util.throw(CONCAT('There is no zone for these parameters ', vTicketFk));
+ END IF;
+
+ IF vLanded IS NULL OR vZoneFk IS NULL THEN
+
+ UPDATE ticket t
+ SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
+ WHERE t.id = vTicketFk AND t.landed IS NULL;
+
+ IF vZoneFk IS NULL THEN
+ SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
+ UPDATE ticket t
+ SET t.zoneFk = vZoneFk
+ WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
+ END IF;
+
+ END IF;
+
+ DROP TEMPORARY TABLE tmp.zoneGetLanded;
+
+ -- rellena la tabla buyUltimate con la ultima compra
+ CALL buyUltimate (vWarehouseFk, vShipped);
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.sale
+ (PRIMARY KEY (saleFk)) ENGINE = MEMORY
+ SELECT s.id saleFk, vWarehouseFk warehouseFk
+ FROM sale s
+ JOIN tmp.recalculateSales rs ON s.id = rs.id
+ WHERE s.ticketFk = vTicketFk;
+
+ CREATE OR REPLACE TEMPORARY TABLE tmp.ticketLot
+ SELECT vWarehouseFk warehouseFk, NULL available, s.itemFk, bu.buyFk, vZoneFk zoneFk
+ FROM sale s
+ JOIN tmp.recalculateSales rs ON s.id = rs.id
+ LEFT JOIN tmp.buyUltimate bu ON bu.itemFk = s.itemFk
+ WHERE s.ticketFk = vTicketFk
+ GROUP BY s.itemFk;
+
+ CALL catalog_componentPrepare();
+ CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk);
+
+ IF vOption IS NULL THEN
+ SET vOption = IF(vIsEditable, 1, 6);
+ END IF;
+
+ CALL ticketComponentUpdateSale(vOption);
+ CALL catalog_componentPurge();
+
+ DROP TEMPORARY TABLE tmp.buyUltimate;
+ DROP TEMPORARY TABLE tmp.sale;
+
+ END LOOP;
+ CLOSE vCur;
+
+END$$
+DELIMITER ;
+
+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 tmp.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;
+ DECLARE vDone BOOL;
+ DECLARE vTicketFk INT;
+ DECLARE vCursor CURSOR FOR
+ SELECT id
+ FROM tmp.ticketToInvoice;
+
+ DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
+
+ SET vInvoiceDate = IFNULL(vInvoiceDate, util.VN_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 tmp.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 tmp.ticketToInvoice els tickets que no han de ser facturats
+ DELETE ti.*
+ FROM tmp.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 tmp.ticketToInvoice t
+ JOIN sale s ON s.ticketFk = t.id;
+
+ SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice
+ FROM tmp.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,
+ siiTypeInvoiceOutFk
+ )
+ 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;
+
+ OPEN vCursor;
+ l: LOOP
+ SET vDone = FALSE;
+ FETCH vCursor INTO vTicketFk;
+
+ IF vDone THEN
+ LEAVE l;
+ END IF;
+
+ CALL ticket_recalc(vTicketFk, vTaxArea);
+
+ END LOOP;
+ CLOSE vCursor;
+
+ UPDATE ticket t
+ JOIN tmp.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 tmp.ticketToInvoice ti
+ LEFT JOIN ticketState ts ON ti.id = ts.ticketFk
+ JOIN state s
+ WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id);
+
+ INSERT INTO ticketTracking(stateFk,ticketFk,userFk)
+ SELECT * FROM tmp.updateInter;
+
+ CALL invoiceExpenseMake(vNewInvoiceId);
+ CALL invoiceTaxMake(vNewInvoiceId,vTaxArea);
+
+ UPDATE invoiceOut io
+ JOIN (
+ SELECT SUM(amount) total
+ FROM invoiceOutExpense
+ 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 tmp.ticketToInvoice;
+
+ CALL `ticket_getTax`('NATIONAL');
+
+ SET @vTaxableBaseServices := 0.00;
+ SET @vTaxCodeGeneral := NULL;
+
+ INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenseFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInFk,
+ @vTaxableBaseServices,
+ sub.expenseFk,
+ sub.taxTypeSageFk,
+ sub.transactionTypeSageFk
+ FROM (
+ SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase,
+ i.expenseFk,
+ 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, expenseFk, taxTypeSageFk, transactionTypeSageFk)
+ SELECT vNewInvoiceInFk,
+ SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral,
+ @vTaxableBaseServices, 0) taxableBase,
+ i.expenseFk,
+ 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 `tmp`.`ticketToInvoice`;
+END$$
+DELIMITER ;
diff --git a/db/changes/240201/01-triggers.sql b/db/changes/240201/01-triggers.sql
new file mode 100644
index 000000000..a7fa029b4
--- /dev/null
+++ b/db/changes/240201/01-triggers.sql
@@ -0,0 +1,27 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`expedition_beforeInsert`
+ BEFORE INSERT ON `expedition`
+ FOR EACH ROW
+BEGIN
+ DECLARE intcounter INT;
+ DECLARE vShipFk INT;
+
+ SET NEW.editorFk = account.myUser_getId();
+
+ IF NEW.freightItemFk IS NOT NULL THEN
+
+ UPDATE ticket SET packages = nz(packages) + 1 WHERE id = NEW.ticketFk;
+
+ SELECT IFNULL(MAX(counter),0) +1 INTO intcounter
+ FROM expedition e
+ INNER JOIN ticket t1 ON e.ticketFk = t1.id
+ LEFT JOIN ticketState ts ON ts.ticketFk = t1.id
+ INNER JOIN ticket t2 ON t2.addressFk = t1.addressFk AND DATE(t2.shipped) = DATE(t1.shipped)
+ AND t1.warehouseFk = t2.warehouseFk
+ WHERE t2.id = NEW.ticketFk AND ts.alertLevel < 3 AND t1.companyFk = t2.companyFk
+ AND t1.agencyModeFk = t2.agencyModeFk;
+
+ SET NEW.`counter` = intcounter;
+ END IF;
+END$$
+DELIMITER ;
diff --git a/db/changes/240201/01-views.sql b/db/changes/240201/01-views.sql
new file mode 100644
index 000000000..30b08b776
--- /dev/null
+++ b/db/changes/240201/01-views.sql
@@ -0,0 +1,58 @@
+CREATE OR REPLACE DEFINER=`root`@`localhost`
+ SQL SECURITY DEFINER
+ VIEW `vn`.`expeditionRoute_freeTickets` AS
+SELECT
+ `t`.`routeFk` AS `routeFk`,
+ `tss`.`ticketFk` AS `ticket`,
+ `s`.`name` AS `code`,
+ `w`.`name` AS `almacen`,
+ `tss`.`updated` AS `updated`,
+ `p`.`code` AS `parkingCode`
+ FROM `vn`.`ticketState` `tss`
+ JOIN `vn`.`ticket` `t` ON `t`.`id` = `tss`.`ticketFk`
+ JOIN `vn`.`warehouse` `w` ON `w`.`id` = `t`.`warehouseFk`
+ JOIN `vn`.`state` `s` ON `s`.`id` = `tss`.`state`
+ LEFT JOIN `vn`.`ticketParking` `tp` ON `tp`.`ticketFk` = `t`.`id`
+ LEFT JOIN `vn`.`parking` `p` ON `p`.`id` = `tp`.`parkingFk`
+ WHERE IFNULL(`t`.`packages`, 0) = 0;
+
+CREATE OR REPLACE DEFINER=`root`@`localhost`
+ SQL SECURITY DEFINER
+ VIEW `vn`.`ticketState`
+AS SELECT `tt`.`created` AS `updated`,
+ `tt`.`stateFk` AS `stateFk`,
+ `tt`.`userFk` AS `userFk`,
+ `tls`.`ticketFk` AS `ticketFk`,
+ `s`.`id` AS `state`,
+ `s`.`order` AS `productionOrder`,
+ `s`.`alertLevel` AS `alertLevel`,
+ `s`.`code` AS `code`,
+ `s`.`isPreviousPreparable` AS `isPreviousPreparable`,
+ `s`.`isPicked` AS `isPicked`
+FROM (
+ (
+ `vn`.`ticketLastState` `tls`
+ JOIN `vn`.`ticketTracking` `tt` ON(`tt`.`id` = `tls`.`ticketTrackingFk`)
+ )
+ JOIN `vn`.`state` `s` ON(`s`.`id` = `tt`.`stateFk`)
+ );
+
+CREATE OR REPLACE DEFINER=`root`@`localhost`
+ SQL SECURITY DEFINER
+ VIEW `vn`.`ticketStateToday`
+AS
+SELECT
+ `ts`.`ticketFk` AS `ticket`,
+ `ts`.`state` AS `state`,
+ `ts`.`productionOrder` AS `productionOrder`,
+ `ts`.`alertLevel` AS `alertLevel`,
+ `ts`.`userFk` AS `worker`,
+ `ts`.`code` AS `code`,
+ `ts`.`updated` AS `updated`,
+ `ts`.`isPicked` AS `isPicked`
+FROM
+ `vn`.`ticketState` `ts`
+ JOIN `vn`.`ticket` `t` ON `t`.`id` = `ts`.`ticketFk`
+WHERE
+ `t`.`shipped` BETWEEN `util`.`VN_CURDATE`() AND `util`.`VN_CURDATE`() + INTERVAL 1 DAY;
+
diff --git a/db/changes/240401/00-fixInvoiceOutBeforeInsert.sql b/db/changes/240401/00-fixInvoiceOutBeforeInsert.sql
new file mode 100644
index 000000000..10e8d6356
--- /dev/null
+++ b/db/changes/240401/00-fixInvoiceOutBeforeInsert.sql
@@ -0,0 +1,58 @@
+DELIMITER $$
+CREATE OR REPLACE DEFINER=`root`@`localhost` TRIGGER `vn`.`invoiceOut_beforeInsert`
+ BEFORE INSERT ON `invoiceOut`
+ FOR EACH ROW
+BEGIN
+/**
+ * Reference format:
+ * - 0: Serial [A-Z]
+ * - 1: Sage company id
+ * - 2-3: Last two digits of issued year
+ * - 4-8: Autoincrement identifier
+ **/
+ DECLARE vNewRef INT DEFAULT 0;
+ DECLARE vCompanyCode INT;
+ DECLARE vLastRef VARCHAR(255);
+ DECLARE vRefStr VARCHAR(255);
+ DECLARE vRefLen INT DEFAULT 5;
+ DECLARE vYearLen INT DEFAULT 2;
+ DECLARE vPrefixLen INT;
+
+ SELECT companyCode INTO vCompanyCode
+ FROM company
+ WHERE id = NEW.companyFk;
+
+ IF vCompanyCode IS NULL THEN
+ CALL util.throw('sageCompanyNotDefined');
+ END IF;
+
+ SELECT MAX(i.ref) INTO vLastRef
+ FROM invoiceOut i
+ WHERE i.serial = NEW.serial
+ AND i.issued BETWEEN util.firstDayOfYear(NEW.issued) AND util.dayEnd(util.lastDayOfYear(NEW.issued))
+ AND i.companyFk = NEW.companyFk;
+
+ IF vLastRef IS NOT NULL THEN
+ SET vPrefixLen = LENGTH(NEW.serial) + LENGTH(vCompanyCode) + vYearLen;
+ SET vRefLen = LENGTH(vLastRef) - vPrefixLen;
+ SET vRefStr = SUBSTRING(vLastRef, vPrefixLen + 1);
+ SET vNewRef = vRefStr + 1;
+
+ IF LENGTH(vNewRef) > vRefLen THEN
+ CALL util.throw('refLenExceeded');
+ END IF;
+
+ SET NEW.ref = CONCAT(
+ SUBSTRING(vLastRef, 1, vPrefixLen),
+ LPAD(vNewRef, LENGTH(vRefStr), '0')
+ );
+ ELSE
+ SET NEW.ref = CONCAT(
+ NEW.serial,
+ vCompanyCode,
+ RIGHT(YEAR(NEW.issued), vYearLen),
+ LPAD(1, vRefLen, '0')
+ );
+ END IF;
+END$$
+DELIMITER ;
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 8fd1961bb..fb7569f70 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -2911,8 +2911,7 @@ INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`, `payMethodFk
INSERT INTO `vn`.`ticketRefund`(`refundTicketFk`, `originalTicketFk`)
VALUES
- (1, 12),
- (8, 10);
+ (24, 7);
INSERT INTO `vn`.`deviceProductionModels` (`code`)
VALUES
@@ -3011,6 +3010,15 @@ INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
(2, 'Error in sales details'),
(3, 'Error in customer data');
+UPDATE `vn`.`client`
+ SET fi='65004204V'
+ WHERE id=1;
+
+UPDATE `vn`.`worker`
+ SET fi='59328808D'
+ WHERE id=1106;
+
+
INSERT INTO `account`.`mailAliasAcl` (`mailAliasFk`, `roleFk`)
VALUES
(1, 1),
@@ -3022,16 +3030,16 @@ INSERT INTO `vn`.`docuwareTablet` (`tablet`,`description`)
('Tablet1','Jarvis tablet'),
('Tablet2','Avengers tablet');
-INSERT INTO `vn`.`sms` (`id`, `senderFk`, `sender`, `destination`, `message`, `statusCode`, `status`, `created`)
+INSERT INTO `vn`.`sms` (`id`, `senderFk`, `sender`, `destination`, `message`, `statusCode`, `status`, `created`)
VALUES (1, 66, '111111111', '0001111111111', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE()),
(2, 66, '222222222', '0002222222222', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'PENDING', util.VN_CURDATE()),
(3, 66, '333333333', '0003333333333', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'ERROR', util.VN_CURDATE()),
(4, 66, '444444444', '0004444444444', 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 0, 'OK', util.VN_CURDATE());
-INSERT INTO `vn`.`clientSms` (`id`, `clientFk`, `smsFk`, `ticketFk`)
+INSERT INTO `vn`.`clientSms` (`id`, `clientFk`, `smsFk`, `ticketFk`)
VALUES(1, 1103, 1, NULL),
(2, 1103, 2, NULL),
(3, 1103, 3, 32),
(4, 1103, 4, 32),
(13, 1101, 1, NULL),
- (14, 1101, 4, 27);
\ No newline at end of file
+ (14, 1101, 4, 27);
diff --git a/db/tests/vn/ticket_componentMakeUpdate.spec.js b/db/tests/vn/ticket_componentMakeUpdate.spec.js
deleted file mode 100644
index a059f1060..000000000
--- a/db/tests/vn/ticket_componentMakeUpdate.spec.js
+++ /dev/null
@@ -1,123 +0,0 @@
-const app = require('vn-loopback/server/server');
-const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
-
-// 2277 solucionar problema al testear procedimiento con start transaction / rollback
-xdescribe('ticket_componentMakeUpdate()', () => {
- it('should recalculate the ticket components without make modifications', async() => {
- let stmts = [];
- let stmt;
-
- let params = {
- ticketId: 11,
- clientId: 1102,
- agencyModeId: 2,
- addressId: 122,
- zoneId: 3,
- warehouseId: 1,
- companyId: 442,
- isDeleted: 0,
- hasToBeUnrouted: 0,
- componentOption: 1
- };
-
- stmts.push('START TRANSACTION');
-
- stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
- params.ticketId
- ]);
- stmts.push(stmt);
-
- let originalTicketIndex = stmts.push(stmt) - 1;
-
- stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
- params.ticketId,
- params.clientId,
- params.agencyModeId,
- params.addressId,
- params.zoneId,
- params.warehouseId,
- params.companyId,
- params.isDeleted,
- params.hasToBeUnrouted,
- params.componentOption
- ]);
- stmts.push(stmt);
-
- stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
- params.ticketId
- ]);
- stmts.push(stmt);
-
- let updatedTicketIndex = stmts.push(stmt) - 1;
-
- stmts.push('ROLLBACK');
-
- let sql = ParameterizedSQL.join(stmts, ';');
- let result = await app.models.Ticket.rawStmt(sql);
-
- let originalTicketData = result[originalTicketIndex];
- let updatedTicketData = result[updatedTicketIndex];
-
- expect(originalTicketData[0].isDeleted).toEqual(updatedTicketData[0].isDeleted);
- expect(originalTicketData[0].routeFk).toEqual(updatedTicketData[0].routeFk);
- });
-
- it('should delete and unroute a ticket and recalculate the components', async() => {
- let stmts = [];
- let stmt;
-
- let params = {
- ticketId: 11,
- clientId: 1102,
- agencyModeId: 2,
- addressId: 122,
- zoneId: 3,
- warehouseId: 1,
- companyId: 442,
- isDeleted: 1,
- hasToBeUnrouted: 1,
- componentOption: 1
- };
-
- stmts.push('START TRANSACTION');
-
- stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
- params.ticketId
- ]);
- stmts.push(stmt);
-
- let originalTicketIndex = stmts.push(stmt) - 1;
-
- stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
- params.ticketId,
- params.clientId,
- params.agencyModeId,
- params.addressId,
- params.zoneId,
- params.warehouseId,
- params.companyId,
- params.isDeleted,
- params.hasToBeUnrouted,
- params.componentOption
- ]);
- stmts.push(stmt);
-
- stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
- params.ticketId
- ]);
- stmts.push(stmt);
-
- let updatedTicketIndex = stmts.push(stmt) - 1;
-
- stmts.push('ROLLBACK');
-
- let sql = ParameterizedSQL.join(stmts, ';');
- let result = await app.models.Ticket.rawStmt(sql);
-
- let originalTicketData = result[originalTicketIndex];
- let updatedTicketData = result[updatedTicketIndex];
-
- expect(originalTicketData[0].isDeleted).not.toEqual(updatedTicketData[0].isDeleted);
- expect(originalTicketData[0].routeFk).not.toEqual(updatedTicketData[0].routeFk);
- });
-});
diff --git a/e2e/paths/03-worker/04_time_control.spec.js b/e2e/paths/03-worker/04_time_control.spec.js
index 5f64aa6ce..c6589d2e3 100644
--- a/e2e/paths/03-worker/04_time_control.spec.js
+++ b/e2e/paths/03-worker/04_time_control.spec.js
@@ -56,63 +56,6 @@ describe('Worker time control path', () => {
expect(result).toContain(month);
});
- it(`should return error when insert 'out' of first entry`, async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
- await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
- await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
- await page.respondToDialog('accept');
- const message = await page.waitForSnackbar();
-
- expect(message.text).toBeDefined();
- });
-
- it(`should insert 'in' monday`, async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
- await page.pickTime(selectors.workerTimeControl.dialogTimeInput, eightAm);
- await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'in');
- await page.respondToDialog('accept');
- const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
-
- expect(result).toEqual(eightAm);
- });
-
- it(`should insert 'out' monday`, async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- await page.waitToClick(selectors.workerTimeControl.mondayAddTimeButton);
- await page.pickTime(selectors.workerTimeControl.dialogTimeInput, fourPm);
- await page.autocompleteSearch(selectors.workerTimeControl.dialogTimeDirection, 'out');
- await page.respondToDialog('accept');
- const result = await page.waitToGetProperty(selectors.workerTimeControl.secondEntryOfMonday, 'innerText');
-
- expect(result).toEqual(fourPm);
- });
-
- it(`should check Hank Pym worked 8:20 hours`, async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- await page.waitForTextInElement(selectors.workerTimeControl.mondayWorkedHours, '08:20 h.');
- await page.waitForTextInElement(selectors.workerTimeControl.weekWorkedHours, '08:20 h.');
- });
-
- it('should remove first entry of monday', async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- await page.waitForTextInElement(selectors.workerTimeControl.firstEntryOfMonday, eightAm);
- await page.waitForTextInElement(selectors.workerTimeControl.secondEntryOfMonday, fourPm);
- await page.waitToClick(selectors.workerTimeControl.firstEntryOfMondayDelete);
- await page.respondToDialog('accept');
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Entry removed');
- });
-
- it(`should be the 'out' the first entry of monday`, async() => {
- pending('https://redmine.verdnatura.es/issues/4707');
- const result = await page.waitToGetProperty(selectors.workerTimeControl.firstEntryOfMonday, 'innerText');
-
- expect(result).toEqual(fourPm);
- });
-
it('should change week of month', async() => {
await page.click(selectors.workerTimeControl.thrirdWeekDay);
const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
diff --git a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
index 5e82306cc..9095eee0a 100644
--- a/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
+++ b/e2e/paths/05-ticket/01-sale/02_edit_sale.spec.js
@@ -188,17 +188,6 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50');
});
- it('should check in the history that logs has been added', async() => {
- pending('https://redmine.verdnatura.es/issues/5455');
- await page.reload({waitUntil: ['networkidle0', 'domcontentloaded']});
- await page.waitToClick(selectors.ticketSales.firstSaleHistoryButton);
- await page.waitForSelector(selectors.ticketSales.firstSaleHistory);
- const result = await page.countElement(selectors.ticketSales.firstSaleHistory);
-
- expect(result).toBeGreaterThan(0);
- await page.waitToClick(selectors.ticketSales.closeHistory);
- });
-
it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
@@ -236,7 +225,7 @@ describe('Ticket Edit sale path', () => {
});
it('should show error trying to delete a ticket with a refund', async() => {
- await page.accessToSearchResult('16');
+ await page.accessToSearchResult('6');
await page.waitToClick(selectors.ticketDescriptor.moreMenu);
await page.waitToClick(selectors.ticketDescriptor.moreMenuDeleteTicket);
await page.waitToClick(selectors.globalItems.acceptButton);
diff --git a/e2e/paths/05-ticket/11_diary.spec.js b/e2e/paths/05-ticket/11_diary.spec.js
deleted file mode 100644
index e4c63855a..000000000
--- a/e2e/paths/05-ticket/11_diary.spec.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import selectors from '../../helpers/selectors.js';
-import getBrowser from '../../helpers/puppeteer';
-
-// #2221 Local MySQL8 crashes when rest method Items/getBalance is called
-xdescribe('Ticket diary path', () => {
- let page;
-
- beforeAll(async() => {
- page = (await getBrowser()).page;
- await page.loginAndModule('employee', 'ticket');
- });
-
- afterAll(async() => {
- await page.browser().close();
- });
-
- it(`should navigate to item diary from ticket sale and check the lines`, async() => {
- await page.accessToSearchResult('1');
- await page.waitToClick(selectors.ticketSummary.firstSaleItemId);
- await page.waitToClick(selectors.ticketSummary.popoverDiaryButton);
- await page.waitForState('item.card.diary');
-
- const secondIdClass = await page.getClassName(selectors.itemDiary.secondTicketId);
- const fourthBalanceClass = await page.getClassName(selectors.itemDiary.fourthBalance);
- const firstBalanceClass = await page.getClassName(selectors.itemDiary.firstBalance);
-
- expect(secondIdClass).toContain('message');
- expect(fourthBalanceClass).toContain('message');
- expect(firstBalanceClass).toContain('balance');
- });
-});
diff --git a/e2e/paths/06-claim/02_detail.spec.js b/e2e/paths/06-claim/02_detail.spec.js
deleted file mode 100644
index eb4ac5d71..000000000
--- a/e2e/paths/06-claim/02_detail.spec.js
+++ /dev/null
@@ -1,114 +0,0 @@
-import selectors from '../../helpers/selectors.js';
-import getBrowser from '../../helpers/puppeteer.js';
-
-// #1528 e2e claim/detail
-xdescribe('Claim detail', () => {
- let browser;
- let page;
-
- beforeAll(async() => {
- browser = await getBrowser();
- page = browser.page;
- await page.loginAndModule('salesPerson', 'claim');
- await page.accessToSearchResult('1');
- await page.accessToSection('claim.card.detail');
- });
-
- afterAll(async() => {
- await browser.close();
- });
-
- it('should add the first claimable item from ticket to the claim', async() => {
- await page.waitToClick(selectors.claimDetail.addItemButton);
- await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket);
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Data saved!');
- });
-
- it('should confirm the claim contains now two items', async() => {
- const result = await page.countElement(selectors.claimDetail.claimDetailLine);
-
- expect(result).toEqual(2);
- });
-
- it('should edit de first item claimed quantity', async() => {
- await page.clearInput(selectors.claimDetail.firstItemQuantityInput); // selector deleted, find new upon fixes
- await page.write(selectors.claimDetail.firstItemQuantityInput, '4'); // selector deleted, find new upon fixes
- await page.keyboard.press('Enter');
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Data saved!');
- });
-
- it('should confirm the first item quantity, and the claimed total were correctly edited', async() => {
- const claimedQuantity = page
- .waitToGetProperty(selectors.claimDetail.firstItemQuantityInput, 'value'); // selector deleted, find new upon fixes
-
- const totalClaimed = page
- .waitToGetProperty(selectors.claimDetail.totalClaimed, 'innerText');
-
- expect(claimedQuantity).toEqual('4');
- expect(totalClaimed).toContain('€47.62');
- });
-
- it('should login as salesAssistant and navigate to the claim.detail section', async() => {
- await page.loginAndModule('salesAssistant', 'claim');
- await page.accessToSearchResult('1');
- await page.accessToSection('claim.card.detail');
- let url = await page.expectURL('/detail'); // replace with waitForState
-
- expect(url).toBe(true);
- });
-
- it('should edit de second item claimed discount', async() => {
- await page.waitToClick(selectors.claimDetail.secondItemDiscount);
- await page.write(selectors.claimDetail.discount, '100');
- await page.keyboard.press('Enter');
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Data saved!');
- });
-
- it('should check the mana is the expected one', async() => {
- await page.waitToClick(selectors.claimDetail.secondItemDiscount);
- const result = await page.waitToGetProperty(selectors.claimDetail.discoutPopoverMana, 'innerText');
-
- expect(result).toContain('MANÁ: €106');
- });
-
- it('should delete the second item from the claim', async() => {
- await page.waitToClick(selectors.claimDetail.secondItemDeleteButton);
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Data saved!');
- });
-
- it('should confirm the claim contains now one item', async() => {
- const result = await page.countElement(selectors.claimDetail.claimDetailLine);
-
- expect(result).toEqual(1);
- });
-
- it('should add the deleted ticket from to the claim', async() => {
- await page.waitToClick(selectors.claimDetail.addItemButton);
- await page.waitToClick(selectors.claimDetail.firstClaimableSaleFromTicket);
- const message = await page.waitForSnackbar();
-
- expect(message.text).toContain('Data saved!');
- });
-
- it(`should have been redirected to the next section in claims`, async() => {
- let url = await page.expectURL('development'); // replace with waitForState
-
- expect(url).toBe(true);
- });
-
- it('should navigate back to claim.detail to confirm the claim contains now two items', async() => {
- await page.accessToSection('claim.card.detail');
- await page.waitForSelector(selectors.claimDetail.claimDetailLine);
- const result = await page.countElement(selectors.claimDetail.claimDetailLine);
-
- expect(result).toEqual(2);
- });
-});
diff --git a/e2e/paths/10-travel/02_basic_data_and_log.spec.js b/e2e/paths/10-travel/02_basic_data_and_log.spec.js
index 0079e8023..e6c601d7d 100644
--- a/e2e/paths/10-travel/02_basic_data_and_log.spec.js
+++ b/e2e/paths/10-travel/02_basic_data_and_log.spec.js
@@ -105,17 +105,4 @@ describe('Travel basic data path', () => {
it(`should check the received checkbox was saved even tho it doesn't make sense`, async() => {
await page.waitForClassPresent(selectors.travelBasicData.received, 'checked');
});
-
- it('should navigate to the travel logs', async() => {
- pending('https://redmine.verdnatura.es/issues/5455');
- await page.accessToSection('travel.card.log');
- await page.waitForState('travel.card.log');
- });
-
- it('should check the 1st log contains details from the changes made', async() => {
- pending('https://redmine.verdnatura.es/issues/5455');
- const result = await page.waitToGetProperty(selectors.travelLog.firstLogFirstTD, 'innerText');
-
- expect(result).toContain('new reference!');
- });
});
diff --git a/front/core/components/treeview/index.spec.js b/front/core/components/treeview/index.spec.js
index 9277f3ee4..1979d517f 100644
--- a/front/core/components/treeview/index.spec.js
+++ b/front/core/components/treeview/index.spec.js
@@ -33,18 +33,6 @@ describe('Component vnTreeview', () => {
$element.remove();
});
- // See how to test DOM element in Jest
- xdescribe('undrop()', () => {
- it(`should reset all drop events and properties`, () => {
- controller.dropping = angular.element(``);
- jest.spyOn(controller.dropping.classList, 'remove');
-
- controller.undrop();
-
- expect(controller.dropping).toBeNull();
- });
- });
-
describe('dragOver()', () => {
it(`should set the dragClientY property`, () => {
const event = new Event('dragover');
diff --git a/loopback/locale/en.json b/loopback/locale/en.json
index 2c7dc6be1..611c409ca 100644
--- a/loopback/locale/en.json
+++ b/loopback/locale/en.json
@@ -69,8 +69,7 @@
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Change quantity": "{{concept}} change of {{oldQuantity}} to {{newQuantity}}",
"Claim will be picked": "The product from the claim [({{claimId}})]({{{claimUrl}}}) from the client *{{clientName}}* will be picked",
- "Claim state has changed to incomplete": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *incomplete*",
- "Claim state has changed to canceled": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *canceled*",
+ "Claim state has changed to": "The state of the claim [({{claimId}})]({{{claimUrl}}}) from client *{{clientName}}* has changed to *{{newState}}*",
"Customs agent is required for a non UEE member": "Customs agent is required for a non UEE member",
"Incoterms is required for a non UEE member": "Incoterms is required for a non UEE member",
"Client checked as validated despite of duplication": "Client checked as validated despite of duplication from client id {{clientId}}",
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index 25c76971d..5555ef8b0 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -136,8 +136,7 @@
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
"Change quantity": "{{concept}} cambia de {{oldQuantity}} a {{newQuantity}}",
"Claim will be picked": "Se recogerá el género de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}*",
- "Claim state has changed to incomplete": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *incompleta*",
- "Claim state has changed to canceled": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *anulado*",
+ "Claim state has changed to": "Se ha cambiado el estado de la reclamación [({{claimId}})]({{{claimUrl}}}) del cliente *{{clientName}}* a *{{newState}}*",
"Client checked as validated despite of duplication": "Cliente comprobado a pesar de que existe el cliente id {{clientId}}",
"ORDER_ROW_UNAVAILABLE": "No hay disponibilidad de este producto",
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
@@ -332,6 +331,7 @@
"quantityLessThanMin": "La cantidad no puede ser menor que la cantidad mínima",
"Cannot past travels with entries": "No se pueden pasar envíos con entradas",
"It was not able to remove the next expeditions:": "No se pudo eliminar las siguientes expediciones: {{expeditions}}",
+ "This claim has been updated": "La reclamación con Id: {{claimId}}, ha sido actualizada",
"This user does not have an assigned tablet": "Este usuario no tiene tablet asignada",
"Incorrect pin": "Pin incorrecto.",
"You already have the mailAlias": "Ya tienes este alias de correo",
diff --git a/modules/account/back/models/samba-config.js b/modules/account/back/models/samba-config.js
index f5672ca21..927510a29 100644
--- a/modules/account/back/models/samba-config.js
+++ b/modules/account/back/models/samba-config.js
@@ -7,7 +7,8 @@ const execFile = require('child_process').execFile;
* https://docs.microsoft.com/en-us/troubleshoot/windows-server/identity/useraccountcontrol-manipulate-account-properties
*/
const UserAccountControlFlags = {
- ACCOUNTDISABLE: 2
+ ACCOUNTDISABLE: 0x2,
+ DONT_EXPIRE_PASSWD: 0x10000
};
module.exports = Self => {
@@ -118,7 +119,8 @@ module.exports = Self => {
}
entry = {
- userAccountControl: sambaUser.userAccountControl
+ userAccountControl: (sambaUser.userAccountControl
+ | UserAccountControlFlags.DONT_EXPIRE_PASSWD)
& ~UserAccountControlFlags.ACCOUNTDISABLE,
uidNumber: info.uidNumber,
accountExpires: 0,
diff --git a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js
index a01590f58..393c3b10d 100644
--- a/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js
+++ b/modules/claim/back/methods/claim-beginning/importToNewRefundTicket.js
@@ -120,7 +120,7 @@ module.exports = Self => {
observationTypeFk: obsevationType.id
}, myOptions);
- await models.TicketTracking.create({
+ await models.Ticket.state(ctx, {
ticketFk: newRefundTicket.id,
stateFk: state.id,
userFk: worker.id
diff --git a/modules/claim/back/methods/claim/specs/updateClaim.spec.js b/modules/claim/back/methods/claim/specs/updateClaim.spec.js
index 85ada869a..e2d5fcfeb 100644
--- a/modules/claim/back/methods/claim/specs/updateClaim.spec.js
+++ b/modules/claim/back/methods/claim/specs/updateClaim.spec.js
@@ -1,8 +1,9 @@
const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
-
+const i18n = require('i18n');
describe('Update Claim', () => {
let url;
+ let claimStatesMap = {};
beforeAll(async() => {
url = await app.models.Url.getUrl();
const activeCtx = {
@@ -16,6 +17,8 @@ describe('Update Claim', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
+ const claimStates = await app.models.ClaimState.find();
+ claimStatesMap = claimStates.reduce((acc, state) => ({...acc, [state.code]: state.id}), {});
});
const newDate = Date.vnNew();
const originalData = {
@@ -62,6 +65,123 @@ describe('Update Claim', () => {
expect(error.message).toEqual(`You don't have enough privileges to change that field`);
});
+ it(`should success to update the claimState to 'pending' and send a rocket message`, async() => {
+ const tx = await app.models.Claim.beginTransaction({});
+
+ try {
+ const options = {transaction: tx};
+
+ const newClaim = await app.models.Claim.create(originalData, options);
+
+ const chatModel = app.models.Chat;
+ spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
+
+ const pendingState = claimStatesMap.pending;
+ const claimManagerId = 72;
+ const ctx = {
+ req: {
+ accessToken: {userId: claimManagerId},
+ headers: {origin: url}
+ },
+ args: {
+ observation: 'valid observation',
+ claimStateFk: pendingState,
+ hasToPickUp: false
+ }
+ };
+ ctx.req.__ = i18n.__;
+ await app.models.Claim.updateClaim(ctx, newClaim.id, options);
+
+ let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
+
+ expect(updatedClaim.observation).toEqual(ctx.args.observation);
+ expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+
+ it(`should success to update the claimState to 'managed' and send a rocket message`, async() => {
+ const tx = await app.models.Claim.beginTransaction({});
+
+ try {
+ const options = {transaction: tx};
+
+ const newClaim = await app.models.Claim.create(originalData, options);
+
+ const chatModel = app.models.Chat;
+ spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
+
+ const managedState = claimStatesMap.managed;
+ const claimManagerId = 72;
+ const ctx = {
+ req: {
+ accessToken: {userId: claimManagerId},
+ headers: {origin: url}
+ },
+ args: {
+ observation: 'valid observation',
+ claimStateFk: managedState,
+ hasToPickUp: false
+ }
+ };
+ ctx.req.__ = i18n.__;
+ await app.models.Claim.updateClaim(ctx, newClaim.id, options);
+
+ let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
+
+ expect(updatedClaim.observation).toEqual(ctx.args.observation);
+ expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+
+ it(`should success to update the claimState to 'resolved' and send a rocket message`, async() => {
+ const tx = await app.models.Claim.beginTransaction({});
+
+ try {
+ const options = {transaction: tx};
+
+ const newClaim = await app.models.Claim.create(originalData, options);
+
+ const chatModel = app.models.Chat;
+ spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
+
+ const resolvedState = claimStatesMap.resolved;
+ const claimManagerId = 72;
+ const ctx = {
+ req: {
+ accessToken: {userId: claimManagerId},
+ headers: {origin: url}
+ },
+ args: {
+ observation: 'valid observation',
+ claimStateFk: resolvedState,
+ hasToPickUp: false
+ }
+ };
+ ctx.req.__ = i18n.__;
+ await app.models.Claim.updateClaim(ctx, newClaim.id, options);
+
+ let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
+
+ expect(updatedClaim.observation).toEqual(ctx.args.observation);
+ expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+
it(`should success to update the claimState to 'canceled' and send a rocket message`, async() => {
const tx = await app.models.Claim.beginTransaction({});
@@ -73,7 +193,7 @@ describe('Update Claim', () => {
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
- const canceledState = 4;
+ const canceledState = claimStatesMap.canceled;
const claimManagerId = 72;
const ctx = {
req: {
@@ -86,9 +206,7 @@ describe('Update Claim', () => {
hasToPickUp: false
}
};
- ctx.req.__ = (value, params) => {
- return params.nickname;
- };
+ ctx.req.__ = i18n.__;
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
@@ -127,9 +245,7 @@ describe('Update Claim', () => {
hasToPickUp: false
}
};
- ctx.req.__ = (value, params) => {
- return params.nickname;
- };
+ ctx.req.__ = i18n.__;
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
@@ -168,9 +284,7 @@ describe('Update Claim', () => {
hasToPickUp: true
}
};
- ctx.req.__ = (value, params) => {
- return params.nickname;
- };
+ ctx.req.__ = i18n.__;
await app.models.Claim.updateClaim(ctx, newClaim.id, options);
let updatedClaim = await app.models.Claim.findById(newClaim.id, null, options);
diff --git a/modules/claim/back/methods/claim/updateClaim.js b/modules/claim/back/methods/claim/updateClaim.js
index d99528413..68fff7846 100644
--- a/modules/claim/back/methods/claim/updateClaim.js
+++ b/modules/claim/back/methods/claim/updateClaim.js
@@ -96,12 +96,9 @@ module.exports = Self => {
// When claimState has been changed
if (args.claimStateFk) {
const newState = await models.ClaimState.findById(args.claimStateFk, null, myOptions);
- if (newState.hasToNotify) {
- if (newState.code == 'incomplete')
- await notifyStateChange(ctx, salesPerson.id, claim, newState.code);
- if (newState.code == 'canceled')
- await notifyStateChange(ctx, claim.workerFk, claim, newState.code);
- }
+ await notifyStateChange(ctx, salesPerson.id, claim, newState.code);
+ if (newState.code == 'canceled')
+ await notifyStateChange(ctx, claim.workerFk, claim, newState.code);
}
if (tx) await tx.commit();
@@ -113,15 +110,16 @@ module.exports = Self => {
}
};
- async function notifyStateChange(ctx, workerId, claim, state) {
+ async function notifyStateChange(ctx, workerId, claim, newState) {
const models = Self.app.models;
const url = await models.Url.getUrl();
const $t = ctx.req.__; // $translate
- const message = $t(`Claim state has changed to ${state}`, {
+ const message = $t(`Claim state has changed to`, {
claimId: claim.id,
clientName: claim.client().name,
- claimUrl: `${url}claim/${claim.id}/summary`
+ claimUrl: `${url}claim/${claim.id}/summary`,
+ newState
});
await models.Chat.sendCheckingPresence(ctx, workerId, message);
}
diff --git a/modules/claim/front/note/create/index.html b/modules/claim/front/note/create/index.html
index 304a8c004..8a882a4f5 100644
--- a/modules/claim/front/note/create/index.html
+++ b/modules/claim/front/note/create/index.html
@@ -27,4 +27,4 @@
label="Cancel">
-
\ No newline at end of file
+
diff --git a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
index 5f2428539..782eaf6d8 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/transferInvoice.js
@@ -78,7 +78,7 @@ module.exports = Self => {
const sales = await models.Sale.find(filterTicket, myOptions);
const salesIds = sales.map(sale => sale.id);
- const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, myOptions);
+ const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, myOptions);
const clonedTicketIds = [];
for (const clonedTicket of clonedTickets) {
diff --git a/modules/ticket/back/methods/boxing/specs/getVideo.spec.js b/modules/ticket/back/methods/boxing/specs/getVideo.spec.js
deleted file mode 100644
index 8e8cdc5b9..000000000
--- a/modules/ticket/back/methods/boxing/specs/getVideo.spec.js
+++ /dev/null
@@ -1,40 +0,0 @@
-const models = require('vn-loopback/server/server').models;
-const https = require('https');
-
-xdescribe('boxing getVideo()', () => {
- it('should return data', async() => {
- const tx = await models.PackingSiteConfig.beginTransaction({});
-
- try {
- const options = {transaction: tx};
-
- const id = 1;
- const video = 'video.mp4';
-
- const response = {
- pipe: () => {},
- on: () => {},
- end: () => {},
- };
-
- const req = {
- headers: 'apiHeader',
- data: {
- pipe: () => {},
- on: () => {},
- }
- };
-
- spyOn(https, 'request').and.returnValue(response);
-
- const result = await models.Boxing.getVideo(id, video, req, null, options);
-
- expect(result[0]).toEqual(response.data.videos[0].filename);
-
- await tx.rollback();
- } catch (e) {
- await tx.rollback();
- throw e;
- }
- });
-});
diff --git a/modules/ticket/back/methods/sale/clone.js b/modules/ticket/back/methods/sale/clone.js
index a5ccb6de4..661b656df 100644
--- a/modules/ticket/back/methods/sale/clone.js
+++ b/modules/ticket/back/methods/sale/clone.js
@@ -1,5 +1,5 @@
module.exports = Self => {
- Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, group, negative, options) => {
+ Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, negative, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
@@ -28,8 +28,6 @@ module.exports = Self => {
const mappedTickets = new Map();
- if (group) ticketsIds = [ticketsIds[0]];
-
for (let ticketId of ticketsIds) {
const newTicket = await createTicket(
ctx,
@@ -109,7 +107,10 @@ module.exports = Self => {
const newTicket = await models.Ticket.new(ctx, myOptions);
- if (negative) {
+ const ticketRefund = await models.TicketRefund.findOne({
+ where: {refundTicketFk: ticketId}
+ }, myOptions);
+ if (negative && (withWarehouse || !ticketRefund?.id)) {
await models.TicketRefund.create({
originalTicketFk: ticketId,
refundTicketFk: newTicket.id
diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js
index a7831e7e3..a19bb3df8 100644
--- a/modules/ticket/back/methods/sale/refund.js
+++ b/modules/ticket/back/methods/sale/refund.js
@@ -47,7 +47,6 @@ module.exports = Self => {
salesIds,
servicesIds,
withWarehouse,
- false,
true,
myOptions
);
diff --git a/modules/ticket/back/methods/sale/specs/clone.spec.js b/modules/ticket/back/methods/sale/specs/clone.spec.js
new file mode 100644
index 000000000..26506485a
--- /dev/null
+++ b/modules/ticket/back/methods/sale/specs/clone.spec.js
@@ -0,0 +1,70 @@
+const models = require('vn-loopback/server/server').models;
+const LoopBackContext = require('loopback-context');
+
+describe('Ticket cloning - clone function', () => {
+ let ctx;
+ let options;
+ let tx;
+
+ beforeEach(async() => {
+ ctx = {
+ req: {
+ accessToken: {userId: 9},
+ headers: {origin: 'http://localhost'}
+ },
+ args: {}
+ };
+
+ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
+ active: ctx.req
+ });
+
+ options = {transaction: tx};
+ tx = await models.Sale.beginTransaction({});
+ options.transaction = tx;
+ });
+
+ afterEach(async() => {
+ await tx.rollback();
+ });
+
+ it('should create new tickets with cloned sales with warehouse', async() => {
+ const salesIds = [1, 2, 3];
+ const servicesIds = [];
+ const withWarehouse = true;
+ const negative = false;
+ const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, withWarehouse, negative, options);
+
+ expect(newTickets).toBeDefined();
+ expect(newTickets.length).toBeGreaterThan(0);
+ });
+
+ it('should handle negative quantities correctly', async() => {
+ const negative = true;
+ const salesIds = [7, 8];
+ const servicesIds = [];
+ const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, negative, options);
+
+ for (const ticket of newTickets) {
+ const sales = await models.Sale.find({where: {ticketFk: ticket.id}}, options);
+ sales.forEach(sale => {
+ expect(sale.quantity).toBeLessThan(0);
+ });
+ }
+ });
+
+ it('should create new components and services for cloned tickets', async() => {
+ const servicesIds = [2];
+ const salesIds = [5];
+ const newTickets = await models.Sale.clone(ctx, salesIds, servicesIds, false, false, options);
+
+ for (const ticket of newTickets) {
+ const sale = await models.Sale.findOne({where: {ticketFk: ticket.id}}, options);
+ const components = await models.SaleComponent.find({where: {saleFk: sale.id}}, options);
+ const services = await models.TicketService.find({where: {ticketFk: ticket.id}}, options);
+
+ expect(components.length).toBeGreaterThan(0);
+ expect(services.length).toBeGreaterThan(0);
+ }
+ });
+});
diff --git a/modules/ticket/back/methods/ticket-tracking/setDelivered.js b/modules/ticket/back/methods/ticket-tracking/setDelivered.js
index d3cdb192f..eded63d11 100644
--- a/modules/ticket/back/methods/ticket-tracking/setDelivered.js
+++ b/modules/ticket/back/methods/ticket-tracking/setDelivered.js
@@ -49,7 +49,7 @@ module.exports = Self => {
for (const id of ticketIds) {
const promise = await models.Ticket.state(ctx, {
stateFk: state.id,
- workerFk: worker.id,
+ userFk: worker.id,
ticketFk: id
}, myOptions);
promises.push(promise);
diff --git a/modules/ticket/back/methods/ticket/saveSign.js b/modules/ticket/back/methods/ticket/saveSign.js
index 9888328e7..e062e6f84 100644
--- a/modules/ticket/back/methods/ticket/saveSign.js
+++ b/modules/ticket/back/methods/ticket/saveSign.js
@@ -130,7 +130,17 @@ module.exports = Self => {
await models.TicketDms.create({ticketFk: ticketId, dmsFk: dms[0].id}, myOptions);
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
await ticket.updateAttribute('isSigned', true, myOptions);
- await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [ticketId, 'DELIVERED'], myOptions);
+
+ const deliveryState = await models.State.find({
+ where: {
+ code: 'DELIVERED'
+ }
+ }, options);
+
+ await models.Ticket.state(ctx, {
+ ticketFk: ticketId,
+ stateFk: deliveryState.id
+ }, myOptions);
}
if (tx) await tx.commit();
diff --git a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js
index b2e70697a..43bc2c2d9 100644
--- a/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js
+++ b/modules/ticket/back/methods/ticket/specs/setDeleted.spec.js
@@ -3,7 +3,6 @@ const LoopBackContext = require('loopback-context');
describe('ticket setDeleted()', () => {
const userId = 1106;
- const employeeUser = 1110;
const activeCtx = {
accessToken: {userId: userId},
};
@@ -118,7 +117,7 @@ describe('ticket setDeleted()', () => {
return value;
};
- const ticketId = 12;
+ const ticketId = 7;
await models.Ticket.setDeleted(ctx, ticketId, options);
await tx.rollback();
diff --git a/modules/ticket/back/methods/ticket/specs/state.spec.js b/modules/ticket/back/methods/ticket/specs/state.spec.js
index f369932de..947e72b79 100644
--- a/modules/ticket/back/methods/ticket/specs/state.spec.js
+++ b/modules/ticket/back/methods/ticket/specs/state.spec.js
@@ -45,9 +45,8 @@ describe('ticket state()', () => {
const options = {transaction: tx};
activeCtx.accessToken.userId = salesPersonId;
- const params = {ticketFk: 2, stateFk: 3};
- await models.Ticket.state(ctx, params, options);
+ await models.Ticket.state(ctx, {ticketFk: 2, stateFk: 3}, options);
await tx.rollback();
} catch (e) {
@@ -67,9 +66,8 @@ describe('ticket state()', () => {
const options = {transaction: tx};
activeCtx.accessToken.userId = employeeId;
- const params = {ticketFk: 11, stateFk: 13};
- await models.Ticket.state(ctx, params, options);
+ await models.Ticket.state(ctx, {ticketFk: 11, stateFk: 13}, options);
await tx.rollback();
} catch (e) {
diff --git a/modules/ticket/back/methods/ticket/state.js b/modules/ticket/back/methods/ticket/state.js
index adac2e42f..fea9475f8 100644
--- a/modules/ticket/back/methods/ticket/state.js
+++ b/modules/ticket/back/methods/ticket/state.js
@@ -7,7 +7,6 @@ module.exports = Self => {
accepts: [
{
arg: 'data',
- description: 'Model instance data',
type: 'Object',
required: true,
http: {source: 'body'}
@@ -37,25 +36,21 @@ module.exports = Self => {
}
try {
- const userId = ctx.req.accessToken.userId;
-
if (!params.stateFk && !params.code)
throw new UserError('State cannot be blank');
- if (params.code) {
- const state = await models.State.findOne({
- where: {code: params.code},
- fields: ['id']
- }, myOptions);
-
- params.stateFk = state.id;
+ if (params.stateFk) {
+ const {code} = await models.State.findById(params.stateFk, {fields: ['code']}, myOptions);
+ params.code = code;
+ } else {
+ const {id} = await models.State.findOne({where: {code: params.code}}, myOptions);
+ params.stateFk = id;
}
if (!params.userFk) {
const worker = await models.Worker.findOne({
- where: {id: userId}
+ where: {id: ctx.req.accessToken.userId}
}, myOptions);
-
params.userFk = worker.id;
}
@@ -63,17 +58,21 @@ module.exports = Self => {
fields: ['stateFk']
}, myOptions);
- let oldStateAllowed;
- if (ticketState)
- oldStateAllowed = await models.State.isEditable(ctx, ticketState.stateFk, myOptions);
+ const oldStateAllowed = ticketState && await models.State.isEditable(ctx, ticketState.stateFk, myOptions);
const newStateAllowed = await models.State.isEditable(ctx, params.stateFk, myOptions);
- const isAllowed = (!ticketState || oldStateAllowed == true) && newStateAllowed == true;
-
- if (!isAllowed)
+ if ((ticketState && !oldStateAllowed) || !newStateAllowed)
throw new UserError(`You don't have enough privileges`, 'ACCESS_DENIED');
- const ticketTracking = await models.TicketTracking.create(params, myOptions);
+ await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [params.ticketFk, params.code], myOptions);
+
+ const ticketTracking = await models.TicketTracking.findOne({
+ where: {ticketFk: params.ticketFk},
+ order: 'id DESC',
+ limit: 1
+ }, myOptions);
+
+ await ticketTracking.updateAttribute('userFk', params.userFk, myOptions);
if (tx) await tx.commit();
diff --git a/modules/ticket/back/models/specs/sale.spec.js b/modules/ticket/back/models/specs/sale.spec.js
index 4af44c991..d078dc8e2 100644
--- a/modules/ticket/back/models/specs/sale.spec.js
+++ b/modules/ticket/back/models/specs/sale.spec.js
@@ -132,7 +132,7 @@ describe('sale model ', () => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue(getActiveCtx(9));
const tx = await models.Sale.beginTransaction({});
- const saleId = 13;
+ const saleId = 32;
const newQuantity = -10;
try {
diff --git a/modules/ticket/back/models/ticket-state.json b/modules/ticket/back/models/ticket-state.json
index 3dd322939..93d8509d5 100644
--- a/modules/ticket/back/models/ticket-state.json
+++ b/modules/ticket/back/models/ticket-state.json
@@ -36,7 +36,7 @@
"user": {
"type": "belongsTo",
"model": "VnUser",
- "foreignKey": "workerFk"
+ "foreignKey": "userFk"
}
}
}
diff --git a/modules/ticket/front/tracking/index/index.html b/modules/ticket/front/tracking/index/index.html
index 10ee6d848..539f5e538 100644
--- a/modules/ticket/front/tracking/index/index.html
+++ b/modules/ticket/front/tracking/index/index.html
@@ -22,9 +22,9 @@
{{::tracking.state.name}}
-
+
{{::tracking.user.name || 'System' | translate}}
diff --git a/modules/ticket/front/tracking/index/index.js b/modules/ticket/front/tracking/index/index.js
index ff3dc881b..c697412b5 100644
--- a/modules/ticket/front/tracking/index/index.js
+++ b/modules/ticket/front/tracking/index/index.js
@@ -9,7 +9,13 @@ class Controller extends Section {
{
relation: 'user',
scope: {
- fields: ['name']
+ fields: ['id', 'name'],
+ include: {
+ relation: 'worker',
+ scope: {
+ fields: ['id']
+ }
+ }
}
}, {
relation: 'state',
diff --git a/modules/worker/back/methods/worker-time-control/getClockIn.js b/modules/worker/back/methods/worker-time-control/getClockIn.js
index 470700643..458cadafb 100644
--- a/modules/worker/back/methods/worker-time-control/getClockIn.js
+++ b/modules/worker/back/methods/worker-time-control/getClockIn.js
@@ -5,7 +5,7 @@ module.exports = Self => {
accepts: [
{
arg: 'workerFk',
- type: 'int',
+ type: 'number',
required: true,
},
diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js
index 985d83e9f..b475bf26e 100644
--- a/modules/worker/back/models/worker.js
+++ b/modules/worker/back/models/worker.js
@@ -1,4 +1,5 @@
module.exports = Self => {
+ const validateTin = require('vn-loopback/util/validateTin');
require('../methods/worker/filter')(Self);
require('../methods/worker/mySubordinates')(Self);
require('../methods/worker/isSubordinate')(Self);
@@ -23,4 +24,21 @@ module.exports = Self => {
Self.validatesUniquenessOf('locker', {
message: 'This locker has already been assigned'
});
+
+ Self.validateAsync('fi', tinIsValid, {
+ message: 'Invalid TIN'
+ });
+
+ async function tinIsValid(err, done) {
+ const filter = {
+ fields: ['code'],
+ where: {id: this.countryFk}
+ };
+ const country = await Self.app.models.Country.findOne(filter);
+ const code = country ? country.code.toLowerCase() : null;
+
+ if (!this.fi || !validateTin(this.fi, code))
+ err();
+ done();
+ }
};
diff --git a/modules/zone/back/methods/zone/deleteZone.js b/modules/zone/back/methods/zone/deleteZone.js
index 38e724cd3..a75302703 100644
--- a/modules/zone/back/methods/zone/deleteZone.js
+++ b/modules/zone/back/methods/zone/deleteZone.js
@@ -61,7 +61,7 @@ module.exports = Self => {
for (ticket of ticketList) {
if (ticket.ticketState().alertLevel == 0) {
- promises.push(models.TicketTracking.create({
+ promises.push(models.Ticket.state(ctx, {
ticketFk: ticket.id,
stateFk: fixingState.id,
userFk: worker.id