Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2879-import_buys
This commit is contained in:
commit
886d38ad8a
|
@ -6,6 +6,9 @@
|
||||||
"table": "sage.TiposRetencion"
|
"table": "sage.TiposRetencion"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"log": {
|
||||||
|
"showField": "withholding"
|
||||||
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"type": "Number",
|
"type": "Number",
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
ALTER TABLE `vn`.`sample` ADD COLUMN
|
||||||
|
(`datepickerEnabled` TINYINT(1) NOT NULL DEFAULT 0);
|
||||||
|
|
||||||
|
ALTER TABLE `vn`.`sample` MODIFY code VARCHAR(25) charset utf8 NOT NULL;
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`sample` (code, description, isVisible, hasCompany, hasPreview, datepickerEnabled)
|
||||||
|
VALUES ('client-debt-statement', 'Extracto del cliente', 1, 0, 1, 1);
|
|
@ -0,0 +1,109 @@
|
||||||
|
drop procedure `vn`.`ticket_close`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
create
|
||||||
|
definer = root@`%` procedure `vn`.`ticket_close`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Realiza el cierre de todos los
|
||||||
|
* tickets de la tabla tmp.ticket_close.
|
||||||
|
*
|
||||||
|
* @table tmp.ticket_close(ticketFk) Identificadores de los tickets a cerrar
|
||||||
|
*/
|
||||||
|
DECLARE vDone BOOL;
|
||||||
|
DECLARE vClientFk INT;
|
||||||
|
DECLARE vCurTicketFk INT;
|
||||||
|
DECLARE vIsTaxDataChecked BOOL;
|
||||||
|
DECLARE vCompanyFk INT;
|
||||||
|
DECLARE vShipped DATE;
|
||||||
|
DECLARE vNewInvoiceId INT;
|
||||||
|
DECLARE vHasDailyInvoice BOOL;
|
||||||
|
DECLARE vWithPackage BOOL;
|
||||||
|
DECLARE vHasToInvoice BOOL;
|
||||||
|
|
||||||
|
DECLARE cur CURSOR FOR
|
||||||
|
SELECT ticketFk FROM tmp.ticket_close;
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||||
|
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
|
||||||
|
RESIGNAL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
OPEN cur;
|
||||||
|
|
||||||
|
proc: LOOP
|
||||||
|
SET vDone = FALSE;
|
||||||
|
|
||||||
|
FETCH cur INTO vCurTicketFk;
|
||||||
|
|
||||||
|
IF vDone THEN
|
||||||
|
LEAVE proc;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- Fetch ticket data
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.isTaxDataChecked,
|
||||||
|
t.companyFk,
|
||||||
|
t.shipped,
|
||||||
|
co.hasDailyInvoice,
|
||||||
|
w.isManaged,
|
||||||
|
c.hasToInvoice
|
||||||
|
INTO vClientFk,
|
||||||
|
vIsTaxDataChecked,
|
||||||
|
vCompanyFk,
|
||||||
|
vShipped,
|
||||||
|
vHasDailyInvoice,
|
||||||
|
vWithPackage,
|
||||||
|
vHasToInvoice
|
||||||
|
FROM ticket t
|
||||||
|
JOIN `client` c ON c.id = t.clientFk
|
||||||
|
JOIN province p ON p.id = c.provinceFk
|
||||||
|
JOIN country co ON co.id = p.countryFk
|
||||||
|
JOIN warehouse w ON w.id = t.warehouseFk
|
||||||
|
WHERE t.id = vCurTicketFk;
|
||||||
|
|
||||||
|
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
|
||||||
|
(SELECT vCurTicketFk, p.id, COUNT(*)
|
||||||
|
FROM expedition e
|
||||||
|
JOIN packaging p ON p.itemFk = e.itemFk
|
||||||
|
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
|
||||||
|
AND vWithPackage
|
||||||
|
GROUP BY p.itemFk);
|
||||||
|
|
||||||
|
-- No retornables o no catalogados
|
||||||
|
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
|
||||||
|
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
|
||||||
|
FROM expedition e
|
||||||
|
JOIN item i ON i.id = e.itemFk
|
||||||
|
LEFT JOIN packaging p ON p.itemFk = i.id
|
||||||
|
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
|
||||||
|
AND getSpecialPrice(e.itemFk, vClientFk) > 0
|
||||||
|
GROUP BY e.itemFk);
|
||||||
|
|
||||||
|
CALL vn.zonePromo_Make();
|
||||||
|
|
||||||
|
IF(vHasDailyInvoice) AND vHasToInvoice THEN
|
||||||
|
|
||||||
|
-- Facturacion rapida
|
||||||
|
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
|
||||||
|
-- Facturar si está contabilizado
|
||||||
|
IF vIsTaxDataChecked THEN
|
||||||
|
CALL invoiceOut_newFromClient(
|
||||||
|
vClientFk,
|
||||||
|
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
|
||||||
|
vShipped,
|
||||||
|
vCompanyFk,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
vNewInvoiceId);
|
||||||
|
END IF;
|
||||||
|
ELSE
|
||||||
|
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
CLOSE cur;
|
||||||
|
END;;$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,118 @@
|
||||||
|
drop procedure `vn`.`ticket_closeAll`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
create definer = root@`%` procedure `vn`.`ticket_closeAll__`()
|
||||||
|
BEGIN
|
||||||
|
/**
|
||||||
|
* Realiza el cierre de todos los
|
||||||
|
* tickets de la tabla tmp.ticketClosure.
|
||||||
|
*
|
||||||
|
* @param vTicketFk Id del ticket
|
||||||
|
*/
|
||||||
|
DECLARE vDone BOOL;
|
||||||
|
DECLARE vClientFk INT;
|
||||||
|
DECLARE vCurTicketFk INT;
|
||||||
|
DECLARE vIsTaxDataChecked BOOL;
|
||||||
|
DECLARE vCompanyFk INT;
|
||||||
|
DECLARE vShipped DATE;
|
||||||
|
DECLARE vNewInvoiceId INT;
|
||||||
|
DECLARE vHasDailyInvoice BOOL;
|
||||||
|
DECLARE vWithPackage BOOL;
|
||||||
|
DECLARE vHasToInvoice BOOL;
|
||||||
|
|
||||||
|
DECLARE cur CURSOR FOR
|
||||||
|
SELECT ticketFk FROM tmp.ticketClosure;
|
||||||
|
|
||||||
|
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
|
||||||
|
DECLARE EXIT HANDLER FOR SQLEXCEPTION BEGIN
|
||||||
|
RESIGNAL;
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure2;
|
||||||
|
CREATE TEMPORARY TABLE tmp.ticketClosure2
|
||||||
|
SELECT ticketFk FROM tmp.ticketClosure;
|
||||||
|
|
||||||
|
INSERT INTO tmp.ticketClosure
|
||||||
|
SELECT id FROM stowaway s
|
||||||
|
JOIN tmp.ticketClosure2 tc ON s.shipFk = tc.ticketFk;
|
||||||
|
OPEN cur;
|
||||||
|
|
||||||
|
proc: LOOP
|
||||||
|
SET vDone = FALSE;
|
||||||
|
|
||||||
|
FETCH cur INTO vCurTicketFk;
|
||||||
|
|
||||||
|
IF vDone THEN
|
||||||
|
LEAVE proc;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
-- ticketClosure start
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.isTaxDataChecked,
|
||||||
|
t.companyFk,
|
||||||
|
t.shipped,
|
||||||
|
co.hasDailyInvoice,
|
||||||
|
w.isManaged,
|
||||||
|
c.hasToInvoice
|
||||||
|
INTO vClientFk,
|
||||||
|
vIsTaxDataChecked,
|
||||||
|
vCompanyFk,
|
||||||
|
vShipped,
|
||||||
|
vHasDailyInvoice,
|
||||||
|
vWithPackage,
|
||||||
|
vHasToInvoice
|
||||||
|
FROM ticket t
|
||||||
|
JOIN `client` c ON c.id = t.clientFk
|
||||||
|
JOIN province p ON p.id = c.provinceFk
|
||||||
|
JOIN country co ON co.id = p.countryFk
|
||||||
|
JOIN warehouse w ON w.id = t.warehouseFk
|
||||||
|
WHERE t.id = vCurTicketFk;
|
||||||
|
|
||||||
|
INSERT INTO ticketPackaging (ticketFk, packagingFk, quantity)
|
||||||
|
(SELECT vCurTicketFk, p.id, COUNT(*)
|
||||||
|
FROM expedition e
|
||||||
|
JOIN packaging p ON p.itemFk = e.itemFk
|
||||||
|
WHERE e.ticketFk = vCurTicketFk AND p.isPackageReturnable
|
||||||
|
AND vWithPackage
|
||||||
|
GROUP BY p.itemFk);
|
||||||
|
|
||||||
|
-- No retornables o no catalogados
|
||||||
|
INSERT INTO sale (itemFk, ticketFk, concept, quantity, price, isPriceFixed)
|
||||||
|
(SELECT e.itemFk, vCurTicketFk, i.name, COUNT(*) AS amount, getSpecialPrice(e.itemFk, vClientFk), 1
|
||||||
|
FROM expedition e
|
||||||
|
JOIN item i ON i.id = e.itemFk
|
||||||
|
LEFT JOIN packaging p ON p.itemFk = i.id
|
||||||
|
WHERE e.ticketFk = vCurTicketFk AND IFNULL(p.isPackageReturnable, 0) = 0
|
||||||
|
AND getSpecialPrice(e.itemFk, vClientFk) > 0
|
||||||
|
GROUP BY e.itemFk);
|
||||||
|
|
||||||
|
CALL vn.zonePromo_Make();
|
||||||
|
|
||||||
|
IF(vHasDailyInvoice) AND vHasToInvoice THEN
|
||||||
|
|
||||||
|
-- Facturacion rapida
|
||||||
|
CALL ticketTrackingAdd(vCurTicketFk, 'DELIVERED', NULL);
|
||||||
|
-- Facturar si está contabilizado
|
||||||
|
IF vIsTaxDataChecked THEN
|
||||||
|
CALL invoiceOut_newFromClient(
|
||||||
|
vClientFk,
|
||||||
|
(SELECT invoiceSerial(vClientFk, vCompanyFk, 'M')),
|
||||||
|
vShipped,
|
||||||
|
vCompanyFk,
|
||||||
|
NULL,
|
||||||
|
NULL,
|
||||||
|
vNewInvoiceId);
|
||||||
|
END IF;
|
||||||
|
ELSE
|
||||||
|
CALL ticketTrackingAdd(vCurTicketFk, (SELECT vn.getAlert3State(vCurTicketFk)), NULL);
|
||||||
|
END IF;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
CLOSE cur;
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.ticketClosure;
|
||||||
|
END;;$$
|
||||||
|
DELIMITER ;
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
drop procedure `vn`.`ticket_closeByTicket`;
|
||||||
|
|
||||||
|
DELIMITER $$
|
||||||
|
$$
|
||||||
|
create
|
||||||
|
definer = root@`%` procedure `vn`.`ticket_closeByTicket`(IN vTicketFk int)
|
||||||
|
BEGIN
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserta el ticket en la tabla temporal
|
||||||
|
* para ser cerrado.
|
||||||
|
*
|
||||||
|
* @param vTicketFk Id del ticket
|
||||||
|
*/
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close;
|
||||||
|
CREATE TEMPORARY TABLE tmp.ticket_close ENGINE = MEMORY (
|
||||||
|
SELECT
|
||||||
|
t.id AS ticketFk
|
||||||
|
FROM expedition e
|
||||||
|
INNER JOIN ticket t ON t.id = e.ticketFk
|
||||||
|
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
|
||||||
|
JOIN alertLevel al ON al.id = ts.alertLevel
|
||||||
|
WHERE
|
||||||
|
al.code = 'PACKED'
|
||||||
|
AND t.id = vTicketFk
|
||||||
|
AND t.refFk IS NULL
|
||||||
|
GROUP BY e.ticketFk);
|
||||||
|
|
||||||
|
CALL ticket_close();
|
||||||
|
|
||||||
|
DROP TEMPORARY TABLE tmp.ticket_close;
|
||||||
|
END;;$$
|
||||||
|
DELIMITER ;
|
|
@ -0,0 +1,2 @@
|
||||||
|
INSERT INTO `vn`.`sample` (`code`, `description`, `isVisible`, `hasCompany`, `hasPreview`)
|
||||||
|
VALUES ('credit-request', 'Solicitud de crédito', 1, 1, 1);
|
|
@ -2339,6 +2339,14 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
|
||||||
(9, 9, 9),
|
(9, 9, 9),
|
||||||
(10, 10, 10);
|
(10, 10, 10);
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxCodeFk`, `taxableBase`, `expenceFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`, `created`)
|
||||||
|
VALUES
|
||||||
|
(1, 4, 99.99, '2000000000', null, null, null, CURDATE()),
|
||||||
|
(2, 4, 999.99, '2000000000', null, null, null, CURDATE()),
|
||||||
|
(3, 4, 1000.50, '2000000000', null, null, null, CURDATE()),
|
||||||
|
(4, 4, 0.50, '2000000000', null, null, null, CURDATE()),
|
||||||
|
(5, 4, 150.50, '2000000000', null, null, null, CURDATE());
|
||||||
|
|
||||||
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
|
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
|
||||||
SELECT `id`
|
SELECT `id`
|
||||||
FROM `vn`.`ticket` t
|
FROM `vn`.`ticket` t
|
||||||
|
|
|
@ -258,7 +258,9 @@ export default {
|
||||||
},
|
},
|
||||||
clientLog: {
|
clientLog: {
|
||||||
lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before',
|
lastModificationPreviousValue: 'vn-client-log vn-table vn-td.before',
|
||||||
lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after'
|
lastModificationCurrentValue: 'vn-client-log vn-table vn-td.after',
|
||||||
|
penultimateModificationPreviousValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.before',
|
||||||
|
penultimateModificationCurrentValue: 'vn-client-log vn-table vn-tr:nth-child(2) vn-td.after'
|
||||||
|
|
||||||
},
|
},
|
||||||
clientBalance: {
|
clientBalance: {
|
||||||
|
@ -462,8 +464,8 @@ export default {
|
||||||
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
|
itemDescriptorPopover: '.vn-popover.shown vn-item-descriptor',
|
||||||
itemDescriptorPopoverItemDiaryButton: 'vn-item-descriptor a[href="#!/item/2/diary?warehouseFk=5&lineFk=16"]',
|
itemDescriptorPopoverItemDiaryButton: 'vn-item-descriptor a[href="#!/item/2/diary?warehouseFk=5&lineFk=16"]',
|
||||||
popoverDiaryButton: '.vn-popover.shown vn-item-descriptor vn-icon[icon="icon-transaction"]',
|
popoverDiaryButton: '.vn-popover.shown vn-item-descriptor vn-icon[icon="icon-transaction"]',
|
||||||
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(4)',
|
firstSaleQuantity: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(5)',
|
||||||
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(7)',
|
firstSaleDiscount: 'vn-ticket-summary [name="sales"] vn-table > div > vn-tbody > vn-tr > vn-td:nth-child(8)',
|
||||||
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
|
invoiceOutRef: 'vn-ticket-summary > vn-card > vn-horizontal > vn-one:nth-child(1) > vn-label-value:nth-child(7) > section > span',
|
||||||
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button',
|
setOk: 'vn-ticket-summary vn-button[label="SET OK"] > button',
|
||||||
descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div'
|
descriptorTicketId: 'vn-ticket-descriptor > vn-descriptor-content > div > div.body > div.top > div'
|
||||||
|
@ -566,18 +568,18 @@ export default {
|
||||||
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
|
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
|
||||||
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
|
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
|
||||||
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',
|
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',
|
||||||
firstSaleId: 'vn-ticket-sale vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(5) > span',
|
firstSaleId: 'vn-ticket-sale vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(6) > span',
|
||||||
firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]',
|
firstSaleClaimIcon: 'vn-ticket-sale vn-table vn-tbody > vn-tr:nth-child(1) vn-icon[icon="icon-claims"]',
|
||||||
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
|
firstSaleDescriptorImage: '.vn-popover.shown vn-item-descriptor img',
|
||||||
firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img',
|
firstSaleThumbnailImage: 'vn-ticket-sale:nth-child(1) vn-tr:nth-child(1) vn-td:nth-child(3) > img',
|
||||||
firstSaleZoomedImage: 'body > div > div > img',
|
firstSaleZoomedImage: 'body > div > div > img',
|
||||||
firstSaleQuantity: 'vn-ticket-sale [ng-model="sale.quantity"]',
|
firstSaleQuantity: 'vn-ticket-sale [ng-model="sale.quantity"]',
|
||||||
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(6)',
|
firstSaleQuantityCell: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td-editable:nth-child(7)',
|
||||||
firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(8) > span',
|
firstSalePrice: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(9) > span',
|
||||||
firstSalePriceInput: '.vn-popover.shown input[ng-model="$ctrl.field"]',
|
firstSalePriceInput: '.vn-popover.shown input[ng-model="$ctrl.field"]',
|
||||||
firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(9) > span',
|
firstSaleDiscount: 'vn-ticket-sale vn-table vn-tr:nth-child(1) > vn-td:nth-child(10) > span',
|
||||||
firstSaleDiscountInput: '.vn-popover.shown [ng-model="$ctrl.field"]',
|
firstSaleDiscountInput: '.vn-popover.shown [ng-model="$ctrl.field"]',
|
||||||
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(10)',
|
firstSaleImport: 'vn-ticket-sale:nth-child(1) vn-td:nth-child(11)',
|
||||||
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
|
firstSaleReservedIcon: 'vn-ticket-sale vn-tr:nth-child(1) > vn-td:nth-child(2) > vn-icon:nth-child(3)',
|
||||||
firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-fetched-tags section',
|
firstSaleColour: 'vn-ticket-sale vn-tr:nth-child(1) vn-fetched-tags section',
|
||||||
firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[ng-model="sale.checked"]',
|
firstSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(1) vn-check[ng-model="sale.checked"]',
|
||||||
|
@ -585,8 +587,8 @@ export default {
|
||||||
secondSaleId: 'vn-ticket-sale:nth-child(2) vn-td-editable:nth-child(4) text > span',
|
secondSaleId: 'vn-ticket-sale:nth-child(2) vn-td-editable:nth-child(4) text > span',
|
||||||
secondSaleIdAutocomplete: 'vn-ticket-sale vn-tr:nth-child(2) vn-autocomplete[ng-model="sale.itemFk"]',
|
secondSaleIdAutocomplete: 'vn-ticket-sale vn-tr:nth-child(2) vn-autocomplete[ng-model="sale.itemFk"]',
|
||||||
secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number',
|
secondSaleQuantity: 'vn-ticket-sale vn-table vn-tr:nth-child(2) vn-input-number',
|
||||||
secondSaleQuantityCell: 'vn-ticket-sale > div > vn-card > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(6)',
|
secondSaleQuantityCell: 'vn-ticket-sale > div > vn-card > vn-table > div > vn-tbody > vn-tr:nth-child(2) > vn-td-editable:nth-child(7)',
|
||||||
secondSaleConceptCell: 'vn-ticket-sale vn-tbody > :nth-child(2) > :nth-child(7)',
|
secondSaleConceptCell: 'vn-ticket-sale vn-tbody > :nth-child(2) > :nth-child(8)',
|
||||||
secondSaleConceptInput: 'vn-ticket-sale vn-tbody > :nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield',
|
secondSaleConceptInput: 'vn-ticket-sale vn-tbody > :nth-child(2) > vn-td-editable.ng-isolate-scope.selected vn-textfield',
|
||||||
totalImport: 'vn-ticket-sale vn-one.taxes > p:nth-child(3) > strong',
|
totalImport: 'vn-ticket-sale vn-one.taxes > p:nth-child(3) > strong',
|
||||||
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
|
selectAllSalesCheckbox: 'vn-ticket-sale vn-thead vn-check',
|
||||||
|
@ -936,7 +938,8 @@ export default {
|
||||||
invoiceInDescriptor: {
|
invoiceInDescriptor: {
|
||||||
moreMenu: 'vn-invoice-in-descriptor vn-icon-button[icon=more_vert]',
|
moreMenu: 'vn-invoice-in-descriptor vn-icon-button[icon=more_vert]',
|
||||||
moreMenuDeleteInvoiceIn: '.vn-menu [name="deleteInvoice"]',
|
moreMenuDeleteInvoiceIn: '.vn-menu [name="deleteInvoice"]',
|
||||||
acceptDeleteButton: '.vn-confirm.shown button[response="accept"]'
|
moreMenuCloneInvoiceIn: '.vn-menu [name="cloneInvoice"]',
|
||||||
|
acceptButton: '.vn-confirm.shown button[response="accept"]'
|
||||||
},
|
},
|
||||||
invoiceInBasicData: {
|
invoiceInBasicData: {
|
||||||
issued: 'vn-invoice-in-basic-data vn-date-picker[ng-model="$ctrl.invoiceIn.issued"]',
|
issued: 'vn-invoice-in-basic-data vn-date-picker[ng-model="$ctrl.invoiceIn.issued"]',
|
||||||
|
|
|
@ -16,8 +16,15 @@ describe('Client Edit web access path', () => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should uncheck the Enable web access checkbox and update the name`, async() => {
|
it('should uncheck the Enable web access checkbox', async() => {
|
||||||
await page.waitToClick(selectors.clientWebAccess.enableWebAccessCheckbox);
|
await page.waitToClick(selectors.clientWebAccess.enableWebAccessCheckbox);
|
||||||
|
await page.waitToClick(selectors.clientWebAccess.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toContain('Data saved!');
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should update the name`, async() => {
|
||||||
await page.clearInput(selectors.clientWebAccess.userName);
|
await page.clearInput(selectors.clientWebAccess.userName);
|
||||||
await page.write(selectors.clientWebAccess.userName, 'Hulk');
|
await page.write(selectors.clientWebAccess.userName, 'Hulk');
|
||||||
await page.waitToClick(selectors.clientWebAccess.saveButton);
|
await page.waitToClick(selectors.clientWebAccess.saveButton);
|
||||||
|
@ -26,9 +33,8 @@ describe('Client Edit web access path', () => {
|
||||||
expect(message.text).toContain('Data saved!');
|
expect(message.text).toContain('Data saved!');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should confirm web access is now unchecked', async() => {
|
it('should reload the section and confirm web access is now unchecked', async() => {
|
||||||
await page.accessToSection('client.card.basicData');
|
await page.reloadSection('client.card.webAccess');
|
||||||
await page.accessToSection('client.card.webAccess');
|
|
||||||
const result = await page.checkboxState(selectors.clientWebAccess.enableWebAccessCheckbox);
|
const result = await page.checkboxState(selectors.clientWebAccess.enableWebAccessCheckbox);
|
||||||
|
|
||||||
expect(result).toBe('unchecked');
|
expect(result).toBe('unchecked');
|
||||||
|
@ -44,13 +50,23 @@ describe('Client Edit web access path', () => {
|
||||||
await page.accessToSection('client.card.log');
|
await page.accessToSection('client.card.log');
|
||||||
});
|
});
|
||||||
|
|
||||||
it(`should confirm the log is showing the updated data for the client`, async() => {
|
it(`should confirm the last log is showing the updated client name and no modifications on the active checkbox`, async() => {
|
||||||
let lastModificationPreviousValue = await page
|
let lastModificationPreviousValue = await page
|
||||||
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
|
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
|
||||||
let lastModificationCurrentValue = await page
|
let lastModificationCurrentValue = await page
|
||||||
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
|
.waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
|
||||||
|
|
||||||
expect(lastModificationPreviousValue).toEqual('name BruceBanner active true');
|
expect(lastModificationPreviousValue).toEqual('name BruceBanner active false');
|
||||||
expect(lastModificationCurrentValue).toEqual('name Hulk active false');
|
expect(lastModificationCurrentValue).toEqual('name Hulk active false');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it(`should confirm the penultimate log is showing the updated avtive field and no modifications on the client name`, async() => {
|
||||||
|
let penultimateModificationPreviousValue = await page
|
||||||
|
.waitToGetProperty(selectors.clientLog.penultimateModificationPreviousValue, 'innerText');
|
||||||
|
let penultimateModificationCurrentValue = await page
|
||||||
|
.waitToGetProperty(selectors.clientLog.penultimateModificationCurrentValue, 'innerText');
|
||||||
|
|
||||||
|
expect(penultimateModificationPreviousValue).toEqual('name BruceBanner active true');
|
||||||
|
expect(penultimateModificationCurrentValue).toEqual('name BruceBanner active false');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -10,16 +10,30 @@ describe('InvoiceIn descriptor path', () => {
|
||||||
page = browser.page;
|
page = browser.page;
|
||||||
await page.loginAndModule('administrative', 'invoiceIn');
|
await page.loginAndModule('administrative', 'invoiceIn');
|
||||||
await page.accessToSearchResult('10');
|
await page.accessToSearchResult('10');
|
||||||
|
await page.accessToSection('invoiceIn.card.basicData');
|
||||||
});
|
});
|
||||||
|
|
||||||
afterAll(async() => {
|
afterAll(async() => {
|
||||||
await browser.close();
|
await browser.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the invoiceIn using the descriptor more menu', async() => {
|
it('should clone the invoiceIn using the descriptor more menu', async() => {
|
||||||
|
await page.waitToClick(selectors.invoiceInDescriptor.moreMenu);
|
||||||
|
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuCloneInvoiceIn);
|
||||||
|
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toContain('InvoiceIn cloned');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have been redirected to the created invoiceIn summary', async() => {
|
||||||
|
await page.waitForState('invoiceIn.card.summary');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should delete the cloned invoiceIn using the descriptor more menu', async() => {
|
||||||
await page.waitToClick(selectors.invoiceInDescriptor.moreMenu);
|
await page.waitToClick(selectors.invoiceInDescriptor.moreMenu);
|
||||||
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuDeleteInvoiceIn);
|
await page.waitToClick(selectors.invoiceInDescriptor.moreMenuDeleteInvoiceIn);
|
||||||
await page.waitToClick(selectors.invoiceInDescriptor.acceptDeleteButton);
|
await page.waitToClick(selectors.invoiceInDescriptor.acceptButton);
|
||||||
const message = await page.waitForSnackbar();
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
expect(message.text).toContain('InvoiceIn deleted');
|
expect(message.text).toContain('InvoiceIn deleted');
|
||||||
|
|
|
@ -57,7 +57,14 @@
|
||||||
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
||||||
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
||||||
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
||||||
"Bought units from buy request": "Bought {{quantity}} units of {{concept}} [{{itemId}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
"Deleted sales from ticket": "I have deleted the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
|
||||||
|
"Added sale to ticket": "I have added the following line to the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
|
||||||
|
"Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Created claim": "I have created the claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Changed sale price": "I have changed the price of [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale quantity": "I have changed the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Bought units from buy request": "Bought {{quantity}} units of [{{itemId}} {{concept}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
||||||
"Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
"Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
@ -99,5 +106,8 @@
|
||||||
"None": "None",
|
"None": "None",
|
||||||
"error densidad = 0": "error densidad = 0",
|
"error densidad = 0": "error densidad = 0",
|
||||||
"nickname": "nickname",
|
"nickname": "nickname",
|
||||||
"This document already exists on this ticket": "This document already exists on this ticket"
|
"This document already exists on this ticket": "This document already exists on this ticket",
|
||||||
|
"State": "State",
|
||||||
|
"regular": "regular",
|
||||||
|
"reserved": "reserved"
|
||||||
}
|
}
|
|
@ -54,7 +54,7 @@
|
||||||
"This address doesn't exist": "Este consignatario no existe",
|
"This address doesn't exist": "Este consignatario no existe",
|
||||||
"You can't create an order for a inactive client": "You can't create an order for a inactive client",
|
"You can't create an order for a inactive client": "You can't create an order for a inactive client",
|
||||||
"You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified",
|
"You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified",
|
||||||
"You must delete the claim id %d first": "Antes debes borrar la reclamacion %d",
|
"You must delete the claim id %d first": "Antes debes borrar la reclamación %d",
|
||||||
"You don't have enough privileges": "No tienes suficientes permisos",
|
"You don't have enough privileges": "No tienes suficientes permisos",
|
||||||
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
|
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
|
||||||
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
|
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
|
||||||
|
@ -122,7 +122,17 @@
|
||||||
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
||||||
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
||||||
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
||||||
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de {{concept}} [{{itemId}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
"Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
|
||||||
|
"Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
|
||||||
|
"Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale quantity": "He cambiado la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"State": "Estado",
|
||||||
|
"regular": "normal",
|
||||||
|
"reserved": "reservado",
|
||||||
|
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
||||||
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
|
|
@ -3,17 +3,20 @@ const UserError = require('vn-loopback/util/user-error');
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('createFromSales', {
|
Self.remoteMethodCtx('createFromSales', {
|
||||||
description: 'Create a claim',
|
description: 'Create a claim',
|
||||||
accepts: [{
|
accepts: [
|
||||||
arg: 'ticketId',
|
{
|
||||||
type: 'Number',
|
arg: 'ticketId',
|
||||||
required: true,
|
type: 'number',
|
||||||
description: 'The origin ticket id'
|
required: true,
|
||||||
}, {
|
description: 'The origin ticket id'
|
||||||
arg: 'sales',
|
},
|
||||||
type: ['Object'],
|
{
|
||||||
required: true,
|
arg: 'sales',
|
||||||
description: 'The claimed sales'
|
type: ['object'],
|
||||||
}],
|
required: true,
|
||||||
|
description: 'The claimed sales'
|
||||||
|
}
|
||||||
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
|
@ -25,6 +28,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.createFromSales = async(ctx, ticketId, sales, options) => {
|
Self.createFromSales = async(ctx, ticketId, sales, options) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
let tx;
|
let tx;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
|
@ -39,7 +43,20 @@ module.exports = Self => {
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
if (ticket.isDeleted)
|
if (ticket.isDeleted)
|
||||||
throw new UserError(`You can't create a claim for a removed ticket`);
|
throw new UserError(`You can't create a claim for a removed ticket`);
|
||||||
|
|
||||||
|
@ -49,6 +66,8 @@ module.exports = Self => {
|
||||||
ticketCreated: ticket.shipped,
|
ticketCreated: ticket.shipped,
|
||||||
workerFk: userId
|
workerFk: userId
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
|
let changesMade = '';
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
|
@ -59,10 +78,25 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
promises.push(newClaimBeginning);
|
promises.push(newClaimBeginning);
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Created claim', {
|
||||||
|
claimId: newClaim.id,
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
claimUrl: `${origin}/#!/claim/${newClaim.id}/summary`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return newClaim;
|
return newClaim;
|
||||||
|
|
|
@ -7,7 +7,13 @@ describe('Claim createFromSales()', () => {
|
||||||
instance: 0,
|
instance: 0,
|
||||||
quantity: 10
|
quantity: 10
|
||||||
}];
|
}];
|
||||||
const ctx = {req: {accessToken: {userId: 1}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 1},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
it('should create a new claim', async() => {
|
it('should create a new claim', async() => {
|
||||||
const tx = await app.models.Claim.beginTransaction({});
|
const tx = await app.models.Claim.beginTransaction({});
|
||||||
|
|
|
@ -9,23 +9,26 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"id": true,
|
"id": true,
|
||||||
"type": "Number",
|
"type": "number",
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"code": {
|
"code": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "String"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"isVisible": {
|
"isVisible": {
|
||||||
"type": "Boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"hasCompany": {
|
"hasCompany": {
|
||||||
"type": "Boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"hasPreview": {
|
"hasPreview": {
|
||||||
"type": "Boolean"
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"datepickerEnabled": {
|
||||||
|
"type": "boolean"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"scopes": {
|
"scopes": {
|
||||||
|
|
|
@ -25,38 +25,48 @@
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textfield
|
|
||||||
label="Recipient"
|
|
||||||
ng-model="$ctrl.clientSample.recipient"
|
|
||||||
info="Its only used when sample is sent">
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textfield
|
|
||||||
label="Reply to"
|
|
||||||
ng-model="$ctrl.clientSample.replyTo"
|
|
||||||
info="To who should the recipient reply?">
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
vn-id="sampleType"
|
vn-id="sampleType"
|
||||||
ng-model="$ctrl.clientSample.typeFk"
|
ng-model="$ctrl.clientSample.typeFk"
|
||||||
model="ClientSample.typeFk"
|
model="ClientSample.typeFk"
|
||||||
fields="['code','hasCompany', 'hasPreview']"
|
|
||||||
data="samplesVisible"
|
data="samplesVisible"
|
||||||
show-field="description"
|
show-field="description"
|
||||||
label="Sample">
|
label="Sample"
|
||||||
|
required="true">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
label="Recipient"
|
||||||
|
ng-model="$ctrl.clientSample.recipient"
|
||||||
|
info="Its only used when sample is sent"
|
||||||
|
required="true">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Reply to"
|
||||||
|
ng-model="$ctrl.clientSample.replyTo"
|
||||||
|
info="To who should the recipient reply?"
|
||||||
|
required="true">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal ng-if="sampleType.selection.hasCompany || sampleType.selection.datepickerEnabled">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
ng-model="$ctrl.companyId"
|
ng-model="$ctrl.companyId"
|
||||||
model="ClientSample.companyFk"
|
model="ClientSample.companyFk"
|
||||||
data="companiesData"
|
data="companiesData"
|
||||||
show-field="code"
|
show-field="code"
|
||||||
label="Company"
|
label="Company"
|
||||||
ng-if="sampleType.selection.hasCompany">
|
ng-if="sampleType.selection.hasCompany"
|
||||||
|
required="true">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
|
<vn-date-picker
|
||||||
|
vn-one
|
||||||
|
label="From"
|
||||||
|
ng-model="$ctrl.clientSample.from"
|
||||||
|
ng-if="sampleType.selection.datepickerEnabled"
|
||||||
|
required="true">
|
||||||
|
</vn-date-picker>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
<vn-button-bar>
|
<vn-button-bar>
|
||||||
|
|
|
@ -80,6 +80,12 @@ class Controller extends Section {
|
||||||
if (sampleType.hasCompany)
|
if (sampleType.hasCompany)
|
||||||
params.companyId = this.clientSample.companyFk;
|
params.companyId = this.clientSample.companyFk;
|
||||||
|
|
||||||
|
if (sampleType.datepickerEnabled && !this.clientSample.from)
|
||||||
|
return this.vnApp.showError(this.$t('Choose a date'));
|
||||||
|
|
||||||
|
if (sampleType.datepickerEnabled)
|
||||||
|
params.from = this.clientSample.from;
|
||||||
|
|
||||||
let query = `email/${sampleType.code}`;
|
let query = `email/${sampleType.code}`;
|
||||||
if (isPreview)
|
if (isPreview)
|
||||||
query = `email/${sampleType.code}/preview`;
|
query = `email/${sampleType.code}/preview`;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
Choose a sample: Selecciona una plantilla
|
Choose a sample: Selecciona una plantilla
|
||||||
Choose a company: Selecciona una empresa
|
Choose a company: Selecciona una empresa
|
||||||
|
Choose a date: Selecciona una fecha
|
||||||
Email cannot be blank: Debes introducir un email
|
Email cannot be blank: Debes introducir un email
|
||||||
Recipient: Destinatario
|
Recipient: Destinatario
|
||||||
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla
|
Its only used when sample is sent: Se utiliza únicamente cuando se envía la plantilla
|
||||||
|
|
|
@ -293,6 +293,8 @@
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-th field="id" number>Id</vn-th>
|
<vn-th field="id" number>Id</vn-th>
|
||||||
<vn-th field="nickname" expand>Nickname</vn-th>
|
<vn-th field="nickname" expand>Nickname</vn-th>
|
||||||
|
<vn-th field="agencyModeFk" expand>Agency</vn-th>
|
||||||
|
<vn-th field="routeFk" expand>Route</vn-th>
|
||||||
<vn-th field="packages" shrink>Packages</vn-th>
|
<vn-th field="packages" shrink>Packages</vn-th>
|
||||||
<vn-th field="shipped" shrink-date>Date</vn-th>
|
<vn-th field="shipped" shrink-date>Date</vn-th>
|
||||||
<vn-th>State</vn-th>
|
<vn-th>State</vn-th>
|
||||||
|
@ -313,6 +315,17 @@
|
||||||
{{::ticket.nickname}}
|
{{::ticket.nickname}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
<vn-td shrink>
|
||||||
|
{{::ticket.agencyMode.name}}
|
||||||
|
</vn-td>
|
||||||
|
<vn-td shrink>
|
||||||
|
<span
|
||||||
|
title="{{::ticket.route}}"
|
||||||
|
vn-click-stop="routeDescriptor.show($event, ticket.routeFk)"
|
||||||
|
class="link">
|
||||||
|
{{::ticket.routeFk}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
{{::ticket.packages}}
|
{{::ticket.packages}}
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
@ -371,6 +384,9 @@
|
||||||
<vn-client-descriptor-popover
|
<vn-client-descriptor-popover
|
||||||
vn-id="clientDescriptor">
|
vn-id="clientDescriptor">
|
||||||
</vn-client-descriptor-popover>
|
</vn-client-descriptor-popover>
|
||||||
|
<vn-route-descriptor-popover
|
||||||
|
vn-id="routeDescriptor">
|
||||||
|
</vn-route-descriptor-popover>
|
||||||
<vn-worker-descriptor-popover
|
<vn-worker-descriptor-popover
|
||||||
vn-id="workerDescriptor">
|
vn-id="workerDescriptor">
|
||||||
</vn-worker-descriptor-popover>
|
</vn-worker-descriptor-popover>
|
||||||
|
|
|
@ -22,7 +22,13 @@ class Controller extends Summary {
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id']
|
fields: ['id']
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
relation: 'agencyMode',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-check
|
<vn-check
|
||||||
|
vn-id="active"
|
||||||
|
disabled="watcher.orgData.name != $ctrl.account.name"
|
||||||
vn-one
|
vn-one
|
||||||
label="Enable web access"
|
label="Enable web access"
|
||||||
ng-model="$ctrl.account.active"
|
ng-model="$ctrl.account.active"
|
||||||
|
@ -17,6 +19,8 @@
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-textfield
|
<vn-textfield
|
||||||
|
vn-id="name"
|
||||||
|
disabled="watcher.orgData.active != $ctrl.account.active"
|
||||||
vn-focus
|
vn-focus
|
||||||
vn-one
|
vn-one
|
||||||
label="User"
|
label="User"
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
|
||||||
|
const loggable = require('vn-loopback/util/log');
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('clone', {
|
||||||
|
description: 'Clone the invoiceIn and as many invoiceInTax and invoiceInDueDay referencing it',
|
||||||
|
accessType: 'WRITE',
|
||||||
|
accepts: {
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
required: true,
|
||||||
|
description: 'The invoiceIn id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
returns: {
|
||||||
|
type: 'object',
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: '/:id/clone',
|
||||||
|
verb: 'POST'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.clone = async(ctx, id, options) => {
|
||||||
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const models = Self.app.models;
|
||||||
|
let tx;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
if (!myOptions.transaction) {
|
||||||
|
tx = await Self.beginTransaction({});
|
||||||
|
myOptions.transaction = tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const sourceInvoiceIn = await Self.findById(id, {
|
||||||
|
fields: [
|
||||||
|
'id',
|
||||||
|
'serial',
|
||||||
|
'supplierRef',
|
||||||
|
'supplierFk',
|
||||||
|
'issued',
|
||||||
|
'currencyFk',
|
||||||
|
'companyFk',
|
||||||
|
'isVatDeductible',
|
||||||
|
'withholdingSageFk',
|
||||||
|
'deductibleExpenseFk',
|
||||||
|
]
|
||||||
|
}, myOptions);
|
||||||
|
const sourceInvoiceInTax = await models.InvoiceInTax.find({where: {invoiceInFk: id}}, myOptions);
|
||||||
|
const sourceInvoiceInDueDay = await models.InvoiceInDueDay.find({where: {invoiceInFk: id}}, myOptions);
|
||||||
|
|
||||||
|
const issued = new Date(sourceInvoiceIn.issued);
|
||||||
|
issued.setMonth(issued.getMonth() + 1);
|
||||||
|
|
||||||
|
const clone = await models.InvoiceIn.create({
|
||||||
|
serial: sourceInvoiceIn.serial,
|
||||||
|
supplierRef: sourceInvoiceIn.supplierRef,
|
||||||
|
supplierFk: sourceInvoiceIn.supplierFk,
|
||||||
|
issued: issued,
|
||||||
|
currencyFk: sourceInvoiceIn.currencyFk,
|
||||||
|
companyFk: sourceInvoiceIn.companyFk,
|
||||||
|
isVatDeductible: sourceInvoiceIn.isVatDeductible,
|
||||||
|
withholdingSageFk: sourceInvoiceIn.withholdingSageFk,
|
||||||
|
deductibleExpenseFk: sourceInvoiceIn.deductibleExpenseFk,
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const oldProperties = await loggable.translateValues(Self, sourceInvoiceIn);
|
||||||
|
const newProperties = await loggable.translateValues(Self, clone);
|
||||||
|
await models.InvoiceInLog.create({
|
||||||
|
originFk: clone.id,
|
||||||
|
userFk: userId,
|
||||||
|
action: 'insert',
|
||||||
|
changedModel: 'InvoiceIn',
|
||||||
|
changedModelId: clone.id,
|
||||||
|
oldInstance: oldProperties,
|
||||||
|
newInstance: newProperties
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
for (let tax of sourceInvoiceInTax) {
|
||||||
|
promises.push(models.InvoiceInTax.create({
|
||||||
|
invoiceInFk: clone.id,
|
||||||
|
taxableBase: tax.taxableBase,
|
||||||
|
expenceFk: tax.expenceFk,
|
||||||
|
foreignValue: tax.foreignValue,
|
||||||
|
taxTypeSageFk: tax.taxTypeSageFk,
|
||||||
|
transactionTypeSageFk: tax.transactionTypeSageFk
|
||||||
|
}, myOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let dueDay of sourceInvoiceInDueDay) {
|
||||||
|
const dueDated = dueDay.dueDated;
|
||||||
|
dueDated.setMonth(dueDated.getMonth() + 1);
|
||||||
|
|
||||||
|
promises.push(models.InvoiceInDueDay.create({
|
||||||
|
invoiceInFk: clone.id,
|
||||||
|
dueDated: dueDated,
|
||||||
|
bankFk: dueDay.bankFk,
|
||||||
|
amount: dueDay.amount,
|
||||||
|
foreignValue: dueDated.foreignValue,
|
||||||
|
}, myOptions));
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
|
return clone;
|
||||||
|
} catch (e) {
|
||||||
|
if (tx) await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,36 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('invoiceIn clone()', () => {
|
||||||
|
it('should return the cloned invoiceIn and also clone invoiceInDueDays and invoiceInTaxes if there are any referencing the invoiceIn', async() => {
|
||||||
|
const userId = 1;
|
||||||
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
|
||||||
|
accessToken: {userId: userId},
|
||||||
|
headers: {origin: 'http://localhost:5000'},
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tx = await models.InvoiceIn.beginTransaction({});
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clone = await models.InvoiceIn.clone(ctx, 1, options);
|
||||||
|
|
||||||
|
expect(clone.supplierRef).toEqual('1234');
|
||||||
|
|
||||||
|
const invoiceInTaxes = await models.InvoiceInTax.find({where: {invoiceInFk: clone.id}}, options);
|
||||||
|
|
||||||
|
expect(invoiceInTaxes.length).toEqual(1);
|
||||||
|
|
||||||
|
const invoiceInDueDays = await models.InvoiceInDueDay.find({where: {invoiceInFk: clone.id}}, options);
|
||||||
|
|
||||||
|
expect(invoiceInDueDays.length).toEqual(2);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -2,6 +2,9 @@
|
||||||
"InvoiceIn": {
|
"InvoiceIn": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"InvoiceInTax": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"InvoiceInDueDay": {
|
"InvoiceInDueDay": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -24,6 +24,9 @@
|
||||||
"amount": {
|
"amount": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"foreignValue": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
{
|
||||||
|
"name": "InvoiceInTax",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "invoiceInTax"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"id": true,
|
||||||
|
"type": "number",
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"invoiceInFk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"taxCodeFk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"taxableBase": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"expenceFk": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"foreignValue": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"taxTypeSageFk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"transactionTypeSageFk": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"created": {
|
||||||
|
"type": "date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
require('../methods/invoice-in/filter')(Self);
|
require('../methods/invoice-in/filter')(Self);
|
||||||
require('../methods/invoice-in/summary')(Self);
|
require('../methods/invoice-in/summary')(Self);
|
||||||
|
require('../methods/invoice-in/clone')(Self);
|
||||||
};
|
};
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
"properties": {
|
"properties": {
|
||||||
"id": {
|
"id": {
|
||||||
"id": true,
|
"id": true,
|
||||||
"type": "Number",
|
"type": "number",
|
||||||
"description": "Identifier"
|
"description": "Identifier"
|
||||||
},
|
},
|
||||||
"serialNumber": {
|
"serialNumber": {
|
||||||
|
@ -36,6 +36,9 @@
|
||||||
"booked": {
|
"booked": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
"isVatDeductible": {
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
"operated": {
|
"operated": {
|
||||||
"type": "date"
|
"type": "date"
|
||||||
},
|
},
|
||||||
|
|
|
@ -8,6 +8,13 @@
|
||||||
translate>
|
translate>
|
||||||
Delete Invoice
|
Delete Invoice
|
||||||
</vn-item>
|
</vn-item>
|
||||||
|
<vn-item
|
||||||
|
ng-click="cloneConfirmation.show()"
|
||||||
|
vn-acl="invoicing"
|
||||||
|
name="cloneInvoice"
|
||||||
|
translate>
|
||||||
|
Clone Invoice
|
||||||
|
</vn-item>
|
||||||
</slot-menu>
|
</slot-menu>
|
||||||
<slot-body>
|
<slot-body>
|
||||||
<div class="attributes">
|
<div class="attributes">
|
||||||
|
@ -42,8 +49,16 @@
|
||||||
</div>
|
</div>
|
||||||
</slot-body>
|
</slot-body>
|
||||||
</vn-descriptor-content>
|
</vn-descriptor-content>
|
||||||
<vn-confirm vn-id="deleteConfirmation" on-accept="$ctrl.deleteInvoiceIn()"
|
<vn-confirm
|
||||||
|
vn-id="deleteConfirmation"
|
||||||
|
on-accept="$ctrl.deleteInvoiceIn()"
|
||||||
question="Are you sure you want to delete this invoice?">
|
question="Are you sure you want to delete this invoice?">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
<vn-supplier-descriptor-popover vn-id="supplierDescriptor">
|
<vn-confirm
|
||||||
|
vn-id="cloneConfirmation"
|
||||||
|
on-accept="$ctrl.cloneInvoiceIn()"
|
||||||
|
question="Are you sure you want to clone this invoice?">
|
||||||
|
</vn-confirm>
|
||||||
|
<vn-supplier-descriptor-popover
|
||||||
|
vn-id="supplierDescriptor">
|
||||||
</vn-supplier-descriptor-popover>
|
</vn-supplier-descriptor-popover>
|
||||||
|
|
|
@ -30,6 +30,12 @@ class Controller extends Descriptor {
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn deleted')));
|
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn deleted')));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cloneInvoiceIn() {
|
||||||
|
return this.$http.post(`InvoiceIns/${this.id}/clone`)
|
||||||
|
.then(res => this.$state.go('invoiceIn.card.summary', {id: res.data.id}))
|
||||||
|
.then(() => this.vnApp.showSuccess(this.$t('InvoiceIn cloned')));
|
||||||
|
}
|
||||||
|
|
||||||
loadData() {
|
loadData() {
|
||||||
const filter = {
|
const filter = {
|
||||||
include: [
|
include: [
|
||||||
|
|
|
@ -3,3 +3,4 @@ Search invoices in by reference: Buscar facturas recibidas por referencia
|
||||||
Entries list: Listado de entradas
|
Entries list: Listado de entradas
|
||||||
Invoice list: Listado de facturas recibidas
|
Invoice list: Listado de facturas recibidas
|
||||||
InvoiceIn deleted: Factura eliminada
|
InvoiceIn deleted: Factura eliminada
|
||||||
|
InvoiceIn cloned: Factura clonada
|
|
@ -21,7 +21,7 @@ class Controller extends ModuleCard {
|
||||||
}, {
|
}, {
|
||||||
relation: 'client',
|
relation: 'client',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['id', 'socialName', 'name']
|
fields: ['id', 'socialName', 'name', 'email']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -9,6 +9,12 @@
|
||||||
translate>
|
translate>
|
||||||
Show invoice PDF
|
Show invoice PDF
|
||||||
</a>
|
</a>
|
||||||
|
<vn-item
|
||||||
|
ng-click="invoiceConfirmation.show()"
|
||||||
|
name="sendInvoice"
|
||||||
|
translate>
|
||||||
|
Send invoice PDF
|
||||||
|
</vn-item>
|
||||||
<vn-item
|
<vn-item
|
||||||
ng-click="deleteConfirmation.show()"
|
ng-click="deleteConfirmation.show()"
|
||||||
vn-acl="invoicing"
|
vn-acl="invoicing"
|
||||||
|
@ -98,3 +104,11 @@
|
||||||
question="Are you sure you want to regenerate the invoice PDF document?"
|
question="Are you sure you want to regenerate the invoice PDF document?"
|
||||||
message="You are going to regenerate the invoice PDF document">
|
message="You are going to regenerate the invoice PDF document">
|
||||||
</vn-confirm>
|
</vn-confirm>
|
||||||
|
|
||||||
|
<!-- Send invoice confirmation popup -->
|
||||||
|
<vn-confirm
|
||||||
|
vn-id="invoiceConfirmation"
|
||||||
|
on-accept="$ctrl.sendInvoice()"
|
||||||
|
question="Are you sure you want to send it?"
|
||||||
|
message="Send invoice PDF">
|
||||||
|
</vn-confirm>
|
|
@ -59,6 +59,14 @@ class Controller extends Descriptor {
|
||||||
return this.getData(`InvoiceOuts/${this.id}`, {filter})
|
return this.getData(`InvoiceOuts/${this.id}`, {filter})
|
||||||
.then(res => this.entity = res.data);
|
.then(res => this.entity = res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendInvoice() {
|
||||||
|
return this.vnEmail.send('invoice', {
|
||||||
|
recipientId: this.invoiceOut.client.id,
|
||||||
|
recipient: this.invoiceOut.client.email,
|
||||||
|
invoiceId: this.id
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutDescriptor', {
|
ngModule.vnComponent('vnInvoiceOutDescriptor', {
|
||||||
|
|
|
@ -3,9 +3,12 @@ Volume: Volumen
|
||||||
Client card: Ficha del cliente
|
Client card: Ficha del cliente
|
||||||
Invoice ticket list: Listado de tickets de la factura
|
Invoice ticket list: Listado de tickets de la factura
|
||||||
Show invoice PDF: Ver factura en PDF
|
Show invoice PDF: Ver factura en PDF
|
||||||
|
Send invoice PDF: Enviar factura en PDF
|
||||||
Delete Invoice: Eliminar factura
|
Delete Invoice: Eliminar factura
|
||||||
|
Clone Invoice: Clonar factura
|
||||||
InvoiceOut deleted: Factura eliminada
|
InvoiceOut deleted: Factura eliminada
|
||||||
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
|
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
|
||||||
|
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
|
||||||
Book invoice: Asentar factura
|
Book invoice: Asentar factura
|
||||||
InvoiceOut booked: Factura asentada
|
InvoiceOut booked: Factura asentada
|
||||||
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
|
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="ticketsModel"
|
vn-id="ticketsModel"
|
||||||
limit="10"
|
limit="10"
|
||||||
|
auto-load="true"
|
||||||
data="tickets">
|
data="tickets">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-card class="summary">
|
<vn-card class="summary">
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Controller extends Summary {
|
||||||
this._invoiceOut = value;
|
this._invoiceOut = value;
|
||||||
if (value && value.id) {
|
if (value && value.id) {
|
||||||
this.getSummary();
|
this.getSummary();
|
||||||
this.getTickets();
|
this.$.ticketsModel.url = `InvoiceOuts/${this.invoiceOut.id}/getTickets`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,14 +19,6 @@ class Controller extends Summary {
|
||||||
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`)
|
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`)
|
||||||
.then(res => this.summary = res.data);
|
.then(res => this.summary = res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTickets() {
|
|
||||||
this.$.$applyAsync(() => {
|
|
||||||
const query = `InvoiceOuts/${this.invoiceOut.id}/getTickets`;
|
|
||||||
this.$.ticketsModel.url = query;
|
|
||||||
this.$.ticketsModel.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutSummary', {
|
ngModule.vnComponent('vnInvoiceOutSummary', {
|
||||||
|
|
|
@ -27,17 +27,5 @@ describe('InvoiceOut', () => {
|
||||||
expect(controller.summary).toEqual('the data you are looking for');
|
expect(controller.summary).toEqual('the data you are looking for');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTickets()', () => {
|
|
||||||
it('should perform a and then call to the ticketModel refresh() method', () => {
|
|
||||||
jest.spyOn(controller.$.ticketsModel, 'refresh');
|
|
||||||
|
|
||||||
controller.getTickets();
|
|
||||||
$scope.$apply();
|
|
||||||
|
|
||||||
expect(controller.$.ticketsModel.url).toEqual('InvoiceOuts/1/getTickets');
|
|
||||||
expect(controller.$.ticketsModel.refresh).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -71,7 +71,7 @@ module.exports = Self => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const originalTaxes = await models.ItemTaxCountry.find({
|
const originalTaxes = await models.ItemTaxCountry.find({
|
||||||
where: {itemFk: originalId},
|
where: {itemFk: originalId},
|
||||||
fields: ['botanical', 'countryFk', 'taxClassFk']
|
fields: ['countryFk', 'taxClassFk']
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
@ -99,7 +99,7 @@ module.exports = Self => {
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const botanical = await models.ItemBotanical.findOne({
|
const botanical = await models.ItemBotanical.findOne({
|
||||||
where: {itemFk: originalId},
|
where: {itemFk: originalId},
|
||||||
fields: ['botanical', 'genusFk', 'specieFk']
|
fields: ['genusFk', 'specieFk']
|
||||||
}, options);
|
}, options);
|
||||||
|
|
||||||
if (botanical) {
|
if (botanical) {
|
||||||
|
|
|
@ -3,8 +3,7 @@
|
||||||
"base": "Loggable",
|
"base": "Loggable",
|
||||||
"log": {
|
"log": {
|
||||||
"model": "ItemLog",
|
"model": "ItemLog",
|
||||||
"relation": "item",
|
"relation": "item"
|
||||||
"showField": "botanical"
|
|
||||||
},
|
},
|
||||||
"options": {
|
"options": {
|
||||||
"mysql": {
|
"mysql": {
|
||||||
|
|
|
@ -6,18 +6,18 @@ module.exports = Self => {
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'sales',
|
arg: 'sales',
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The sales to remove'
|
description: 'The sales to remove'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'ticketId',
|
arg: 'ticketId',
|
||||||
type: 'Number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ticket id'
|
description: 'The ticket id'
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -27,10 +27,25 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.deleteSales = async(ctx, sales, ticketId) => {
|
Self.deleteSales = async(ctx, sales, ticketId) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const canEditSales = await models.Sale.canEdit(ctx, sales);
|
const canEditSales = await models.Sale.canEdit(ctx, sales);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
||||||
if (!isTicketEditable)
|
if (!isTicketEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
@ -39,11 +54,26 @@ module.exports = Self => {
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
let deletions = '';
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
const deletedSale = models.Sale.destroyById(sale.id);
|
const deletedSale = models.Sale.destroyById(sale.id);
|
||||||
|
deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
|
|
||||||
promises.push(deletedSale);
|
promises.push(deletedSale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Deleted sales from ticket', {
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
deletions: deletions
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,24 +5,27 @@ module.exports = Self => {
|
||||||
Self.remoteMethodCtx('reserve', {
|
Self.remoteMethodCtx('reserve', {
|
||||||
description: 'Change the state of a ticket',
|
description: 'Change the state of a ticket',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [
|
||||||
arg: 'ticketId',
|
{
|
||||||
type: 'Number',
|
arg: 'ticketId',
|
||||||
required: true,
|
type: 'number',
|
||||||
description: 'The ticket id'
|
required: true,
|
||||||
}, {
|
description: 'The ticket id'
|
||||||
arg: 'sales',
|
},
|
||||||
type: ['Object'],
|
{
|
||||||
required: true,
|
arg: 'sales',
|
||||||
description: 'The sale to reserve'
|
type: ['object'],
|
||||||
},
|
required: true,
|
||||||
{
|
description: 'The sale to reserve'
|
||||||
arg: 'reserved',
|
},
|
||||||
type: 'Boolean',
|
{
|
||||||
required: true
|
arg: 'reserved',
|
||||||
}],
|
type: 'boolean',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -32,7 +35,9 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.reserve = async(ctx, ticketId, sales, reserved) => {
|
Self.reserve = async(ctx, ticketId, sales, reserved) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
||||||
if (!isTicketEditable)
|
if (!isTicketEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
@ -42,12 +47,50 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
|
let changesMade = '';
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
|
if (sale.reserved != reserved) {
|
||||||
promises.push(reservedSale);
|
const oldState = sale.reserved ? 'reserved' : 'regular';
|
||||||
|
const newState = reserved ? 'reserved' : 'regular';
|
||||||
|
|
||||||
|
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
|
||||||
|
|
||||||
|
promises.push(reservedSale);
|
||||||
|
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
const result = await Promise.all(promises);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Changed sale reserved state', {
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,14 @@ describe('sale deleteSales()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket of the given sales is not editable', async() => {
|
it('should throw an error if the ticket of the given sales is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
|
|
||||||
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
|
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
|
||||||
|
@ -33,7 +40,13 @@ describe('sale deleteSales()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the sale', async() => {
|
it('should delete the sale', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const sales = [{id: newSale.id, instance: 0}];
|
const sales = [{id: newSale.id, instance: 0}];
|
||||||
const ticketId = 16;
|
const ticketId = 16;
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('sale reserve()', () => {
|
describe('sale reserve()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
afterAll(async done => {
|
afterAll(async done => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {req: {accessToken: {userId: 9}}};
|
||||||
let params = {
|
let params = {
|
||||||
|
|
|
@ -17,7 +17,13 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket is not editable', async() => {
|
it('should throw an error if the ticket is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let immutableSaleId = 1;
|
let immutableSaleId = 1;
|
||||||
let price = 5;
|
let price = 5;
|
||||||
|
|
||||||
|
@ -31,7 +37,14 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 0 if the price is an empty string', async() => {
|
it('should return 0 if the price is an empty string', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let price = '';
|
let price = '';
|
||||||
|
|
||||||
await app.models.Sale.updatePrice(ctx, saleId, price);
|
await app.models.Sale.updatePrice(ctx, saleId, price);
|
||||||
|
@ -46,7 +59,14 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should now set price as a number in a string', async() => {
|
it('should now set price as a number in a string', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let price = '8';
|
let price = '8';
|
||||||
|
|
||||||
await app.models.Sale.updatePrice(ctx, saleId, price);
|
await app.models.Sale.updatePrice(ctx, saleId, price);
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('sale updateQuantity()', () => {
|
describe('sale updateQuantity()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
it('should throw an error if the quantity is not a number', async() => {
|
it('should throw an error if the quantity is not a number', async() => {
|
||||||
let error;
|
let error;
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Number',
|
type: 'number',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -29,29 +29,37 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updatePrice = async(ctx, id, newPrice) => {
|
Self.updatePrice = async(ctx, id, newPrice) => {
|
||||||
let models = Self.app.models;
|
const $t = ctx.req.__; // $translate
|
||||||
let tx = await Self.beginTransaction({});
|
const models = Self.app.models;
|
||||||
|
const tx = await Self.beginTransaction({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
let filter = {
|
const filter = {
|
||||||
include: {
|
include: {
|
||||||
relation: 'ticket',
|
relation: 'ticket',
|
||||||
scope: {
|
scope: {
|
||||||
include: {
|
include: {
|
||||||
relation: 'client',
|
relation: 'client',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['salesPersonFk']
|
fields: ['salesPersonFk'],
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fields: ['id', 'clientFk']
|
fields: ['id', 'clientFk']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let sale = await models.Sale.findById(id, filter, options);
|
|
||||||
|
|
||||||
let isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
|
const sale = await models.Sale.findById(id, filter, options);
|
||||||
|
|
||||||
|
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
|
||||||
if (!isEditable)
|
if (!isEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
|
||||||
|
@ -60,21 +68,19 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
|
const oldPrice = sale.price;
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
|
||||||
|
const componentCode = usesMana ? 'mana' : 'buyerDiscount';
|
||||||
|
const discount = await models.Component.findOne({where: {code: componentCode}}, options);
|
||||||
|
const componentId = discount.id;
|
||||||
|
const componentValue = newPrice - sale.price;
|
||||||
|
|
||||||
let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
|
const where = {
|
||||||
|
|
||||||
let componentCode = usesMana ? 'mana' : 'buyerDiscount';
|
|
||||||
|
|
||||||
let discount = await models.Component.findOne({where: {code: componentCode}}, options);
|
|
||||||
let componentId = discount.id;
|
|
||||||
let componentValue = newPrice - sale.price;
|
|
||||||
|
|
||||||
let where = {
|
|
||||||
componentFk: componentId,
|
componentFk: componentId,
|
||||||
saleFk: id
|
saleFk: id
|
||||||
};
|
};
|
||||||
let saleComponent = await models.SaleComponent.findOne({where}, options);
|
const saleComponent = await models.SaleComponent.findOne({where}, options);
|
||||||
|
|
||||||
if (saleComponent) {
|
if (saleComponent) {
|
||||||
await models.SaleComponent.updateAll(where, {
|
await models.SaleComponent.updateAll(where, {
|
||||||
|
@ -92,6 +98,22 @@ module.exports = Self => {
|
||||||
query = `CALL vn.manaSpellersRequery(?)`;
|
query = `CALL vn.manaSpellersRequery(?)`;
|
||||||
await Self.rawSql(query, [userId], options);
|
await Self.rawSql(query, [userId], options);
|
||||||
|
|
||||||
|
const salesPerson = sale.ticket().client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
const message = $t('Changed sale price', {
|
||||||
|
ticketId: sale.ticket().id,
|
||||||
|
itemId: sale.itemFk,
|
||||||
|
concept: sale.concept,
|
||||||
|
quantity: sale.quantity,
|
||||||
|
oldPrice: oldPrice,
|
||||||
|
newPrice: newPrice,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
|
||||||
|
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
|
|
||||||
return sale;
|
return sale;
|
||||||
|
|
|
@ -26,7 +26,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updateQuantity = async(ctx, id, quantity) => {
|
Self.updateQuantity = async(ctx, id, newQuantity) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const canEditSale = await models.Sale.canEdit(ctx, [id]);
|
const canEditSale = await models.Sale.canEdit(ctx, [id]);
|
||||||
|
@ -34,13 +35,51 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
if (isNaN(quantity))
|
if (isNaN(newQuantity))
|
||||||
throw new UserError(`The value should be a number`);
|
throw new UserError(`The value should be a number`);
|
||||||
|
|
||||||
let currentLine = await models.Sale.findOne({where: {id: id}});
|
const filter = {
|
||||||
if (quantity > currentLine.quantity)
|
include: {
|
||||||
|
relation: 'ticket',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sale = await models.Sale.findById(id, filter);
|
||||||
|
|
||||||
|
if (newQuantity > sale.quantity)
|
||||||
throw new UserError('The new quantity should be smaller than the old one');
|
throw new UserError('The new quantity should be smaller than the old one');
|
||||||
|
|
||||||
return await currentLine.updateAttributes({quantity: quantity});
|
const oldQuantity = sale.quantity;
|
||||||
|
const result = await sale.updateAttributes({quantity: newQuantity});
|
||||||
|
|
||||||
|
const salesPerson = sale.ticket().client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
const message = $t('Changed sale quantity', {
|
||||||
|
ticketId: sale.ticket().id,
|
||||||
|
itemId: sale.itemFk,
|
||||||
|
concept: sale.concept,
|
||||||
|
oldQuantity: oldQuantity,
|
||||||
|
newQuantity: newQuantity,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
|
||||||
|
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,6 +33,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.addSale = async(ctx, id, itemId, quantity) => {
|
Self.addSale = async(ctx, id, itemId, quantity) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const isEditable = await models.Ticket.isEditable(ctx, id);
|
const isEditable = await models.Ticket.isEditable(ctx, id);
|
||||||
|
@ -40,7 +41,19 @@ module.exports = Self => {
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
|
||||||
const item = await models.Item.findById(itemId);
|
const item = await models.Item.findById(itemId);
|
||||||
const ticket = await models.Ticket.findById(id);
|
const ticket = await models.Ticket.findById(id, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
|
const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
|
||||||
|
|
||||||
|
@ -63,6 +76,20 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addition = `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Added sale to ticket', {
|
||||||
|
ticketId: id,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${id}/sale`,
|
||||||
|
addition: addition
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
return sale;
|
return sale;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -230,7 +230,7 @@ module.exports = Self => {
|
||||||
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
|
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
|
||||||
changes: changesMade
|
changes: changesMade
|
||||||
});
|
});
|
||||||
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message, myOptions);
|
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
|
@ -60,8 +60,12 @@ module.exports = Self => {
|
||||||
const [salesAvailable] = await Self.rawSql(query, [id], myOptions);
|
const [salesAvailable] = await Self.rawSql(query, [id], myOptions);
|
||||||
|
|
||||||
const itemAvailable = new Map();
|
const itemAvailable = new Map();
|
||||||
for (let sale of salesAvailable)
|
for (let sale of salesAvailable) {
|
||||||
itemAvailable.set(sale.itemFk, sale.available);
|
itemAvailable.set(sale.itemFk, {
|
||||||
|
visible: sale.visible,
|
||||||
|
available: sale.available
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Get claimed sales
|
// Get claimed sales
|
||||||
const saleIds = sales.map(sale => sale.id);
|
const saleIds = sales.map(sale => sale.id);
|
||||||
|
@ -84,7 +88,9 @@ module.exports = Self => {
|
||||||
|
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
const problems = saleProblems.get(sale.id);
|
const problems = saleProblems.get(sale.id);
|
||||||
sale.available = itemAvailable.get(sale.itemFk);
|
const itemStock = itemAvailable.get(sale.itemFk);
|
||||||
|
sale.available = itemStock.available;
|
||||||
|
sale.visible = itemStock.visible;
|
||||||
sale.claim = claimedSales.get(sale.id);
|
sale.claim = claimedSales.get(sale.id);
|
||||||
if (problems) {
|
if (problems) {
|
||||||
sale.isAvailable = problems.isAvailable;
|
sale.isAvailable = problems.isAvailable;
|
||||||
|
|
|
@ -12,7 +12,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new sale for the ticket with id 13', async() => {
|
it('should create a new sale for the ticket with id 13', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const itemId = 4;
|
const itemId = 4;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
newSale = await app.models.Ticket.addSale(ctx, ticketId, itemId, quantity);
|
newSale = await app.models.Ticket.addSale(ctx, ticketId, itemId, quantity);
|
||||||
|
@ -21,7 +27,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add a sale if the item quantity is not available', async() => {
|
it('should not be able to add a sale if the item quantity is not available', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const itemId = 11;
|
const itemId = 11;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
|
|
||||||
|
@ -36,7 +48,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add a sale if the ticket is not editable', async() => {
|
it('should not be able to add a sale if the ticket is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const notEditableTicketId = 1;
|
const notEditableTicketId = 1;
|
||||||
const itemId = 4;
|
const itemId = 4;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
|
|
|
@ -35,7 +35,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if no sales were selected', async() => {
|
it('should throw an error if no sales were selected', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [];
|
const sales = [];
|
||||||
|
@ -51,7 +57,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if no sales belong to different tickets', async() => {
|
it('should throw an error if no sales belong to different tickets', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [1, 14];
|
const sales = [1, 14];
|
||||||
|
@ -67,7 +79,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket is invoiced already', async() => {
|
it('should throw an error if the ticket is invoiced already', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 1;
|
const ticketId = 1;
|
||||||
const sales = [1];
|
const sales = [1];
|
||||||
|
@ -83,7 +101,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the discount if the salesPerson has mana', async() => {
|
it('should update the discount if the salesPerson has mana', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [originalSaleId];
|
const sales = [originalSaleId];
|
||||||
const newDiscount = 100;
|
const newDiscount = 100;
|
||||||
|
@ -105,7 +129,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the discount and add company discount component if the worker does not have mana', async() => {
|
it('should update the discount and add company discount component if the worker does not have mana', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [originalSaleId];
|
const sales = [originalSaleId];
|
||||||
const newDiscount = 100;
|
const newDiscount = 100;
|
||||||
|
|
|
@ -17,7 +17,8 @@ module.exports = Self => {
|
||||||
description: 'The sales id',
|
description: 'The sales id',
|
||||||
type: ['number'],
|
type: ['number'],
|
||||||
required: true,
|
required: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'newDiscount',
|
arg: 'newDiscount',
|
||||||
description: 'The new discount',
|
description: 'The new discount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -35,6 +36,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
|
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const tx = await Self.beginTransaction({});
|
const tx = await Self.beginTransaction({});
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let sales = await models.Sale.find(filter, options);
|
const sales = await models.Sale.find(filter, options);
|
||||||
|
|
||||||
if (sales.length === 0)
|
if (sales.length === 0)
|
||||||
throw new UserError('Please select at least one sale');
|
throw new UserError('Please select at least one sale');
|
||||||
|
@ -94,8 +96,11 @@ module.exports = Self => {
|
||||||
where: {code: componentCode}}, options);
|
where: {code: componentCode}}, options);
|
||||||
|
|
||||||
const componentId = discountComponent.id;
|
const componentId = discountComponent.id;
|
||||||
let promises = [];
|
const promises = [];
|
||||||
|
let changesMade = '';
|
||||||
|
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
|
const oldDiscount = sale.discount;
|
||||||
const value = ((-sale.price * newDiscount) / 100);
|
const value = ((-sale.price * newDiscount) / 100);
|
||||||
const newComponent = models.SaleComponent.upsert({
|
const newComponent = models.SaleComponent.upsert({
|
||||||
saleFk: sale.id,
|
saleFk: sale.id,
|
||||||
|
@ -105,6 +110,7 @@ module.exports = Self => {
|
||||||
const updatedSale = sale.updateAttribute('discount', newDiscount, options);
|
const updatedSale = sale.updateAttribute('discount', newDiscount, options);
|
||||||
|
|
||||||
promises.push(newComponent, updatedSale);
|
promises.push(newComponent, updatedSale);
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${oldDiscount}% ➔ *${newDiscount}%*`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -112,6 +118,32 @@ module.exports = Self => {
|
||||||
const query = `call vn.manaSpellersRequery(?)`;
|
const query = `call vn.manaSpellersRequery(?)`;
|
||||||
await Self.rawSql(query, [userId], options);
|
await Self.rawSql(query, [userId], options);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(id, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Changed sale discount', {
|
||||||
|
ticketId: id,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${id}/sale`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -82,7 +82,10 @@ class Controller extends Section {
|
||||||
if (!this.ticket) return;
|
if (!this.ticket) return;
|
||||||
|
|
||||||
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
|
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
|
||||||
.then(res => this.ticketVolume = res.data[0].volume);
|
.then(res => {
|
||||||
|
if (res.data.length)
|
||||||
|
this.ticketVolume = res.data[0].volume;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -59,6 +59,7 @@
|
||||||
</vn-th>
|
</vn-th>
|
||||||
<vn-th shrink></vn-th>
|
<vn-th shrink></vn-th>
|
||||||
<vn-th shrink></vn-th>
|
<vn-th shrink></vn-th>
|
||||||
|
<vn-th shrink>Visible</vn-th>
|
||||||
<vn-th shrink>Available</vn-th>
|
<vn-th shrink>Available</vn-th>
|
||||||
<vn-th>Id</vn-th>
|
<vn-th>Id</vn-th>
|
||||||
<vn-th shrink>Quantity</vn-th>
|
<vn-th shrink>Quantity</vn-th>
|
||||||
|
@ -112,6 +113,13 @@
|
||||||
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
zoom-image="{{::$root.imagePath('catalog', '1600x900', sale.itemFk)}}"
|
||||||
on-error-src/>
|
on-error-src/>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
<vn-td shrink>
|
||||||
|
<vn-chip
|
||||||
|
class="transparent"
|
||||||
|
ng-class="{'alert': sale.visible < 0}">
|
||||||
|
{{::sale.visible}}
|
||||||
|
</vn-chip>
|
||||||
|
</vn-td>
|
||||||
<vn-td shrink>
|
<vn-td shrink>
|
||||||
<vn-chip
|
<vn-chip
|
||||||
class="transparent"
|
class="transparent"
|
||||||
|
|
|
@ -47,7 +47,13 @@ class Controller extends Section {
|
||||||
|
|
||||||
getMana() {
|
getMana() {
|
||||||
this.$http.get(`Tickets/${this.$params.id}/getSalesPersonMana`)
|
this.$http.get(`Tickets/${this.$params.id}/getSalesPersonMana`)
|
||||||
.then(res => this.edit.mana = res.data);
|
.then(res => {
|
||||||
|
this.edit.mana = res.data;
|
||||||
|
this.$.$applyAsync(() => {
|
||||||
|
this.$.editDiscount.relocate();
|
||||||
|
this.$.editPricePopover.relocate();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,6 +42,8 @@ describe('Ticket', () => {
|
||||||
$scope.sms = {open: () => {}};
|
$scope.sms = {open: () => {}};
|
||||||
$scope.ticket = ticket;
|
$scope.ticket = ticket;
|
||||||
$scope.model = crudModel;
|
$scope.model = crudModel;
|
||||||
|
$scope.editDiscount = {relocate: () => {}};
|
||||||
|
$scope.editPricePopover = {relocate: () => {}};
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
Object.defineProperties($state.params, {
|
Object.defineProperties($state.params, {
|
||||||
id: {
|
id: {
|
||||||
|
|
|
@ -120,6 +120,7 @@
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-th shrink></vn-th>
|
<vn-th shrink></vn-th>
|
||||||
<vn-th number shrink>Item</vn-th>
|
<vn-th number shrink>Item</vn-th>
|
||||||
|
<vn-th number shrink>Visible</vn-th>
|
||||||
<vn-th number shrink>Available</vn-th>
|
<vn-th number shrink>Available</vn-th>
|
||||||
<vn-th number shrink>Quantity</vn-th>
|
<vn-th number shrink>Quantity</vn-th>
|
||||||
<vn-th>Description</vn-th>
|
<vn-th>Description</vn-th>
|
||||||
|
@ -169,6 +170,13 @@
|
||||||
{{sale.itemFk | zeroFill:6}}
|
{{sale.itemFk | zeroFill:6}}
|
||||||
</span>
|
</span>
|
||||||
</vn-td>
|
</vn-td>
|
||||||
|
<vn-td number shrink>
|
||||||
|
<vn-chip
|
||||||
|
class="transparent"
|
||||||
|
ng-class="{'alert': sale.visible < 0}">
|
||||||
|
{{::sale.visible}}
|
||||||
|
</vn-chip>
|
||||||
|
</vn-td>
|
||||||
<vn-td number shrink>
|
<vn-td number shrink>
|
||||||
<vn-chip
|
<vn-chip
|
||||||
class="transparent"
|
class="transparent"
|
||||||
|
|
|
@ -35,7 +35,7 @@
|
||||||
{
|
{
|
||||||
"accessType": "READ",
|
"accessType": "READ",
|
||||||
"principalType": "ROLE",
|
"principalType": "ROLE",
|
||||||
"principalId": "$everyone",
|
"principalId": "employee",
|
||||||
"permission": "ALLOW"
|
"permission": "ALLOW"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -106,6 +106,7 @@ class Controller extends Section {
|
||||||
};
|
};
|
||||||
this.$.model.applyFilter(filter, params).then(() => {
|
this.$.model.applyFilter(filter, params).then(() => {
|
||||||
this.getWorkedHours(this.started, this.ended);
|
this.getWorkedHours(this.started, this.ended);
|
||||||
|
this.getAbsences();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +130,7 @@ class Controller extends Section {
|
||||||
onData(data) {
|
onData(data) {
|
||||||
const events = {};
|
const events = {};
|
||||||
|
|
||||||
let addEvent = (day, event) => {
|
const addEvent = (day, event) => {
|
||||||
events[new Date(day).getTime()] = event;
|
events[new Date(day).getTime()] = event;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -132,6 +132,16 @@
|
||||||
width: 8em
|
width: 8em
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.field.wide-rectangle {
|
||||||
|
white-space: nowrap
|
||||||
|
}
|
||||||
|
|
||||||
|
.field.wide-rectangle span {
|
||||||
|
display: inline-block;
|
||||||
|
height: 2em;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.pull-left {
|
.pull-left {
|
||||||
float: left !important
|
float: left !important
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
buttons:
|
buttons:
|
||||||
webAcccess: Visit our website
|
webAcccess: Visit our website
|
||||||
info: Help us to improve
|
info: Help us to improve
|
||||||
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
|
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
|
||||||
· www.verdnatura.es · clientes@verdnatura.es
|
· www.verdnatura.es · clientes@verdnatura.es
|
||||||
disclaimer: '- NOTICE - This message is private and confidential, and should be used
|
disclaimer: '- NOTICE - This message is private and confidential, and should be used
|
||||||
exclusively by the person receiving it. If you have received this message by mistake,
|
exclusively by the person receiving it. If you have received this message by mistake,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
buttons:
|
buttons:
|
||||||
webAcccess: Visitez notre site web
|
webAcccess: Visitez notre site web
|
||||||
info: Aidez-nous à améliorer
|
info: Aidez-nous à améliorer
|
||||||
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
|
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
|
||||||
· www.verdnatura.es · clientes@verdnatura.es
|
· www.verdnatura.es · clientes@verdnatura.es
|
||||||
disclaimer: "- AVIS - Ce message est privé et confidentiel et doit être utilisé
|
disclaimer: "- AVIS - Ce message est privé et confidentiel et doit être utilisé
|
||||||
exclusivement par le destinataire. Si vous avez reçu ce message par erreur,
|
exclusivement par le destinataire. Si vous avez reçu ce message par erreur,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
buttons:
|
buttons:
|
||||||
webAcccess: Visite o nosso site
|
webAcccess: Visite o nosso site
|
||||||
info: Ajude-nos a melhorar
|
info: Ajude-nos a melhorar
|
||||||
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 Avda. Espioca, 100, 46460 Silla
|
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESI
|
||||||
· www.verdnatura.es · clientes@verdnatura.es
|
· www.verdnatura.es · clientes@verdnatura.es
|
||||||
disclaimer: '- AVISO - Esta mensagem é privada e confidencial e deve ser usada exclusivamente
|
disclaimer: '- AVISO - Esta mensagem é privada e confidencial e deve ser usada exclusivamente
|
||||||
pela pessoa que a recebe. Se você recebeu esta mensagem por engano, notifique o remetente e
|
pela pessoa que a recebe. Se você recebeu esta mensagem por engano, notifique o remetente e
|
||||||
|
|
|
@ -89,8 +89,7 @@ const dbHelper = {
|
||||||
const absolutePath = path.join(__dirname, '../', this.path, 'sql', queryName);
|
const absolutePath = path.join(__dirname, '../', this.path, 'sql', queryName);
|
||||||
return db.getSqlFromDef(absolutePath);
|
return db.getSqlFromDef(absolutePath);
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
props: ['tplPath']
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Vue.mixin(dbHelper);
|
Vue.mixin(dbHelper);
|
||||||
|
|
|
@ -27,8 +27,8 @@ module.exports = {
|
||||||
await db.rawSql(`
|
await db.rawSql(`
|
||||||
INSERT INTO vn.mail (sender, replyTo, sent, subject, body, status)
|
INSERT INTO vn.mail (sender, replyTo, sent, subject, body, status)
|
||||||
VALUES (?, ?, 1, ?, ?, ?)`, [
|
VALUES (?, ?, 1, ?, ?, ?)`, [
|
||||||
options.replyTo,
|
|
||||||
options.to,
|
options.to,
|
||||||
|
options.replyTo,
|
||||||
options.subject,
|
options.subject,
|
||||||
options.text || options.html,
|
options.text || options.html,
|
||||||
error && error.message || 'Sent'
|
error && error.message || 'Sent'
|
||||||
|
|
|
@ -172,7 +172,7 @@ module.exports = app => {
|
||||||
|
|
||||||
for (const ticket of tickets) {
|
for (const ticket of tickets) {
|
||||||
try {
|
try {
|
||||||
await db.rawSql(`CALL vn.ticket_close(?)`, [ticket.id]);
|
await db.rawSql(`CALL vn.ticket_closeByTicket(?)`, [ticket.id]);
|
||||||
|
|
||||||
const hasToInvoice = ticket.hasToInvoice && ticket.hasDailyInvoice;
|
const hasToInvoice = ticket.hasToInvoice && ticket.hasDailyInvoice;
|
||||||
if (!ticket.salesPersonFk || !ticket.isToBeMailed || hasToInvoice) continue;
|
if (!ticket.salesPersonFk || !ticket.isToBeMailed || hasToInvoice) continue;
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/email.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "client-debt-statement.pdf",
|
||||||
|
"component": "client-debt-statement"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header v-bind="$props"></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<h1>{{ $t('title') }}</h1>
|
||||||
|
<p>{{$t('description.instructions')}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Preview block -->
|
||||||
|
<div class="grid-row" v-if="isPreview">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<attachment v-for="attachment in attachments"
|
||||||
|
v-bind:key="attachment.filename"
|
||||||
|
v-bind:attachment="attachment"
|
||||||
|
v-bind:args="$props">
|
||||||
|
</attachment>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer v-bind="$props"></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,25 @@
|
||||||
|
const Component = require(`${appPath}/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
const attachment = new Component('attachment');
|
||||||
|
const attachments = require('./attachments.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'client-debt-statement',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build(),
|
||||||
|
'attachment': attachment.build()
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {attachments};
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipientId: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
subject: Extracto de tu balance
|
||||||
|
title: Extracto de tu balance
|
||||||
|
description:
|
||||||
|
instructions: Adjuntamos el extracto de tu balance.
|
|
@ -0,0 +1,8 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/email.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "credit-request.pdf",
|
||||||
|
"component": "credit-request"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,55 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header v-bind="$props"></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<h1>{{ $t('title') }}</h1>
|
||||||
|
<p>{{$t('description.instructions')}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Preview block -->
|
||||||
|
<div class="grid-row" v-if="isPreview">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<attachment v-for="attachment in attachments"
|
||||||
|
v-bind:key="attachment.filename"
|
||||||
|
v-bind:attachment="attachment"
|
||||||
|
v-bind:args="$props">
|
||||||
|
</attachment>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer v-bind="$props"></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,17 @@
|
||||||
|
const Component = require(`${appPath}/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
const attachment = new Component('attachment');
|
||||||
|
const attachments = require('./attachments.json');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'credit-request',
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build(),
|
||||||
|
'attachment': attachment.build()
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {attachments};
|
||||||
|
},
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
subject: Solicitud de crédito
|
||||||
|
title: Solicitud de crédito
|
||||||
|
description:
|
||||||
|
instructions: Adjuntamos el formulario para solicitud de crédito.
|
|
@ -1,5 +1,5 @@
|
||||||
subject: Vossa nota de entrega
|
subject: Sua nota de entrega
|
||||||
title: Vossa nota de entrega
|
title: Sua nota de entrega
|
||||||
dear: Estimado cliente
|
dear: Estimado cliente
|
||||||
description: Já está disponível sua nota de entrega correspondente a encomenda numero <strong>{0}</strong>. <br/>
|
description: Já está disponível sua nota de entrega correspondente a encomenda numero <strong>{0}</strong>. <br/>
|
||||||
Para ver-lo faça um clique <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">neste link</a>.
|
Para ver-lo faça um clique <a href="https://www.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">neste link</a>.
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
subject: Vossa nota de entrega
|
subject: Sua nota de entrega
|
||||||
title: Vossa nota de entrega
|
title: Sua nota de entrega
|
||||||
dear: Estimado cliente
|
dear: Estimado cliente
|
||||||
description: Já está disponível sua nota de entrega correspondente a encomenda {0}. <br/>
|
description: Já está disponível sua nota de entrega correspondente a encomenda {0}. <br/>
|
||||||
Podes descarregar-la fazendo um clique no arquivo anexado ao e-mail.
|
Podes descarregar-la fazendo um clique no arquivo anexado ao e-mail.
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/email.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,6 @@
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"filename": "invoice.pdf",
|
||||||
|
"component": "invoice"
|
||||||
|
}
|
||||||
|
]
|
|
@ -0,0 +1,49 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<head>
|
||||||
|
<meta name="viewport" content="width=device-width">
|
||||||
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
<title>{{ $t('subject') }}</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
<!-- Header block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-header v-bind="$props"></email-header>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block vn-pa-ml">
|
||||||
|
<h1>{{ $t('title') }}</h1>
|
||||||
|
<p>{{$t('dear')}},</p>
|
||||||
|
<p v-html="$t('description', [invoice.ref, invoice.ticketFk])"></p>
|
||||||
|
<p v-html="$t('poll')"></p>
|
||||||
|
<p v-html="$t('help')"></p>
|
||||||
|
<p v-html="$t('conclusion')"></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<email-footer v-bind="$props"></email-footer>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Empty block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block empty"></div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,25 @@
|
||||||
|
const Component = require(`${appPath}/core/component`);
|
||||||
|
const emailHeader = new Component('email-header');
|
||||||
|
const emailFooter = new Component('email-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'invoice',
|
||||||
|
async serverPrefetch() {
|
||||||
|
this.invoice = await this.fetchInvoice(this.invoiceId);
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchInvoice(invoiceId) {
|
||||||
|
return this.findOneFromDef('invoice', [invoiceId]);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'email-header': emailHeader.build(),
|
||||||
|
'email-footer': emailFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
invoiceId: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
subject: Your invoice
|
||||||
|
title: Your invoice
|
||||||
|
dear: Dear client
|
||||||
|
description: The invoice <strong>{0}</strong> from the order <strong>{1}</strong> is now available. <br/>
|
||||||
|
You can download it by clicking on the attachment of this email.
|
||||||
|
poll: If you wish, you can answer our satisfaction survey to
|
||||||
|
help us provide better service. Your opinion is very important for us!
|
||||||
|
help: Any questions that arise, do not hesitate to consult it, <strong>we are here to assist you!</strong>
|
||||||
|
conclusion: Thanks for your attention!
|
|
@ -0,0 +1,10 @@
|
||||||
|
subject: Tu factura
|
||||||
|
title: Tu factura
|
||||||
|
dear: Estimado cliente
|
||||||
|
description: Ya está disponible la factura <strong>{0}</strong> correspondiente al pedido <strong>{1}</strong>. <br/>
|
||||||
|
Puedes descargarla haciendo clic en el adjunto de este correo.
|
||||||
|
poll: Si lo deseas, puedes responder a nuestra encuesta de satisfacción para
|
||||||
|
ayudarnos a prestar un mejor servicio. ¡Tu opinión es muy importante para nosotros!
|
||||||
|
help: Cualquier duda que te surja, no dudes en consultarla, <strong>¡estamos para
|
||||||
|
atenderte!</strong>
|
||||||
|
conclusion: ¡Gracias por tu atención!
|
|
@ -0,0 +1,9 @@
|
||||||
|
subject: Votre facture
|
||||||
|
title: Votre facture
|
||||||
|
dear: Cher client,
|
||||||
|
description: Le facture <strong>{0}</strong> correspondant à la commande <strong>{1}</strong> est maintenant disponible.<br/>
|
||||||
|
Vous pouvez le télécharger en cliquant sur la pièce jointe dans cet email.
|
||||||
|
poll: Si vous le souhaitez, vous pouvez répondre à notre questionaire de satisfaction
|
||||||
|
pour nous aider à améliorer notre service. Votre avis est très important pour nous!
|
||||||
|
help: N'hésitez pas nous envoyer toute doute ou question, <strong>nous sommes là pour vous aider!</strong>
|
||||||
|
conclusion: Merci pour votre attention!
|
|
@ -0,0 +1,9 @@
|
||||||
|
subject: Sua fatura
|
||||||
|
title: Sua fatura
|
||||||
|
dear: Estimado cliente
|
||||||
|
description: Já está disponível sua fatura <strong>{0}</strong> correspondente a encomenda {1}. <br/>
|
||||||
|
Podes descarregar-la fazendo um clique no arquivo anexado ao e-mail.
|
||||||
|
poll: Si o deseja, podes responder nosso questionário de satiscação para ajudar-nos a prestar-vos um melhor serviço. Tua opinião é muito importante para nós!
|
||||||
|
help: Cualquer dúvida que surja, no hesites em consultar-la, <strong>Estamos aqui para
|
||||||
|
atender-te!</strong>
|
||||||
|
conclusion: Obrigado por tua atenção!
|
|
@ -0,0 +1,7 @@
|
||||||
|
SELECT
|
||||||
|
io.ref,
|
||||||
|
t.id AS ticketFk
|
||||||
|
FROM invoiceOut io
|
||||||
|
JOIN ticket t ON t.refFk = io.ref
|
||||||
|
JOIN client c ON c.id = io.clientFk
|
||||||
|
WHERE io.id = ?
|
|
@ -0,0 +1,9 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/report.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,3 @@
|
||||||
|
table.column-oriented {
|
||||||
|
margin-top: 50px !important
|
||||||
|
}
|
|
@ -0,0 +1,95 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Header block -->
|
||||||
|
<report-header v-bind="$props"></report-header>
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div class="columns">
|
||||||
|
<div class="size50">
|
||||||
|
<div class="size75">
|
||||||
|
<h1 class="title uppercase">{{$t('title')}}</h1>
|
||||||
|
<table class="row-oriented">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray uppercase">{{$t('clientId')}}</td>
|
||||||
|
<th>{{client.id}}</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="font gray uppercase">{{$t('date')}}</td>
|
||||||
|
<th>{{dated}}</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="size50">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="header">{{$t('clientData')}}</div>
|
||||||
|
<div class="body">
|
||||||
|
<h3 class="uppercase">{{client.socialName}}</h3>
|
||||||
|
<div>
|
||||||
|
{{client.street}}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{client.postcode}}, {{client.city}} ({{client.province}})
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
{{client.country}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="column-oriented">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{$t('date')}}</th>
|
||||||
|
<th>{{$t('concept')}}</th>
|
||||||
|
<th class="number">{{$t('invoiced')}}</th>
|
||||||
|
<th class="number">{{$t('payed')}}</th>
|
||||||
|
<th class="number">{{$t('balance')}}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody v-for="sale in sales" :key="sale.id">
|
||||||
|
<tr>
|
||||||
|
<td>{{sale.issued | date('%d-%m-%Y')}}</td>
|
||||||
|
<td>{{sale.ref}}</td>
|
||||||
|
<td class="number">{{sale.debtOut}}</td>
|
||||||
|
<td class="number">{{sale.debtIn}}</td>
|
||||||
|
<td class="number">{{getBalance(sale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
<tfoot>
|
||||||
|
<tr>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
<td class="number">
|
||||||
|
<strong class="pull-left">Total</strong>
|
||||||
|
{{getTotalDebtOut() | currency('EUR', $i18n.locale)}}
|
||||||
|
</td>
|
||||||
|
<td class="number">{{getTotalDebtIn() | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
<td class="number">{{totalBalance | currency('EUR', $i18n.locale)}}</td>
|
||||||
|
</tr>
|
||||||
|
</tfoot>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<report-footer id="pageFooter"
|
||||||
|
v-bind:left-text="$t('client', [client.id])"
|
||||||
|
v-bind:center-text="client.socialName"
|
||||||
|
v-bind="$props">
|
||||||
|
</report-footer>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
|
@ -0,0 +1,78 @@
|
||||||
|
const Component = require(`${appPath}/core/component`);
|
||||||
|
const reportHeader = new Component('report-header');
|
||||||
|
const reportFooter = new Component('report-footer');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
name: 'client-debt-statement',
|
||||||
|
async serverPrefetch() {
|
||||||
|
this.client = await this.fetchClient(this.recipientId);
|
||||||
|
this.sales = await this.fetchSales(this.recipientId, this.from);
|
||||||
|
|
||||||
|
if (!this.client)
|
||||||
|
throw new Error('Something went wrong');
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
dated: function() {
|
||||||
|
const filters = this.$options.filters;
|
||||||
|
|
||||||
|
return filters.date(new Date(), '%d-%m-%Y');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {totalBalance: 0.00};
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
fetchClient(clientId) {
|
||||||
|
return this.findOneFromDef('client', [clientId]);
|
||||||
|
},
|
||||||
|
fetchSales(clientId, from) {
|
||||||
|
return this.rawSqlFromDef('sales', [
|
||||||
|
from,
|
||||||
|
clientId,
|
||||||
|
from,
|
||||||
|
clientId,
|
||||||
|
from,
|
||||||
|
clientId,
|
||||||
|
from,
|
||||||
|
clientId,
|
||||||
|
from,
|
||||||
|
clientId
|
||||||
|
]);
|
||||||
|
},
|
||||||
|
getBalance(sale) {
|
||||||
|
if (sale.debtOut)
|
||||||
|
this.totalBalance += parseFloat(sale.debtOut);
|
||||||
|
|
||||||
|
if (sale.debtIn)
|
||||||
|
this.totalBalance -= parseFloat(sale.debtIn);
|
||||||
|
|
||||||
|
return parseFloat(this.totalBalance.toFixed(2));
|
||||||
|
},
|
||||||
|
getTotalDebtOut() {
|
||||||
|
let debtOut = 0.00;
|
||||||
|
for (let sale of this.sales)
|
||||||
|
debtOut += sale.debtOut ? parseFloat(sale.debtOut) : 0;
|
||||||
|
|
||||||
|
return debtOut.toFixed(2);
|
||||||
|
},
|
||||||
|
getTotalDebtIn() {
|
||||||
|
let debtIn = 0.00;
|
||||||
|
for (let sale of this.sales)
|
||||||
|
debtIn += sale.debtIn ? parseFloat(sale.debtIn) : 0;
|
||||||
|
|
||||||
|
return debtIn.toFixed(2);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
components: {
|
||||||
|
'report-header': reportHeader.build(),
|
||||||
|
'report-footer': reportFooter.build()
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
recipientId: {
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
from: {
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,9 @@
|
||||||
|
title: Extracto
|
||||||
|
clientId: Cliente
|
||||||
|
clientData: Datos del cliente
|
||||||
|
date: Fecha
|
||||||
|
concept: Concepto
|
||||||
|
invoiced: Facturado
|
||||||
|
payed: Pagado
|
||||||
|
balance: Saldo
|
||||||
|
client: Cliente {0}
|
|
@ -0,0 +1,9 @@
|
||||||
|
title: Relevé de compte
|
||||||
|
clientId: Client
|
||||||
|
clientData: Données client
|
||||||
|
date: Date
|
||||||
|
concept: Objet
|
||||||
|
invoiced: Facturé
|
||||||
|
payed: Payé
|
||||||
|
balance: Solde
|
||||||
|
client: Client {0}
|
|
@ -0,0 +1,13 @@
|
||||||
|
SELECT
|
||||||
|
c.id,
|
||||||
|
c.socialName,
|
||||||
|
c.street,
|
||||||
|
c.postcode,
|
||||||
|
c.city,
|
||||||
|
c.fi,
|
||||||
|
p.name AS province,
|
||||||
|
ct.country
|
||||||
|
FROM client c
|
||||||
|
JOIN country ct ON ct.id = c.countryFk
|
||||||
|
LEFT JOIN province p ON p.id = c.provinceFk
|
||||||
|
WHERE c.id = ?
|
|
@ -0,0 +1,53 @@
|
||||||
|
SELECT
|
||||||
|
issued,
|
||||||
|
CAST(debtOut AS DECIMAL(10,2)) debtOut,
|
||||||
|
CAST(debtIn AS DECIMAL(10,2)) debtIn,
|
||||||
|
ref,
|
||||||
|
companyFk,
|
||||||
|
priority
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
? AS issued,
|
||||||
|
SUM(amountUnpaid) AS debtOut,
|
||||||
|
NULL AS debtIn,
|
||||||
|
'Saldo Anterior' AS ref,
|
||||||
|
companyFk,
|
||||||
|
0 as priority
|
||||||
|
FROM (
|
||||||
|
SELECT SUM(amount) AS amountUnpaid, companyFk, 0
|
||||||
|
FROM invoiceOut io
|
||||||
|
WHERE io.clientFk = ?
|
||||||
|
AND io.issued < ?
|
||||||
|
GROUP BY io.companyFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT SUM(-1 * amountPaid), companyFk, 0
|
||||||
|
FROM receipt
|
||||||
|
WHERE clientFk = ?
|
||||||
|
AND payed < ?
|
||||||
|
GROUP BY companyFk) AS transactions
|
||||||
|
GROUP BY companyFk
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
issued,
|
||||||
|
amount as debtOut,
|
||||||
|
NULL AS debtIn,
|
||||||
|
ref,
|
||||||
|
companyFk,
|
||||||
|
1
|
||||||
|
FROM invoiceOut
|
||||||
|
WHERE clientFk = ?
|
||||||
|
AND issued >= ?
|
||||||
|
UNION ALL
|
||||||
|
SELECT
|
||||||
|
r.payed,
|
||||||
|
NULL as debtOut,
|
||||||
|
r.amountPaid,
|
||||||
|
r.invoiceFk,
|
||||||
|
r.companyFk,
|
||||||
|
0
|
||||||
|
FROM receipt r
|
||||||
|
WHERE r.clientFk = ?
|
||||||
|
AND r.payed >= ?) t
|
||||||
|
INNER JOIN `client` c ON c.id = ?
|
||||||
|
HAVING debtOut <> 0 OR debtIn <> 0
|
||||||
|
ORDER BY issued, priority DESC, debtIn;
|
|
@ -0,0 +1,9 @@
|
||||||
|
const Stylesheet = require(`${appPath}/core/stylesheet`);
|
||||||
|
|
||||||
|
module.exports = new Stylesheet([
|
||||||
|
`${appPath}/common/css/spacing.css`,
|
||||||
|
`${appPath}/common/css/misc.css`,
|
||||||
|
`${appPath}/common/css/layout.css`,
|
||||||
|
`${appPath}/common/css/report.css`,
|
||||||
|
`${__dirname}/style.css`])
|
||||||
|
.mergeStyles();
|
|
@ -0,0 +1,52 @@
|
||||||
|
.wide {
|
||||||
|
width: 900px !important
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
position: absolute;
|
||||||
|
margin-top: -200px;
|
||||||
|
height: 400px;
|
||||||
|
top: 50%
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: 1.2em;
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
td > span {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
.green-background {
|
||||||
|
color: white;
|
||||||
|
background-color: #8dba25;
|
||||||
|
height: 2em;
|
||||||
|
line-height: 2em;
|
||||||
|
padding-left: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-panel td, .info-panel th {
|
||||||
|
padding: 1em 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.info-panel {
|
||||||
|
margin-bottom: 5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.field {
|
||||||
|
float: none
|
||||||
|
}
|
|
@ -0,0 +1,190 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html v-bind:lang="$i18n.locale">
|
||||||
|
<body>
|
||||||
|
<table class="grid">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<!-- Header block -->
|
||||||
|
<report-header v-bind="$props"></report-header>
|
||||||
|
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row info-panel">
|
||||||
|
<div class="grid-block">
|
||||||
|
<h1 class="title centered uppercase">{{$t('fields.title')}}</h1>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Block -->
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 70%; text-align: right; padding-right: 2em">{{$t('fields.date')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div class="info-panel">
|
||||||
|
<div class="green-background">
|
||||||
|
<h3>{{$t('fields.companyInfo')}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<table style="width: 100%">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>{{$t('fields.companyName')}}:</td>
|
||||||
|
<th colspan="3">
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{$t('fields.businessType')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{{$t('fields.antiquity')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{$t('fields.surface')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{{$t('fields.numberOfEmployees')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{$t('fields.owner')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{{$t('fields.phone')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>{{$t('fields.payer')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{{$t('fields.phone')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div class="info-panel" style="width: 98%">
|
||||||
|
<div class="green-background">
|
||||||
|
<h3>{{$t('fields.economicInfo')}}</h3>
|
||||||
|
</div>
|
||||||
|
<div class="body">
|
||||||
|
<table>
|
||||||
|
<tbody>
|
||||||
|
<tr class="row-oriented">
|
||||||
|
<td>{{$t('fields.previousSalesVolume')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-oriented">
|
||||||
|
<td>{{$t('fields.forecastedSalesVolume')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
<tr class="row-oriented">
|
||||||
|
<td>{{$t('fields.forecastedPurchases')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Block -->
|
||||||
|
<div class="grid-row">
|
||||||
|
<div class="grid-block">
|
||||||
|
<div>
|
||||||
|
<table style="border-top: 1px solid #DDD" class="info-panel">
|
||||||
|
<tbody>
|
||||||
|
<tr class="row-oriented" style="width: 100%">
|
||||||
|
<td>{{$t('fields.personFilling')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
<td>{{$t('fields.phone')}}:</td>
|
||||||
|
<th>
|
||||||
|
<div class="field wide-rectangle">
|
||||||
|
<span></span>
|
||||||
|
</div>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Footer block -->
|
||||||
|
<report-footer
|
||||||
|
id="pageFooter"
|
||||||
|
v-bind:left-text="dated"
|
||||||
|
v-bind="$props">
|
||||||
|
</report-footer>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue