Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 5316-kkeoEntryNotes

This commit is contained in:
Carlos Satorres 2023-04-17 08:55:22 +02:00
commit 60d3729b4a
145 changed files with 1756 additions and 939 deletions

View File

@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2316.01] - 2023-05-04
### Added
-
### Changed
-
### Fixed
-
## [2314.01] - 2023-04-20 ## [2314.01] - 2023-04-20
### Added ### Added
@ -12,9 +23,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia - (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
- (Facturas recibidas -> Bases negativas) Nueva sección - (Facturas recibidas -> Bases negativas) Nueva sección
### Changed
-
### Fixed ### Fixed
- (Clientes -> Morosos) Ahora se mantienen los elementos seleccionados al hacer sroll. - (Clientes -> Morosos) Ahora se mantienen los elementos seleccionados al hacer sroll.

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`ticketConfig` ADD daysForWarningClaim INT DEFAULT 2 NOT NULL COMMENT 'dias restantes hasta que salte el aviso de reclamación fuerade plazo';

View File

@ -0,0 +1,74 @@
DROP TABLE `vn`.`dmsRecover`;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT NOT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DELETE FROM `salix`.`ACL` WHERE `property` = 'saveSign';
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Ticket','saveSign','WRITE','ALLOW','employee');
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT)
BEGIN
/**
* Pasado un RouteFk devuelve la información
* de sus tickets.
*
* @param vRouteFk
*
* @select Información de los tickets
*/
SELECT
t.id Id,
t.clientFk Client,
a.id Address,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.Note Note,
t.isSigned Signed
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON t.id = d.ticketFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN
(SELECT tob.description Note, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN
(SELECT sub.ticketFk,
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id,i.itemPackingTypeFk)sub
GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
GROUP BY t.id
ORDER BY t.priority;
END$$
DELIMITER ;

View File

@ -0,0 +1,67 @@
DELETE FROM `salix`.`ACL` WHERE `property` = 'saveSign';
INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`)
VALUES
('Ticket','saveSign','WRITE','ALLOW','employee');
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT)
BEGIN
/**
* Pasado un RouteFk devuelve la información
* de sus tickets.
*
* @param vRouteFk
*
* @select Información de los tickets
*/
SELECT
t.id Id,
t.clientFk Client,
a.id Address,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.Note Note,
t.isSigned Signed
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON t.id = d.ticketFk
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN
(SELECT tob.description Note, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN
(SELECT sub.ticketFk,
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id,i.itemPackingTypeFk)sub
GROUP BY sub.ticketFk
) sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
GROUP BY t.id
ORDER BY t.priority;
END$$
DELIMITER ;

View File

@ -0,0 +1,83 @@
CREATE TABLE `vn`.`dmsRecover` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ticketFk` int(11) DEFAULT NULL,
`sign` text DEFAULT NULL,
`created` timestamp NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `ticketFk_idx` (`ticketFk`),
CONSTRAINT `ticketFk` FOREIGN KEY (`ticketFk`) REFERENCES `ticket` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=31917 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci;
ALTER TABLE `vn`.`delivery` ADD addressFk INT;
DROP PROCEDURE IF EXISTS `vn`.`route_getTickets`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`localhost` PROCEDURE `vn`.`route_getTickets`(vRouteFk INT)
BEGIN
/**
* Pasado un RouteFk devuelve la información
* de sus tickets.
*
* @param vRouteFk
* @select Información de los tickets
*/
SELECT *
FROM (
SELECT t.id Id,
t.clientFk Client,
a.id Address,
a.nickname ClientName,
t.packages Packages,
a.street AddressName,
a.postalCode PostalCode,
a.city City,
sub2.itemPackingTypeFk PackingType,
c.phone ClientPhone,
c.mobile ClientMobile,
a.phone AddressPhone,
a.mobile AddressMobile,
d.longitude Longitude,
d.latitude Latitude,
wm.mediaValue SalePersonPhone,
tob.description Note,
t.isSigned Signed,
t.priority
FROM ticket t
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN delivery d ON d.addressFk = a.id
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
LEFT JOIN(
SELECT tob.description, t.id
FROM ticketObservation tob
JOIN ticket t ON tob.ticketFk = t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk
WHERE t.routeFk = vRouteFk
AND ot.code = 'delivery'
)tob ON tob.id = t.id
LEFT JOIN(
SELECT sub.ticketFk,
CONCAT('(',
GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk
ORDER BY sub.items DESC SEPARATOR ','),
') ') itemPackingTypeFk
FROM (
SELECT s.ticketFk, i.itemPackingTypeFk, COUNT(*) items
FROM ticket t
JOIN sale s ON s.ticketFk = t.id
JOIN item i ON i.id = s.itemFk
WHERE t.routeFk = vRouteFk
GROUP BY t.id, i.itemPackingTypeFk
)sub
GROUP BY sub.ticketFk
)sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
ORDER BY d.id DESC
LIMIT 10000000000000000000
)sub3
GROUP BY sub3.id
ORDER BY sub3.priority;
END$$
DELIMITER ;

View File

@ -0,0 +1 @@
DROP TRIGGER IF EXISTS `vn`.`claimBeginning_afterInsert`;

View File

@ -0,0 +1,14 @@
CREATE TABLE `vn`.`workerObservation` (
`id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`workerFk` int(10) unsigned DEFAULT NULL,
`userFk` int(10) unsigned DEFAULT NULL,
`text` text COLLATE utf8mb3_unicode_ci NOT NULL,
`created` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
CONSTRAINT `workerFk_workerObservation_FK` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE,
CONSTRAINT `userFk_workerObservation_FK` FOREIGN KEY (`userFk`) REFERENCES `account`.`user`(`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Todas las observaciones referentes a un trabajador';
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('WorkerObservation', '*', '*', 'ALLOW', 'ROLE', 'hr');

View File

View File

@ -1774,12 +1774,12 @@ INSERT INTO `vn`.`claimState`(`id`, `code`, `description`, `roleFk`, `priority`,
( 6, 'mana', 'Mana', 72, 4, 0), ( 6, 'mana', 'Mana', 72, 4, 0),
( 7, 'lack', 'Faltas', 72, 2, 0); ( 7, 'lack', 'Faltas', 72, 2, 0);
INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`) INSERT INTO `vn`.`claim`(`id`, `ticketCreated`, `claimStateFk`, `clientFk`, `workerFk`, `responsibility`, `isChargedToMana`, `created`, `packages`, `rma`, `ticketFk`)
VALUES VALUES
(1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183'), (1, util.VN_CURDATE(), 1, 1101, 18, 3, 0, util.VN_CURDATE(), 0, '02676A049183', 11),
(2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL), (2, util.VN_CURDATE(), 2, 1101, 18, 3, 0, util.VN_CURDATE(), 1, NULL, 16),
(3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL), (3, util.VN_CURDATE(), 3, 1101, 18, 1, 1, util.VN_CURDATE(), 5, NULL, 7),
(4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL); (4, util.VN_CURDATE(), 3, 1104, 18, 5, 0, util.VN_CURDATE(), 10, NULL, 8);
INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`) INSERT INTO `vn`.`claimObservation` (`claimFk`, `workerFk`, `text`, `created`)
VALUES VALUES

View File

@ -426,7 +426,8 @@ export default {
fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]', fourthStarted: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.started"]',
fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]', fourthEnded: 'vn-fixed-price tr:nth-child(5) vn-date-picker[ng-model="price.ended"]',
fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]', fourthDeleteIcon: 'vn-fixed-price tr:nth-child(5) > td:nth-child(9) > vn-icon-button[icon="delete"]',
orderColumnId: 'vn-fixed-price th[field="itemFk"]' orderColumnId: 'vn-fixed-price th[field="itemFk"]',
removeWarehouseFilter: 'vn-searchbar > form > vn-textfield > div.container > div.prepend > prepend > div > span:nth-child(1) > vn-icon > i'
}, },
itemCreateView: { itemCreateView: {
temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]', temporalName: 'vn-item-create vn-textfield[ng-model="$ctrl.item.provisionalName"]',
@ -987,6 +988,12 @@ export default {
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]', locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
saveButton: 'vn-worker-basic-data button[type=submit]' saveButton: 'vn-worker-basic-data button[type=submit]'
}, },
workerNotes: {
addNoteFloatButton: 'vn-float-button',
note: 'vn-textarea[ng-model="$ctrl.note.text"]',
saveButton: 'button[type=submit]',
firstNoteText: 'vn-worker-note .text'
},
workerPbx: { workerPbx: {
extension: 'vn-worker-pbx vn-textfield[ng-model="$ctrl.worker.sip.extension"]', extension: 'vn-worker-pbx vn-textfield[ng-model="$ctrl.worker.sip.extension"]',
saveButton: 'vn-worker-pbx button[type=submit]' saveButton: 'vn-worker-pbx button[type=submit]'

View File

@ -90,7 +90,7 @@ describe('SmartTable SearchBar integration', () => {
await page.waitToClick(selectors.itemFixedPrice.orderColumnId); await page.waitToClick(selectors.itemFixedPrice.orderColumnId);
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('13'); expect(result).toEqual('3');
}); });
it('should reload page and have same order', async() => { it('should reload page and have same order', async() => {
@ -99,7 +99,7 @@ describe('SmartTable SearchBar integration', () => {
}); });
const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value'); const result = await page.waitToGetProperty(selectors.itemFixedPrice.firstItemID, 'value');
expect(result).toEqual('13'); expect(result).toEqual('3');
}); });
}); });
}); });

View File

@ -28,22 +28,4 @@ describe('Client log path', () => {
it('should navigate to the log section', async() => { it('should navigate to the log section', async() => {
await page.accessToSection('client.card.log'); await page.accessToSection('client.card.log');
}); });
it('should check the previous value of the last logged change', async() => {
let lastModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
expect(lastModificationPreviousValue).toContain('DavidCharlesHaller');
});
it('should check the current value of the last logged change', async() => {
let lastModificationPreviousValue = await page
.waitToGetProperty(selectors.clientLog.lastModificationPreviousValue, 'innerText');
let lastModificationCurrentValue = await page.
waitToGetProperty(selectors.clientLog.lastModificationCurrentValue, 'innerText');
expect(lastModificationPreviousValue).toEqual('DavidCharlesHaller');
expect(lastModificationCurrentValue).toEqual('this is a test');
});
}); });

View File

@ -0,0 +1,42 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Worker Add notes path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
await page.accessToSearchResult('Bruce Banner');
await page.accessToSection('worker.card.note.index');
});
afterAll(async() => {
await browser.close();
});
it(`should reach the notes index`, async() => {
await page.waitForState('worker.card.note.index');
});
it(`should click on the add note button`, async() => {
await page.waitToClick(selectors.workerNotes.addNoteFloatButton);
await page.waitForState('worker.card.note.create');
});
it(`should create a note`, async() => {
await page.waitForSelector(selectors.workerNotes.note);
await page.type(`${selectors.workerNotes.note} textarea`, 'Meeting with Black Widow 21st 9am');
await page.waitToClick(selectors.workerNotes.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should confirm the note was created', async() => {
const result = await page.waitToGetProperty(selectors.workerNotes.firstNoteText, 'innerText');
expect(result).toEqual('Meeting with Black Widow 21st 9am');
});
});

View File

@ -42,23 +42,4 @@ describe('Item log path', () => {
await page.waitForSelector(selectors.itemsIndex.createItemButton); await page.waitForSelector(selectors.itemsIndex.createItemButton);
await page.waitForState('item.index'); await page.waitForState('item.index');
}); });
it(`should search for the created item and navigate to it's log section`, async() => {
await page.accessToSearchResult('Knowledge artifact');
await page.accessToSection('item.card.log');
});
it(`should confirm the log is showing 4 entries`, async() => {
await page.waitForSelector(selectors.itemLog.anyLineCreated);
const anyLineCreatedCount = await page.countElement(selectors.itemLog.anyLineCreated);
expect(anyLineCreatedCount).toEqual(4);
});
xit(`should confirm the log is showing the intrastat for the created item`, async() => {
const fifthLineCreatedProperty = await page
.waitToGetProperty(selectors.itemLog.fifthLineCreatedProperty, 'innerText');
expect(fifthLineCreatedProperty).toEqual('05080000');
});
}); });

View File

@ -15,8 +15,9 @@ describe('Item fixed prices path', () => {
await browser.close(); await browser.close();
}); });
it('should click on the add new foxed price button', async() => { it('should click on the add new fixed price button', async() => {
await page.doSearch(); await page.waitToClick(selectors.itemFixedPrice.removeWarehouseFilter);
await page.waitForSpinnerLoad();
await page.waitToClick(selectors.itemFixedPrice.add); await page.waitToClick(selectors.itemFixedPrice.add);
await page.waitForSelector(selectors.itemFixedPrice.fourthFixedPrice); await page.waitForSelector(selectors.itemFixedPrice.fourthFixedPrice);
}); });
@ -37,7 +38,8 @@ describe('Item fixed prices path', () => {
it('should reload the section and check the created price has the expected ID', async() => { it('should reload the section and check the created price has the expected ID', async() => {
await page.accessToSection('item.index'); await page.accessToSection('item.index');
await page.accessToSection('item.fixedPrice'); await page.accessToSection('item.fixedPrice');
await page.doSearch(); await page.waitToClick(selectors.itemFixedPrice.removeWarehouseFilter);
await page.waitForSpinnerLoad();
const result = await page.waitToGetProperty(selectors.itemFixedPrice.fourthItemID, 'value'); const result = await page.waitToGetProperty(selectors.itemFixedPrice.fourthItemID, 'value');

View File

@ -29,20 +29,4 @@ describe('Ticket expeditions and log path', () => {
expect(result).toEqual(3); expect(result).toEqual(3);
}); });
it(`should confirm the expedition deleted is shown now in the ticket log`, async() => {
await page.accessToSection('ticket.card.log');
const user = await page
.waitToGetProperty(selectors.ticketLog.user, 'innerText');
const action = await page
.waitToGetProperty(selectors.ticketLog.action, 'innerText');
const id = await page
.waitToGetProperty(selectors.ticketLog.id, 'innerText');
expect(user).toContain('production');
expect(action).toContain('Deletes');
expect(id).toEqual('2');
});
}); });

View File

@ -31,30 +31,4 @@ describe('Ticket log path', () => {
expect(message.text).toContain('Data saved!'); expect(message.text).toContain('Data saved!');
}); });
it('should navigate to the log section', async() => {
await page.accessToSection('ticket.card.log');
});
it('should set the viewport width to 1920 to see the table full width', async() => {
await page.setViewport({
width: 1920,
height: 0,
});
const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText');
expect(result.length).not.toBeGreaterThan('20');
});
it('should set the viewport width to 800 to see the table shrink and move data to the 1st column', async() => {
await page.setViewport({
width: 800,
height: 0,
});
const result = await page.waitToGetProperty(selectors.ticketLog.firstTD, 'innerText');
expect(result.length).toBeGreaterThan('15');
});
}); });

View File

@ -29,14 +29,4 @@ describe('Zone descriptor path', () => {
expect(count).toEqual(0); expect(count).toEqual(0);
}); });
it('should check the ticket whom lost the zone and see evidence on the logs', async() => {
await page.waitToClick(selectors.globalItems.homeButton);
await page.selectModule('ticket');
await page.accessToSearchResult('20');
await page.accessToSection('ticket.card.log');
const lastChanges = await page.waitToGetProperty(selectors.ticketLog.changes, 'innerText');
expect(lastChanges).toContain('1');
});
}); });

View File

@ -64,14 +64,4 @@ describe('Supplier basic data path', () => {
expect(result).toEqual('Some notes'); expect(result).toEqual('Some notes');
}); });
it('should navigate to the log section', async() => {
await page.accessToSection('supplier.card.log');
});
it('should check the changes have been recorded', async() => {
const result = await page.waitToGetProperty('vn-tr table tr:nth-child(3) td.after', 'innerText');
expect(result).toEqual('Some notes');
});
}); });

View File

@ -1,6 +1,20 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Model crud()', () => { describe('Model crud()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
let insertId; let insertId;
const barcodeModel = app.models.ItemBarcode; const barcodeModel = app.models.ItemBarcode;

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Model rewriteDbError()', () => { describe('Model rewriteDbError()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should extend rewriteDbError properties to any model passed', () => { it('should extend rewriteDbError properties to any model passed', () => {
const exampleModel = models.ItemTag; const exampleModel = models.ItemTag;

View File

@ -274,5 +274,6 @@
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Insert a date range": "Inserte un rango de fechas", "Insert a date range": "Inserte un rango de fechas",
"Added observation": "{{user}} añadió esta observacion: {{text}}", "Added observation": "{{user}} añadió esta observacion: {{text}}",
"Comment added to client": "Observación añadida al cliente {{clientFk}}" "Comment added to client": "Observación añadida al cliente {{clientFk}}",
"Cannot create a new claimBeginning from a different ticket": "No se puede crear una línea de reclamación de un ticket diferente al origen"
} }

View File

@ -1,41 +1,9 @@
const mysql = require('mysql'); const mysql = require('mysql');
const MySQL = require('loopback-connector-mysql').MySQL; const MySQL = require('loopback-connector-mysql').MySQL;
const EnumFactory = require('loopback-connector-mysql').EnumFactory; const EnumFactory = require('loopback-connector-mysql').EnumFactory;
const { Transaction, SQLConnector, ParameterizedSQL } = require('loopback-connector'); const {Transaction, SQLConnector, ParameterizedSQL} = require('loopback-connector');
const fs = require('fs'); const fs = require('fs');
const limitSet = new Set([
'save',
'updateOrCreate',
'replaceOrCreate',
'replaceById',
'update'
]);
const opOpts = {
update: [
'update',
'replaceById',
// |insert
'save',
'updateOrCreate',
'replaceOrCreate'
],
delete: [
'destroy',
'destroyAll'
],
insert: [
'create'
]
};
const opMap = new Map();
for (const op in opOpts) {
for (const met of opOpts[op])
opMap.set(met, op);
}
class VnMySQL extends MySQL { class VnMySQL extends MySQL {
/** /**
* Promisified version of execute(). * Promisified version of execute().
@ -253,49 +221,49 @@ class VnMySQL extends MySQL {
} }
create(model, data, opts, cb) { create(model, data, opts, cb) {
const ctx = { data }; const ctx = {data};
this.invokeMethod('create', this.invokeMethod('create',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
createAll(model, data, opts, cb) { createAll(model, data, opts, cb) {
const ctx = { data }; const ctx = {data};
this.invokeMethod('createAll', this.invokeMethod('createAll',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
save(model, data, opts, cb) { save(model, data, opts, cb) {
const ctx = { data }; const ctx = {data};
this.invokeMethod('save', this.invokeMethod('save',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
updateOrCreate(model, data, opts, cb) { updateOrCreate(model, data, opts, cb) {
const ctx = { data }; const ctx = {data};
this.invokeMethod('updateOrCreate', this.invokeMethod('updateOrCreate',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
replaceOrCreate(model, data, opts, cb) { replaceOrCreate(model, data, opts, cb) {
const ctx = { data }; const ctx = {data};
this.invokeMethod('replaceOrCreate', this.invokeMethod('replaceOrCreate',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
destroyAll(model, where, opts, cb) { destroyAll(model, where, opts, cb) {
const ctx = { where }; const ctx = {where};
this.invokeMethod('destroyAll', this.invokeMethod('destroyAll',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
update(model, where, data, opts, cb) { update(model, where, data, opts, cb) {
const ctx = { where, data }; const ctx = {where, data};
this.invokeMethod('update', this.invokeMethod('update',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
replaceById(model, id, data, opts, cb) { replaceById(model, id, data, opts, cb) {
const ctx = { id, data }; const ctx = {id, data};
this.invokeMethod('replaceById', this.invokeMethod('replaceById',
arguments, model, ctx, opts, cb); arguments, model, ctx, opts, cb);
} }
@ -311,91 +279,34 @@ class VnMySQL extends MySQL {
return super[method].apply(this, args); return super[method].apply(this, args);
this.invokeMethodP(method, [...args], model, ctx, opts) this.invokeMethodP(method, [...args], model, ctx, opts)
.then(res => cb(...res), cb); .then(res => cb(...[null].concat(res)), cb);
} }
async invokeMethodP(method, args, model, ctx, opts) { async invokeMethodP(method, args, model, ctx, opts) {
const Model = this.getModelDefinition(model).model; const Model = this.getModelDefinition(model).model;
const settings = Model.definition.settings;
let tx; let tx;
if (!opts.transaction) { if (!opts.transaction) {
tx = await Transaction.begin(this, {}); tx = await Transaction.begin(this, {});
opts = Object.assign({ transaction: tx, httpCtx: opts.httpCtx }, opts); opts = Object.assign({transaction: tx, httpCtx: opts.httpCtx}, opts);
} }
try { try {
// Fetch old values (update|delete) or login const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
let where, id, data, idName, limit, op, oldInstances, newInstances; if (userId) {
const hasGrabUser = settings.log && settings.log.grabUser; const user = await Model.app.models.Account.findById(userId, {fields: ['name']}, opts);
if (hasGrabUser) {
const userId = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
const user = await Model.app.models.Account.findById(userId, { fields: ['name'] }, opts);
await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts); await this.executeP(`CALL account.myUser_loginWithName(?)`, [user.name], opts);
} }
else {
where = ctx.where;
id = ctx.id;
data = ctx.data;
idName = this.idName(model);
limit = limitSet.has(method); const res = await new Promise((resolve, reject) => {
op = opMap.get(method);
if (!where) {
if (id) where = { [idName]: id };
else where = { [idName]: data[idName] };
}
// Fetch old values
switch (op) {
case 'update':
case 'delete':
// Single entity operation
const stmt = this.buildSelectStmt(op, data, idName, model, where, limit);
stmt.merge(`FOR UPDATE`);
oldInstances = await this.executeStmt(stmt, opts);
}
}
const res = await new Promise(resolve => {
const fnArgs = args.slice(0, -2); const fnArgs = args.slice(0, -2);
fnArgs.push(opts, (...args) => resolve(args)); fnArgs.push(opts, (err, ...args) => {
if (err) return reject(err);
resolve(args);
});
super[method].apply(this, fnArgs); super[method].apply(this, fnArgs);
}); });
if (hasGrabUser) if (userId) await this.executeP(`CALL account.myUser_logout()`, null, opts);
await this.executeP(`CALL account.myUser_logout()`, null, opts);
else {
// Fetch new values
const ids = [];
switch (op) {
case 'insert':
case 'update': {
switch (method) {
case 'createAll':
for (const row of res[1])
ids.push(row[idName]);
break;
case 'create':
ids.push(res[1]);
break;
case 'update':
if (data[idName] != null)
ids.push(data[idName]);
break;
}
const newWhere = ids.length ? { [idName]: ids } : where;
const stmt = this.buildSelectStmt(op, data, idName, model, newWhere, limit);
newInstances = await this.executeStmt(stmt, opts);
}
}
await this.createLogRecord(oldInstances, newInstances, model, opts);
}
if (tx) await tx.commit(); if (tx) await tx.commit();
return res; return res;
} catch (err) { } catch (err) {
@ -403,125 +314,6 @@ class VnMySQL extends MySQL {
throw err; throw err;
} }
} }
buildSelectStmt(op, data, idName, model, where, limit) {
const Model = this.getModelDefinition(model).model;
const properties = Object.keys(Model.definition.properties);
const fields = data ? Object.keys(data) : [];
if (op == 'delete')
properties.forEach(property => fields.push(property));
else {
const log = Model.definition.settings.log;
fields.push(idName);
if (log.relation) fields.push(Model.relations[log.relation].keyFrom);
if (log.showField) fields.push(log.showField);
else {
const showFieldNames = ['name', 'description', 'code', 'nickname'];
for (const field of showFieldNames) {
if (properties.includes(field)) {
log.showField = field;
fields.push(field);
break;
}
}
}
}
const stmt = new ParameterizedSQL(
'SELECT ' +
this.buildColumnNames(model, { fields }) +
' FROM ' +
this.tableEscaped(model)
);
stmt.merge(this.buildWhere(model, where));
if (limit) stmt.merge(`LIMIT 1`);
return stmt;
}
async createLogRecord(oldInstances, newInstances, model, opts) {
function setActionType() {
if (oldInstances && newInstances)
return 'update';
else if (!oldInstances && newInstances)
return 'insert';
return 'delete';
}
const action = setActionType();
if (!newInstances && action != 'delete') return;
const Model = this.getModelDefinition(model).model;
const models = Model.app.models;
const definition = Model.definition;
const log = definition.settings.log;
const primaryKey = this.idName(model);
const originRelation = log.relation;
const originFkField = originRelation
? Model.relations[originRelation].keyFrom
: primaryKey;
// Prevent adding logs when deleting a principal entity (Client, Zone...)
if (action == 'delete' && !originRelation) return;
function map(instances) {
const map = new Map();
if (!instances) return;
for (const instance of instances)
map.set(instance[primaryKey], instance);
return map;
}
const changedModel = definition.name;
const userFk = opts.httpCtx && opts.httpCtx.active.accessToken.userId;
const oldMap = map(oldInstances);
const newMap = map(newInstances);
const ids = (oldMap || newMap).keys();
const logEntries = [];
function insertValuesLogEntry(logEntry, instance) {
logEntry.originFk = instance[originFkField];
logEntry.changedModelId = instance[primaryKey];
if (log.showField) logEntry.changedModelValue = instance[log.showField];
}
for (const id of ids) {
const oldI = oldMap && oldMap.get(id);
const newI = newMap && newMap.get(id);
const logEntry = {
action,
userFk,
changedModel,
};
if (newI) {
insertValuesLogEntry(logEntry, newI);
// Delete unchanged properties
if (oldI) {
Object.keys(oldI).forEach(prop => {
const hasChanges = oldI[prop] instanceof Date ?
oldI[prop]?.getTime() != newI[prop]?.getTime() :
oldI[prop] != newI[prop];
if (!hasChanges) {
delete oldI[prop];
delete newI[prop];
}
});
}
} else
insertValuesLogEntry(logEntry, oldI);
logEntry.oldInstance = oldI;
logEntry.newInstance = newI;
logEntries.push(logEntry);
}
await models[log.model].create(logEntries, opts);
}
} }
exports.VnMySQL = VnMySQL; exports.VnMySQL = VnMySQL;
@ -542,7 +334,7 @@ exports.initialize = function initialize(dataSource, callback) {
if (callback) { if (callback) {
if (dataSource.settings.lazyConnect) { if (dataSource.settings.lazyConnect) {
process.nextTick(function () { process.nextTick(function() {
callback(); callback();
}); });
} else } else
@ -550,13 +342,13 @@ exports.initialize = function initialize(dataSource, callback) {
} }
}; };
MySQL.prototype.connect = function (callback) { MySQL.prototype.connect = function(callback) {
const self = this; const self = this;
const options = generateOptions(this.settings); const options = generateOptions(this.settings);
if (this.client) { if (this.client) {
if (callback) { if (callback) {
process.nextTick(function () { process.nextTick(function() {
callback(null, self.client); callback(null, self.client);
}); });
} }
@ -565,7 +357,7 @@ MySQL.prototype.connect = function (callback) {
function connectionHandler(options, callback) { function connectionHandler(options, callback) {
const client = mysql.createPool(options); const client = mysql.createPool(options);
client.getConnection(function (err, connection) { client.getConnection(function(err, connection) {
const conn = connection; const conn = connection;
if (!err) { if (!err) {
if (self.debug) if (self.debug)
@ -645,30 +437,27 @@ function generateOptions(settings) {
return options; return options;
} }
SQLConnector.prototype.all = function find(model, filter, options, cb) { SQLConnector.prototype.all = function find(model, filter, options, cb) {
const self = this; const self = this;
// Order by id if no order is specified // Order by id if no order is specified
filter = filter || {}; filter = filter || {};
const stmt = this.buildSelect(model, filter, options); const stmt = this.buildSelect(model, filter, options);
this.execute(stmt.sql, stmt.params, options, function (err, data) { this.execute(stmt.sql, stmt.params, options, function(err, data) {
if (err) { if (err)
return cb(err, []); return cb(err, []);
}
try { try {
const objs = data.map(function (obj) { const objs = data.map(function(obj) {
return self.fromRow(model, obj); return self.fromRow(model, obj);
}); });
if (filter && filter.include) { if (filter && filter.include) {
self.getModelDefinition(model).model.include( self.getModelDefinition(model).model.include(
objs, filter.include, options, cb, objs, filter.include, options, cb,
); );
} else { } else
cb(null, objs); cb(null, objs);
}
} catch (error) { } catch (error) {
cb(error, []) cb(error, []);
} }
}); });
}; };

View File

@ -2,9 +2,9 @@ const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context'); const LoopBackContext = require('loopback-context');
describe('Claim createFromSales()', () => { describe('Claim createFromSales()', () => {
const ticketId = 16; const ticketId = 23;
const newSale = [{ const newSale = [{
id: 3, id: 31,
instance: 0, instance: 0,
quantity: 10 quantity: 10
}]; }];

View File

@ -1,6 +1,20 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Update Claim', () => { describe('Update Claim', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const newDate = Date.vnNew(); const newDate = Date.vnNew();
const originalData = { const originalData = {
ticketFk: 3, ticketFk: 3,

View File

@ -1,6 +1,20 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('Update Claim', () => { describe('Update Claim', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const newDate = Date.vnNew(); const newDate = Date.vnNew();
const original = { const original = {
ticketFk: 3, ticketFk: 3,

View File

@ -10,8 +10,16 @@ module.exports = Self => {
}); });
Self.observe('before save', async ctx => { Self.observe('before save', async ctx => {
if (ctx.isNewInstance) return; if (ctx.isNewInstance) {
//await claimIsEditable(ctx); const models = Self.app.models;
const options = ctx.options;
const instance = ctx.instance;
const ticket = await models.Sale.findById(instance.saleFk, {fields: ['ticketFk']}, options);
const claim = await models.Claim.findById(instance.claimFk, {fields: ['ticketFk']}, options);
if (ticket.ticketFk != claim.ticketFk)
throw new UserError(`Cannot create a new claimBeginning from a different ticket`);
}
// await claimIsEditable(ctx);
}); });
Self.observe('before delete', async ctx => { Self.observe('before delete', async ctx => {

View File

@ -1,11 +1,6 @@
{ {
"name": "ClaimBeginning", "name": "ClaimBeginning",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim",
"showField": "quantity"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimBeginning" "table": "claimBeginning"

View File

@ -1,10 +1,6 @@
{ {
"name": "ClaimDevelopment", "name": "ClaimDevelopment",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimDevelopment" "table": "claimDevelopment"

View File

@ -1,18 +1,14 @@
{ {
"name": "ClaimDms", "name": "ClaimDms",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimDms" "table": "claimDms"
} }
}, },
"allowedContentTypes": [ "allowedContentTypes": [
"image/png", "image/png",
"image/jpeg", "image/jpeg",
"image/jpg" "image/jpg"
], ],
"properties": { "properties": {
@ -34,4 +30,4 @@
"foreignKey": "dmsFk" "foreignKey": "dmsFk"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "ClaimEnd", "name": "ClaimEnd",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimEnd" "table": "claimEnd"

View File

@ -1,10 +1,6 @@
{ {
"name": "ClaimObservation", "name": "ClaimObservation",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimObservation" "table": "claimObservation"
@ -40,4 +36,4 @@
"foreignKey": "claimFk" "foreignKey": "claimFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "ClaimState", "name": "ClaimState",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"relation": "claim",
"showField": "description"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claimState" "table": "claimState"

View File

@ -1,10 +1,6 @@
{ {
"name": "Claim", "name": "Claim",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClaimLog",
"showField": "id"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "claim" "table": "claim"

View File

@ -2,11 +2,6 @@
"name": "Address", "name": "Address",
"description": "Client addresses", "description": "Client addresses",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client",
"showField": "nickname"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "address" "table": "address"
@ -88,4 +83,4 @@
"foreignKey": "customsAgentFk" "foreignKey": "customsAgentFk"
} }
} }
} }

View File

@ -2,11 +2,6 @@
"name": "ClientContact", "name": "ClientContact",
"description": "Client phone contacts", "description": "Client phone contacts",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client",
"showField": "name"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "clientContact" "table": "clientContact"
@ -33,4 +28,4 @@
"foreignKey": "clientFk" "foreignKey": "clientFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "ClientDms", "name": "ClientDms",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"ClientLog",
"relation": "client",
"showField": "dmsFk"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "clientDms" "table": "clientDms"

View File

@ -2,10 +2,6 @@
"name": "ClientObservation", "name": "ClientObservation",
"description": "Client notes", "description": "Client notes",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "clientObservation" "table": "clientObservation"

View File

@ -1,11 +1,6 @@
{ {
"name": "ClientSample", "name": "ClientSample",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client",
"showField": "type"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "clientSample" "table": "clientSample"

View File

@ -1,10 +1,6 @@
{ {
"name": "Client", "name": "Client",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"ClientLog",
"showField": "id"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "client" "table": "client"
@ -260,4 +256,4 @@
} }
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "Greuge", "name": "Greuge",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client",
"showField": "description"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "greuge" "table": "greuge"
@ -58,4 +53,4 @@
"foreignKey": "userFk" "foreignKey": "userFk"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "Recovery", "name": "Recovery",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ClientLog",
"relation": "client"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "recovery" "table": "recovery"
@ -38,4 +34,4 @@
"foreignKey": "clientFk" "foreignKey": "clientFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "Buy", "name": "Buy",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "EntryLog",
"relation": "entry",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "buy" "table": "buy"

View File

@ -1,10 +1,6 @@
{ {
"name": "EntryObservation", "name": "EntryObservation",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "EntryLog",
"relation": "entry"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "entryObservation" "table": "entryObservation"

View File

@ -1,10 +1,6 @@
{ {
"name": "Entry", "name": "Entry",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"EntryLog",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "entry" "table": "entry"

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('invoiceIn clone()', () => { describe('invoiceIn clone()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return the cloned invoiceIn and also clone invoiceInDueDays and invoiceInTaxes if there are any referencing the invoiceIn', async() => { it('should return the cloned invoiceIn and also clone invoiceInDueDays and invoiceInTaxes if there are any referencing the invoiceIn', async() => {
const userId = 1; const userId = 1;
const ctx = { const ctx = {

View File

@ -1,10 +1,6 @@
{ {
"name": "InvoiceInTax", "name": "InvoiceInTax",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "InvoiceInLog",
"relation": "invoiceIn"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "invoiceInTax" "table": "invoiceInTax"
@ -55,4 +51,4 @@
"foreignKey": "transactionTypeSageFk" "foreignKey": "transactionTypeSageFk"
} }
} }
} }

View File

@ -1,9 +1,6 @@
{ {
"name": "InvoiceIn", "name": "InvoiceIn",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "InvoiceInLog"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "invoiceIn" "table": "invoiceIn"

View File

@ -56,7 +56,7 @@ module.exports = Self => {
reference: invoiceOut.ref, reference: invoiceOut.ref,
recipientId: invoiceOut.clientFk recipientId: invoiceOut.clientFk
}); });
const stream = await invoiceReport.toPdfStream(); const buffer = await invoiceReport.toPdfStream();
const issued = invoiceOut.issued; const issued = invoiceOut.issued;
const year = issued.getFullYear().toString(); const year = issued.getFullYear().toString();
@ -66,7 +66,7 @@ module.exports = Self => {
const fileName = `${year}${invoiceOut.ref}.pdf`; const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice // Store invoice
print.storage.write(stream, { await print.storage.write(buffer, {
type: 'invoice', type: 'invoice',
path: `${year}/${month}/${day}`, path: `${year}/${month}/${day}`,
fileName: fileName fileName: fileName

View File

@ -100,16 +100,23 @@ class Controller extends Section {
}; };
this.$http.post(`InvoiceOuts/invoiceClient`, params) this.$http.post(`InvoiceOuts/invoiceClient`, params)
.then(() => this.invoiceNext())
.catch(res => { .catch(res => {
this.errors.unshift({ const message = res.data?.error?.message || res.message;
address, if (res.status >= 400 && res.status < 500) {
message: res.data.error.message this.errors.unshift({address, message});
}); this.invoiceNext();
} else {
this.invoicing = false;
this.status = 'done';
throw new UserError(`Critical invoicing error, proccess stopped`);
}
}) })
.finally(() => { }
this.addressIndex++;
this.invoiceOut(); invoiceNext() {
}); this.addressIndex++;
this.invoiceOut();
} }
get nAddresses() { get nAddresses() {

View File

@ -17,4 +17,5 @@ Ended process: Proceso finalizado
Invoice out: Facturar Invoice out: Facturar
One client: Un solo cliente One client: Un solo cliente
Choose a valid client: Selecciona un cliente válido Choose a valid client: Selecciona un cliente válido
Stop: Parar Stop: Parar
Critical invoicing error, proccess stopped: Error crítico al facturar, proceso detenido

View File

@ -0,0 +1,96 @@
module.exports = Self => {
Self.remoteMethodCtx('editFixedPrice', {
description: 'Updates a column for one or more fixed price',
accessType: 'WRITE',
accepts: [{
arg: 'field',
type: 'string',
required: true,
description: `the column to edit`
},
{
arg: 'newValue',
type: 'any',
required: true,
description: `The new value to save`
},
{
arg: 'lines',
type: ['object'],
required: true,
description: `the buys which will be modified`
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/editFixedPrice`,
verb: 'POST'
}
});
Self.editFixedPrice = async(ctx, field, newValue, lines, filter, options) => {
let tx;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
let modelName;
let identifier;
switch (field) {
case 'hasMinPrice':
case 'minPrice':
modelName = 'Item';
identifier = 'itemFk';
break;
case 'rate2':
case 'rate3':
case 'started':
case 'ended':
case 'warehouseFk':
modelName = 'FixedPrice';
identifier = 'id';
}
const models = Self.app.models;
const model = models[modelName];
try {
const promises = [];
const value = {};
value[field] = newValue;
if (filter) {
filter = {where: filter};
lines = await models.FixedPrice.filter(ctx, filter, myOptions);
}
const targets = lines.map(line => {
return line[identifier];
});
for (let target of targets)
promises.push(model.upsertWithWhere({id: target}, value, myOptions));
const result = await Promise.all(promises);
if (tx) await tx.commit();
return result;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -184,8 +184,7 @@ module.exports = Self => {
} }
} }
stmt.merge(conn.makeWhere(filter.where)); stmt.merge(conn.makeSuffix(filter));
stmt.merge(conn.makePagination(filter));
const fixedPriceIndex = stmts.push(stmt) - 1; const fixedPriceIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');

View File

@ -0,0 +1,63 @@
const models = require('vn-loopback/server/server').models;
describe('Item editFixedPrice()', () => {
it('should change the value of a given column for the selected buys', async() => {
const tx = await models.FixedPrice.beginTransaction({});
const options = {transaction: tx};
try {
const ctx = {
args: {
search: '1'
},
req: {accessToken: {userId: 1}}
};
const [original] = await models.FixedPrice.filter(ctx, null, options);
const field = 'rate2';
const newValue = 99;
const lines = [{itemFk: original.itemFk, id: original.id}];
await models.FixedPrice.editFixedPrice(ctx, field, newValue, lines, null, options);
const [result] = await models.FixedPrice.filter(ctx, null, options);
expect(result[field]).toEqual(newValue);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should change the value of a given column for filter', async() => {
const tx = await models.FixedPrice.beginTransaction({});
const options = {transaction: tx};
try {
const filter = {'it.categoryFk': 1};
const ctx = {
args: {
filter: filter
},
req: {accessToken: {userId: 1}}
};
const field = 'rate2';
const newValue = 88;
await models.FixedPrice.editFixedPrice(ctx, field, newValue, null, filter, options);
const [result] = await models.FixedPrice.filter(ctx, filter, options);
expect(result[field]).toEqual(newValue);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -42,7 +42,7 @@ describe('upsertFixedPrice()', () => {
delete ctx.args.started; delete ctx.args.started;
delete ctx.args.ended; delete ctx.args.ended;
ctx.args.hasMinPrice = true; ctx.args.hasMinPrice = false;
expect(result).toEqual(jasmine.objectContaining(ctx.args)); expect(result).toEqual(jasmine.objectContaining(ctx.args));
@ -74,7 +74,7 @@ describe('upsertFixedPrice()', () => {
delete ctx.args.started; delete ctx.args.started;
delete ctx.args.ended; delete ctx.args.ended;
ctx.args.hasMinPrice = false; ctx.args.hasMinPrice = true;
expect(result).toEqual(jasmine.objectContaining(ctx.args)); expect(result).toEqual(jasmine.objectContaining(ctx.args));
@ -105,7 +105,7 @@ describe('upsertFixedPrice()', () => {
rate2: rate2, rate2: rate2,
rate3: firstRate3, rate3: firstRate3,
minPrice: 0, minPrice: 0,
hasMinPrice: false hasMinPrice: true
}}; }};
// create new fixed price // create new fixed price

View File

@ -87,7 +87,7 @@ module.exports = Self => {
await targetItem.updateAttributes({ await targetItem.updateAttributes({
minPrice: args.minPrice, minPrice: args.minPrice,
hasMinPrice: args.minPrice ? true : false hasMinPrice: args.hasMinPrice
}, myOptions); }, myOptions);
const itemFields = [ const itemFields = [

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('item updateTaxes()', () => { describe('item updateTaxes()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should throw an error if the taxClassFk is blank', async() => { it('should throw an error if the taxClassFk is blank', async() => {
const tx = await models.Item.beginTransaction({}); const tx = await models.Item.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('tag onSubmit()', () => { describe('tag onSubmit()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should delete a tag', async() => { it('should delete a tag', async() => {
const tx = await models.Item.beginTransaction({}); const tx = await models.Item.beginTransaction({});
const options = {transaction: tx}; const options = {transaction: tx};

View File

@ -2,4 +2,5 @@ module.exports = Self => {
require('../methods/fixed-price/filter')(Self); require('../methods/fixed-price/filter')(Self);
require('../methods/fixed-price/upsertFixedPrice')(Self); require('../methods/fixed-price/upsertFixedPrice')(Self);
require('../methods/fixed-price/getRate2')(Self); require('../methods/fixed-price/getRate2')(Self);
require('../methods/fixed-price/editFixedPrice')(Self);
}; };

View File

@ -1,11 +1,6 @@
{ {
"name": "ItemBarcode", "name": "ItemBarcode",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ItemLog",
"relation": "item",
"showField": "code"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "itemBarcode" "table": "itemBarcode"
@ -27,6 +22,6 @@
"type": "belongsTo", "type": "belongsTo",
"model": "Item", "model": "Item",
"foreignKey": "itemFk" "foreignKey": "itemFk"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "ItemBotanical", "name": "ItemBotanical",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ItemLog",
"relation": "item"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "itemBotanical" "table": "itemBotanical"
@ -34,4 +30,4 @@
"foreignKey": "specieFk" "foreignKey": "specieFk"
} }
} }
} }

View File

@ -20,6 +20,9 @@
}, },
"created": { "created": {
"type": "date" "type": "date"
},
"isChecked": {
"type": "boolean"
} }
}, },
"relations": { "relations": {

View File

@ -1,11 +1,6 @@
{ {
"name": "ItemTag", "name": "ItemTag",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ItemLog",
"relation": "item",
"showField": "value"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "itemTag" "table": "itemTag"

View File

@ -1,11 +1,6 @@
{ {
"name": "ItemTaxCountry", "name": "ItemTaxCountry",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ItemLog",
"relation": "item",
"showField": "countryFk"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "itemTaxCountry" "table": "itemTaxCountry"
@ -47,4 +42,4 @@
"foreignKey": "taxClassFk" "foreignKey": "taxClassFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "Item", "name": "Item",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ItemLog",
"showField": "id",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "item" "table": "item"

View File

@ -1,6 +1,7 @@
<vn-crud-model <vn-crud-model
vn-id="model" vn-id="model"
url="FixedPrices/filter" url="FixedPrices/filter"
user-params="::$ctrl.filterParams"
limit="20" limit="20"
data="prices" data="prices"
order="itemFk" order="itemFk"
@ -17,6 +18,8 @@
auto-state="false" auto-state="false"
panel="vn-fixed-price-search-panel" panel="vn-fixed-price-search-panel"
info="Search prices by item ID or code" info="Search prices by item ID or code"
suggested-filter="$ctrl.filterParams"
filter="$ctrl.filterParams"
placeholder="Search fixed prices" placeholder="Search fixed prices"
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
@ -31,15 +34,21 @@
<table> <table>
<thead> <thead>
<tr> <tr>
<th shrink>
<vn-multi-check
model="model"
checked="$ctrl.checkAll"
check-field="checked"
check-dummy-enabled="true"
checked-dummy-count="$ctrl.checkedDummyCount">
</vn-multi-check>
</th>
<th field="itemFk"> <th field="itemFk">
<span translate>Item ID</span> <span translate>Item ID</span>
</th> </th>
<th field="name"> <th field="name">
<span translate>Description</span> <span translate>Description</span>
</th> </th>
<th field="warehouseFk">
<span translate>Warehouse</span>
</th>
<th <th
field="rate2"> field="rate2">
<span translate>Grouping price</span> <span translate>Grouping price</span>
@ -57,13 +66,24 @@
<th field="ended"> <th field="ended">
<span translate>Ended</span> <span translate>Ended</span>
</th> </th>
<th field="warehouseFk">
<span translate>Warehouse</span>
</th>
<th shrink></th> <th shrink></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
<tr ng-repeat="price in prices"> <tr ng-repeat="price in prices">
<td>
<vn-check
ng-model="price.checked"
on-change="$ctrl.saveChecked(price.id)"
vn-click-stop>
</vn-check>
</td>
<td shrink-field> <td shrink-field>
<vn-autocomplete <vn-autocomplete
vn-id="itemFk"
class="dense" class="dense"
url="Items/withName" url="Items/withName"
ng-model="price.itemFk" ng-model="price.itemFk"
@ -88,7 +108,7 @@
ng-if="price.itemFk" ng-if="price.itemFk"
ng-click="itemDescriptor.show($event, price.itemFk)" ng-click="itemDescriptor.show($event, price.itemFk)"
class="link"> class="link">
{{price.name}} {{itemFk.selection.name}}
</span> </span>
<vn-one ng-if="price.subName"> <vn-one ng-if="price.subName">
<h3 title="{{price.subName}}">{{price.subName}}</h3> <h3 title="{{price.subName}}">{{price.subName}}</h3>
@ -100,18 +120,11 @@
tabindex="-1"> tabindex="-1">
</vn-fetched-tags> </vn-fetched-tags>
</td> </td>
<td shrink-field-expand>
<vn-autocomplete
vn-one
ng-model="price.warehouseFk"
data="warehouses"
on-change="$ctrl.upsertPrice(price)"
tabindex="2">
</vn-autocomplete>
</td>
<td shrink-field> <td shrink-field>
<vn-td-editable number> <vn-td-editable number>
<text>{{price.rate2 | currency: 'EUR':2}}</text> <text>
<strong>{{price.rate2 | currency: 'EUR':2}}</strong>
</text>
<field> <field>
<vn-input-number <vn-input-number
class="dense" class="dense"
@ -125,7 +138,9 @@
</td> </td>
<td shrink-field> <td shrink-field>
<vn-td-editable number> <vn-td-editable number>
<text>{{price.rate3 | currency: 'EUR':2}}</text> <text>
<strong>{{price.rate3 | currency: 'EUR':2}}</strong>
</text>
<field> <field>
<vn-input-number <vn-input-number
class="dense" class="dense"
@ -140,28 +155,42 @@
<td shrink-field-expand class="minPrice"> <td shrink-field-expand class="minPrice">
<vn-check <vn-check
vn-one vn-one
ng-model="price.hasMinPrice"> ng-model="price.hasMinPrice"
on-change="$ctrl.upsertPrice(price)">
</vn-check> </vn-check>
<vn-input-number <vn-input-number
disabled="!price.hasMinPrice" ng-class="{inactive: !price.hasMinPrice}"
ng-model="price.minPrice" ng-model="price.minPrice"
on-change="$ctrl.upsertPrice(price)" on-change="$ctrl.upsertPrice(price)"
step="0.01"> step="0.01">
</vn-input-number> </vn-input-number>
</td> </td>
<td shrink-date> <td shrink-date>
<vn-date-picker <vn-chip class="chip {{$ctrl.isBigger(price.started)}} transparent">
vn-one <vn-date-picker
ng-model="price.started" vn-one
on-change="$ctrl.upsertPrice(price)"> ng-model="price.started"
</vn-date-picker> on-change="$ctrl.upsertPrice(price)">
</vn-date-picker>
</vn-chip>
</td> </td>
<td shrink-date> <td shrink-date>
<vn-date-picker <vn-chip class="chip {{$ctrl.isLower(price.ended)}} transparent">
<vn-date-picker
vn-one
ng-model="price.ended"
on-change="$ctrl.upsertPrice(price)">
</vn-date-picker>
</vn-chip>
</td>
<td expand>
<vn-autocomplete
vn-one vn-one
ng-model="price.ended" ng-model="price.warehouseFk"
on-change="$ctrl.upsertPrice(price)"> data="warehouses"
</vn-date-picker> on-change="$ctrl.upsertPrice(price)"
tabindex="2">
</vn-autocomplete>
</td> </td>
<td shrink> <td shrink>
<vn-icon-button <vn-icon-button
@ -185,6 +214,69 @@
</smart-table> </smart-table>
</vn-card> </vn-card>
</div> </div>
<div fixed-bottom-right>
<vn-vertical style="align-items: center;">
<vn-button class="round sm vn-mb-sm"
icon="edit"
ng-show="$ctrl.totalChecked > 0"
ng-click="edit.show($event)"
vn-tooltip="Edit fixed price(s)"
tooltip-position="left">
</vn-button>
</vn-vertical>
</div>
<vn-dialog class="edit"
vn-id="edit"
on-accept="$ctrl.onEditAccept()"
on-close="$ctrl.editedColumn = null">
<tpl-body style="width: 400px;">
<span translate>Edit</span>
<span class="countLines">
{{::$ctrl.totalChecked}}
</span>
<span translate>buy(s)</span>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="$ctrl.editedColumn.field"
data="$ctrl.columns"
value-field="field"
show-field="displayName"
label="Field to edit">
</vn-autocomplete>
<vn-input-number
vn-one
ng-if="$ctrl.editedColumn.field == 'rate2' || $ctrl.editedColumn.field == 'rate3' || $ctrl.editedColumn.field == 'minPrice'"
label="Value"
ng-model="$ctrl.editedColumn.newValue">
</vn-input-number>
<vn-check
vn-one
ng-if="$ctrl.editedColumn.field == 'hasMinPrice'"
ng-model="$ctrl.editedColumn.newValue">
</vn-check>
<vn-date-picker
vn-one
ng-if="$ctrl.editedColumn.field == 'started' || $ctrl.editedColumn.field == 'ended'"
label="Date"
ng-model="$ctrl.editedColumn.newValue">
</vn-date-picker>
<vn-autocomplete
vn-one
ng-if="$ctrl.editedColumn.field == 'warehouseFk'"
label="Warehouse"
ng-model="$ctrl.editedColumn.newValue"
data="warehouses">
</vn-autocomplete>
</vn-horizontal>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<vn-item-descriptor-popover <vn-item-descriptor-popover
vn-id="item-descriptor" vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk"> warehouse-fk="$ctrl.vnConfig.warehouseFk">

View File

@ -5,6 +5,9 @@ import './style.scss';
export default class Controller extends Section { export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.editedColumn;
this.checkAll = false;
this.checkedFixedPrices = [];
this.smartTableOptions = { this.smartTableOptions = {
activeButtons: { activeButtons: {
@ -30,13 +33,146 @@ export default class Controller extends Section {
} }
] ]
}; };
this.filterParams = {
warehouseFk: this.vnConfig.warehouseFk
};
}
getFilterParams() {
return {
warehouseFk: this.vnConfig.warehouseFk
};
}
get columns() {
if (this._columns) return this._columns;
this._columns = [
{field: 'rate2', displayName: this.$t('Grouping price')},
{field: 'rate3', displayName: this.$t('Packing price')},
{field: 'hasMinPrice', displayName: this.$t('Has min price')},
{field: 'minPrice', displayName: this.$t('Min price')},
{field: 'started', displayName: this.$t('Started')},
{field: 'ended', displayName: this.$t('Ended')},
{field: 'warehouseFk', displayName: this.$t('Warehouse')}
];
return this._columns;
}
get checked() {
const fixedPrices = this.$.model.data || [];
const checkedBuys = [];
for (let fixedPrice of fixedPrices) {
if (fixedPrice.checked)
checkedBuys.push(fixedPrice);
}
return checkedBuys;
}
uncheck() {
this.checkAll = false;
this.checkedFixedPrices = [];
}
get totalChecked() {
if (this.checkedDummyCount)
return this.checkedDummyCount;
return this.checked.length;
}
saveChecked(fixedPriceId) {
const index = this.checkedFixedPrices.indexOf(fixedPriceId);
if (index !== -1)
return this.checkedFixedPrices.splice(index, 1);
return this.checkedFixedPrices.push(fixedPriceId);
}
reCheck() {
if (!this.$.model.data) return;
if (!this.checkedFixedPrices.length) return;
this.$.model.data.forEach(fixedPrice => {
if (this.checkedFixedPrices.includes(fixedPrice.id))
fixedPrice.checked = true;
});
}
onEditAccept() {
const rowsToEdit = [];
for (let row of this.checked)
rowsToEdit.push({id: row.id, itemFk: row.itemFk});
const data = {
field: this.editedColumn.field,
newValue: this.editedColumn.newValue,
lines: rowsToEdit
};
if (this.checkedDummyCount && this.checkedDummyCount > 0) {
const params = {};
if (this.$.model.userParams) {
const userParams = this.$.model.userParams;
for (let param in userParams) {
let newParam = this.exprBuilder(param, userParams[param]);
if (!newParam)
newParam = {[param]: userParams[param]};
Object.assign(params, newParam);
}
}
if (this.$.model.userFilter)
Object.assign(params, this.$.model.userFilter.where);
data.filter = params;
}
return this.$http.post('FixedPrices/editFixedPrice', data)
.then(() => {
this.uncheck();
this.$.model.refresh();
});
}
isBigger(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
date = new Date(date);
date.setHours(0, 0, 0, 0);
const timeDifference = today - date;
if (timeDifference < 0) return 'warning';
}
isLower(date) {
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
date = new Date(date);
date.setHours(0, 0, 0, 0);
const timeDifference = today - date;
if (timeDifference > 0) return 'warning';
} }
add() { add() {
if (!this.$.model.data || this.$.model.data.length == 0) { if (!this.$.model.data || this.$.model.data.length == 0) {
this.$.model.data = []; this.$.model.data = [];
this.$.model.proxiedData = []; this.$.model.proxiedData = [];
this.$.model.insert({});
const today = Date.vnNew();
const millisecsInDay = 86400000;
const daysInWeek = 7;
const nextWeek = new Date(today.getTime() + daysInWeek * millisecsInDay);
this.$.model.insert({
started: today,
ended: nextWeek
});
return; return;
} }
@ -66,10 +202,8 @@ export default class Controller extends Section {
if (resetMinPrice) if (resetMinPrice)
delete price['minPrice']; delete price['minPrice'];
price.hasMinPrice = price.minPrice ? true : false; const requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3'];
for (const field of requiredFields)
let requiredFields = ['itemFk', 'started', 'ended', 'rate2', 'rate3'];
for (let field of requiredFields)
if (price[field] == undefined) return; if (price[field] == undefined) return;
const query = 'FixedPrices/upsertFixedPrice'; const query = 'FixedPrices/upsertFixedPrice';

View File

@ -12,8 +12,92 @@ describe('fixed price', () => {
const $scope = $rootScope.$new(); const $scope = $rootScope.$new();
const $element = angular.element('<vn-fixed-price></vn-fixed-price>'); const $element = angular.element('<vn-fixed-price></vn-fixed-price>');
controller = $componentController('vnFixedPrice', {$element, $scope}); controller = $componentController('vnFixedPrice', {$element, $scope});
controller.$ = {
model: {refresh: () => {}},
edit: {hide: () => {}}
};
})); }));
describe('get columns', () => {
it(`should return a set of columns`, () => {
let result = controller.columns;
let length = result.length;
let anyColumn = Object.keys(result[Math.floor(Math.random() * Math.floor(length))]);
expect(anyColumn).toContain('field', 'displayName');
});
});
describe('get checked', () => {
it(`should return a set of checked lines`, () => {
controller.$.model.data = [
{checked: true, id: 1},
{checked: true, id: 2},
{checked: true, id: 3},
{checked: false, id: 4},
];
let result = controller.checked;
expect(result.length).toEqual(3);
});
});
describe('reCheck()', () => {
it(`should recheck buys`, () => {
controller.$.model.data = [
{checked: false, id: 1},
{checked: false, id: 2},
{checked: false, id: 3},
{checked: false, id: 4},
];
controller.checkedFixedPrices = [1, 2];
controller.reCheck();
expect(controller.$.model.data[0].checked).toEqual(true);
expect(controller.$.model.data[1].checked).toEqual(true);
expect(controller.$.model.data[2].checked).toEqual(false);
expect(controller.$.model.data[3].checked).toEqual(false);
});
});
describe('saveChecked()', () => {
it(`should check buy`, () => {
const buyCheck = 3;
controller.checkedFixedPrices = [1, 2];
controller.saveChecked(buyCheck);
expect(controller.checkedFixedPrices[2]).toEqual(buyCheck);
});
it(`should uncheck buy`, () => {
const buyUncheck = 3;
controller.checkedFixedPrices = [1, 2, 3];
controller.saveChecked(buyUncheck);
expect(controller.checkedFixedPrices[2]).toEqual(undefined);
});
});
describe('onEditAccept()', () => {
it(`should perform a query to update columns`, () => {
controller.editedColumn = {field: 'my field', newValue: 'the new value'};
const query = 'FixedPrices/editFixedPrice';
$httpBackend.expectPOST(query).respond();
controller.onEditAccept();
$httpBackend.flush();
const result = controller.checked;
expect(result.length).toEqual(0);
});
});
describe('upsertPrice()', () => { describe('upsertPrice()', () => {
it('should do nothing if one or more required arguments are missing', () => { it('should do nothing if one or more required arguments are missing', () => {
jest.spyOn(controller.vnApp, 'showSuccess'); jest.spyOn(controller.vnApp, 'showSuccess');

View File

@ -3,3 +3,5 @@ Search prices by item ID or code: Buscar por ID de artículo o código
Search fixed prices: Buscar precios fijados Search fixed prices: Buscar precios fijados
Add fixed price: Añadir precio fijado Add fixed price: Añadir precio fijado
This row will be removed: Esta linea se eliminará This row will be removed: Esta linea se eliminará
Edit fixed price(s): Editar precio(s) fijado(s)
Has min price: Tiene precio mínimo

View File

@ -1,20 +1,46 @@
@import "variables"; @import "variables";
smart-table table{ vn-fixed-price{
[shrink-field]{ smart-table table{
width: 80px; [shrink-field]{
max-width: 80px; width: 80px;
max-width: 80px;
}
[shrink-field-expand]{
width: 150px;
max-width: 150px;
}
} }
[shrink-field-expand]{
width: 150px; .minPrice {
max-width: 150px; align-items: center;
text-align: center;
vn-input-number {
width: 90px;
max-width: 90px;
}
}
smart-table table tbody > * > td .chip {
padding: 0px;
}
smart-table table tbody > * > td{
padding: 0px;
padding-left: 5px;
padding-right: 5px;
}
smart-table table tbody > * > td .chip.warning {
color: $color-font-bg
}
.vn-field > .container > .infix > .control > input {
color: inherit;
}
vn-input-number.inactive{
input {
color: $color-font-light !important;
}
} }
} }
.minPrice {
align-items: center;
text-align: center;
vn-input-number {
width: 90px;
max-width: 90px;
}
}

View File

@ -1,6 +1,20 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('AgencyTerm createInvoiceIn()', () => { describe('AgencyTerm createInvoiceIn()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
const rows = [ const rows = [
{ {
routeFk: 2, routeFk: 2,

View File

@ -1,10 +1,6 @@
{ {
"name": "Route", "name": "Route",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"RouteLog",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "route" "table": "route"

View File

@ -1,10 +1,6 @@
{ {
"name": "Shelving", "name": "Shelving",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "ShelvingLog",
"showField": "id"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "shelving" "table": "shelving"

View File

@ -1,10 +1,6 @@
{ {
"name": "SupplierAccount", "name": "SupplierAccount",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"SupplierLog",
"relation": "supplier"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "supplierAccount" "table": "supplierAccount"
@ -35,4 +31,4 @@
"foreignKey": "bankEntityFk" "foreignKey": "bankEntityFk"
} }
} }
} }

View File

@ -2,11 +2,6 @@
"name": "SupplierAddress", "name": "SupplierAddress",
"description": "Supplier addresses", "description": "Supplier addresses",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "SupplierLog",
"relation": "supplier",
"showField": "name"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "supplierAddress" "table": "supplierAddress"
@ -52,4 +47,4 @@
"foreignKey": "supplierFk" "foreignKey": "supplierFk"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "SupplierContact", "name": "SupplierContact",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"SupplierLog",
"relation": "supplier"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "supplierContact" "table": "supplierContact"
@ -50,4 +46,4 @@
"permission": "ALLOW" "permission": "ALLOW"
} }
] ]
} }

View File

@ -1,9 +1,6 @@
{ {
"name": "Supplier", "name": "Supplier",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"SupplierLog"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "supplier" "table": "supplier"

View File

@ -22,7 +22,7 @@
value-field="id" value-field="id"
rule> rule>
</vn-autocomplete> </vn-autocomplete>
<vn-input-number <vn-input-number
type="number" type="number"
label="Minimum M3" label="Minimum M3"
ng-model="$ctrl.supplierAgencyTerm.minimumM3" ng-model="$ctrl.supplierAgencyTerm.minimumM3"
@ -31,19 +31,20 @@
</vn-input-number> </vn-input-number>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number
type="number" type="number"
label="Package Price" label="Package Price"
ng-model="$ctrl.supplierAgencyTerm.packagePrice" ng-model="$ctrl.supplierAgencyTerm.packagePrice"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
type="number" type="number"
label="Km Price" label="Km Price"
ng-model="$ctrl.supplierAgencyTerm.kmPrice" ng-model="$ctrl.supplierAgencyTerm.kmPrice"
step="0.01"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
type="number" type="number"
label="M3 Price" label="M3 Price"
ng-model="$ctrl.supplierAgencyTerm.m3Price" ng-model="$ctrl.supplierAgencyTerm.m3Price"
@ -52,13 +53,13 @@
</vn-input-number> </vn-input-number>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number
type="number" type="number"
label="Route Price" label="Route Price"
ng-model="$ctrl.supplierAgencyTerm.routePrice" ng-model="$ctrl.supplierAgencyTerm.routePrice"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-input-number <vn-input-number
type="number" type="number"
label="Minimum Km" label="Minimum Km"
ng-model="$ctrl.supplierAgencyTerm.minimumKm" ng-model="$ctrl.supplierAgencyTerm.minimumKm"
@ -73,4 +74,4 @@
ui-sref="supplier.card.agencyTerm.index"> ui-sref="supplier.card.agencyTerm.index">
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket deleteExpeditions()', () => { describe('ticket deleteExpeditions()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should delete the selected expeditions', async() => { it('should delete the selected expeditions', async() => {
const tx = await models.Expedition.beginTransaction({}); const tx = await models.Expedition.beginTransaction({});

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket moveExpeditions()', () => { describe('ticket moveExpeditions()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should move the selected expeditions to new ticket', async() => { it('should move the selected expeditions to new ticket', async() => {
const tx = await models.Expedition.beginTransaction({}); const tx = await models.Expedition.beginTransaction({});
const ctx = { const ctx = {

View File

@ -1,6 +1,20 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket-request confirm()', () => { describe('ticket-request confirm()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
let ctx = { let ctx = {
req: { req: {
accessToken: {userId: 9}, accessToken: {userId: 9},

View File

@ -1,6 +1,21 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket-request deny()', () => { describe('ticket-request deny()', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should return the denied ticket request', async() => { it('should return the denied ticket request', async() => {
const tx = await models.TicketRequest.beginTransaction({}); const tx = await models.TicketRequest.beginTransaction({});

View File

@ -46,7 +46,7 @@ module.exports = async function(Self, tickets, reqArgs = {}) {
const fileName = `${year}${invoiceOut.ref}.pdf`; const fileName = `${year}${invoiceOut.ref}.pdf`;
// Store invoice // Store invoice
storage.write(stream, { await storage.write(stream, {
type: 'invoice', type: 'invoice',
path: `${year}/${month}/${day}`, path: `${year}/${month}/${day}`,
fileName: fileName fileName: fileName

View File

@ -17,6 +17,17 @@ describe('ticket componentUpdate()', () => {
let componentValue; let componentValue;
beforeAll(async() => { beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
const deliveryComponenet = await models.Component.findOne({where: {code: 'delivery'}}); const deliveryComponenet = await models.Component.findOne({where: {code: 'delivery'}});
deliveryComponentId = deliveryComponenet.id; deliveryComponentId = deliveryComponenet.id;
componentOfSaleSeven = `SELECT value componentOfSaleSeven = `SELECT value
@ -180,9 +191,6 @@ describe('ticket componentUpdate()', () => {
} }
}; };
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: ctx.req
});
const oldTicket = await models.Ticket.findById(ticketID, null, options); const oldTicket = await models.Ticket.findById(ticketID, null, options);
await models.Ticket.componentUpdate(ctx, options); await models.Ticket.componentUpdate(ctx, options);

View File

@ -1,10 +1,6 @@
{ {
"name": "Expedition", "name": "Expedition",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "expedition" "table": "expedition"
@ -59,4 +55,3 @@
} }
} }
} }

View File

@ -1,12 +1,6 @@
{ {
"name": "Sale", "name": "Sale",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket",
"showField": "concept",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "sale" "table": "sale"

View File

@ -1,6 +1,20 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('ticket model TicketTracking', () => { describe('ticket model TicketTracking', () => {
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
let ticketTrackingId; let ticketTrackingId;
afterAll(async() => { afterAll(async() => {

View File

@ -14,6 +14,15 @@
}, },
"scopeDays": { "scopeDays": {
"type": "number" "type": "number"
},
"pickingDelay": {
"type": "number"
},
"packagingInvoicingDated": {
"type": "date"
},
"daysForWarningClaim": {
"type": "number"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "TicketDms", "name": "TicketDms",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketDms" "table": "ticketDms"
@ -29,4 +25,4 @@
"foreignKey": "dmsFk" "foreignKey": "dmsFk"
} }
} }
} }

View File

@ -1,10 +1,6 @@
{ {
"name": "TicketObservation", "name": "TicketObservation",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketObservation" "table": "ticketObservation"

View File

@ -1,10 +1,6 @@
{ {
"name": "TicketPackaging", "name": "TicketPackaging",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketPackaging" "table": "ticketPackaging"

View File

@ -6,10 +6,6 @@
"table": "ticketRefund" "table": "ticketRefund"
} }
}, },
"log": {
"model": "TicketLog",
"relation": "originalTicket"
},
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,

View File

@ -1,10 +1,6 @@
{ {
"name": "TicketRequest", "name": "TicketRequest",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketRequest" "table": "ticketRequest"
@ -64,4 +60,4 @@
"foreignKey": "itemFk" "foreignKey": "itemFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "TicketService", "name": "TicketService",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket",
"showField": "description"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketService" "table": "ticketService"
@ -59,4 +54,4 @@
"foreignKey": "ticketServiceTypeFk" "foreignKey": "ticketServiceTypeFk"
} }
} }
} }

View File

@ -1,16 +1,11 @@
{ {
"name": "TicketTracking", "name": "TicketTracking",
"base": "Loggable", "base": "Loggable",
"log": { "options": {
"model": "TicketLog", "mysql": {
"relation": "ticket", "table": "ticketTracking"
"showField": "stateFk" }
}, },
"options": {
"mysql": {
"table": "ticketTracking"
}
},
"properties": { "properties": {
"id": { "id": {
"id": true, "id": true,
@ -48,4 +43,3 @@
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "TicketWeekly", "name": "TicketWeekly",
"base": "Loggable", "base": "Loggable",
"log": {
"model": "TicketLog",
"relation": "ticket",
"showField": "ticketFk"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticketWeekly" "table": "ticketWeekly"
@ -32,4 +27,4 @@
"foreignKey": "agencyModeFk" "foreignKey": "agencyModeFk"
} }
} }
} }

View File

@ -1,11 +1,6 @@
{ {
"name": "Ticket", "name": "Ticket",
"base": "Loggable", "base": "Loggable",
"log": {
"model":"TicketLog",
"showField": "id",
"grabUser": true
},
"options": { "options": {
"mysql": { "mysql": {
"table": "ticket" "table": "ticket"

View File

@ -481,6 +481,13 @@
on-accept="$ctrl.transferSales($ctrl.transfer.ticketId)"> on-accept="$ctrl.transferSales($ctrl.transfer.ticketId)">
</vn-confirm> </vn-confirm>
<vn-confirm
vn-id="claimConfirm"
question="Do you want to continue?"
message="Claim out of time"
on-accept="$ctrl.onCreateClaimAccepted()">
</vn-confirm>
<vn-menu vn-id="moreOptions"> <vn-menu vn-id="moreOptions">
<vn-item translate <vn-item translate
name="sms" name="sms"
@ -503,6 +510,7 @@
ng-click="$ctrl.createClaim()" ng-click="$ctrl.createClaim()"
ng-if="$ctrl.isClaimable"> ng-if="$ctrl.isClaimable">
Add claim Add claim
</vn-item> </vn-item>
<vn-item translate <vn-item translate
name="reserve" name="reserve"

View File

@ -7,6 +7,7 @@ class Controller extends Section {
super($element, $); super($element, $);
this._sales = []; this._sales = [];
this.manaCode = 'mana'; this.manaCode = 'mana';
this.getConfig();
} }
get manaCode() { get manaCode() {
@ -43,6 +44,15 @@ class Controller extends Section {
return ticketState && ticketState.state.code; return ticketState && ticketState.state.code;
} }
getConfig() {
let filter = {
fields: ['daysForWarningClaim'],
};
this.$http.get(`TicketConfigs`, {filter})
.then(res => {
this.ticketConfig = res.data;
});
}
get isClaimable() { get isClaimable() {
if (this.ticket) { if (this.ticket) {
@ -184,6 +194,16 @@ class Controller extends Section {
} }
createClaim() { createClaim() {
const timeDifference = new Date().getTime() - new Date(this.ticket.shipped).getTime();
const pastDays = Math.floor(timeDifference / 86400000);
if (pastDays >= this.ticketConfig[0].daysForWarningClaim)
this.$.claimConfirm.show();
else
this.onCreateClaimAccepted();
}
onCreateClaimAccepted() {
const sales = this.selectedValidSales(); const sales = this.selectedValidSales();
const params = {ticketId: this.ticket.id, sales: sales}; const params = {ticketId: this.ticket.id, sales: sales};
this.resetChanges(); this.resetChanges();

View File

@ -45,6 +45,7 @@ describe('Ticket', () => {
$scope.model = crudModel; $scope.model = crudModel;
$scope.editDiscount = {relocate: () => {}, hide: () => {}}; $scope.editDiscount = {relocate: () => {}, hide: () => {}};
$scope.editPricePopover = {relocate: () => {}}; $scope.editPricePopover = {relocate: () => {}};
$scope.claimConfirm = {show: () => {}};
$httpBackend = _$httpBackend_; $httpBackend = _$httpBackend_;
Object.defineProperties($state.params, { Object.defineProperties($state.params, {
id: { id: {
@ -61,6 +62,10 @@ describe('Ticket', () => {
controller.card = {reload: () => {}}; controller.card = {reload: () => {}};
controller._ticket = ticket; controller._ticket = ticket;
controller._sales = sales; controller._sales = sales;
controller.ticketConfig = [
{daysForWarningClaim: 1}
];
$httpBackend.expect('GET', 'TicketConfigs').respond(200);
})); }));
describe('ticket() setter', () => { describe('ticket() setter', () => {
@ -113,7 +118,6 @@ describe('Ticket', () => {
it('should make an HTTP GET query and return the worker mana', () => { it('should make an HTTP GET query and return the worker mana', () => {
controller.edit = {}; controller.edit = {};
const expectedAmount = 250; const expectedAmount = 250;
$httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount); $httpBackend.expect('GET', 'Tickets/1/getSalesPersonMana').respond(200, expectedAmount);
$httpBackend.expect('GET', 'Sales/usesMana').respond(200); $httpBackend.expect('GET', 'Sales/usesMana').respond(200);
$httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount); $httpBackend.expect('GET', 'WorkerManas/getCurrentWorkerMana').respond(200, expectedAmount);
@ -279,7 +283,17 @@ describe('Ticket', () => {
}); });
describe('createClaim()', () => { describe('createClaim()', () => {
it('should perform a query and call windows open', () => { it('should call to the claimConfirm show() method', () => {
jest.spyOn(controller.$.claimConfirm, 'show').mockReturnThis();
controller.createClaim();
expect(controller.$.claimConfirm.show).toHaveBeenCalledWith();
});
});
describe('onCreateClaimAccepted()', () => {
it('should perform a query and call window open', () => {
jest.spyOn(controller, 'resetChanges').mockReturnThis(); jest.spyOn(controller, 'resetChanges').mockReturnThis();
jest.spyOn(controller.$state, 'go').mockReturnThis(); jest.spyOn(controller.$state, 'go').mockReturnThis();
@ -290,7 +304,7 @@ describe('Ticket', () => {
const expectedParams = {ticketId: 1, sales: [firstSale]}; const expectedParams = {ticketId: 1, sales: [firstSale]};
$httpBackend.expect('POST', `Claims/createFromSales`, expectedParams).respond(200, {id: 1}); $httpBackend.expect('POST', `Claims/createFromSales`, expectedParams).respond(200, {id: 1});
controller.createClaim(); controller.onCreateClaimAccepted();
$httpBackend.flush(); $httpBackend.flush();
expect(controller.resetChanges).toHaveBeenCalledWith(); expect(controller.resetChanges).toHaveBeenCalledWith();

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