From 4cc6485cbd0297e6d7551a9ba3ecfac170ce03fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s?= Date: Thu, 23 Feb 2023 09:25:23 +0100 Subject: [PATCH] refs #5000 Invoicing RC --- .vscode/settings.json | 3 +- db/changes/230601/00-invoiceOut.sql | 6 + .../230601/01-invoiceOut_getMaxIssued.sql | 34 +++ db/changes/230601/02-invoiceOut_new.sql | 258 ++++++++++++++++++ db/changes/230601/03-ticketPackaging_add.sql | 141 ++++++++++ db/dump/fixtures.sql | 13 +- loopback/locale/es.json | 13 +- .../methods/invoiceOut/clientsToInvoice.js | 181 ++++-------- .../back/methods/invoiceOut/getInvoiceDate.js | 39 +++ .../back/methods/invoiceOut/invoiceClient.js | 123 ++++----- modules/invoiceOut/back/models/invoice-out.js | 1 + .../front/global-invoicing/index.html | 118 +++++--- .../front/global-invoicing/index.js | 215 ++++++++------- .../front/global-invoicing/index.spec.js | 107 +++----- .../front/global-invoicing/locale/es.yml | 15 +- modules/ticket/front/sale-tracking/style.scss | 10 +- 16 files changed, 838 insertions(+), 439 deletions(-) create mode 100644 db/changes/230601/00-invoiceOut.sql create mode 100644 db/changes/230601/01-invoiceOut_getMaxIssued.sql create mode 100644 db/changes/230601/02-invoiceOut_new.sql create mode 100644 db/changes/230601/03-ticketPackaging_add.sql create mode 100644 modules/invoiceOut/back/methods/invoiceOut/getInvoiceDate.js diff --git a/.vscode/settings.json b/.vscode/settings.json index b5da1e8e69..82815d5880 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,5 +4,6 @@ "files.eol": "\n", "editor.codeActionsOnSave": { "source.fixAll.eslint": true - } + }, + "search.useIgnoreFiles": false } diff --git a/db/changes/230601/00-invoiceOut.sql b/db/changes/230601/00-invoiceOut.sql new file mode 100644 index 0000000000..4404c8f4a3 --- /dev/null +++ b/db/changes/230601/00-invoiceOut.sql @@ -0,0 +1,6 @@ +ALTER TABLE vn.invoiceOutSerial + ADD `type` ENUM('global', 'quick') DEFAULT NULL NULL; + + UPDATE vn.invoiceOutSerial + SET type = 'global' + WHERE code IN ('A','V'); \ No newline at end of file diff --git a/db/changes/230601/01-invoiceOut_getMaxIssued.sql b/db/changes/230601/01-invoiceOut_getMaxIssued.sql new file mode 100644 index 0000000000..e120b949d5 --- /dev/null +++ b/db/changes/230601/01-invoiceOut_getMaxIssued.sql @@ -0,0 +1,34 @@ +DROP FUNCTION IF EXISTS `vn`.`invoiceOut_getMaxIssued`; + +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` FUNCTION `vn`.`invoiceOut_getMaxIssued`( + vSerial VARCHAR(2), + vCompanyFk INT, + vYear INT +) RETURNS DATE + READS SQL DATA +BEGIN +/** + * Retorna la fecha a partir de la cual es válido emitir una factura + * + * @param vSerial Serie de facturación + * @param vCompanyFk Empresa factura emitida + * @param vYear Año contable + * @return vInvoiceOutIssued fecha factura válida + */ + DECLARE vInvoiceOutIssued DATE; + DECLARE vFirstDayOfYear DATE; + + SET vFirstDayOfYear := MAKEDATE(vYear, 1); + + SELECT IFNULL(MAX(io.issued), vFirstDayOfYear) INTO vInvoiceOutIssued + FROM invoiceOut io + WHERE io.serial = vSerial + AND io.companyFk = vCompanyFk + AND io.issued BETWEEN vFirstDayOfYear + AND util.lastDayOfYear(vFirstDayOfYear); + + RETURN vInvoiceOutIssued; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/230601/02-invoiceOut_new.sql b/db/changes/230601/02-invoiceOut_new.sql new file mode 100644 index 0000000000..dd7136ff70 --- /dev/null +++ b/db/changes/230601/02-invoiceOut_new.sql @@ -0,0 +1,258 @@ +DROP PROCEDURE IF EXISTS `vn`.`invoiceOut_new`; + +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`invoiceOut_new`( + vSerial VARCHAR(255), + vInvoiceDate DATE, + vTaxArea VARCHAR(25), + OUT vNewInvoiceId INT) +BEGIN +/** + * Creación de facturas emitidas. + * requiere previamente tabla ticketToInvoice(id). + * + * @param vSerial serie a la cual se hace la factura + * @param vInvoiceDate fecha de la factura + * @param vTaxArea tipo de iva en relacion a la empresa y al cliente + * @param vNewInvoiceId id de la factura que se acaba de generar + * @return vNewInvoiceId + */ + DECLARE vIsAnySaleToInvoice BOOL; + DECLARE vIsAnyServiceToInvoice BOOL; + DECLARE vNewRef VARCHAR(255); + DECLARE vWorker INT DEFAULT account.myUser_getId(); + DECLARE vCompanyFk INT; + DECLARE vInterCompanyFk INT; + DECLARE vClientFk INT; + DECLARE vCplusStandardInvoiceTypeFk INT DEFAULT 1; + DECLARE vCplusCorrectingInvoiceTypeFk INT DEFAULT 6; + DECLARE vCplusSimplifiedInvoiceTypeFk INT DEFAULT 2; + DECLARE vCorrectingSerial VARCHAR(1) DEFAULT 'R'; + DECLARE vSimplifiedSerial VARCHAR(1) DEFAULT 'S'; + DECLARE vNewInvoiceInFk INT; + DECLARE vIsInterCompany BOOL DEFAULT FALSE; + DECLARE vIsCEESerial BOOL DEFAULT FALSE; + DECLARE vIsCorrectInvoiceDate BOOL; + DECLARE vMaxShipped DATE; + + SET vInvoiceDate = IFNULL(vInvoiceDate, util.CURDATE()); + + SELECT t.clientFk, + t.companyFk, + MAX(DATE(t.shipped)), + DATE(vInvoiceDate) >= invoiceOut_getMaxIssued( + vSerial, + t.companyFk, + YEAR(vInvoiceDate)) + INTO vClientFk, + vCompanyFk, + vMaxShipped, + vIsCorrectInvoiceDate + FROM ticketToInvoice tt + JOIN ticket t ON t.id = tt.id; + + IF(vMaxShipped > vInvoiceDate) THEN + CALL util.throw("Invoice date can't be less than max date"); + END IF; + + IF NOT vIsCorrectInvoiceDate THEN + CALL util.throw('Exists an invoice with a previous date'); + END IF; + + -- Eliminem de ticketToInvoice els tickets que no han de ser facturats + DELETE ti.* + FROM ticketToInvoice ti + JOIN ticket t ON t.id = ti.id + JOIN sale s ON s.ticketFk = t.id + JOIN item i ON i.id = s.itemFk + JOIN supplier su ON su.id = t.companyFk + JOIN client c ON c.id = t.clientFk + LEFT JOIN itemTaxCountry itc ON itc.itemFk = i.id AND itc.countryFk = su.countryFk + WHERE YEAR(t.shipped) < 2001 + OR c.isTaxDataChecked = FALSE + OR t.isDeleted + OR c.hasToInvoice = FALSE + OR itc.id IS NULL; + + SELECT SUM(s.quantity * s.price * (100 - s.discount)/100) <> 0 + INTO vIsAnySaleToInvoice + FROM ticketToInvoice t + JOIN sale s ON s.ticketFk = t.id; + + SELECT COUNT(*) > 0 INTO vIsAnyServiceToInvoice + FROM ticketToInvoice t + JOIN ticketService ts ON ts.ticketFk = t.id; + + IF (vIsAnySaleToInvoice OR vIsAnyServiceToInvoice) + AND (vCorrectingSerial = vSerial OR NOT hasAnyNegativeBase()) + THEN + + -- el trigger añade el siguiente Id_Factura correspondiente a la vSerial + INSERT INTO invoiceOut( + ref, + serial, + issued, + clientFk, + dued, + companyFk, + cplusInvoiceType477Fk + ) + SELECT + 1, + vSerial, + vInvoiceDate, + vClientFk, + getDueDate(vInvoiceDate, dueDay), + vCompanyFk, + IF(vSerial = vCorrectingSerial, + vCplusCorrectingInvoiceTypeFk, + IF(vSerial = vSimplifiedSerial, + vCplusSimplifiedInvoiceTypeFk, + vCplusStandardInvoiceTypeFk)) + FROM client + WHERE id = vClientFk; + + SET vNewInvoiceId = LAST_INSERT_ID(); + + SELECT `ref` + INTO vNewRef + FROM invoiceOut + WHERE id = vNewInvoiceId; + + UPDATE ticket t + JOIN ticketToInvoice ti ON ti.id = t.id + SET t.refFk = vNewRef; + + DROP TEMPORARY TABLE IF EXISTS tmp.updateInter; + CREATE TEMPORARY TABLE tmp.updateInter ENGINE = MEMORY + SELECT s.id,ti.id ticket_id,vWorker Id_Trabajador + FROM ticketToInvoice ti + LEFT JOIN ticketState ts ON ti.id = ts.ticket + JOIN state s + WHERE IFNULL(ts.alertLevel,0) < 3 and s.`code` = getAlert3State(ti.id); + + INSERT INTO ticketTracking(stateFk,ticketFk,workerFk) + SELECT * FROM tmp.updateInter; + + INSERT INTO ticketLog (action, userFk, originFk, description) + SELECT 'UPDATE', account.myUser_getId(), ti.id, CONCAT('Crea factura ', vNewRef) + FROM ticketToInvoice ti; + + CALL invoiceExpenceMake(vNewInvoiceId); + CALL invoiceTaxMake(vNewInvoiceId,vTaxArea); + + UPDATE invoiceOut io + JOIN ( + SELECT SUM(amount) total + FROM invoiceOutExpence + WHERE invoiceOutFk = vNewInvoiceId + ) base + JOIN ( + SELECT SUM(vat) total + FROM invoiceOutTax + WHERE invoiceOutFk = vNewInvoiceId + ) vat + SET io.amount = base.total + vat.total + WHERE io.id = vNewInvoiceId; + + DROP TEMPORARY TABLE tmp.updateInter; + + SELECT COUNT(*), id + INTO vIsInterCompany, vInterCompanyFk + FROM company + WHERE clientFk = vClientFk; + + IF (vIsInterCompany) THEN + + INSERT INTO invoiceIn(supplierFk, supplierRef, issued, companyFk) + SELECT vCompanyFk, vNewRef, vInvoiceDate, vInterCompanyFk; + + SET vNewInvoiceInFk = LAST_INSERT_ID(); + + DROP TEMPORARY TABLE IF EXISTS tmp.ticket; + CREATE TEMPORARY TABLE tmp.ticket + (KEY (ticketFk)) + ENGINE = MEMORY + SELECT id ticketFk + FROM ticketToInvoice; + + CALL `ticket_getTax`('NATIONAL'); + + SET @vTaxableBaseServices := 0.00; + SET @vTaxCodeGeneral := NULL; + + INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInFk, + @vTaxableBaseServices, + sub.expenceFk, + sub.taxTypeSageFk, + sub.transactionTypeSageFk + FROM ( + SELECT @vTaxableBaseServices := SUM(tst.taxableBase) taxableBase, + i.expenceFk, + i.taxTypeSageFk, + i.transactionTypeSageFk, + @vTaxCodeGeneral := i.taxClassCodeFk + FROM tmp.ticketServiceTax tst + JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tst.code + WHERE i.isService + HAVING taxableBase + ) sub; + + INSERT INTO invoiceInTax(invoiceInFk, taxableBase, expenceFk, taxTypeSageFk, transactionTypeSageFk) + SELECT vNewInvoiceInFk, + SUM(tt.taxableBase) - IF(tt.code = @vTaxCodeGeneral, + @vTaxableBaseServices, 0) taxableBase, + i.expenceFk, + i.taxTypeSageFk , + i.transactionTypeSageFk + FROM tmp.ticketTax tt + JOIN invoiceOutTaxConfig i ON i.taxClassCodeFk = tt.code + WHERE !i.isService + GROUP BY tt.pgcFk + HAVING taxableBase + ORDER BY tt.priority; + + CALL invoiceInDueDay_calculate(vNewInvoiceInFk); + + SELECT COUNT(*) INTO vIsCEESerial + FROM invoiceOutSerial + WHERE code = vSerial; + + IF vIsCEESerial THEN + + INSERT INTO invoiceInIntrastat ( + invoiceInFk, + intrastatFk, + amount, + stems, + countryFk, + net) + SELECT + vNewInvoiceInFk, + i.intrastatFk, + SUM(CAST((s.quantity * s.price * (100 - s.discount) / 100 ) AS DECIMAL(10, 2))), + SUM(CAST(IFNULL(i.stems, 1) * s.quantity AS DECIMAL(10, 2))), + su.countryFk, + CAST(SUM(IFNULL(i.stems, 1) + * s.quantity + * IF(ic.grams, ic.grams, IFNULL(i.weightByPiece, 0)) / 1000) AS DECIMAL(10, 2)) + FROM sale s + JOIN ticket t ON s.ticketFk = t.id + JOIN supplier su ON su.id = t.companyFk + JOIN item i ON i.id = s.itemFk + LEFT JOIN itemCost ic ON ic.itemFk = i.id AND ic.warehouseFk = t.warehouseFk + WHERE t.refFk = vNewRef + GROUP BY i.intrastatFk; + + END IF; + DROP TEMPORARY TABLE tmp.ticket; + DROP TEMPORARY TABLE tmp.ticketAmount; + DROP TEMPORARY TABLE tmp.ticketTax; + DROP TEMPORARY TABLE tmp.ticketServiceTax; + END IF; + END IF; + DROP TEMPORARY TABLE `ticketToInvoice`; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/changes/230601/03-ticketPackaging_add.sql b/db/changes/230601/03-ticketPackaging_add.sql new file mode 100644 index 0000000000..f5e3d50ad3 --- /dev/null +++ b/db/changes/230601/03-ticketPackaging_add.sql @@ -0,0 +1,141 @@ +DROP PROCEDURE IF EXISTS `vn`.`ticketPackaging_add`; + +DELIMITER $$ +$$ +CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`ticketPackaging_add`( + vClientFk INT, + vDated DATE, + vCompanyFk INT, + vWithoutPeriodGrace BOOLEAN) +BEGIN +/** + * Genera nuevos tickets de embalajes para los clientes no han los han retornado + * y actualiza los valores para la tabla ticketPackaging + * + * @param vClientFk Cliente en caso de NULL todos los clientes + * @param vDated Fecha hasta la cual se revisan los embalajes + * @param vCompanyFk Empresa de la cual se comprobaran sus clientes + * @param vWithoutPeriodGrace si no se aplica el periodo de gracia de un mes + */ + DECLARE vNewTicket INT; + DECLARE vDateStart DATE; + DECLARE vDateEnd DATE; + DECLARE vGraceDate DATE DEFAULT vDated; + DECLARE vWarehouseInventory INT; + DECLARE vComponentCost INT; + DECLARE vDone INT DEFAULT FALSE; + DECLARE vClientId INT; + + DECLARE vCursor CURSOR FOR + SELECT DISTINCT clientFk + FROM ( + SELECT clientFk, SUM(quantity) totalQuantity + FROM tmp.packagingToInvoice + GROUP BY itemFk, clientFk + HAVING totalQuantity > 0)sub; + + DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE; + + DECLARE EXIT HANDLER FOR SQLEXCEPTION + BEGIN + ROLLBACK; + RESIGNAL; + END; + + SELECT id INTO vWarehouseInventory + FROM warehouse + WHERE `code`= 'inv'; + + SELECT id INTO vComponentCost + FROM component + WHERE `code`= 'purchaseValue'; + + SELECT packagingInvoicingDated INTO vDateStart + FROM ticketConfig; + + IF vWarehouseInventory IS NULL THEN + CALL util.throw('Warehouse inventory not seted'); + END IF; + + IF vComponentCost IS NULL THEN + CALL util.throw('Component cost not seted'); + END IF; + + SET vDateEnd = vDated + INTERVAL 1 DAY; + + IF NOT vWithoutPeriodGrace THEN + SET vGraceDate = vGraceDate -INTERVAL 1 MONTH; + END IF; + + DROP TEMPORARY TABLE IF EXISTS tmp.packagingToInvoice; + CREATE TEMPORARY TABLE tmp.packagingToInvoice + (INDEX (clientFk)) + ENGINE = MEMORY + SELECT p.itemFk, + tp.packagingFk, + tp.quantity, + tp.ticketFk, + p.price, + t.clientFk + FROM ticketPackaging tp + JOIN packaging p ON p.id = tp.packagingFk + JOIN ticket t ON t.id = tp.ticketFk + JOIN client c ON c.id = t.clientFk + WHERE c.isActive + AND (vClientFk IS NULL OR t.clientFk = vClientFk) + AND t.shipped BETWEEN vDateStart AND vDateEnd + AND (tp.quantity < 0 OR (tp.quantity > 0 AND t.shipped < vGraceDate)) + AND tp.quantity + AND p.itemFk; + + OPEN vCursor; + l: LOOP + + FETCH vCursor INTO vClientId; + + IF vDone THEN + LEAVE l; + END IF; + + START TRANSACTION; + + CALL ticket_add( + vClientId, + vDateEnd, + vWarehouseInventory, + vCompanyFk, + NULL, + NULL, + NULL, + vDateEnd, + account.myUser_getId(), + TRUE, + vNewTicket); + + INSERT INTO ticketPackaging(ticketFk, packagingFk, quantity, pvp) + SELECT vNewTicket, packagingFk, - SUM(quantity) totalQuantity, price + FROM tmp.packagingToInvoice + WHERE clientFk = vClientId + GROUP BY packagingFk + HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity < 0); + + INSERT INTO sale(ticketFk, itemFk, concept, quantity, price) + SELECT vNewTicket, pti.itemFk, i.name, SUM(pti.quantity) totalQuantity, pti.price + FROM tmp.packagingToInvoice pti + JOIN item i ON i.id = pti.itemFk + WHERE pti.clientFk = vClientId + GROUP BY pti.itemFk + HAVING IF(vWithoutPeriodGrace, totalQuantity <> 0, totalQuantity > 0); + + INSERT INTO saleComponent(saleFk, componentFk, value) + SELECT id, vComponentCost, price + FROM sale + WHERE ticketFk = vNewTicket; + + COMMIT; + END LOOP; + CLOSE vCursor; + + DROP TEMPORARY TABLE tmp.packagingToInvoice; +END$$ +DELIMITER ; \ No newline at end of file diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index d20b5274f3..ae98e5cdea 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -572,14 +572,13 @@ INSERT INTO `vn`.`taxArea` (`code`, `claveOperacionFactura`, `CodigoTransaccion` ('NATIONAL', 0, 1), ('WORLD', 2, 15); -INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`) +INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaFk`, `isCEE`, `type`) VALUES - ('A', 'Global nacional', 1, 'NATIONAL', 0), - ('T', 'Española rapida', 1, 'NATIONAL', 0), - ('V', 'Intracomunitaria global', 0, 'CEE', 1), - ('M', 'Múltiple nacional', 1, 'NATIONAL', 0), - ('E', 'Exportación rápida', 0, 'WORLD', 0); -; + ('A', 'Global nacional', 1, 'NATIONAL', 0, 'global'), + ('T', 'Española rapida', 1, 'NATIONAL', 0, 'quick'), + ('V', 'Intracomunitaria global', 0, 'CEE', 1, 'global'), + ('M', 'Múltiple nacional', 1, 'NATIONAL', 0, 'quick'), + ('E', 'Exportación rápida', 0, 'WORLD', 0, 'quick'); INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`) VALUES diff --git a/loopback/locale/es.json b/loopback/locale/es.json index 8dee811fb9..4d7159fe88 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -260,10 +260,11 @@ "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9", "Failed to upload file": "Error al subir archivo", "The DOCUWARE PDF document does not exists": "El documento PDF Docuware no existe", - "It is not possible to modify tracked sales": "No es posible modificar líneas de pedido que se hayan empezado a preparar", - "It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo", - "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", + "It is not possible to modify tracked sales": "No es posible modificar líneas de pedido que se hayan empezado a preparar", + "It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo", + "It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas", "A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.", - "There is no assigned email for this client": "No hay correo asignado para este cliente" -} - + "There is no assigned email for this client": "No hay correo asignado para este cliente", + "Exists an invoice with a previous date": "Existe una factura con fecha anterior", + "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite" +} \ No newline at end of file diff --git a/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js b/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js index 7eeb82e353..f18b0c6828 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js +++ b/modules/invoiceOut/back/methods/invoiceOut/clientsToInvoice.js @@ -4,47 +4,37 @@ module.exports = Self => { accessType: 'WRITE', accepts: [ { + arg: 'clientId', + type: 'number', + description: 'The client id' + }, { arg: 'invoiceDate', type: 'date', - description: 'The invoice date' - }, - { + description: 'The invoice date', + required: true + }, { arg: 'maxShipped', type: 'date', - description: 'The maximum shipped date' - }, - { - arg: 'fromClientId', - type: 'number', - description: 'The minimum client id' - }, - { - arg: 'toClientId', - type: 'number', - description: 'The maximum client id' - }, - { + description: 'The maximum shipped date', + required: true + }, { arg: 'companyFk', type: 'number', - description: 'The company id to invoice' - } + description: 'The company id to invoice', + required: true + }, ], - returns: [{ - arg: 'clientsAndAddresses', - type: ['object'] + returns: { + type: 'Object', + root: true }, - { - arg: 'invoice', - type: 'object' - }], http: { path: '/clientsToInvoice', verb: 'POST' } }); - Self.clientsToInvoice = async(ctx, options) => { - const args = ctx.args; + Self.clientsToInvoice = async(ctx, clientId, invoiceDate, maxShipped, companyFk, options) => { let tx; const myOptions = {}; @@ -56,121 +46,52 @@ module.exports = Self => { myOptions.transaction = tx; } - let query; try { - query = ` - SELECT MAX(issued) issued - FROM vn.invoiceOut io - JOIN vn.time t ON t.dated = io.issued - WHERE io.serial = 'A' - AND t.year = YEAR(?) - AND io.companyFk = ?`; - const [maxIssued] = await Self.rawSql(query, [ - args.invoiceDate, - args.companyFk - ], myOptions); - - const maxSerialDate = maxIssued.issued || args.invoiceDate; - if (args.invoiceDate < maxSerialDate) - args.invoiceDate = maxSerialDate; - - if (args.invoiceDate < args.maxShipped) - args.maxShipped = args.invoiceDate; - - const minShipped = Date.vnNew(); - minShipped.setFullYear(minShipped.getFullYear() - 1); - minShipped.setMonth(1); - minShipped.setDate(1); - minShipped.setHours(0, 0, 0, 0); - // Packaging liquidation const vIsAllInvoiceable = false; - const clientsWithPackaging = await getClientsWithPackaging(ctx, myOptions); - for (let client of clientsWithPackaging) { - await Self.rawSql('CALL packageInvoicing(?, ?, ?, ?, @newTicket)', [ - client.id, - args.invoiceDate, - args.companyFk, - vIsAllInvoiceable - ], myOptions); - } + await Self.rawSql('CALL ticketPackaging_add(?, ?, ?, ?)', [ + clientId, + invoiceDate, + companyFk, + vIsAllInvoiceable + ], myOptions); - const invoiceableClients = await getInvoiceableClients(ctx, minShipped, myOptions); + const minShipped = Date.vnNew(); + minShipped.setFullYear(maxShipped.getFullYear() - 1); - if (!invoiceableClients) return; + const query = ` + SELECT c.id clientId, + c.name clientName, + a.id, + a.nickname + FROM ticket t + JOIN address a ON a.id = t.addressFk + JOIN client c ON c.id = t.clientFk + WHERE t.refFk IS NULL + AND t.shipped BETWEEN ? AND util.dayEnd(?) + AND (t.clientFk = ? OR ? IS NULL ) + AND t.companyFk = ? + AND c.hasToInvoice + AND c.isTaxDataChecked + AND c.isActive + AND NOT t.isDeleted + GROUP BY c.id, IF(c.hasToInvoiceByAddress, a.id, TRUE) + HAVING SUM(t.totalWithVat) > 0;`; - const clientsAndAddresses = invoiceableClients.map(invoiceableClient => { - return { - clientId: invoiceableClient.id, - addressId: invoiceableClient.addressFk - }; - } - ); + const addresses = await Self.rawSql(query, [ + minShipped, + maxShipped, + clientId, + clientId, + companyFk + ], myOptions); if (tx) await tx.commit(); - return [ - clientsAndAddresses, - { - invoiceDate: args.invoiceDate, - maxShipped: args.maxShipped, - fromClientId: args.fromClientId, - toClientId: args.toClientId, - companyFk: args.companyFk, - minShipped: minShipped - } - ]; + return addresses; } catch (e) { if (tx) await tx.rollback(); throw e; } }; - - async function getClientsWithPackaging(ctx, options) { - const models = Self.app.models; - const args = ctx.args; - const query = `SELECT DISTINCT clientFk AS id - FROM ticket t - JOIN ticketPackaging tp ON t.id = tp.ticketFk - JOIN client c ON c.id = t.clientFk - WHERE t.shipped BETWEEN '2017-11-21' AND ? - AND t.clientFk >= ? - AND (t.clientFk <= ? OR ? IS NULL) - AND c.isActive`; - return models.InvoiceOut.rawSql(query, [ - args.maxShipped, - args.fromClientId, - args.toClientId, - args.toClientId - ], options); - } - - async function getInvoiceableClients(ctx, minShipped, options) { - const models = Self.app.models; - const args = ctx.args; - minShipped.setFullYear(minShipped.getFullYear() - 1); - - const query = `SELECT c.id, - c.hasToInvoiceByAddress, - a.id addressFk, - sum(t.totalWithVat) totalAmount - FROM ticket t - JOIN address a ON a.id = t.addressFk - JOIN client c ON c.id = t.clientFk - WHERE t.refFk IS NULL - AND t.shipped BETWEEN ? AND util.dayEnd(?) - AND t.companyFk = ? - AND c.hasToInvoice - AND c.isTaxDataChecked - AND c.isActive - AND NOT t.isDeleted - GROUP BY c.id, IF(c.hasToInvoiceByAddress, a.id, TRUE) - HAVING totalAmount > 0;`; - - return models.InvoiceOut.rawSql(query, [ - minShipped, - args.maxShipped, - args.companyFk - ], options); - } }; diff --git a/modules/invoiceOut/back/methods/invoiceOut/getInvoiceDate.js b/modules/invoiceOut/back/methods/invoiceOut/getInvoiceDate.js new file mode 100644 index 0000000000..e2ab94f01a --- /dev/null +++ b/modules/invoiceOut/back/methods/invoiceOut/getInvoiceDate.js @@ -0,0 +1,39 @@ +module.exports = Self => { + Self.remoteMethod('getInvoiceDate', { + description: 'Returns default Invoice Date', + accessType: 'READ', + accepts: [ + { + arg: 'year', + type: 'number', + required: true + }, { + arg: 'companyFk', + type: 'number', + required: true + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/getInvoiceDate`, + verb: 'GET' + } + }); + + Self.getInvoiceDate = async(year, companyFk) => { + const models = Self.app.models; + const [invoiceDate] = await models.InvoiceOut.rawSql(` + SELECT MAX(io.issued) issued + FROM invoiceOut io + JOIN invoiceOutSerial ios ON ios.code = io.serial + WHERE ios.type = 'global' + AND io.issued BETWEEN MAKEDATE(?, 1) AND + util.lastDayOfYear(MAKEDATE(?, 1)) + AND io.companyFk = ?`, + [year, year, companyFk]); + return invoiceDate; + }; +}; diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index ce700796fb..c8f8a6778d 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -4,48 +4,39 @@ module.exports = Self => { Self.remoteMethodCtx('invoiceClient', { description: 'Make a invoice of a client', accessType: 'WRITE', - accepts: [{ - arg: 'clientId', - type: 'number', - description: 'The client id to invoice', - required: true - }, - { - arg: 'addressId', - type: 'number', - description: 'The address id to invoice', - required: true - }, - { - arg: 'invoiceDate', - type: 'date', - description: 'The invoice date', - required: true - }, - { - arg: 'maxShipped', - type: 'date', - description: 'The maximum shipped date', - required: true - }, - { - arg: 'companyFk', - type: 'number', - description: 'The company id to invoice', - required: true - }, - { - arg: 'minShipped', - type: 'date', - description: 'The minium shupped date', - required: true - }, - { - arg: 'printerFk', - type: 'number', - description: 'The printer to print', - required: true - }], + accepts: [ + { + arg: 'clientId', + type: 'number', + description: 'The client id to invoice', + required: true + }, { + arg: 'addressId', + type: 'number', + description: 'The address id to invoice', + required: true + }, { + arg: 'invoiceDate', + type: 'date', + description: 'The invoice date', + required: true + }, { + arg: 'maxShipped', + type: 'date', + description: 'The maximum shipped date', + required: true + }, { + arg: 'companyFk', + type: 'number', + description: 'The company id to invoice', + required: true + }, { + arg: 'printerFk', + type: 'number', + description: 'The printer to print', + required: true + } + ], returns: { type: 'object', root: true @@ -70,6 +61,9 @@ module.exports = Self => { myOptions.transaction = tx; } + const minShipped = Date.vnNew(); + minShipped.setFullYear(args.maxShipped.getFullYear() - 1); + let invoiceId; let invoiceOut; try { @@ -79,7 +73,7 @@ module.exports = Self => { if (client.hasToInvoiceByAddress) { await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [ - args.minShipped, + minShipped, args.maxShipped, args.addressId, args.companyFk @@ -133,26 +127,26 @@ module.exports = Self => { throw e; } - if (invoiceId && !invoiceOut.client().isToBeMailed) { - const query = `CALL vn.report_print( - 'invoice', - ?, - account.myUser_getId(), - JSON_OBJECT('refFk', ?), - 'normal' - );`; - await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]); + if (invoiceId) { + if (!invoiceOut.client().isToBeMailed) { + const query = ` + CALL vn.report_print( + 'invoice', + ?, + account.myUser_getId(), + JSON_OBJECT('refFk', ?), + 'normal' + );`; + await models.InvoiceOut.rawSql(query, [args.printerFk, invoiceOut.ref]); + } else { + ctx.args = { + reference: invoiceOut.ref, + recipientId: invoiceOut.clientFk, + recipient: invoiceOut.client().email + }; + await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); + } } - - if (invoiceId && invoiceOut.client().isToBeMailed) { - ctx.args = { - reference: invoiceOut.ref, - recipientId: invoiceOut.clientFk, - recipient: invoiceOut.client().email - }; - await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref); - } - return invoiceId; }; @@ -160,13 +154,12 @@ module.exports = Self => { const models = Self.app.models; const query = 'SELECT hasAnyNegativeBase() AS base'; const [result] = await models.InvoiceOut.rawSql(query, null, options); - return result && result.base; } async function getIsSpanishCompany(companyId, options) { const models = Self.app.models; - const query = `SELECT COUNT(*) AS total + const query = `SELECT COUNT(*) isSpanishCompany FROM supplier s JOIN country c ON c.id = s.countryFk AND c.code = 'ES' @@ -175,6 +168,6 @@ module.exports = Self => { companyId ], options); - return supplierCompany && supplierCompany.total; + return supplierCompany && supplierCompany.isSpanishCompany; } }; diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js index eb8df246ca..6205abe7b8 100644 --- a/modules/invoiceOut/back/models/invoice-out.js +++ b/modules/invoiceOut/back/models/invoice-out.js @@ -16,4 +16,5 @@ module.exports = Self => { require('../methods/invoiceOut/invoiceCsv')(Self); require('../methods/invoiceOut/invoiceCsvEmail')(Self); require('../methods/invoiceOut/invoiceOutPdf')(Self); + require('../methods/invoiceOut/getInvoiceDate')(Self); }; diff --git a/modules/invoiceOut/front/global-invoicing/index.html b/modules/invoiceOut/front/global-invoicing/index.html index 53df446700..ebe46b84a8 100644 --- a/modules/invoiceOut/front/global-invoicing/index.html +++ b/modules/invoiceOut/front/global-invoicing/index.html @@ -1,59 +1,66 @@ - -
{{'Calculating packages to invoice...'}}
- -
Invoicing
-
Ended process
-
- {{'Current client id' | translate}}: {{$ctrl.currentClientId}} -
-
- {{($ctrl.percentage / 100) | percentage: 0}} ({{$ctrl.currentClient}} {{'of' | translate}} {{$ctrl.clients.length}}) -
-
+ + + +
+
+ + Build packaging tickets + + + {{'Invoicing client' | translate}} {{$ctrl.currentAddress.clientId}} + + + Stopping process + + + Ended process + +
+
+ {{$ctrl.percentage | percentage: 0}} ({{$ctrl.addressNumber}} {{'of' | translate}} {{$ctrl.nAddresses}}) +
+
- - + + - Client id - Nickname - Address id - Street - Error + Id + Client + Address id + Street + Error - + - {{::client.id}} + {{::error.address.clientId}} - {{::client.address.nickname}} + {{::error.address.clientName}} - {{::client.address.id}} + {{::error.address.id}} - {{::client.address.street}} + {{::error.address.nickname}} - - - - {{::client.error}} + + {{::error.message}} - -
+ + + + + + + + {{::id}} - {{::name}} + + ng-model="$ctrl.invoiceDate"> + ng-model="$ctrl.maxShipped"> + ng-model="$ctrl.companyFk"> + ng-model="$ctrl.printerFk"> - + + + +
diff --git a/modules/invoiceOut/front/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js index 4e90709461..e910368fab 100644 --- a/modules/invoiceOut/front/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -4,128 +4,131 @@ import UserError from 'core/lib/user-error'; import './style.scss'; class Controller extends Section { - constructor($element, $, $transclude) { - super($element, $, $transclude); - this.invoice = { - maxShipped: Date.vnNew(), - companyFk: this.vnConfig.companyFk - }; - } - $onInit() { - this.getMinClientId(); - this.getMaxClientId(); - } + const date = Date.vnNew(); + Object.assign(this, { + maxShipped: new Date(date.getFullYear(), date.getMonth(), 0), + clientsToInvoice: 'all', + }); - getMinClientId() { - this.getClientId('min') - .then(res => this.invoice.fromClientId = res.data.id); - } - - getMaxClientId() { - this.getClientId('max') - .then(res => this.invoice.toClientId = res.data.id); - } - - getClientId(func) { - const order = func == 'min' ? 'ASC' : 'DESC'; - const params = { - filter: { - order: 'id ' + order, - limit: 1 - } - }; - return this.$http.get('Clients/findOne', {params}); - } - - getPercentage() { - this.percentage = ((this.currentClient - 1) * 100) / this.clients.length; - } - - restartValues() { - this.$.invoiceButton.disabled = false; - this.packageInvoicing = false; - } - - invoiceOut(invoice, clientsAndAddresses) { - const [clientAndAddress] = clientsAndAddresses; - if (!clientAndAddress) { - this.percentage = 100; - return; - } - - this.currentClientId = clientAndAddress.clientId; - this.currentClient = ++this.currentClient; - this.getPercentage(); - - const params = { - clientId: clientAndAddress.clientId, - addressId: clientAndAddress.addressId, - invoiceDate: invoice.invoiceDate, - maxShipped: invoice.maxShipped, - companyFk: invoice.companyFk, - minShipped: invoice.minShipped, - printerFk: this.invoice.printerFk, - - }; - this.$http.get(`Addresses/${clientAndAddress.addressId}`) + this.$http.get('UserConfigs/getUserConfig') .then(res => { - this.address = res.data; - return this.$http.post(`InvoiceOuts/invoiceClient`, params) - .catch(res => { - this.$.data.unshift({ - id: clientAndAddress.clientId, - address: this.address, - status: 'error', - error: res.data.error.message - }); - }).finally(() => { - clientsAndAddresses.shift(); - return this.invoiceOut(invoice, clientsAndAddresses); - }); + this.companyFk = res.data.companyFk; + const params = { + year: this.maxShipped.getFullYear(), + companyFk: this.companyFk + }; + return this.$http.get('InvoiceOuts/getInvoiceDate', {params}); + }) + .then(res => { + this.minInvoicingDate = new Date(res.data.issued); + this.invoiceDate = this.minInvoicingDate; }); } + stopInvoicing() { + this.status = 'stopping'; + } + makeInvoice() { + this.invoicing = true; + this.status = 'packageInvoicing'; + this.errors = []; + this.addresses = null; + try { - if (!this.invoice.invoiceDate || !this.invoice.maxShipped) - throw new Error('Invoice date and the max date should be filled'); + if (this.clientsToInvoice == 'one' && !this.clientId) + throw new UserError('Choose a valid client'); + if (!this.invoiceDate || !this.maxShipped) + throw new UserError('Invoice date and the max date should be filled'); + if (this.invoiceDate < this.maxShipped) + throw new UserError('Invoice date can\'t be less than max date'); + if (this.invoiceDate.getTime() < this.minInvoicingDate.getTime()) + throw new UserError('Exists an invoice with a previous date'); + if (!this.companyFk) + throw new UserError('Choose a valid company'); + if (!this.printerFk) + throw new UserError('Choose a valid printer'); - if (!this.invoice.companyFk) - throw new Error('Choose a valid company'); + if (this.clientsToInvoice == 'all') + this.clientId = undefined; - if (!this.invoice.printerFk) - throw new Error('Choose a valid printer'); - - this.$.invoiceButton.disabled = true; - this.$.data = []; - this.packageInvoicing = true; - - this.$http.post(`InvoiceOuts/clientsToInvoice`, this.invoice) + const params = { + invoiceDate: this.invoiceDate, + maxShipped: this.maxShipped, + clientId: this.clientId, + companyFk: this.companyFk + }; + this.$http.post(`InvoiceOuts/clientsToInvoice`, params) .then(res => { - this.packageInvoicing = false; - const invoice = res.data.invoice; - this.currentClient = 0; + console.log(res.data); + this.addresses = res.data; + console.log(this.address); + if (!this.addresses.length) + throw new UserError(`There aren't tickets to invoice`); - const clientsAndAddresses = res.data.clientsAndAddresses; - if (!clientsAndAddresses.length) throw new UserError(`There aren't clients to invoice`); - - this.clients = []; - for (const clientAndAddress of clientsAndAddresses) - this.clients.push(clientAndAddress.clientId); - - return this.invoiceOut(invoice, clientsAndAddresses); + this.addressIndex = 0; + return this.invoiceOut(); }) - .finally(() => this.restartValues()); - } catch (e) { - this.vnApp.showError(this.$t(e.message)); - this.restartValues(); - return false; + .catch(err => this.handleError(err)); + } catch (err) { + this.handleError(err); } } -} -Controller.$inject = ['$element', '$scope', '$transclude']; + handleError(err) { + this.invoicing = false; + this.status = null; + throw err; + } + + invoiceOut() { + if (this.addressIndex == this.addresses.length || this.status == 'stopping') { + this.invoicing = false; + this.status = 'done'; + return; + } + + this.status = 'invoicing'; + const address = this.addresses[this.addressIndex]; + this.currentAddress = address; + + const params = { + clientId: address.clientId, + addressId: address.id, + invoiceDate: this.invoiceDate, + maxShipped: this.maxShipped, + companyFk: this.companyFk, + printerFk: this.printerFk, + }; + + this.$http.post(`InvoiceOuts/invoiceClient`, params) + .catch(res => { + this.errors.unshift({ + address, + message: res.data.error.message + }); + }) + .finally(() => { + this.addressIndex++; + this.invoiceOut(); + }); + } + + get nAddresses() { + if (!this.addresses) return 0; + return this.addresses.length; + } + + get addressNumber() { + return Math.min(this.addressIndex + 1, this.nAddresses); + } + + get percentage() { + const len = this.nAddresses; + return Math.min(this.addressIndex, len) / len; + } +} ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', { template: require('./index.html'), diff --git a/modules/invoiceOut/front/global-invoicing/index.spec.js b/modules/invoiceOut/front/global-invoicing/index.spec.js index 96cd38d513..fcffeb6eea 100644 --- a/modules/invoiceOut/front/global-invoicing/index.spec.js +++ b/modules/invoiceOut/front/global-invoicing/index.spec.js @@ -1,5 +1,5 @@ import './index'; - +const UserError = require('vn-loopback/util/user-error'); describe('InvoiceOut', () => { describe('Component vnInvoiceOutGlobalInvoicing', () => { let controller; @@ -22,99 +22,60 @@ describe('InvoiceOut', () => { controller.$.invoiceButton = {disabled: false}; })); - describe('getMinClientId()', () => { - it('should set the invoice fromClientId property', () => { - const filter = { - order: 'id ASC', - limit: 1 - }; - - const serializedParams = $httpParamSerializer({filter}); - $httpBackend.expectGET(`Clients/findOne?${serializedParams}`).respond(200, {id: 1101}); - - controller.getMinClientId(); - $httpBackend.flush(); - - expect(controller.invoice.fromClientId).toEqual(1101); - }); - }); - - describe('getMaxClientId()', () => { - it('should set the invoice toClientId property', () => { - const filter = { - order: 'id DESC', - limit: 1 - }; - - const serializedParams = $httpParamSerializer({filter}); - $httpBackend.expectGET(`Clients/findOne?${serializedParams}`).respond(200, {id: 1112}); - - controller.getMaxClientId(); - $httpBackend.flush(); - - expect(controller.invoice.toClientId).toEqual(1112); - }); - }); - describe('makeInvoice()', () => { it('should throw an error when invoiceDate or maxShipped properties are not filled in', () => { jest.spyOn(controller.vnApp, 'showError'); + controller.clientsToInvoice = 'all'; - controller.invoice = { - fromClientId: 1101, - toClientId: 1101 - }; - - controller.makeInvoice(); + let error; + try { + controller.makeInvoice(); + } catch (e) { + error = e.message; + } const expectedError = 'Invoice date and the max date should be filled'; - expect(controller.vnApp.showError).toHaveBeenCalledWith(expectedError); + expect(error).toBe(expectedError); }); - it('should throw an error when fromClientId or toClientId properties are not filled in', () => { + it('should throw an error when select one client and clientId is not filled in', () => { jest.spyOn(controller.vnApp, 'showError'); + controller.clientsToInvoice = 'one'; - controller.invoice = { - invoiceDate: Date.vnNew(), - maxShipped: Date.vnNew() - }; + let error; + try { + controller.makeInvoice(); + } catch (e) { + error = e.message; + } - controller.makeInvoice(); + const expectedError = 'Choose a valid client'; - expect(controller.vnApp.showError).toHaveBeenCalledWith(`Choose a valid clients range`); + expect(error).toBe(expectedError); }); it('should make an http POST query and then call to the showSuccess() method', () => { jest.spyOn(controller.vnApp, 'showSuccess'); - - const minShipped = Date.vnNew(); - minShipped.setFullYear(minShipped.getFullYear() - 1); - minShipped.setMonth(1); - minShipped.setDate(1); - minShipped.setHours(0, 0, 0, 0); - controller.invoice = { - invoiceDate: Date.vnNew(), - maxShipped: Date.vnNew(), - fromClientId: 1101, - toClientId: 1101, - companyFk: 442, - minShipped: minShipped - }; - const response = { - clientsAndAddresses: [{clientId: 1101, addressId: 121}], - invoice: controller.invoice - }; - - const address = {id: 121}; - + const date = Date.vnNew(); + date.setDate(date.getDate() + 1); + controller.invoiceDate = date; + controller.maxShipped = date; + controller.minInvoicingDate = Date.vnNew(); + controller.clientsToInvoice = 'one'; + controller.clientId = 1101; + controller.companyFk = 442; + controller.printerFk = 1; + const response = [{ + clientId: 1101, + id: 121 + }]; $httpBackend.expect('POST', `InvoiceOuts/clientsToInvoice`).respond(response); - $httpBackend.expect('GET', `Addresses/${response.clientsAndAddresses[0].addressId}`).respond(address); - $httpBackend.expect('POST', `InvoiceOuts/invoiceClient`).respond({id: 1}); + $httpBackend.expect('POST', `InvoiceOuts/invoiceClient`).respond(1); controller.makeInvoice(); $httpBackend.flush(); - expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + expect(controller.status).toEqual('done'); }); }); }); diff --git a/modules/invoiceOut/front/global-invoicing/locale/es.yml b/modules/invoiceOut/front/global-invoicing/locale/es.yml index 0c4cf14fc5..242b5a93ff 100644 --- a/modules/invoiceOut/front/global-invoicing/locale/es.yml +++ b/modules/invoiceOut/front/global-invoicing/locale/es.yml @@ -1,21 +1,20 @@ -There aren't clients to invoice: No existen clientes para facturar +There aren't tickets to invoice: No existen tickets para facturar Max date: Fecha límite Invoice date: Fecha de factura +Invoice date can't be less than max date: La fecha de factura no puede ser inferior a la fecha límite Invoice date and the max date should be filled: La fecha de factura y la fecha límite deben rellenarse -Choose a valid clients range: Selecciona un rango válido de clientes Choose a valid company: Selecciona un empresa válida Choose a valid printer: Selecciona una impresora válida -Clients range: Rango de clientes All clients: Todos los clientes -Calculating packages to invoice...: Calculando paquetes a facturar... -Clean: Limpiar -From client: Desde cliente -To client: Hasta cliente +Build packaging tickets: Generando tickets de embalajes Address id: Id dirección Printer: Impresora of: de Client: Cliente Current client id: Id cliente actual -Invoicing: Facturando +Invoicing client: Facturando cliente Ended process: Proceso finalizado Invoice out: Facturar +One client: Un solo cliente +Choose a valid client: Selecciona un cliente válido +Stop: Parar \ No newline at end of file diff --git a/modules/ticket/front/sale-tracking/style.scss b/modules/ticket/front/sale-tracking/style.scss index 6d8b3db69f..4b10ec3c28 100644 --- a/modules/ticket/front/sale-tracking/style.scss +++ b/modules/ticket/front/sale-tracking/style.scss @@ -1,7 +1,9 @@ @import "variables"; -.chip { - display: inline-block; - min-width: 15px; - min-height: 25px; +vn-sale-tracking { + .chip { + display: inline-block; + min-width: 15px; + min-height: 25px; + } }