2263_e2e_zone_descriptor #352

Merged
jgallego merged 5 commits from 2263_e2e_zone_descriptor into dev 2020-08-14 13:58:07 +00:00
157 changed files with 4125 additions and 2347 deletions
Showing only changes of commit 859f01aafe - Show all commits

View File

@ -8,7 +8,7 @@ RUN apt-get update \
ca-certificates \
gnupg2 \
libfontconfig \
&& curl -sL https://deb.nodesource.com/setup_10.x | bash - \
&& curl -sL https://deb.nodesource.com/setup_12.x | bash - \
&& apt-get install -y --no-install-recommends \
nodejs \
&& apt-get purge -y --auto-remove \

View File

@ -9,7 +9,7 @@ Salix is also the scientific name of a beautifull tree! :)
Required applications.
* Visual Studio Code
* Node.js = 10.15.3 LTS
* Node.js = 12.17.0 LTS
* Docker
In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command.

View File

@ -0,0 +1 @@
INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('WorkerLog', '*', 'READ', 'ALLOW', 'ROLE', 'hr');

View File

@ -0,0 +1,4 @@
UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213';
INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,42 @@
DROP procedure IF EXISTS `vn`.`itemLastEntries`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`itemLastEntries`(IN `vItem` INT, IN `vDays` DATE)
BEGIN
SELECT
w.id AS warehouseFk,
w.name AS warehouse,
tr.landed,
b.entryFk,
b.isIgnored,
b.price2,
b.price3,
b.stickers,
b.packing,
b.`grouping`,
b.groupingMode,
b.weight,
i.stems,
b.quantity,
b.buyingValue,
b.packageFk ,
s.id AS supplierFk,
s.name AS supplier
FROM itemType it
RIGHT JOIN (entry e
LEFT JOIN supplier s ON s.id = e.supplierFk
RIGHT JOIN buy b ON b.entryFk = e.id
LEFT JOIN item i ON i.id = b.itemFk
LEFT JOIN ink ON ink.id = i.inkFk
LEFT JOIN travel tr ON tr.id = e.travelFk
LEFT JOIN warehouse w ON w.id = tr.warehouseInFk
LEFT JOIN origin o ON o.id = i.originFk
) ON it.id = i.typeFk
LEFT JOIN edi.ekt ek ON b.ektFk = ek.id
WHERE b.itemFk = vItem And tr.shipped BETWEEN vDays AND DATE_ADD(CURDATE(), INTERVAl + 10 DAY)
ORDER BY tr.landed DESC , b.id DESC;
END$$
DELIMITER ;

View File

@ -0,0 +1,8 @@
ALTER TABLE `vn`.`route`
DROP FOREIGN KEY `fk_route_1`;
ALTER TABLE `vn`.`route`
ADD CONSTRAINT `fk_route_1`
FOREIGN KEY (`zoneFk`)
REFERENCES `vn`.`zone` (`id`)
ON DELETE SET NULL
ON UPDATE CASCADE;

View File

@ -0,0 +1,99 @@
DROP procedure IF EXISTS `vn`.`sale_calculateComponent`;
DELIMITER $$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponent`(vSale INT, vOption INT)
proc: BEGIN
/**
* Actualiza los componentes
*
* @param vSale Delivery date
* @param vOption indica en que componente pone el descuadre, NULL en casos habituales
*/
DECLARE vShipped DATE;
DECLARE vWarehouseFk SMALLINT;
DECLARE vAgencyModeFk INT;
DECLARE vAddressFk INT;
DECLARE vTicketFk BIGINT;
DECLARE vItemFk BIGINT;
DECLARE vLanded DATE;
DECLARE vIsEditable BOOLEAN;
DECLARE vZoneFk INTEGER;
SELECT t.refFk IS NULL AND (IFNULL(ts.alertLevel, 0) = 0 OR s.price = 0),
s.ticketFk,
s.itemFk ,
t.zoneFk,
t.warehouseFk,
t.shipped,
t.addressFk,
t.agencyModeFk,
t.landed
INTO vIsEditable,
vTicketFk,
vItemFk,
vZoneFk,
vWarehouseFk,
vShipped,
vAddressFk,
vAgencyModeFk,
vLanded
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
LEFT JOIN ticketState ts ON ts.ticketFk = t.id
WHERE s.id = vSale;
IF vLanded IS NULL OR vZoneFk IS NULL THEN
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');
END IF;
UPDATE ticket t
SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
WHERE t.id = vTicketFk AND t.landed IS NULL;
IF vZoneFk IS NULL THEN
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
UPDATE ticket t
SET t.zoneFk = vZoneFk
WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetLanded;
END IF;
-- rellena la tabla buyUltimate con la ultima compra
CALL buyUltimate (vWarehouseFk, vShipped);
DELETE FROM tmp.buyUltimate WHERE itemFk != vItemFk;
DROP TEMPORARY TABLE IF EXISTS tmp.ticketLot;
CREATE TEMPORARY TABLE tmp.ticketLot
SELECT vWarehouseFk warehouseFk, NULL available, vItemFk itemFk, buyFk, vZoneFk zoneFk
FROM tmp.buyUltimate
WHERE itemFk = vItemFk;
CALL catalog_componentPrepare();
CALL catalog_componentCalculate(vZoneFk, vAddressFk, vShipped, vWarehouseFk);
DROP TEMPORARY TABLE IF EXISTS tmp.sale;
CREATE TEMPORARY TABLE tmp.sale
(PRIMARY KEY (saleFk)) ENGINE = MEMORY
SELECT vSale saleFk,vWarehouseFk warehouseFk;
IF vOption IS NULL THEN
SET vOption = IF(vIsEditable, 1, 6);
END IF;
CALL ticketComponentUpdateSale(vOption);
CALL catalog_componentPurge();
DROP TEMPORARY TABLE tmp.buyUltimate;
DROP TEMPORARY TABLE tmp.sale;
END$$
DELIMITER ;

File diff suppressed because one or more lines are too long

View File

@ -1216,23 +1216,23 @@ INSERT INTO `bs`.`waste`(`buyer`, `year`, `week`, `family`, `saleTotal`, `saleWa
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Miscellaneous Accessories', '186', '0', '0.0'),
('HankPym', YEAR(DATE_ADD(CURDATE(), INTERVAL -1 WEEK)), WEEK(DATE_ADD(CURDATE(), INTERVAL -1 WEEK), 1), 'Adhesives', '277', '0', '0.0');
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`, `created`)
INSERT INTO `vn`.`buy`(`id`,`entryFk`,`itemFk`,`buyingValue`,`quantity`,`packageFk`,`stickers`,`freightValue`,`packageValue`,`comissionValue`,`packing`,`grouping`,`groupingMode`,`location`,`price1`,`price2`,`price3`,`minPrice`,`producer`,`printedStickers`,`isChecked`,`isIgnored`,`weight`, `created`)
VALUES
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, CURDATE());
(1, 1, 1, 50, 5000, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -2 MONTH)),
(2, 2, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, DATE_ADD(CURDATE(), INTERVAL -1 MONTH)),
(3, 3, 1, 50, 100, 4, 1, 1.500, 1.500, 0.000, 1, 1, 0, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 1, CURDATE()),
(4, 2, 2, 5, 450, 3, 1, 1.000, 1.000, 0.000, 10, 10, 0, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(5, 3, 3, 55, 500, 5, 1, 1.000, 1.000, 0.000, 1, 1, 0, NULL, 0.00, 78.3, 75.6, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(6, 4, 8, 50, 1000, 4, 1, 1.000, 1.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(7, 4, 9, 20, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 30.50, 29.00, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(8, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 2.5, CURDATE()),
(9, 4, 4, 1.25, 1000, 3, 1, 0.500, 0.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(10, 5, 1, 50, 10, 4, 1, 2.500, 2.500, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(11, 5, 4, 1.25, 10, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(12, 6, 4, 1.25, 0, 3, 1, 2.500, 2.500, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(13, 7, 1, 50, 0, 3, 1, 2.000, 2.000, 0.000, 1, 1, 1, NULL, 0.00, 99.6, 99.4, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(14, 7, 2, 5, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 7.30, 7.00, 0.00, NULL, 0, 1, 0, 4, CURDATE()),
(15, 7, 4, 1.25, 0, 3, 1, 2.000, 2.000, 0.000, 10, 10, 1, NULL, 0.00, 1.75, 1.67, 0.00, NULL, 0, 1, 0, 4, CURDATE());
INSERT INTO `hedera`.`order`(`id`, `date_send`, `customer_id`, `delivery_method_id`, `agency_id`, `address_id`, `company_id`, `note`, `source_app`, `confirmed`, `date_make`, `first_row_stamp`, `confirm_date`)
VALUES

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,123 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
// 2277 solucionar problema al testear procedimiento con start transaction / rollback
xdescribe('ticket_componentMakeUpdate()', () => {
it('should recalculate the ticket components without make modifications', async() => {
let stmts = [];
let stmt;
let params = {
ticketId: 11,
clientId: 102,
agencyModeId: 2,
addressId: 122,
zoneId: 3,
warehouseId: 1,
companyId: 442,
isDeleted: 0,
hasToBeUnrouted: 0,
componentOption: 1
};
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let originalTicketIndex = stmts.push(stmt) - 1;
stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
params.ticketId,
params.clientId,
params.agencyModeId,
params.addressId,
params.zoneId,
params.warehouseId,
params.companyId,
params.isDeleted,
params.hasToBeUnrouted,
params.componentOption
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let updatedTicketIndex = stmts.push(stmt) - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let originalTicketData = result[originalTicketIndex];
let updatedTicketData = result[updatedTicketIndex];
expect(originalTicketData[0].isDeleted).toEqual(updatedTicketData[0].isDeleted);
expect(originalTicketData[0].routeFk).toEqual(updatedTicketData[0].routeFk);
});
it('should delete and unroute a ticket and recalculate the components', async() => {
let stmts = [];
let stmt;
let params = {
ticketId: 11,
clientId: 102,
agencyModeId: 2,
addressId: 122,
zoneId: 3,
warehouseId: 1,
companyId: 442,
isDeleted: 1,
hasToBeUnrouted: 1,
componentOption: 1
};
stmts.push('START TRANSACTION');
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let originalTicketIndex = stmts.push(stmt) - 1;
stmt = new ParameterizedSQL('CALL vn.ticket_componentMakeUpdate(?, ?, ?, ?, ?, ?, ?, DATE_ADD(CURDATE(), INTERVAL +1 DAY), DATE_ADD(CURDATE(), INTERVAL +1 DAY), ?, ?, ?)', [
params.ticketId,
params.clientId,
params.agencyModeId,
params.addressId,
params.zoneId,
params.warehouseId,
params.companyId,
params.isDeleted,
params.hasToBeUnrouted,
params.componentOption
]);
stmts.push(stmt);
stmt = new ParameterizedSQL('SELECT * FROM vn.ticket WHERE id = ?', [
params.ticketId
]);
stmts.push(stmt);
let updatedTicketIndex = stmts.push(stmt) - 1;
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let originalTicketData = result[originalTicketIndex];
let updatedTicketData = result[updatedTicketIndex];
expect(originalTicketData[0].isDeleted).not.toEqual(updatedTicketData[0].isDeleted);
expect(originalTicketData[0].routeFk).not.toEqual(updatedTicketData[0].routeFk);
});
});

View File

@ -0,0 +1,31 @@
const app = require('vn-loopback/server/server');
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
describe('zone zone_getEvents()', () => {
it(`should return data for a agencyMode with deliveryMethod pickup`, async() => {
let stmts = [];
let stmt;
stmts.push('START TRANSACTION');
let params = {
zoneGeoFk: 1,
agencyModeFk: 1};
stmt = new ParameterizedSQL('CALL zone_getEvents(?, ?)', [
params.zoneGeoFk,
params.agencyModeFk,
]);
stmts.push(stmt);
stmts.push('ROLLBACK');
let sql = ParameterizedSQL.join(stmts, ';');
let result = await app.models.Ticket.rawStmt(sql);
let zonesEvents = result[1];
expect(zonesEvents.length).toBeGreaterThan(0);
});
});

View File

@ -86,7 +86,7 @@ export default {
invoiceByAddressCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by address"]',
verifiedDataCheckbox: 'vn-client-fiscal-data vn-check[label="Verified data"]',
hasToInvoiceCheckbox: 'vn-client-fiscal-data vn-check[label="Has to invoice"]',
invoiceByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Invoice by mail"]',
notifyByMailCheckbox: 'vn-client-fiscal-data vn-check[label="Notify by email"]',
viesCheckbox: 'vn-client-fiscal-data vn-check[label="Vies"]',
saveButton: 'button[type=submit]',
acceptDuplicationButton: '.vn-confirm.shown button[response=accept]',
@ -122,6 +122,12 @@ export default {
mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]',
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]',
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentId"] vn-icon-button[icon="add_circle"]',
newCustomsAgentFiscalID: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.nif"]',
newCustomsAgentFiscalName: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.fiscalName"]',
newCustomsAgentStreet: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.street"]',
newCustomsAgentPhone: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.phone"]',
saveNewCustomsAgentButton: 'button[response="accept"]',
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]',
secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
@ -579,6 +585,7 @@ export default {
claimState: 'vn-claim-basic-data vn-autocomplete[ng-model="$ctrl.claim.claimStateFk"]',
responsabilityInputRange: 'vn-range',
observation: 'vn-textarea[ng-model="$ctrl.claim.observation"]',
hasToPickUpCheckbox: 'vn-claim-basic-data vn-check[ng-model="$ctrl.claim.hasToPickUp"]',
saveButton: `button[type=submit]`
},
claimDetail: {
@ -613,8 +620,7 @@ export default {
firstLineDestination: 'vn-claim-action vn-tr:nth-child(1) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
secondLineDestination: 'vn-claim-action vn-tr:nth-child(2) vn-autocomplete[ng-model="saleClaimed.claimDestinationFk"]',
firstDeleteLine: 'vn-claim-action vn-tr:nth-child(1) vn-icon-button[icon="delete"]',
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]',
hasToPickUpCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.hasToPickUp"]'
isPaidWithManaCheckbox: 'vn-claim-action vn-check[ng-model="$ctrl.claim.isChargedToMana"]'
},
ordersIndex: {
searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
@ -828,6 +834,12 @@ export default {
},
travelThermograph: {
add: 'vn-travel-thermograph-index vn-float-button[icon="add"]',
addThermographIcon: 'vn-travel-thermograph-create vn-autocomplete vn-icon[icon="add_circle"]',
newThermographId: 'vn-textfield[ng-model="$ctrl.newThermograph.thermographId"]',
newThermographModel: 'vn-autocomplete[ng-model="$ctrl.newThermograph.model"]',
newThermographWarehouse: 'vn-autocomplete[ng-model="$ctrl.newThermograph.warehouseId"]',
newThermographTemperature: 'vn-autocomplete[ng-model="$ctrl.newThermograph.temperature"]',
createThermographButton: 'form button[response="accept"]',
thermographID: 'vn-travel-thermograph-create vn-autocomplete[ng-model="$ctrl.dms.thermographId"]',
uploadIcon: 'vn-travel-thermograph-create vn-icon[icon="icon-attach"]',
createdThermograph: 'vn-travel-thermograph-index vn-tbody > vn-tr',

View File

@ -72,7 +72,7 @@ describe('Client Edit fiscalData path', () => {
await page.waitToClick(selectors.clientFiscalData.frozenCheckbox);
await page.waitToClick(selectors.clientFiscalData.hasToInvoiceCheckbox);
await page.waitToClick(selectors.clientFiscalData.viesCheckbox);
await page.waitToClick(selectors.clientFiscalData.invoiceByMailCheckbox);
await page.waitToClick(selectors.clientFiscalData.notifyByMailCheckbox);
await page.waitToClick(selectors.clientFiscalData.invoiceByAddressCheckbox);
await page.waitToClick(selectors.clientFiscalData.equalizationTaxCheckbox);
await page.waitToClick(selectors.clientFiscalData.verifiedDataCheckbox);
@ -230,8 +230,8 @@ describe('Client Edit fiscalData path', () => {
expect(result).toBe('checked');
});
it('should confirm Invoice by mail checkbox is unchecked', async() => {
const result = await page.checkboxState(selectors.clientFiscalData.invoiceByMailCheckbox);
it('should confirm Notify by email checkbox is unchecked', async() => {
const result = await page.checkboxState(selectors.clientFiscalData.notifyByMailCheckbox);
expect(result).toBe('unchecked');
});

View File

@ -61,12 +61,18 @@ describe('Client Add address path', () => {
expect(message.text).toBe('Customs agent is required for a non UEE member');
});
it(`should create a new address with all it's data`, async() => {
await page.autocompleteSearch(selectors.clientAddresses.customsAgent, 'Agent one');
it(`should create a new custom agent and then save the address`, async() => {
await page.waitToClick(selectors.clientAddresses.addNewCustomsAgent);
await page.write(selectors.clientAddresses.newCustomsAgentFiscalID, 'ID');
await page.write(selectors.clientAddresses.newCustomsAgentFiscalName, 'name');
await page.write(selectors.clientAddresses.newCustomsAgentStreet, 'street');
await page.write(selectors.clientAddresses.newCustomsAgentPhone, '555555555');
await page.waitToClick(selectors.clientAddresses.saveNewCustomsAgentButton);
await page.waitToClick(selectors.clientAddresses.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
expect(message.text).toBe('Data saved!');
});
it(`should navigate back to the addresses index`, async() => {

View File

@ -34,6 +34,15 @@ describe('Claim edit basic data path', () => {
await page.waitForState('claim.card.detail');
});
it('should check the "Pick up" checkbox', async() => {
await page.reloadSection('claim.card.basicData');
await page.waitToClick(selectors.claimBasicData.hasToPickUpCheckbox);
await page.waitToClick(selectors.claimBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should confirm the claim state was edited', async() => {
await page.reloadSection('claim.card.basicData');
await page.wait(selectors.claimBasicData.claimState);
@ -42,6 +51,12 @@ describe('Claim edit basic data path', () => {
expect(result).toEqual('Gestionado');
});
it('should confirm the "is paid with mana" and "Pick up" checkbox are checked', async() => {
const hasToPickUpCheckbox = await page.checkboxState(selectors.claimBasicData.hasToPickUpCheckbox);
expect(hasToPickUpCheckbox).toBe('checked');
});
it('should confirm the claim observation was edited', async() => {
const result = await page
.waitToGetProperty(selectors.claimBasicData.observation, 'value');

View File

@ -72,19 +72,10 @@ describe('Claim action path', () => {
expect(message.type).toBe('success');
});
it('should check the "Pick up" checkbox', async() => {
await page.waitToClick(selectors.claimAction.hasToPickUpCheckbox);
const message = await page.waitForSnackbar();
expect(message.type).toBe('success');
});
it('should confirm the "is paid with mana" and "Pick up" checkbox are checked', async() => {
it('should confirm the "is paid with mana" is checked', async() => {
await page.reloadSection('claim.card.action');
const isPaidWithManaCheckbox = await page.checkboxState(selectors.claimAction.isPaidWithManaCheckbox);
const hasToPickUpCheckbox = await page.checkboxState(selectors.claimAction.hasToPickUpCheckbox);
expect(isPaidWithManaCheckbox).toBe('checked');
expect(hasToPickUpCheckbox).toBe('checked');
});
});

View File

@ -32,6 +32,7 @@ describe('Order lines', () => {
});
it('should confirm the order subtotal has changed', async() => {
await page.waitForTextInElement(selectors.orderLine.orderSubtotal, '90.10');
const result = await page
.waitToGetProperty(selectors.orderLine.orderSubtotal, 'innerText');

View File

@ -2,6 +2,7 @@ import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Travel thermograph path', () => {
const thermographName = '7H3-37H3RN4L-FL4M3';
let browser;
let page;
@ -26,10 +27,18 @@ describe('Travel thermograph path', () => {
await page.waitForState('travel.card.thermograph.create');
});
it('should select the thermograph and then the file to upload', async() => {
it('should click on the add thermograph icon of the thermograph autocomplete', async() => {
await page.waitToClick(selectors.travelThermograph.addThermographIcon);
await page.write(selectors.travelThermograph.newThermographId, thermographName);
await page.autocompleteSearch(selectors.travelThermograph.newThermographModel, 'TEMPMATE');
await page.autocompleteSearch(selectors.travelThermograph.newThermographWarehouse, 'Warehouse Two');
await page.autocompleteSearch(selectors.travelThermograph.newThermographTemperature, 'WARM');
await page.waitToClick(selectors.travelThermograph.createThermographButton);
});
it('should select the file to upload', async() => {
let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/dms/ecc/3.jpeg`;
await page.autocompleteSearch(selectors.travelThermograph.thermographID, '138350-0');
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
@ -38,11 +47,17 @@ describe('Travel thermograph path', () => {
await fileChooser.accept([filePath]);
await page.waitToClick(selectors.travelThermograph.upload);
const message = await page.waitForSnackbar();
const state = await page.getState();
expect(message.type).toBe('success');
expect(state).toBe('travel.card.thermograph.index');
});
it('should reload the section and check everything was saved', async() => {
let createdThermograph = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText');
it('should check everything was saved correctly', async() => {
const result = await page.waitToGetProperty(selectors.travelThermograph.createdThermograph, 'innerText');
expect(createdThermograph).toContain('138350-0');
expect(result).toContain(thermographName);
});
});

View File

@ -78,7 +78,7 @@ export default class Searchbar extends Component {
}
fetchStateFilter(autoLoad) {
let filter = null;
let filter = this.filter ? this.filter : null;
if (this.$state.is(this.searchState)) {
if (this.$params.q) {

View File

@ -0,0 +1,55 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
/**
* Returns a set of allowed values defined on table scheme
* @param {String} column - Model or Table column name
* @return {Array} - Array of set values
*/
Self.getEnumValues = async function(column) {
let model = this.app.models[this.modelName].definition;
let properties = model.properties;
let tableName = this.modelName;
let schema = null;
if (model.settings && model.settings.mysql) {
let tableSplit = model.settings.mysql.table.split('.');
tableName = tableSplit.pop();
schema = tableSplit.pop() || null;
}
let property = properties[column];
if (!property)
throw new UserError(`Column does not exist`);
let columnName = property.mysql
? property.mysql.columnName
: column;
let columnInfo = await this.rawSql(
`SELECT column_type columnType
FROM information_schema.columns
WHERE table_name = ?
AND table_schema = IFNULL(?, DATABASE())
AND column_name = ?`,
[tableName, schema, columnName]
);
if (!columnInfo || !columnInfo[0])
throw new UserError(`Cannot fetch column values`);
let setValues;
setValues = columnInfo[0].columnType
.replace(/^enum\((.*)\)$/i, '$1')
.replace(/'/g, '')
.match(new RegExp(/(\w+)+/, 'ig'));
let values = [];
setValues.forEach(setValue => {
values.push({value: setValue});
});
return values;
};
};

View File

@ -0,0 +1,18 @@
const app = require('vn-loopback/server/server');
describe('Model getEnumValues()', () => {
it('should extend getEnumValues properties to any model passed', () => {
let exampleModel = app.models.TravelThermograph;
expect(exampleModel.getEnumValues).toBeDefined();
});
it('should return an array of enum values from a given column', async() => {
let result = await app.models.TravelThermograph.getSetValues('temperature');
expect(result.length).toEqual(3);
expect(result[0].value).toEqual('enum');
expect(result[1].value).toEqual('COOL');
expect(result[2].value).toEqual('WARM');
});
});

View File

@ -12,23 +12,34 @@ module.exports = function(Self) {
});
Self.observe('before save', async function(ctx) {
let options = {};
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let oldInstance;
let oldInstanceFk;
let newInstance;
if (ctx.data) {
oldInstanceFk = pick(ctx.currentInstance, Object.keys(ctx.data));
const changes = pick(ctx.currentInstance, Object.keys(ctx.data));
newInstance = await fkToValue(ctx.data, ctx);
oldInstance = await fkToValue(oldInstanceFk, ctx);
oldInstance = await fkToValue(changes, ctx);
if (ctx.where && !ctx.currentInstance) {
let fields = Object.keys(ctx.data);
ctx.oldInstances = await ctx.Model.app.models[ctx.Model.definition.name].find({where: ctx.where, fields: fields}, options);
const fields = Object.keys(ctx.data);
const modelName = definition.name;
ctx.oldInstances = await appModels[modelName].find({
where: ctx.where,
fields: fields
}, options);
}
}
// Get changes from created instance
if (ctx.isNewInstance)
newInstance = await fkToValue(ctx.instance.__data, ctx);
@ -37,18 +48,24 @@ module.exports = function(Self) {
});
Self.observe('before delete', async function(ctx) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const relations = ctx.Model.relations;
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
if (ctx.where) {
let affectedModel = ctx.Model.definition.name;
let definition = ctx.Model.definition;
let deletedInstances = await ctx.Model.app.models[affectedModel].find({where: ctx.where}, options);
let affectedModel = definition.name;
let deletedInstances = await appModels[affectedModel].find({
where: ctx.where
}, options);
let relation = definition.settings.log.relation;
if (relation) {
let primaryKey = ctx.Model.relations[relation].keyFrom;
let primaryKey = relations[relation].keyFrom;
let arrangedDeletedInstances = [];
for (let i = 0; i < deletedInstances.length; i++) {
@ -69,6 +86,8 @@ module.exports = function(Self) {
});
async function logDeletedInstances(ctx, loopBackContext) {
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
let options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
@ -78,14 +97,12 @@ module.exports = function(Self) {
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;
let definition = ctx.Model.definition;
let changedModelValue = definition.settings.log.changedModelValue;
let logRecord = {
originFk: instance.originFk,
userFk: userFk,
action: 'delete',
changedModel: ctx.Model.definition.name,
changedModel: definition.name,
changedModelId: instance.id,
changedModelValue: instance[changedModelValue],
oldInstance: instance,
@ -95,26 +112,44 @@ module.exports = function(Self) {
delete instance.originFk;
let logModel = definition.settings.log.model;
await ctx.Model.app.models[logModel].create(logRecord, options);
await appModels[logModel].create(logRecord, options);
});
}
// Get log values from a foreign key
async function fkToValue(instance, ctx) {
const appModels = ctx.Model.app.models;
const relations = ctx.Model.relations;
let options = {};
// Check for transactions
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let cleanInstance = JSON.parse(JSON.stringify(instance));
let result = {};
for (let key in cleanInstance) {
let val = cleanInstance[key];
if (val === undefined || val === null) continue;
for (let key1 in ctx.Model.relations) {
let val1 = ctx.Model.relations[key1];
if (val1.keyFrom == key && key != 'id') {
let recordSet = await ctx.Model.app.models[val1.modelTo.modelName].findById(val, null, options);
const instanceCopy = JSON.parse(JSON.stringify(instance));
const result = {};
for (const key in instanceCopy) {
let value = instanceCopy[key];
if (value instanceof Object)
continue;
if (value === undefined || value === null) continue;
for (let relationName in relations) {
const relation = relations[relationName];
if (relation.keyFrom == key && key != 'id') {
const model = relation.modelTo;
const modelName = relation.modelTo.modelName;
const properties = model && model.definition.properties;
const settings = model && model.definition.settings;
const recordSet = await appModels[modelName].findById(value, null, options);
const hasShowField = settings.log && settings.log.showField;
let showField = hasShowField && recordSet
&& recordSet[settings.log.showField];
let showField = val1.modelTo && val1.modelTo.definition.settings.log && val1.modelTo.definition.settings.log.showField && recordSet && recordSet[val1.modelTo.definition.settings.log.showField];
if (!showField) {
const showFieldNames = [
'name',
@ -122,7 +157,10 @@ module.exports = function(Self) {
'code'
];
for (field of showFieldNames) {
if (val1.modelTo.definition.properties && val1.modelTo.definition.properties[field] && recordSet && recordSet[field]) {
const propField = properties && properties[field];
const recordField = recordSet && recordSet[field];
if (propField && recordField) {
showField = field;
break;
}
@ -130,25 +168,29 @@ module.exports = function(Self) {
}
if (showField && recordSet && recordSet[showField]) {
val = recordSet[showField];
value = recordSet[showField];
break;
}
val = recordSet && recordSet.id || val;
value = recordSet && recordSet.id || value;
break;
}
}
result[key] = val;
result[key] = value;
}
return result;
}
async function logInModel(ctx, loopBackContext) {
let options = {};
const appModels = ctx.Model.app.models;
const definition = ctx.Model.definition;
const defSettings = ctx.Model.definition.settings;
const relations = ctx.Model.relations;
const options = {};
if (ctx.options && ctx.options.transaction)
options.transaction = ctx.options.transaction;
let definition = ctx.Model.definition;
let primaryKey;
for (let property in definition.properties) {
if (definition.properties[property].id) {
@ -163,11 +205,11 @@ module.exports = function(Self) {
// RELATIONS LOG
let changedModelId;
if (ctx.instance && !definition.settings.log.relation) {
if (ctx.instance && !defSettings.log.relation) {
originId = ctx.instance.id;
changedModelId = ctx.instance.id;
} else if (definition.settings.log.relation) {
primaryKey = ctx.Model.relations[definition.settings.log.relation].keyFrom;
} else if (defSettings.log.relation) {
primaryKey = relations[defSettings.log.relation].keyFrom;
if (ctx.where && ctx.where[primaryKey])
originId = ctx.where[primaryKey];
@ -181,12 +223,16 @@ module.exports = function(Self) {
}
// Sets the changedModelValue to save and the instances changed in case its an updateAll
let showField = definition.settings.log.showField;
let showField = defSettings.log.showField;
let where;
if (showField && (!ctx.instance || !ctx.instance[showField]) && ctx.where) {
changedModelId = [];
where = [];
let changedInstances = await ctx.Model.app.models[definition.name].find({where: ctx.where, fields: ['id', showField, primaryKey]}, options);
let changedInstances = await appModels[definition.name].find({
where: ctx.where,
fields: ['id', showField, primaryKey]
}, options);
changedInstances.forEach(element => {
where.push(element[showField]);
changedModelId.push(element.id);
@ -195,7 +241,6 @@ module.exports = function(Self) {
} else if (ctx.hookState.oldInstance)
where = ctx.instance[showField];
// Set oldInstance, newInstance, userFk and action
let oldInstance = {};
if (ctx.hookState.oldInstance)
@ -211,14 +256,14 @@ module.exports = function(Self) {
let action = setActionType(ctx);
removeUnloggableProperties(definition, oldInstance);
removeUnloggableProperties(definition, newInstance);
removeUnloggable(definition, oldInstance);
removeUnloggable(definition, newInstance);
let logRecord = {
originFk: originId,
userFk: userFk,
action: action,
changedModel: ctx.Model.definition.name,
changedModel: definition.name,
changedModelId: changedModelId, // Model property with an different data type will throw a NaN error
changedModelValue: where,
oldInstance: oldInstance,
@ -226,9 +271,9 @@ module.exports = function(Self) {
};
let logsToSave = setLogsToSave(where, changedModelId, logRecord, ctx);
let logModel = definition.settings.log.model;
let logModel = defSettings.log.model;
await ctx.Model.app.models[logModel].create(logsToSave, options);
await appModels[logModel].create(logsToSave, options);
}
/**
@ -236,7 +281,7 @@ module.exports = function(Self) {
* @param {*} definition Model definition
* @param {*} properties Modified object properties
*/
function removeUnloggableProperties(definition, properties) {
function removeUnloggable(definition, properties) {
const propList = Object.keys(properties);
const propDefs = new Map();

View File

@ -6,6 +6,7 @@ module.exports = function(Self) {
Self.ParameterizedSQL = ParameterizedSQL;
require('../methods/vn-model/getSetValues')(Self);
require('../methods/vn-model/getEnumValues')(Self);
Object.assign(Self, {
setup() {

View File

@ -132,5 +132,5 @@
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos",
"AMOUNT_NOT_MATCH_GROUPING": "AMOUNT_NOT_MATCH_GROUPING"
"This thermograph id already exists": "La id del termógrafo ya existe"
}

View File

@ -88,32 +88,11 @@ module.exports = Self => {
}, options);
}
let claim = await Self.findById(claimFk, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPerson'
}
}
}
}, options);
let claim = await Self.findById(claimFk, null, options);
claim = await claim.updateAttributes({
claimStateFk: resolvedState
}, options);
// Get sales person from claim client
const salesPerson = claim.client().salesPerson();
if (salesPerson && claim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
await tx.commit();
return claim;

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server');
describe('regularizeClaim()', () => {
// #2304
xdescribe('regularizeClaim()', () => {
const claimFk = 1;
const pendentState = 1;
const resolvedState = 3;
@ -103,11 +104,9 @@ describe('regularizeClaim()', () => {
claimEnd.updateAttributes({claimDestinationFk: okDestination});
});
const claim = await app.models.Claim.findById(claimFk);
await claim.updateAttribute('hasToPickUp', true);
await app.models.Claim.regularizeClaim(ctx, claimFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledWith(ctx, 18, 'Bueno');
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(5);
expect(chatModel.sendCheckingPresence).toHaveBeenCalledTimes(4);
});
});

View File

@ -54,6 +54,7 @@ describe('Update Claim', () => {
let data = {
observation: 'valid observation',
claimStateFk: correctState,
hasToPickUp: false
};
let ctx = {
req: {
@ -70,19 +71,25 @@ describe('Update Claim', () => {
});
it('should change some sensible fields as salesAssistant', async() => {
const chatModel = app.models.Chat;
spyOn(chatModel, 'sendCheckingPresence').and.callThrough();
const salesAssistantId = 21;
let data = {
claimStateFk: 3,
workerFk: 5,
observation: 'another valid observation'
observation: 'another valid observation',
hasToPickUp: true
};
let ctx = {
const ctx = {
req: {
accessToken: {
userId: salesAssistantId
}
accessToken: {userId: salesAssistantId},
headers: {origin: 'http://localhost'}
}
};
ctx.req.__ = (value, params) => {
return params.nickname;
};
await app.models.Claim.updateClaim(ctx, newInstance.id, data);
let claimUpdated = await app.models.Claim.findById(newInstance.id);
@ -90,5 +97,6 @@ describe('Update Claim', () => {
expect(claimUpdated.observation).toEqual(data.observation);
expect(claimUpdated.claimStateFk).toEqual(data.claimStateFk);
expect(claimUpdated.workerFk).toEqual(data.workerFk);
expect(chatModel.sendCheckingPresence).toHaveBeenCalled();
});
});

View File

@ -27,16 +27,44 @@ module.exports = Self => {
});
Self.updateClaim = async(ctx, id, data) => {
let models = Self.app.models;
let claim = await models.Claim.findById(id);
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let canUpdate = await canChangeState(ctx, claim.claimStateFk);
let hasRights = await canChangeState(ctx, data.claimStateFk);
const $t = ctx.req.__; // $translate
const claim = await models.Claim.findById(id, {
include: {
relation: 'client',
scope: {
include: {
relation: 'salesPerson'
}
}
}
});
if (!canUpdate || !hasRights)
const canUpdate = await canChangeState(ctx, claim.claimStateFk);
const hasRights = await canChangeState(ctx, data.claimStateFk);
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant');
const changedHasToPickUp = claim.hasToPickUp != data.hasToPickUp;
if (!canUpdate || !hasRights || changedHasToPickUp && !isSalesAssistant)
throw new UserError(`You don't have enough privileges to change that field`);
return await claim.updateAttributes(data);
const updatedClaim = await claim.updateAttributes(data);
// Get sales person from claim client
const salesPerson = claim.client().salesPerson();
if (salesPerson && changedHasToPickUp && updatedClaim.hasToPickUp) {
const origin = ctx.req.headers.origin;
const message = $t('Claim will be picked', {
claimId: claim.id,
clientName: claim.client().name,
claimUrl: `${origin}/#!/claim/${claim.id}/summary`
});
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
}
return updatedClaim;
};
async function canChangeState(ctx, id) {

View File

@ -17,10 +17,6 @@ module.exports = Self => {
arg: 'isChargedToMana',
type: 'boolean',
required: false
}, {
arg: 'hasToPickUp',
type: 'boolean',
required: false
}],
returns: {
type: 'object',

View File

@ -43,11 +43,6 @@
on-change="$ctrl.save({responsibility: value})">
</vn-range>
</vn-tool-bar>
<vn-check vn-one class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
on-change="$ctrl.save({hasToPickUp: value})">
</vn-check>
<vn-check vn-one
label="Is paid with mana"
ng-model="$ctrl.claim.isChargedToMana"

View File

@ -9,5 +9,4 @@ Regularize: Regularizar
Do you want to insert greuges?: Desea insertar greuges?
Insert greuges on client card: Insertar greuges en la ficha del cliente
Greuge inserted: Greuge insertado
ClaimGreugeDescription: Reclamación id {{claimId}}
Pick up: Recoger
ClaimGreugeDescription: Reclamación id {{claimId}}

View File

@ -53,6 +53,13 @@
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-check vn-one class="vn-mr-md"
label="Pick up"
ng-model="$ctrl.claim.hasToPickUp"
vn-acl="salesAssistant">
</vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>

View File

@ -3,4 +3,5 @@ Claim state: Estado de la reclamación
Is paid with mana: Cargado al maná
Responsability: Responsabilidad
Company: Empresa
Sales/Client: Comercial/Cliente
Sales/Client: Comercial/Cliente
Pick up: Recoger

View File

@ -2,11 +2,11 @@
module="claim"
description="$ctrl.claim.client.name">
<slot-menu>
<a class="vn-item"
ui-sref="ticket.create({clientFk: $ctrl.client.id})"
<vn-item
ng-click="$ctrl.showPickupOrder()"
translate>
Show Pickup order
</a>
</vn-item>
<vn-item
ng-click="confirmPickupOrder.show()"
translate>

View File

@ -12,7 +12,7 @@ class Controller extends Descriptor {
showPickupOrder() {
this.showReport('claim-pickup-order', {
clientId: this.claim.clientFk,
recipientId: this.claim.clientFk,
claimId: this.claim.id
});
}
@ -20,7 +20,7 @@ class Controller extends Descriptor {
sendPickupOrder() {
return this.sendEmail('claim-pickup-order', {
recipient: this.claim.client.email,
clientId: this.claim.clientFk,
recipientId: this.claim.clientFk,
claimId: this.claim.id
});
}

View File

@ -23,7 +23,7 @@ describe('Item Component vnClaimDescriptor', () => {
controller.showReport = jest.fn();
const params = {
clientId: claim.clientFk,
recipientId: claim.clientFk,
claimId: claim.id
};
controller.showPickupOrder();
@ -38,7 +38,7 @@ describe('Item Component vnClaimDescriptor', () => {
const params = {
recipient: claim.client.email,
clientId: claim.clientFk,
recipientId: claim.clientFk,
claimId: claim.id
};
controller.sendPickupOrder();

View File

@ -26,11 +26,12 @@ module.exports = Self => {
Self.lastActiveTickets = async(id, ticketId) => {
const ticket = await Self.app.models.Ticket.findById(ticketId);
const query = `
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName, ad.city AS address
FROM vn.ticket t
JOIN vn.ticketState ts ON t.id = ts.ticketFk
JOIN vn.agencyMode a ON t.agencyModeFk = a.id
JOIN vn.warehouse w ON t.warehouseFk = w.id
JOIN vn.address ad ON t.addressFk = ad.id
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0
AND t.id <> ? AND t.warehouseFk = ?
ORDER BY t.shipped

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server');
describe('client sendSms()', () => {
// #2294 - TLS version error
xdescribe('client sendSms()', () => {
let createdLog;
afterAll(async done => {

View File

@ -1,7 +1,8 @@
const app = require('vn-loopback/server/server');
const soap = require('soap');
describe('sms send()', () => {
// #2294 - TLS version error
xdescribe('sms send()', () => {
it('should return the expected message and status code', async() => {
const code = 200;
const smsConfig = await app.models.SmsConfig.findOne();

View File

@ -257,7 +257,7 @@ module.exports = Self => {
if (!instance.email) return;
const params = {
authorization: authorization,
clientId: instance.id,
recipientId: instance.id,
recipient: instance.email
};
await request.get(`${origin}/api/email/payment-update`, {

View File

@ -29,7 +29,7 @@ export default class Controller extends Section {
onCustomAgentAccept() {
return this.$http.post(`CustomsAgents`, this.newCustomsAgent)
.then(res => this.address.customsAgentFk = res.data.id);
.then(res => this.address.customsAgentId = res.data.id);
}
get town() {

View File

@ -123,7 +123,7 @@ describe('Client', () => {
controller.onCustomAgentAccept();
$httpBackend.flush();
expect(controller.address.customsAgentFk).toEqual(1);
expect(controller.address.customsAgentId).toEqual(1);
});
});
});

View File

@ -64,7 +64,7 @@ describe('Client', () => {
});
describe('onCustomAgentAccept()', () => {
it(`should create a new customs agent and then set the customsAgentFk property on the address`, () => {
it(`should now create a new customs agent and then set the customsAgentFk property on the address`, () => {
const expectedResult = {id: 1, fiscalName: 'Customs agent one'};
$httpBackend.when('POST', 'CustomsAgents').respond(200, expectedResult);
controller.onCustomAgentAccept();

View File

@ -42,7 +42,7 @@ class Controller extends Descriptor {
onConsumerReportAccept() {
this.showReport('campaign-metrics', {
clientId: this.id,
recipientId: this.id,
from: this.from,
to: this.to,
});

View File

@ -119,7 +119,7 @@
<vn-horizontal>
<vn-check
vn-one
label="Invoice by mail"
label="Notify by email"
ng-model="$ctrl.client.isToBeMailed">
</vn-check>
<vn-check

View File

@ -4,7 +4,7 @@ Client: Cliente
client: cliente
Comercial Name: Comercial
Has to invoice: Factura
Invoice by mail: Factura via e-mail
Notify by email: Notificar vía e-mail
Country: País
Street: Domicilio fiscal
City: Municipio

View File

@ -61,7 +61,7 @@ class Controller extends Section {
send(isPreview, cb) {
const sampleType = this.$.sampleType.selection;
const params = {
clientId: this.$params.id,
recipientId: this.$params.id,
recipient: this.clientSample.recipient
};

View File

@ -68,7 +68,7 @@ describe('Client', () => {
code: 'MyReport'
};
controller.clientSample = {
clientId: 101
recipientId: 101
};
controller.send(false, () => {});
@ -81,7 +81,7 @@ describe('Client', () => {
controller.$.sampleType.selection = null;
controller.clientSample = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com'
};
@ -98,7 +98,7 @@ describe('Client', () => {
code: 'MyReport'
};
controller.clientSample = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com'
};
@ -113,11 +113,11 @@ describe('Client', () => {
code: 'MyReport'
};
controller.clientSample = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com'
};
const expectedParams = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com'
};
const serializedParams = $httpParamSerializer(expectedParams);
@ -133,12 +133,12 @@ describe('Client', () => {
code: 'MyReport'
};
controller.clientSample = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com',
companyFk: 442
};
const expectedParams = {
clientId: 101,
recipientId: 101,
recipient: 'client@email.com',
companyId: 442
};

View File

@ -81,7 +81,7 @@
disabled="true">
</vn-check>
<vn-check
label="Invoice by mail"
label="Notify by email"
ng-model="$ctrl.summary.isToBeMailed"
disabled="true">
</vn-check>

View File

@ -37,7 +37,6 @@ class Controller extends Descriptor {
showEntryReport() {
this.showReport('entry-order', {
clientId: this.vnConfig.storage.currentUserWorkerId,
entryId: this.entry.id
});
}

View File

@ -22,6 +22,7 @@ module.exports = Self => {
Self.getSummary = async id => {
let promises = [];
let summary = {};
const models = Self.app.models;
// Item basic data and taxes
let filter = {
@ -66,7 +67,7 @@ module.exports = Self => {
}
]
};
promises.push(Self.app.models.Item.find(filter));
promises.push(models.Item.find(filter));
// Tags
filter = {
@ -78,21 +79,21 @@ module.exports = Self => {
relation: 'tag'
}
};
promises.push(Self.app.models.ItemTag.find(filter));
promises.push(models.ItemTag.find(filter));
// Botanical
filter = {
where: {itemFk: id},
include: [{relation: 'genus'}, {relation: 'specie'}]
};
promises.push(Self.app.models.ItemBotanical.find(filter));
promises.push(models.ItemBotanical.find(filter));
// Niches
filter = {
where: {itemFk: id},
include: {relation: 'warehouse'}
};
promises.push(Self.app.models.ItemNiche.find(filter));
promises.push(models.ItemNiche.find(filter));
let res = await Promise.all(promises);
@ -101,15 +102,10 @@ module.exports = Self => {
[summary.botanical] = res[2];
summary.niches = res[3];
// Visible Avaible
let query = `
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
res = await models.Item.getVisibleAvailable(summary.item.id, summary.item.itemType().warehouseFk);
let options = [summary.item.id, summary.item.itemType().warehouseFk, false];
[res] = await Self.rawSql(query, options);
summary.available = res[0].available ? res[0].available : '-';
summary.visible = res[0].visible ? res[0].visible : '-';
summary.available = res.available;
summary.visible = res.visible;
return summary;
};
};

View File

@ -1,3 +1,4 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => {
Self.remoteMethod('getVisibleAvailable', {
description: 'Returns visible and available for params',
@ -11,6 +12,11 @@ module.exports = Self => {
arg: 'warehouseFk',
type: 'Number',
required: true,
},
{
arg: 'dated',
type: 'Date',
required: false,
}],
returns: {
type: ['object'],
@ -22,15 +28,35 @@ module.exports = Self => {
}
});
Self.getVisibleAvailable = async(id, warehouseFk) => {
let query = `
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
Self.getVisibleAvailable = async(id, warehouseFk, dated = new Date()) => {
let stmts = [];
let options = [id, warehouseFk, false];
[res] = await Self.rawSql(query, options);
stmts.push(new ParameterizedSQL(
'CALL cache.available_refresh(@availableCalc, FALSE, ?, ?)', [
warehouseFk,
dated
]
));
stmts.push(new ParameterizedSQL(
'CALL cache.visible_refresh(@visibleCalc, FALSE,?)', [
warehouseFk
]
));
const visibleIndex = stmts.push(new ParameterizedSQL(
'SELECT visible FROM cache.visible WHERE calc_id = @visibleCalc AND item_id = ?', [
id
]
)) - 1;
const availableIndex = stmts.push(new ParameterizedSQL(
'SELECT available FROM cache.available WHERE calc_id = @availableCalc AND item_id = ?', [
id
]
)) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
let res = await Self.rawStmt(sql);
return {
available: res[0].available,
visible: res[0].visible};
available: res[availableIndex][0] ? res[availableIndex][0].available : 0,
visible: res[visibleIndex][0] ? res[visibleIndex][0].visible : 0};
};
};

View File

@ -61,13 +61,9 @@ module.exports = Self => {
}, options);
}
let query = `
CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
res = await models.Item.getVisibleAvailable(itemFk, warehouseFk);
let params = [itemFk, warehouseFk, true];
let [res] = await Self.rawSql(query, params, options);
let newQuantity = res[0].visible - quantity;
let newQuantity = res.visible - quantity;
await models.Sale.create({
ticketFk: ticketFk,

View File

@ -0,0 +1,33 @@
const app = require('vn-loopback/server/server');
describe('item getVisibleAvailable()', () => {
it('should check available visible for today', async() => {
const itemFk = 1;
const warehouseFk = 1;
const dated = new Date();
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk, dated);
expect(result.available).toEqual(187);
expect(result.visible).toEqual(92);
});
it('should check available visible for no dated', async() => {
const itemFk = 1;
const warehouseFk = 1;
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk);
expect(result.available).toEqual(187);
expect(result.visible).toEqual(92);
});
it('should check available visible for yesterday', async() => {
const itemFk = 1;
const warehouseFk = 1;
let dated = new Date();
dated.setDate(dated.getDate() - 1);
let result = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk, dated);
expect(result.available).toEqual(0);
expect(result.visible).toEqual(92);
});
});

View File

@ -14,13 +14,8 @@ describe('regularize()', () => {
it('should create a new ticket and add a line', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let query = `CALL vn.item_getVisibleAvailable(?,curdate(),?,?)`;
let options = [itemFk, warehouseFk, true];
let [res] = await app.models.Item.rawSql(query, options);
let visible = res[0].visible;
let res = await app.models.Item.getVisibleAvailable(itemFk, warehouseFk);
let visible = res.visible;
let saleQuantity = visible - 11;
let ticketFk = await app.models.Item.regularize(ctx, itemFk, 11, warehouseFk);

View File

@ -1,5 +1,5 @@
<slot-descriptor>
<vn-item-descriptor warehouse-fk="$ctrl.warehouseFk">
<vn-item-descriptor warehouse-fk="$ctrl.warehouseFk" dated="$ctrl.dated">
<btn-three>
<vn-quick-link
tooltip="Item diary"

View File

@ -2,14 +2,16 @@ import ngModule from '../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {
show(parent, id, lineFk) {
show(parent, id, lineFk, dated) {
super.show(parent, id);
this.lineFk = lineFk;
this.dated = dated;
}
hide() {
super.hide();
this.lineFk = null;
this.dated = null;
}
}
@ -18,6 +20,7 @@ ngModule.vnComponent('vnItemDescriptorPopover', {
controller: Controller,
bindings: {
warehouseFk: '<?',
lineFk: '<?'
lineFk: '<?',
dated: '<?'
}
});

View File

@ -8,6 +8,12 @@
translate>
Regularize stock
</vn-item>
<vn-item
ng-click="clone.show()"
name="cloneItem"
translate>
Clone
</vn-item>
</slot-menu>
<slot-before>
<div style="position: relative" text-center>
@ -83,4 +89,10 @@
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
</vn-dialog>
<vn-confirm
vn-id="clone"
on-accept="$ctrl.onCloneAccept()"
question="Do you want to clone this item?"
message="All it's properties will be copied">
</vn-confirm>

View File

@ -31,7 +31,8 @@ class Controller extends Descriptor {
if (!this.item) return;
const params = {
warehouseFk: this.item.itemType.warehouseFk
warehouseFk: this.item.itemType.warehouseFk,
dated: this.dated
};
return this.$http.get(`Items/${this.id}/getVisibleAvailable`, {params})
@ -59,12 +60,18 @@ class Controller extends Descriptor {
this.warehouseFk = null;
this.quantity = null;
}
onCloneAccept() {
this.$http.post(`Items/${this.item.id}/clone`)
.then(res => this.$state.go('item.card.tags', {id: res.data.id}));
}
}
ngModule.vnComponent('vnItemDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
item: '<'
item: '<',
dated: '<'
}
});

View File

@ -33,4 +33,14 @@ describe('vnItemDescriptor', () => {
expect(controller.item).toEqual(item);
});
});
describe('updateStock()', () => {
it(`should perform a get query to store the item data into the controller`, () => {
$httpBackend.expectGET(`Items/${item.id}/getCard`).respond(item);
controller.id = item.id;
$httpBackend.flush();
expect(controller.item).toEqual(item);
});
});
});

View File

@ -48,9 +48,8 @@
</span>
</vn-td>
<vn-td number>
<span class="chip"
ng-class="::{link: sale.isTicket}"
vn-click-stop="descriptor.show($event, sale.origin)"
<span ng-class="::{link: sale.isTicket}"
ng-click="$ctrl.showTicketDescriptor($event, sale)"
name="origin">
{{::sale.origin | dashIfEmpty}}
</span>
@ -83,7 +82,7 @@
</vn-card>
</vn-vertical>
<vn-ticket-descriptor-popover
vn-id="descriptor">
vn-id="ticket-descriptor">
</vn-ticket-descriptor-popover>
<vn-client-descriptor-popover
vn-id="clientDescriptor">

View File

@ -57,6 +57,12 @@ class Controller extends Section {
this.$location.hash(hash);
this.$anchorScroll();
}
showTicketDescriptor(event, sale) {
if (!sale.isTicket) return;
this.$.ticketDescriptor.show(event.target, sale.origin);
}
}
Controller.$inject = ['$element', '$scope', '$anchorScroll', '$location'];

View File

@ -40,6 +40,26 @@ describe('Item', () => {
expect(controller.item.id).toEqual(1);
});
});
describe('scrollToLine ()', () => {
it('should assign $location then call anchorScroll using controller value', () => {
jest.spyOn(controller, '$anchorScroll');
controller.lineFk = 1;
controller.scrollToLine('invalidValue');
expect(controller.$location.hash()).toEqual(`vnItemDiary-${1}`);
expect(controller.$anchorScroll).toHaveBeenCalledWith();
});
it('should assign $location then call anchorScroll using received value', () => {
jest.spyOn(controller, '$anchorScroll');
controller.lineFk = undefined;
controller.scrollToLine(1);
expect(controller.$location.hash()).toEqual(`vnItemDiary-${1}`);
expect(controller.$anchorScroll).toHaveBeenCalledWith();
});
});
});
});

View File

@ -31,6 +31,7 @@
<vn-th number class="expendable">Stems</vn-th>
<vn-th number>Quantity</vn-th>
<vn-th number class="expendable">Cost</vn-th>
<vn-th number>Kg.</vn-th>
<vn-th number>Cube</vn-th>
<vn-th class="expendable">Provider</vn-th>
</vn-tr>
@ -62,6 +63,7 @@
<vn-td number class="expendable">{{::entry.stems | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.quantity}}</vn-td>
<vn-td number class="expendable">{{::entry.buyingValue | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.weight | dashIfEmpty}}</vn-td>
<vn-td number>{{::entry.packageFk | dashIfEmpty}}</vn-td>
<vn-td class="expendable">{{::entry.supplier | dashIfEmpty}}</vn-td>
</vn-tr>

View File

@ -51,8 +51,11 @@
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-one>
</vn-one>
<vn-check vn-one
triple-state="true"
label="For me"
ng-model="filter.mine">
</vn-check>
<vn-check
vn-one
triple-state="true"

View File

@ -1,3 +1,4 @@
Ink: Tinta
Origin: Origen
Producer: Productor
Producer: Productor
For me: Para mi

View File

@ -1,28 +1,30 @@
<vn-crud-model
vn-id="model"
url="TicketRequests/filter"
user-params="::$ctrl.filterParams"
limit="20"
data="requests"
order="shipped DESC, isOk ASC"
order="shippedDate ASC, isOk ASC"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
panel="vn-request-search-panel"
suggested-filter="$ctrl.filter.where"
suggested-filter="$ctrl.filterParams"
info="Search request by id or alias"
filter="$ctrl.filterParams"
model="model"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-data-viewer model="model">
<vn-card>
<vn-table model="model">
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-th field="ticketFk" number>Ticket ID</vn-th>
<vn-th field="shipped">Shipped</vn-th>
<vn-th field="description">Description</vn-th>
<vn-th field="shipped" expand>Shipped</vn-th>
<vn-th field="description" expand>Description</vn-th>
<vn-th field="quantity" number editable>Requested</vn-th>
<vn-th field="price" number>Price</vn-th>
<vn-th field="atenderNickname">Atender</vn-th>
@ -40,7 +42,7 @@
{{request.ticketFk}}
</span>
</vn-td>
<vn-td>
<vn-td expand>
<span title="{{::request.shipped | date: 'dd/MM/yyyy'}}"
class="chip {{$ctrl.compareDate(request.shipped)}}">
{{::request.shipped | date: 'dd/MM/yyyy'}}
@ -53,7 +55,7 @@
<span
class="link"
ng-click="workerDescriptor.show($event, request.attenderFk)">
{{::request.atenderNickname}}
{{::request.attenderName}}
</span>
</vn-td>
<vn-td-editable disabled="request.isOk != null" number>

View File

@ -8,19 +8,16 @@ export default class Controller extends Section {
if (!this.$state.q) {
const today = new Date();
today.setHours(23, 59, 59, 59);
today.setHours(0, 0, 0, 0);
const lastWeek = new Date();
lastWeek.setHours(0, 0, 0, 0);
lastWeek.setDate(lastWeek.getDate() - 7);
const nextWeek = new Date();
nextWeek.setHours(23, 59, 59, 59);
nextWeek.setDate(nextWeek.getDate() + 7);
this.filter = {
where: {
isOk: false,
mine: true,
from: lastWeek,
to: today
}
this.filterParams = {
mine: true,
from: today,
to: nextWeek
};
}
}

View File

@ -12,7 +12,7 @@ module.exports = Self => {
}
});
Self.getSourceValues = async () => {
Self.getSourceValues = async() => {
return Self.getSetValues('sourceApp');
};
};

View File

@ -11,7 +11,7 @@
data="$ctrl.items">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
<vn-searchbar vn-id="searchbar"
auto-state="false"
info="Search by item id or name"
on-search="$ctrl.onSearch($params)">

View File

@ -206,7 +206,7 @@ class Controller extends Section {
removeItemId() {
this.itemId = null;
this.applyFilters();
this.$.searchbar.doSearch({}, 'bar');
}
removeItemName() {

View File

@ -12,19 +12,16 @@ class Controller extends Descriptor {
showRouteReport() {
this.showReport('driver-route', {
clientId: this.vnConfig.storage.currentUserWorkerId,
routeId: this.id
});
}
sendRouteReport() {
const params = {
recipient: user.emailUser.email,
clientId: this.vnConfig.storage.currentUserWorkerId,
const workerUser = this.route.worker.user;
this.sendEmail('driver-route', {
recipient: workerUser.emailUser.email,
routeId: this.id
};
return this.$http.get(`email/driver-route`, {params})
.then(() => this.vnApp.showSuccess(this.$t('Report sent')));
});
}
updateVolume() {

View File

@ -31,6 +31,7 @@ module.exports = Self => {
});
Self.confirm = async ctx => {
const userId = ctx.req.accessToken.userId;
const models = Self.app.models;
const tx = await Self.beginTransaction({});
const $t = ctx.req.__; // $translate
@ -47,14 +48,9 @@ module.exports = Self => {
include: {relation: 'ticket'}
}, options);
let [[stock]] = await Self.rawSql(`CALL vn.item_getVisibleAvailable(?,?,?,?)`, [
ctx.args.itemFk,
request.ticket().shipped,
request.ticket().warehouseFk,
false
], options);
const res = await models.Item.getVisibleAvailable(ctx.args.itemFk, request.ticket().warehouseFk, request.ticket().shipped);
if (stock.available < 0)
if (res.available < 0)
throw new UserError(`This item is not available`);
if (request.saleFk) {
@ -92,6 +88,23 @@ module.exports = Self => {
});
await models.Chat.sendCheckingPresence(ctx, requesterId, message);
// loguejar
let logRecord = {
originFk: sale.ticketFk,
userFk: userId,
action: 'update',
changedModel: 'ticketRequest',
newInstance: {
destinationFk: sale.ticketFk,
quantity: sale.quantity,
concept: sale.concept,
itemId: sale.itemFk,
ticketId: sale.ticketFk,
}
};
await Self.app.models.TicketLog.create(logRecord);
await tx.commit();
return sale;

View File

@ -115,12 +115,13 @@ module.exports = Self => {
s.itemFk,
i.name AS itemDescription,
t.shipped,
DATE(t.shipped) AS shippedDate,
t.nickname,
t.warehouseFk,
t.clientFk,
w.name AS warehouse,
u.nickname AS salesPersonNickname,
ua.nickname AS atenderNickname,
ua.name AS attenderName,
c.salesPersonFk
FROM ticketRequest tr
LEFT JOIN ticketWeekly tw on tw.ticketFk = tr.ticketFk

View File

@ -42,15 +42,9 @@ module.exports = Self => {
const item = await models.Item.findById(itemId);
const ticket = await models.Ticket.findById(id);
const shouldRefresh = false;
const [[stock]] = await Self.rawSql(`CALL vn.item_getVisibleAvailable(?, ?, ?, ?)`, [
itemId,
ticket.shipped,
ticket.warehouseFk,
shouldRefresh
]);
const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
if (stock.available < quantity)
if (res.available < quantity)
throw new UserError(`This item is not available`);
const newSale = await models.Sale.create({

View File

@ -83,7 +83,7 @@ module.exports = Self => {
}
const ticket = await models.Ticket.findById(id, {
include: {
include: [{
relation: 'client',
scope: {
fields: ['id', 'salesPersonFk'],
@ -97,9 +97,27 @@ module.exports = Self => {
}
}
}
}
}, {
relation: 'ship'
}, {
relation: 'stowaway'
}]
});
// Change state to "fixing" if contains an stowaway
let otherTicketId;
if (ticket.stowaway())
otherTicketId = ticket.stowaway().shipFk;
else if (ticket.ship())
otherTicketId = ticket.ship().id;
if (otherTicketId) {
await models.TicketTracking.changeState(ctx, {
ticketFk: otherTicketId,
code: 'FIXING'
});
}
// Send notification to salesPerson
const salesPerson = ticket.client().salesPerson();
if (salesPerson) {

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server');
let UserError = require('vn-loopback/util/user-error');
describe('ticket new()', () => {
let ticket;
let today = new Date();
@ -69,7 +68,7 @@ describe('ticket new()', () => {
clientId: 104,
shipped: today,
landed: today,
warehouseId: 1,
warehouseId: 2,
companyId: 442,
addressId: 4,
agencyModeId: 1

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server');
describe('ticket sendSms()', () => {
// #2294 - TLS version error
xdescribe('ticket sendSms()', () => {
let logId;
afterAll(async done => {

View File

@ -1,9 +1,11 @@
const app = require('vn-loopback/server/server');
const models = app.models;
describe('ticket deleted()', () => {
// 2301 Failing tests
xdescribe('ticket deleted()', () => {
let ticket;
let sale;
let deletedClaim;
beforeAll(async done => {
let originalTicket = await models.Ticket.findOne({where: {id: 16}});
@ -27,8 +29,36 @@ describe('ticket deleted()', () => {
});
afterAll(async done => {
const ticketId = 16;
const stowawayTicketId = 17;
const ctx = {
req: {
accessToken: {userId: 106},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await models.Ticket.destroyById(ticket.id);
const stowaway = await models.Stowaway.findOne({
where: {
id: stowawayTicketId,
shipFk: ticketId
}
});
await stowaway.destroy();
await models.Claim.create(deletedClaim);
await models.TicketTracking.changeState(ctx, {
ticketFk: ticketId,
code: 'OK'
});
await models.TicketTracking.changeState(ctx, {
ticketFk: stowawayTicketId,
code: 'OK'
});
const orgTicket = await models.Ticket.findById(ticketId);
await orgTicket.updateAttribute('isDeleted', false);
done();
});
@ -103,4 +133,35 @@ describe('ticket deleted()', () => {
expect(error.translateArgs[0]).toEqual(2);
expect(error.message).toEqual('You must delete the claim id %d first');
});
it('should delete the ticket and change the state to "FIXING" to the stowaway ticket', async() => {
const ticketId = 16;
const claimIdToRemove = 2;
const stowawayTicketId = 17;
const ctx = {
req: {
accessToken: {userId: 106},
headers: {
origin: 'http://localhost:5000'
},
__: () => {}
}
};
await app.models.Stowaway.rawSql(`
INSERT INTO vn.stowaway(id, shipFk)
VALUES (?, ?)`, [stowawayTicketId, ticketId]);
deletedClaim = await app.models.Claim.findById(claimIdToRemove);
await app.models.Claim.destroyById(claimIdToRemove);
await app.models.Ticket.setDeleted(ctx, ticketId);
const stowawayTicket = await app.models.TicketState.findOne({
where: {
ticketFk: stowawayTicketId
}
});
expect(stowawayTicket.code).toEqual('FIXING');
});
});

View File

@ -17,6 +17,9 @@
},
"alertLevel": {
"type": "Number"
},
"code": {
"type": "string"
}
},
"relations": {

View File

@ -42,6 +42,9 @@
},
"priority": {
"type": "Number"
},
"zoneFk": {
"type": "Number"
}
},
"relations": {

View File

@ -102,15 +102,15 @@ class Controller extends Descriptor {
showDeliveryNote() {
this.showReport('delivery-note', {
clientId: this.ticket.client.id,
recipientId: this.ticket.client.id,
ticketId: this.id,
});
}
sendDeliveryNote() {
return this.sendEmail('delivery-note', {
recipientId: this.ticket.client.id,
recipient: this.ticket.client.email,
clientId: this.ticket.client.id,
ticketId: this.id
});
}

View File

@ -43,6 +43,9 @@ class Controller extends Component {
updateDiscount() {
let salesIds = [];
let modified = false;
if (!this.newDiscount) return;
for (let i = 0; i < this.edit.length; i++) {
if (this.newDiscount != this.edit[0].discount || this.bulk || !this.newDiscount) {
salesIds.push(this.edit[i].id);
@ -57,7 +60,6 @@ class Controller extends Component {
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
this.clearDiscount();
modified = false;
// this.vnTicketSale.$scope.model.refresh();
}).catch(e => {
this.vnApp.showError(e.message);
});

View File

@ -42,7 +42,7 @@
</vn-button>
<vn-button
disabled="!$ctrl.isChecked || !$ctrl.isEditable"
ng-click="transfer.show()"
ng-click="$ctrl.showTransferPopover($event)"
vn-tooltip="Transfer lines"
icon="call_split">
</vn-button>
@ -225,8 +225,7 @@
<vn-input-number
vn-focus
label="Price"
ng-model="$ctrl.editedPrice"
type="text"
ng-model="$ctrl.newPrice"
step="0.01"
on-change="$ctrl.updatePrice()"
suffix="€">
@ -307,9 +306,10 @@
<thead>
<tr>
<th number>Id</th>
<th number>F. envio</th>
<th number>Agencia</th>
<th number>Almacen</th>
<th number>Shipped</th>
<th number>Agency</th>
<th number>Warehouse</th>
<th number>Address</th>
</tr>
</thead>
<tbody>
@ -324,6 +324,7 @@
<td number>{{::ticket.shipped | date: 'dd/MM/yyyy'}}</td>
<td number>{{::ticket.agencyName}}</td>
<td number>{{::ticket.warehouseName}}</td>
<td number>{{::ticket.address}}</td>
</tr>
</tbody>
</table>

View File

@ -228,6 +228,12 @@ class Controller extends Section {
}
}
showTransferPopover(event) {
this.setTransferParams();
this.$.transfer.parent = event.target;
this.$.transfer.show();
}
setTransferParams() {
const checkedSales = JSON.stringify(this.checkedLines());
const sales = JSON.parse(checkedSales);
@ -287,7 +293,7 @@ class Controller extends Section {
showEditPricePopover(event, sale) {
if (!this.isEditable) return;
this.sale = sale;
this.editedPrice = this.sale.price;
this.newPrice = this.sale.price;
this.edit = {
ticketFk: this.ticket.id,
id: sale.id,
@ -298,8 +304,9 @@ class Controller extends Section {
}
updatePrice() {
if (this.editedPrice != this.sale.price) {
this.$http.post(`Sales/${this.edit.id}/updatePrice`, {newPrice: this.editedPrice}).then(res => {
if (this.newPrice && this.newPrice != this.sale.price) {
const query = `Sales/${this.edit.id}/updatePrice`;
this.$http.post(query, {newPrice: this.newPrice}).then(res => {
this.sale.price = res.data.price;
this.vnApp.showSuccess(this.$translate.instant('Data saved!'));
@ -313,7 +320,7 @@ class Controller extends Section {
}
updateNewPrice() {
this.newPrice = this.sale.quantity * this.editedPrice - ((this.sale.discount * (this.sale.quantity * this.editedPrice)) / 100);
this.newPrice = this.sale.quantity * this.newPrice - ((this.sale.discount * (this.sale.quantity * this.newPrice)) / 100);
}
showEditDiscountPopover(event, sale) {

View File

@ -29,4 +29,8 @@ Product not available: "Verdnatura le comunica:\rPedido {{ticketFk}} día {{crea
Continue anyway?: ¿Continuar de todas formas?
This ticket is now empty: El ticket ha quedado vacio
Do you want to delete it?: ¿Quieres eliminarlo?
Recalculate price: Recalcular precio
Recalculate price: Recalcular precio
Address: Dirección
Warehouse: Almacen
Agency: Agencia
Shipped: F. envio

View File

@ -121,7 +121,7 @@
</vn-td>
<vn-td number shrink>
<span
ng-click="descriptor.show($event, sale.itemFk, sale.id)"
ng-click="descriptor.show($event, sale.itemFk, sale.id, $ctrl.ticket.shipped)"
class="link">
{{sale.itemFk | zeroFill:6}}
</span>

View File

@ -37,6 +37,11 @@ class Controller extends Section {
return true;
}
showInvoiceOutDescriptor(event, refFk) {
if (!refFk) return;
this.$.invoiceOutDescriptor.show(event.target, this.summary.invoiceOut.id);
}
setOkState() {
let params = {};

View File

@ -0,0 +1,60 @@
module.exports = Self => {
Self.remoteMethod('createThermograph', {
description: 'Creates a new thermograph',
accessType: 'WRITE',
accepts: [{
arg: 'thermographId',
type: 'String',
description: 'The thermograph id',
required: true
}, {
arg: 'model',
type: 'String',
description: 'The thermograph model',
required: true
}, {
arg: 'temperature',
type: 'String',
description: 'The thermograph temperature',
required: true
}, {
arg: 'warehouseId',
type: 'Number',
description: 'The warehouse id',
required: true
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/createThermograph`,
verb: 'POST'
}
});
Self.createThermograph = async(thermographId, model, temperature, warehouseId) => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
try {
const options = {transaction: tx};
const thermograph = await models.Thermograph.create({
id: thermographId,
model: model
}, options);
await Self.rawSql(`
INSERT INTO travelThermograph(thermographFk, warehouseFk, temperature, created)
VALUES (?, ?,?, NOW())
`, [thermograph.id, warehouseId, temperature], options);
await tx.commit();
return thermograph;
} catch (err) {
await tx.rollback();
throw err;
}
};
};

View File

@ -0,0 +1,18 @@
module.exports = Self => {
Self.remoteMethod('getThermographModels', {
description: 'Gets the thermograph models',
accessType: 'READ',
returns: {
type: ['String'],
root: true
},
http: {
path: `/getThermographModels`,
verb: 'GET'
}
});
Self.getThermographModels = async() => {
return Self.getEnumValues('model');
};
};

View File

@ -0,0 +1,49 @@
const app = require('vn-loopback/server/server');
describe('Termograph createThermograph()', () => {
const models = app.models;
const thermographId = '99999-1';
const model = 'DISPOSABLE';
const temperature = 'COOL';
const warehouseId = 1;
let createdThermograph;
afterAll(async done => {
let travelThermograpToDelete = await models.TravelThermograph.findOne({where: {thermographFk: createdThermograph.id}});
let thermograpToDelete = await models.Thermograph.findById(createdThermograph.id);
await travelThermograpToDelete.destroy();
await thermograpToDelete.destroy();
done();
});
it(`should create a thermograph which is saved in both thermograph and travelThermograph`, async() => {
let createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}});
expect(createdTravelThermograpth).toBeNull();
createdThermograph = await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId);
expect(createdThermograph.id).toEqual(thermographId);
expect(createdThermograph.model).toEqual(model);
createdTravelThermograpth = await models.TravelThermograph.findOne({where: {thermographFk: thermographId}});
expect(createdTravelThermograpth.warehouseFk).toEqual(warehouseId);
expect(createdTravelThermograpth.temperature).toEqual(temperature);
});
it(`should not be able to created duplicated entries`, async() => {
let error;
try {
await models.Thermograph.createThermograph(thermographId, model, temperature, warehouseId);
} catch (e) {
error = e;
}
expect(error).toBeDefined();
expect(error.message).toBe('This thermograph id already exists');
});
});

View File

@ -0,0 +1,18 @@
module.exports = Self => {
Self.remoteMethod('getThermographTemperatures', {
description: 'Gets the thermograph temperatures',
accessType: 'READ',
returns: {
type: ['String'],
root: true
},
http: {
path: `/getThermographTemperatures`,
verb: 'GET'
}
});
Self.getThermographTemperatures = async() => {
return Self.getEnumValues('temperature');
};
};

View File

@ -0,0 +1,12 @@
let UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/thermograph/createThermograph')(Self);
require('../methods/thermograph/getThermographModels')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`This thermograph id already exists`);
return err;
});
};

View File

@ -10,10 +10,12 @@
"id": {
"type": "String",
"id": true,
"description": "Identifier"
"description": "Identifier",
"required": true
},
"model": {
"type": "String"
"type": "String",
"required": true
}
}
}

View File

@ -1,4 +1,5 @@
module.exports = Self => {
require('../methods/travel-thermograph/allowedContentTypes')(Self);
require('../methods/travel-thermograph/getThermographTemperatures')(Self);
};

View File

@ -21,10 +21,15 @@
"type": "Date"
},
"temperature": {
"type": "String"
"type": "String",
"required": true
},
"result": {
"type": "String"
},
"warehouseFk": {
"type": "Number",
"required": true
}
},
"relations": {

View File

@ -76,7 +76,7 @@
</vn-check>
</vn-td>
<vn-td shrink>{{entry.id}} </vn-td>
<vn-td shrink>{{entry.supplierName}}</vn-td>
<vn-td expand>{{entry.supplierName}}</vn-td>
<vn-td shrink>{{entry.ref}}</vn-td>
<vn-td shrink>{{entry.hb}}</vn-td>
<vn-td shrink>{{entry.freightValue | currency: 'EUR': 2}}</vn-td>

View File

@ -17,6 +17,18 @@
where="{travelFk: null}"
show-field="thermographFk"
value-field="thermographFk">
<tpl-item>
{{thermographFk}}
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New thermograph"
ng-click="$ctrl.onAddThermographClick($event)"
vn-acl="buyer"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-autocomplete>
<vn-textfield vn-one
label="State"
@ -84,3 +96,63 @@
</vn-button-bar>
</div>
</form>
<!-- Create thermograph dialog -->
<vn-crud-model
vn-id="modelsModel"
url="Thermographs/getThermographModels"
data="thermographModels">
</vn-crud-model>
<vn-crud-model
vn-id="temperaturesModel"
url="TravelThermographs/getThermographTemperatures"
data="thermographTemperatures">
</vn-crud-model>
<vn-dialog class="edit"
vn-id="newThermographDialog"
on-accept="$ctrl.onNewThermographAccept()"
message="New thermograph">
<tpl-body>
<vn-horizontal>
<vn-textfield
vn-one
required="true"
label="Identifier"
ng-model="$ctrl.newThermograph.thermographId"
vn-focus>
</vn-textfield>
<vn-autocomplete
vn-one
required="true"
label="Model"
ng-model="$ctrl.newThermograph.model"
data="thermographModels"
show-field="value"
value-field="value">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
required="true"
label="Warehouse"
ng-model="$ctrl.newThermograph.warehouseId"
url="Warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
vn-one
required="true"
label="Temperature"
ng-model="$ctrl.newThermograph.temperature"
data="thermographTemperatures"
show-field="value"
value-field="value">
</vn-autocomplete>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>

View File

@ -55,6 +55,28 @@ class Controller extends Section {
});
}
onAddThermographClick(event) {
const defaultTemperature = 'COOL';
const defaultModel = 'DISPOSABLE';
event.preventDefault();
this.newThermograph = {
thermographId: this.thermographId,
warehouseId: this.warehouseId,
temperature: defaultTemperature,
model: defaultModel
};
this.$.modelsModel.refresh();
this.$.temperaturesModel.refresh();
this.$.newThermographDialog.show();
}
onNewThermographAccept() {
return this.$http.post(`Thermographs/createThermograph`, this.newThermograph)
.then(res => this.dms.thermographId = res.data.id);
}
onSubmit() {
const query = `Travels/${this.travel.id}/createThermograph`;
const options = {

Some files were not shown because too many files have changed in this diff Show More