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