Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3256-order_transactions

This commit is contained in:
Carlos Jimenez Ruiz 2021-12-02 11:04:44 +01:00
commit 6aa3101e42
68 changed files with 2357 additions and 1438 deletions

View File

@ -1,6 +1,7 @@
const app = require('vn-loopback/server/server'); const app = require('vn-loopback/server/server');
describe('newCollection()', () => { // #3400 analizar que hacer con rutas de back colletion
xdescribe('newCollection()', () => {
it('return a new collection', async() => { it('return a new collection', async() => {
let ctx = {req: {accessToken: {userId: 1106}}}; let ctx = {req: {accessToken: {userId: 1106}}};
let response = await app.models.Collection.newCollection(ctx, 1, 1, 1); let response = await app.models.Collection.newCollection(ctx, 1, 1, 1);

View File

@ -0,0 +1,3 @@
UPDATE vn.absenceType
SET code='halfPaidLeave'
WHERE id=15 AND name='Permiso retribuido 1/2 día' AND rgb='#5151c0' AND code IS NULL AND permissionRate=NULL AND holidayEntitlementRate=0.50 AND discountRate=0.00;

View File

@ -0,0 +1,27 @@
INSERT INTO `salix`.`ACL`
(model, property, accessType, permission, principalType, principalId)
VALUES
('EntryObservation', '*', '*', 'ALLOW', 'ROLE', 'buyer'),
('LdapConfig', '*', '*', 'ALLOW', 'ROLE', 'sysadmin'),
('SambaConfig', '*', '*', 'ALLOW', 'ROLE', 'sysadmin'),
('ACL', '*', '*', 'ALLOW', 'ROLE', 'developer'),
('AccessToken', '*', '*', 'ALLOW', 'ROLE', 'developer'),
('MailAliasAccount', '*', '*', 'ALLOW', 'ROLE', 'marketing'),
('MailAliasAccount', '*', '*', 'ALLOW', 'ROLE', 'hr'),
('MailAlias', '*', '*', 'ALLOW', 'ROLE', 'hr'),
('MailForward', '*', '*', 'ALLOW', 'ROLE', 'marketing'),
('MailForward', '*', '*', 'ALLOW', 'ROLE', 'hr'),
('RoleInherit', '*', '*', 'ALLOW', 'ROLE', 'it'),
('RoleRole', '*', '*', 'ALLOW', 'ROLE', 'it'),
('AccountConfig', '*', '*', 'ALLOW', 'ROLE', 'sysadmin');
UPDATE `salix`.`ACL`
SET accessType='*', principalId='it'
WHERE model = 'Role';
DELETE FROM `salix`.`ACL`
WHERE id IN (280, 281);
UPDATE `salix`.`ACL`
SET accessType='*', principalId='marketing'
WHERE id=279;

View File

@ -0,0 +1,33 @@
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=96;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=95;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=115;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=123;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=94;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=101;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=80;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=125;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=98;
UPDATE vn.department
SET notificationEmail='direccioncomercial@verdnatura.es'
WHERE id=92;
UPDATE vn.department
SET notificationEmail=''
WHERE id=43;

View File

@ -0,0 +1,120 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_recalcComponent`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_recalcComponent`(vOption INT)
proc: BEGIN
/**
* Actualiza los componentes
*
* @table tmp.recalculateSales
*/
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;
DECLARE vOption INTEGER;
DECLARE vSale INTEGER;
DECLARE vDone BOOL DEFAULT FALSE;
DECLARE vCur CURSOR FOR
SELECT id from tmp.recalculateSales;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET vDone = TRUE;
OPEN vCur;
l: LOOP
SET vDone = FALSE;
FETCH vCur INTO vSale;
IF vDone THEN
LEAVE l;
END IF;
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;
CALL zone_getLanded(vShipped, vAddressFk, vAgencyModeFk, vWarehouseFk, TRUE);
IF (SELECT COUNT(*) FROM tmp.zoneGetLanded LIMIT 1) = 0 THEN
CALL util.throw('There is no zone for these parameters');
END IF;
IF vLanded IS NULL OR vZoneFk IS NULL THEN
UPDATE ticket t
SET t.landed = (SELECT landed FROM tmp.zoneGetLanded LIMIT 1)
WHERE t.id = vTicketFk AND t.landed IS NULL;
IF vZoneFk IS NULL THEN
SELECT zoneFk INTO vZoneFk FROM tmp.zoneGetLanded LIMIT 1;
UPDATE ticket t
SET t.zoneFk = vZoneFk
WHERE t.id = vTicketFk AND t.zoneFk IS NULL;
END IF;
END IF;
DROP TEMPORARY TABLE tmp.zoneGetLanded;
-- rellena la tabla buyUltimate con la ultima compra
CALL buyUltimate (vWarehouseFk, vShipped);
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 LOOP;
CLOSE vCur;
END$$
DELIMITER ;

View File

@ -0,0 +1,23 @@
DROP PROCEDURE IF EXISTS `vn`.`sale_calculateComponent`;
DELIMITER $$
$$
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`sale_calculateComponent`(vSale INT, vOption INT)
proc: BEGIN
/**
* Crea tabla temporal para vn.sale_recalcComponent() para recalcular los componentes
*
* @param vSale Id de la venta
* @param vOption indica en que componente pone el descuadre, NULL en casos habituales
*/
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
CREATE TEMPORARY TABLE tmp.recalculateSales
SELECT s.id
FROM sale s
WHERE s.id = vSale;
CALL vn.sale_recalcComponent(vOption);
DROP TEMPORARY TABLE tmp.recalculateSales;
END$$
DELIMITER ;

File diff suppressed because one or more lines are too long

View File

@ -796,25 +796,26 @@ INSERT INTO `vn`.`itemFamily`(`code`, `description`)
('SER', 'Services'), ('SER', 'Services'),
('VT', 'Sales'); ('VT', 'Sales');
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`) INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`,
`comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`, `genericFk`)
VALUES VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0), (1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0, NULL),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0), (2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0, NULL),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0), (3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0, NULL),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0), (4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0, NULL),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0), (5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0, NULL),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0), (6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0, NULL),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0), (7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0, NULL),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0), (8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0, NULL),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1), (9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1, NULL),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0), (10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0, NULL),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0), (11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0, NULL),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0), (12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0, NULL),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1), (13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1, NULL),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1), (14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1, NULL),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0), (15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL),
(16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0), (16, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB', 0, NULL),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0); (71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT', 0, NULL);
-- Update the taxClass after insert of the items -- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2 UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1877,6 +1878,7 @@ INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `
(1, 'Holidays', '#FF4444', 'holiday', 0), (1, 'Holidays', '#FF4444', 'holiday', 0),
(2, 'Leave of absence', '#C71585', 'absence', 0), (2, 'Leave of absence', '#C71585', 'absence', 0),
(6, 'Half holiday', '#E65F00', 'halfHoliday', 0), (6, 'Half holiday', '#E65F00', 'halfHoliday', 0),
(15, 'Half Paid Leave', '#5151c0', 'halfPaidLeave', 0),
(20, 'Furlough', '#97B92F', 'furlough', 1), (20, 'Furlough', '#97B92F', 'furlough', 1),
(21, 'Furlough half day', '#778899', 'halfFurlough', 0.5); (21, 'Furlough half day', '#778899', 'halfFurlough', 0.5);
@ -2419,3 +2421,6 @@ INSERT INTO `vn`.`expeditionScan` (`id`, `expeditionFk`, `scanned`, `palletFk`)
(10, 10, CURDATE(), 1); (10, 10, CURDATE(), 1);
CALL `cache`.`last_buy_refresh`(FALSE); CALL `cache`.`last_buy_refresh`(FALSE);
UPDATE `vn`.`item` SET `genericFk` = 9
WHERE `id` = 2;

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ TABLES=(
ACL ACL
fieldAcl fieldAcl
module module
defaultViewConfig
) )
dump_tables ${TABLES[@]} dump_tables ${TABLES[@]}

View File

@ -27,7 +27,7 @@ export async function getBrowser() {
args, args,
defaultViewport: null, defaultViewport: null,
headless: headless, headless: headless,
slowMo: 5, // slow down by ms slowMo: 1, // slow down by ms
// ignoreDefaultArgs: ['--disable-extensions'], // ignoreDefaultArgs: ['--disable-extensions'],
// executablePath: '/usr/bin/google-chrome-stable', // executablePath: '/usr/bin/google-chrome-stable',
// executablePath: '/usr/bin/firefox-developer-edition', // executablePath: '/usr/bin/firefox-developer-edition',

View File

@ -382,6 +382,7 @@ export default {
relevancy: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.relevancy"]', relevancy: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.relevancy"]',
origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]', origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
compression: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.compression"]', compression: 'vn-item-basic-data vn-input-number[ng-model="$ctrl.item.compression"]',
generic: 'vn-autocomplete[ng-model="$ctrl.item.genericFk"]',
isFragile: 'vn-check[ng-model="$ctrl.item.isFragile"]', isFragile: 'vn-check[ng-model="$ctrl.item.isFragile"]',
longName: 'vn-textfield[ng-model="$ctrl.item.longName"]', longName: 'vn-textfield[ng-model="$ctrl.item.longName"]',
isActiveCheckbox: 'vn-check[label="Active"]', isActiveCheckbox: 'vn-check[label="Active"]',
@ -556,6 +557,7 @@ export default {
moreMenuReserve: 'vn-item[name="reserve"]', moreMenuReserve: 'vn-item[name="reserve"]',
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]', moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]', moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input', moreMenuUpdateDiscountInput: 'vn-input-number[ng-model="$ctrl.edit.discount"] input',
transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text', transferQuantityInput: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable > span > text',
transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable', transferQuantityCell: '.vn-popover.shown vn-table > div > vn-tbody > vn-tr > vn-td-editable',

View File

@ -23,15 +23,17 @@ describe('Item Edit basic data path', () => {
it(`should edit the item basic data`, async() => { it(`should edit the item basic data`, async() => {
await page.clearInput(selectors.itemBasicData.name); await page.clearInput(selectors.itemBasicData.name);
await page.write(selectors.itemBasicData.name, 'Rose of Purity'); await page.write(selectors.itemBasicData.name, 'Rose of Purity');
await page.autocompleteSearch(selectors.itemBasicData.type, 'Anthurium');
await page.autocompleteSearch(selectors.itemBasicData.intrastat, 'Coral y materiales similares');
await page.clearInput(selectors.itemBasicData.relevancy);
await page.write(selectors.itemBasicData.relevancy, '1');
await page.autocompleteSearch(selectors.itemBasicData.origin, 'Spain');
await page.clearInput(selectors.itemBasicData.compression);
await page.write(selectors.itemBasicData.compression, '2');
await page.clearInput(selectors.itemBasicData.longName); await page.clearInput(selectors.itemBasicData.longName);
await page.write(selectors.itemBasicData.longName, 'RS Rose of Purity'); await page.write(selectors.itemBasicData.longName, 'RS Rose of Purity');
await page.autocompleteSearch(selectors.itemBasicData.type, 'Anthurium');
await page.autocompleteSearch(selectors.itemBasicData.intrastat, 'Coral y materiales similares');
await page.autocompleteSearch(selectors.itemBasicData.origin, 'Spain');
await page.clearInput(selectors.itemBasicData.relevancy);
await page.write(selectors.itemBasicData.relevancy, '1');
await page.clearInput(selectors.itemBasicData.compression);
await page.write(selectors.itemBasicData.compression, '2');
await page.clearInput(selectors.itemBasicData.generic);
await page.autocompleteSearch(selectors.itemBasicData.generic, '16');
await page.waitToClick(selectors.itemBasicData.isActiveCheckbox); await page.waitToClick(selectors.itemBasicData.isActiveCheckbox);
await page.waitToClick(selectors.itemBasicData.priceInKgCheckbox); await page.waitToClick(selectors.itemBasicData.priceInKgCheckbox);
await page.waitToClick(selectors.itemBasicData.isFragile); await page.waitToClick(selectors.itemBasicData.isFragile);
@ -101,6 +103,13 @@ describe('Item Edit basic data path', () => {
expect(result).toEqual('2'); expect(result).toEqual('2');
}); });
it(`should confirm the item generic was edited`, async() => {
const result = await page
.waitToGetProperty(selectors.itemBasicData.generic, 'value');
expect(result).toEqual('16 - Pallet');
});
it(`should confirm the item long name was edited`, async() => { it(`should confirm the item long name was edited`, async() => {
const result = await page const result = await page
.waitToGetProperty(selectors.itemBasicData.longName, 'value'); .waitToGetProperty(selectors.itemBasicData.longName, 'value');

View File

@ -195,6 +195,17 @@ describe('Ticket Edit sale path', () => {
expect(result).toContain('22.50'); expect(result).toContain('22.50');
}); });
it('should recalculate price of sales', async() => {
await page.waitToClick(selectors.ticketSales.firstSaleCheckbox);
await page.waitToClick(selectors.ticketSales.secondSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);
await page.waitToClick(selectors.ticketSales.moreMenuRecalculatePrice);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should select the third sale and create a claim of it', async() => { it('should select the third sale and create a claim of it', async() => {
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox); await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu); await page.waitToClick(selectors.ticketSales.moreMenu);

View File

@ -8,7 +8,7 @@ describe('Entry observations path', () => {
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('developer', 'entry'); await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2'); await page.accessToSearchResult('2');
await page.accessToSection('entry.card.observation'); await page.accessToSection('entry.card.observation');
}); });

View File

@ -59,13 +59,12 @@ describe('Account create and basic data path', () => {
await page.accessToSection('account.card.roles'); await page.accessToSection('account.card.roles');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult); const rolesCount = await page.countElement(selectors.accountRoles.anyResult);
expect(rolesCount).toEqual(4); expect(rolesCount).toEqual(3);
}); });
describe('Descriptor option', () => { describe('Descriptor option', () => {
describe('Edit role', () => { describe('Edit role', () => {
it('should edit the role using the descriptor menu', async() => { it('should edit the role using the descriptor menu', async() => {
await page.waitForTimeout(1000); // sometimes descriptor fails to load it's functionalities without this timeout
await page.waitToClick(selectors.accountDescriptor.menuButton); await page.waitToClick(selectors.accountDescriptor.menuButton);
await page.waitToClick(selectors.accountDescriptor.changeRole); await page.waitToClick(selectors.accountDescriptor.changeRole);
await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss'); await page.autocompleteSearch(selectors.accountDescriptor.newRole, 'adminBoss');
@ -76,7 +75,7 @@ describe('Account create and basic data path', () => {
}); });
it('should reload the roles section to see now there are more roles', async() => { it('should reload the roles section to see now there are more roles', async() => {
// when role updated the db takes a while to return the changes, without this timeout the result would have been 4 // when role updates db takes time to return changes, without this timeout the result would have been 3
await page.waitForTimeout(1000); await page.waitForTimeout(1000);
await page.reloadSection('account.card.roles'); await page.reloadSection('account.card.roles');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult); const rolesCount = await page.countElement(selectors.accountRoles.anyResult);

View File

@ -8,7 +8,7 @@ describe('Account Role create and basic data path', () => {
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
page = browser.page; page = browser.page;
await page.loginAndModule('developer', 'account'); await page.loginAndModule('it', 'account');
await page.accessToSection('account.role'); await page.accessToSection('account.role');
}); });
@ -76,11 +76,11 @@ describe('Account Role create and basic data path', () => {
expect(subrolesCount).toEqual(1); expect(subrolesCount).toEqual(1);
}); });
it('should search for the employee role group then access to the roles inheritance section then check the roles listed are the expected ones', async() => { it('should access the employee roles inheritance then check the roles listed are the expected ones', async() => {
await page.accessToSearchResult('employee'); await page.accessToSearchResult('employee');
await page.accessToSection('account.role.card.inherited'); await page.accessToSection('account.role.card.inherited');
const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult); const rolesCount = await page.countElement(selectors.accountRoleInheritance.anyResult);
expect(rolesCount).toEqual(7); expect(rolesCount).toEqual(6);
}); });
}); });

View File

@ -23,321 +23,371 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
.icon-preserved:before {
content: "\e963";
}
.icon-treatments:before {
content: "\e964";
}
.icon-funeral:before {
content: "\e965";
}
.icon-handmadeArtificial:before {
content: "\e966";
}
.icon-fixedPrice:before {
content: "\e962";
}
.icon-accounts:before {
content: "\e95f";
}
.icon-clientConsumption:before {
content: "\e960";
}
.icon-lastBuy:before {
content: "\e961";
}
.icon-zone:before {
content: "\e95d";
}
.icon-inventory:before {
content: "\e95e";
}
.icon-wiki:before {
content: "\e968";
}
.icon-attach:before {
content: "\e96c";
}
.icon-zone2:before {
content: "\e96d";
}
.icon-net:before {
content: "\e95b";
}
.icon-anonymous:before {
content: "\e95c";
}
.icon-buyrequest:before {
content: "\e914";
}
.icon-entry:before {
content: "\e959";
}
.icon-thermometer:before {
content: "\e95a";
}
.icon-deletedTicket:before {
content: "\e958";
}
.icon-fruit:before {
content: "\e957";
}
.icon-deliveryprices:before {
content: "\e956";
}
.icon-basketadd:before {
content: "\e955";
}
.icon-catalog:before {
content: "\e952";
}
.icon-agency:before {
content: "\e910";
}
.icon-delivery:before {
content: "\e94a";
}
.icon-wand:before {
content: "\e954";
}
.icon-unavailable:before {
content: "\e953";
}
.icon-buscaman:before {
content: "\e951";
}
.icon-pbx:before {
content: "\e950";
}
.icon-calendar:before {
content: "\e94f";
}
.icon-linesplit:before {
content: "\e945";
}
.icon-invoices:before {
content: "\e91c";
}
.icon-pets:before {
content: "\e94e";
}
.icon-100:before { .icon-100:before {
content: "\e940"; content: "\e976";
} }
.icon-accessory:before { .icon-account:before {
content: "\e90a";
}
.icon-actions:before {
content: "\e900"; content: "\e900";
} }
.icon-addperson:before { .icon-actions:before {
content: "\e901"; content: "\e901";
} }
.icon-albaran:before { .icon-addperson:before {
content: "\e902"; content: "\e902";
} }
.icon-apps:before { .icon-agency:before {
content: "\e948";
}
.icon-artificial:before {
content: "\e903"; content: "\e903";
} }
.icon-barcode:before { .icon-albaran:before {
content: "\e904"; content: "\e904";
} }
.icon-basket:before { .icon-anonymous:before {
content: "\e942";
}
.icon-bin:before {
content: "\e905"; content: "\e905";
} }
.icon-botanical:before { .icon-apps:before {
content: "\e906"; content: "\e906";
} }
.icon-bucket:before { .icon-artificial:before {
content: "\e907"; content: "\e907";
} }
.icon-claims:before { .icon-attach:before {
content: "\e908"; content: "\e908";
} }
.icon-clone:before { .icon-barcode:before {
content: "\e909"; content: "\e909";
} }
.icon-columnadd:before { .icon-basket:before {
content: "\e944"; content: "\e90a";
} }
.icon-columndelete:before { .icon-basketadd:before {
content: "\e90f";
}
.icon-components:before {
content: "\e90b"; content: "\e90b";
} }
.icon-consignatarios:before { .icon-bin:before {
content: "\e90d";
}
.icon-control:before {
content: "\e93f";
}
.icon-credit:before {
content: "\e90e";
}
.icon-details:before {
content: "\e911";
}
.icon-disabled:before {
content: "\e91b";
}
.icon-doc:before {
content: "\e913";
}
.icon-exit:before {
content: "\e947";
}
.icon-eye:before {
content: "\e915";
}
.icon-fiscal:before {
content: "\e912";
}
.icon-flower:before {
content: "\e916";
}
.icon-frozen:before {
content: "\e917";
}
.icon-greenery:before {
content: "\e93c";
}
.icon-greuge:before {
content: "\e918";
}
.icon-grid:before {
content: "\e919";
}
.icon-handmade:before {
content: "\e90c"; content: "\e90c";
} }
.icon-history:before { .icon-botanical:before {
content: "\e90d";
}
.icon-bucket:before {
content: "\e90e";
}
.icon-buscaman:before {
content: "\e90f";
}
.icon-buyrequest:before {
content: "\e910";
}
.icon-calc_volum .path1:before {
content: "\e911";
color: rgb(0, 0, 0);
}
.icon-calc_volum .path2:before {
content: "\e912";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path3:before {
content: "\e913";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path4:before {
content: "\e914";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path5:before {
content: "\e915";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-calc_volum .path6:before {
content: "\e916";
margin-left: -1em;
color: rgb(255, 255, 255);
}
.icon-calendar:before {
content: "\e917";
}
.icon-catalog:before {
content: "\e918";
}
.icon-claims:before {
content: "\e919";
}
.icon-client:before {
content: "\e91a"; content: "\e91a";
} }
.icon-info:before { .icon-clone:before {
content: "\e949"; content: "\e91b";
} }
.icon-item:before { .icon-columnadd:before {
content: "\e941"; content: "\e91c";
} }
.icon-languaje:before { .icon-columndelete:before {
content: "\e91d"; content: "\e91d";
} }
.icon-linedelete:before { .icon-accessory:before {
content: "\e946";
}
.icon-lines:before {
content: "\e91e"; content: "\e91e";
} }
.icon-linesprepaired:before { .icon-components:before {
content: "\e94b";
}
.icon-logout:before {
content: "\e91f"; content: "\e91f";
} }
.icon-mana:before { .icon-handmade:before {
content: "\e920"; content: "\e920";
} }
.icon-mandatory:before { .icon-consignatarios:before {
content: "\e921"; content: "\e921";
} }
.icon-niche:before { .icon-control:before {
content: "\e922"; content: "\e922";
} }
.icon-no036:before { .icon-credit:before {
content: "\e923"; content: "\e923";
} }
.icon-notes:before { .icon-deletedTicketCross:before {
content: "\e924"; content: "\e924";
} }
.icon-noweb:before { .icon-deleteline:before {
content: "\e925"; content: "\e925";
} }
.icon-onlinepayment:before { .icon-delivery:before {
content: "\e926"; content: "\e926";
} }
.icon-package:before { .icon-deliveryprices:before {
content: "\e927"; content: "\e927";
} }
.icon-payment:before { .icon-details:before {
content: "\e928"; content: "\e928";
} }
.icon-person:before { .icon-dfiscales:before {
content: "\e929"; content: "\e929";
} }
.icon-photo:before { .icon-doc:before {
content: "\e92a"; content: "\e92a";
} }
.icon-plant:before { .icon-entry:before {
content: "\e92b"; content: "\e92b";
} }
.icon-recovery:before { .icon-exit:before {
content: "\e92d";
}
.icon-regentry:before {
content: "\e92e";
}
.icon-reserve:before {
content: "\e92f";
}
.icon-revision:before {
content: "\e94c";
}
.icon-risk:before {
content: "\e930";
}
.icon-services:before {
content: "\e94d";
}
.icon-settings:before {
content: "\e931";
}
.icon-sms:before {
content: "\e932";
}
.icon-solclaim:before {
content: "\e933";
}
.icon-solunion:before {
content: "\e934";
}
.icon-splur:before {
content: "\e935";
}
.icon-stowaway:before {
content: "\e92c"; content: "\e92c";
} }
.icon-supplier:before { .icon-eye:before {
content: "\e92d";
}
.icon-fixedPrice:before {
content: "\e92e";
}
.icon-flower:before {
content: "\e92f";
}
.icon-frozen:before {
content: "\e930";
}
.icon-fruit:before {
content: "\e931";
}
.icon-funeral:before {
content: "\e932";
}
.icon-greuge:before {
content: "\e933";
}
.icon-grid:before {
content: "\e934";
}
.icon-handmadeArtificial:before {
content: "\e935";
}
.icon-headercol:before {
content: "\e936"; content: "\e936";
} }
.icon-tags:before { .icon-history:before {
content: "\e937"; content: "\e937";
} }
.icon-tax:before { .icon-disabled:before {
content: "\e938"; content: "\e938";
} }
.icon-ticket:before { .icon-info:before {
content: "\e939"; content: "\e939";
} }
.icon-traceability:before { .icon-inventory:before {
content: "\e93a"; content: "\e93a";
} }
.icon-transaction:before { .icon-invoice:before {
content: "\e93b"; content: "\e93b";
} }
.icon-volume:before { .icon-invoice-in:before {
content: "\e93c";
}
.icon-invoice-in-create:before {
content: "\e93d"; content: "\e93d";
} }
.icon-web:before { .icon-invoice-out:before {
content: "\e93e"; content: "\e93e";
} }
.icon-worker:before { .icon-item:before {
content: "\e93f";
}
.icon-languaje:before {
content: "\e940";
}
.icon-lines:before {
content: "\e941";
}
.icon-linesprepaired:before {
content: "\e942";
}
.icon-logout:before {
content: "\e943"; content: "\e943";
} }
.icon-mana:before {
content: "\e944";
}
.icon-mandatory:before {
content: "\e945";
}
.icon-net:before {
content: "\e946";
}
.icon-niche:before {
content: "\e947";
}
.icon-no036:before {
content: "\e948";
}
.icon-notes:before {
content: "\e949";
}
.icon-noweb:before {
content: "\e94a";
}
.icon-onlinepayment:before {
content: "\e94b";
}
.icon-package:before {
content: "\e94c";
}
.icon-payment:before {
content: "\e94d";
}
.icon-pbx:before {
content: "\e94e";
}
.icon-Person:before {
content: "\e94f";
}
.icon-pets:before {
content: "\e950";
}
.icon-photo:before {
content: "\e951";
}
.icon-plant:before {
content: "\e952";
}
.icon-stowaway:before {
content: "\e953";
}
.icon-preserved:before {
content: "\e954";
}
.icon-recovery:before {
content: "\e955";
}
.icon-regentry:before {
content: "\e956";
}
.icon-reserve:before {
content: "\e957";
}
.icon-revision:before {
content: "\e958";
}
.icon-risk:before {
content: "\e959";
}
.icon-services:before {
content: "\e95a";
}
.icon-settings:before {
content: "\e95b";
}
.icon-shipment-01 .path1:before {
content: "\e95c";
color: rgb(225, 225, 225);
}
.icon-shipment-01 .path2:before {
content: "\e95d";
margin-left: -1em;
color: rgb(0, 0, 0);
}
.icon-sign:before {
content: "\e95e";
}
.icon-sms:before {
content: "\e95f";
}
.icon-solclaim:before {
content: "\e960";
}
.icon-solunion:before {
content: "\e961";
}
.icon-splitline:before {
content: "\e962";
}
.icon-splur:before {
content: "\e963";
}
.icon-supplier:before {
content: "\e965";
}
.icon-supplierfalse:before {
content: "\e966";
}
.icon-tags:before {
content: "\e967";
}
.icon-tax:before {
content: "\e968";
}
.icon-thermometer:before {
content: "\e969";
}
.icon-ticket:before {
content: "\e96a";
}
.icon-traceability:before {
content: "\e96b";
}
.icon-transaction:before {
content: "\e96c";
}
.icon-treatments:before {
content: "\e96d";
}
.icon-unavailable:before {
content: "\e96e";
}
.icon-greenery:before {
content: "\e96f";
}
.icon-volume:before {
content: "\e970";
}
.icon-wand:before {
content: "\e971";
}
.icon-web:before {
content: "\e972";
}
.icon-wiki:before {
content: "\e973";
}
.icon-worker:before {
content: "\e974";
}
.icon-zone:before {
content: "\e975";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 136 KiB

After

Width:  |  Height:  |  Size: 145 KiB

View File

@ -214,5 +214,7 @@
"You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente", "You can't change the credit set to zero from a manager": "No puedes cambiar el cŕedito establecido a cero por un gerente",
"The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'", "The PDF document does not exists": "El documento PDF no existe. Prueba a regenerarlo desde la opción 'Regenerar PDF factura'",
"The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos", "The type of business must be filled in basic data": "El tipo de negocio debe estar rellenado en datos básicos",
"You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días" "You can't create a claim from a ticket delivered more than seven days ago": "No puedes crear una reclamación de un ticket entregado hace más de siete días",
"The worker has hours recorded that day": "El trabajador tiene horas fichadas ese día",
"The worker has a marked absence that day": "El trabajador tiene marcada una ausencia ese día"
} }

View File

@ -29,5 +29,11 @@
"foreignKey": "mailAlias", "foreignKey": "mailAlias",
"property": "id" "property": "id"
} }
} },
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
} }

View File

@ -5,14 +5,16 @@
<vn-item <vn-item
ng-click="deleteUser.show()" ng-click="deleteUser.show()"
name="deleteUser" name="deleteUser"
vn-acl="developer" vn-acl="it"
vn-acl-action="remove"
translate> translate>
Delete Delete
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="$ctrl.onChangeRole()" ng-click="$ctrl.onChangeRole()"
name="changeRole" name="changeRole"
vn-acl="developer" vn-acl="hr"
vn-acl-action="remove"
translate> translate>
Change role Change role
</vn-item> </vn-item>
@ -20,13 +22,16 @@
ng-if="::$root.user.id == $ctrl.id" ng-if="::$root.user.id == $ctrl.id"
ng-click="$ctrl.onChangePassClick(true)" ng-click="$ctrl.onChangePassClick(true)"
name="changePassword" name="changePassword"
vn-acl="hr"
vn-acl-action="remove"
translate> translate>
Change password Change password
</vn-item> </vn-item>
<vn-item <vn-item
ng-click="$ctrl.onChangePassClick(false)" ng-click="$ctrl.onChangePassClick(false)"
name="setPassword" name="setPassword"
vn-acl="developer" vn-acl="hr"
vn-acl-action="remove"
translate> translate>
Set password Set password
</vn-item> </vn-item>
@ -34,7 +39,8 @@
ng-if="!$ctrl.hasAccount" ng-if="!$ctrl.hasAccount"
ng-click="enableAccount.show()" ng-click="enableAccount.show()"
name="enableAccount" name="enableAccount"
vn-acl="developer" vn-acl="it"
vn-acl-action="remove"
translate> translate>
Enable account Enable account
</vn-item> </vn-item>
@ -42,7 +48,8 @@
ng-if="$ctrl.hasAccount" ng-if="$ctrl.hasAccount"
ng-click="disableAccount.show()" ng-click="disableAccount.show()"
name="disableAccount" name="disableAccount"
vn-acl="developer" vn-acl="it"
vn-acl-action="remove"
translate> translate>
Disable account Disable account
</vn-item> </vn-item>
@ -50,7 +57,8 @@
ng-if="!$ctrl.user.active" ng-if="!$ctrl.user.active"
ng-click="activateUser.show()" ng-click="activateUser.show()"
name="activateUser" name="activateUser"
vn-acl="developer" vn-acl="hr"
vn-acl-action="remove"
translate> translate>
Activate user Activate user
</vn-item> </vn-item>
@ -58,7 +66,8 @@
ng-if="$ctrl.user.active" ng-if="$ctrl.user.active"
ng-click="deactivateUser.show()" ng-click="deactivateUser.show()"
name="deactivateUser" name="deactivateUser"
vn-acl="developer" vn-acl="hr"
vn-acl-action="remove"
translate> translate>
Deactivate user Deactivate user
</vn-item> </vn-item>

View File

@ -36,9 +36,12 @@
<vn-popup vn-id="summary"> <vn-popup vn-id="summary">
<vn-user-summary user="$ctrl.selectedUser"></vn-user-summary> <vn-user-summary user="$ctrl.selectedUser"></vn-user-summary>
</vn-popup> </vn-popup>
<a ui-sref="account.create" <a
fixed-bottom-right
ui-sref="account.create"
vn-tooltip="New user" vn-tooltip="New user"
vn-bind="+" vn-bind="+"
fixed-bottom-right> vn-acl="it"
vn-acl-action="remove">
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>
</a> </a>

View File

@ -35,7 +35,7 @@
ui-sref-opts="{inherit: false}" ui-sref-opts="{inherit: false}"
vn-tooltip="New role" vn-tooltip="New role"
vn-bind="+" vn-bind="+"
vn-acl="developer" vn-acl="it"
vn-acl-action="remove" vn-acl-action="remove"
fixed-bottom-right> fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button> <vn-float-button icon="add"></vn-float-button>

View File

@ -41,24 +41,29 @@
"component": "vn-user", "component": "vn-user",
"description": "Users", "description": "Users",
"abstract": true "abstract": true
}, { },
{
"url": "/index?q", "url": "/index?q",
"state": "account.index", "state": "account.index",
"component": "vn-user-index", "component": "vn-user-index",
"description": "Users", "description": "Users",
"acl": ["hr"] "acl": ["marketing", "hr"]
}, { },
{
"url": "/create", "url": "/create",
"state": "account.create", "state": "account.create",
"component": "vn-user-create", "component": "vn-user-create",
"description": "New user" "description": "New user",
}, { "acl": ["it"]
},
{
"url": "/:id", "url": "/:id",
"state": "account.card", "state": "account.card",
"component": "vn-user-card", "component": "vn-user-card",
"abstract": true, "abstract": true,
"description": "Detail" "description": "Detail"
}, { },
{
"url": "/summary", "url": "/summary",
"state": "account.card.summary", "state": "account.card.summary",
"component": "vn-user-summary", "component": "vn-user-summary",
@ -66,88 +71,110 @@
"params": { "params": {
"user": "$ctrl.user" "user": "$ctrl.user"
} }
}, { },
{
"url": "/basic-data", "url": "/basic-data",
"state": "account.card.basicData", "state": "account.card.basicData",
"component": "vn-user-basic-data", "component": "vn-user-basic-data",
"description": "Basic data" "description": "Basic data",
}, { "acl": ["hr"]
},
{
"url": "/roles", "url": "/roles",
"state": "account.card.roles", "state": "account.card.roles",
"component": "vn-user-roles", "component": "vn-user-roles",
"description": "Inherited roles" "description": "Inherited roles",
}, { "acl": ["it"]
},
{
"url": "/mail-forwarding", "url": "/mail-forwarding",
"state": "account.card.mailForwarding", "state": "account.card.mailForwarding",
"component": "vn-user-mail-forwarding", "component": "vn-user-mail-forwarding",
"description": "Mail forwarding" "description": "Mail forwarding"
}, { },
{
"url": "/aliases", "url": "/aliases",
"state": "account.card.aliases", "state": "account.card.aliases",
"component": "vn-user-aliases", "component": "vn-user-aliases",
"description": "Mail aliases" "description": "Mail aliases",
}, { "acl": ["marketing", "hr"]
},
{
"url": "/role?q", "url": "/role?q",
"state": "account.role", "state": "account.role",
"component": "vn-role", "component": "vn-role",
"description": "Roles" "description": "Roles",
}, { "acl": ["it"]
},
{
"url": "/create", "url": "/create",
"state": "account.role.create", "state": "account.role.create",
"component": "vn-role-create", "component": "vn-role-create",
"description": "New role" "description": "New role",
}, { "acl": ["it"]
},
{
"url": "/:id", "url": "/:id",
"state": "account.role.card", "state": "account.role.card",
"component": "vn-role-card", "component": "vn-role-card",
"abstract": true, "abstract": true,
"description": "Detail" "description": "Detail"
}, { },
{
"url": "/summary", "url": "/summary",
"state": "account.role.card.summary", "state": "account.role.card.summary",
"component": "vn-role-summary", "component": "vn-role-summary",
"description": "Summary", "description": "Summary",
"params": { "params": {
"role": "$ctrl.role" "role": "$ctrl.role"
} },
}, { "acl": ["it"]
},
{
"url": "/basic-data", "url": "/basic-data",
"state": "account.role.card.basicData", "state": "account.role.card.basicData",
"component": "vn-role-basic-data", "component": "vn-role-basic-data",
"description": "Basic data", "description": "Basic data",
"acl": ["developer"],
"params": { "params": {
"role": "$ctrl.role" "role": "$ctrl.role"
} },
}, { "acl": ["it"]
},
{
"url": "/subroles", "url": "/subroles",
"state": "account.role.card.subroles", "state": "account.role.card.subroles",
"component": "vn-role-subroles", "component": "vn-role-subroles",
"acl": ["developer"], "description": "Subroles",
"description": "Subroles" "acl": ["it"]
}, { },
{
"url": "/inherited", "url": "/inherited",
"state": "account.role.card.inherited", "state": "account.role.card.inherited",
"component": "vn-role-inherited", "component": "vn-role-inherited",
"description": "Inherited roles" "description": "Inherited roles",
}, { "acl": ["it"]
},
{
"url": "/alias?q", "url": "/alias?q",
"state": "account.alias", "state": "account.alias",
"component": "vn-alias", "component": "vn-alias",
"description": "Mail aliases", "description": "Mail aliases",
"acl": ["developer"] "acl": ["marketing"]
}, { },
{
"url": "/create", "url": "/create",
"state": "account.alias.create", "state": "account.alias.create",
"component": "vn-alias-create", "component": "vn-alias-create",
"description": "New alias" "description": "New alias"
}, { },
{
"url": "/:id", "url": "/:id",
"state": "account.alias.card", "state": "account.alias.card",
"component": "vn-alias-card", "component": "vn-alias-card",
"abstract": true, "abstract": true,
"description": "Detail" "description": "Detail"
}, { },
{
"url": "/summary", "url": "/summary",
"state": "account.alias.card.summary", "state": "account.alias.card.summary",
"component": "vn-alias-summary", "component": "vn-alias-summary",
@ -155,7 +182,8 @@
"params": { "params": {
"alias": "$ctrl.alias" "alias": "$ctrl.alias"
} }
}, { },
{
"url": "/basic-data", "url": "/basic-data",
"state": "account.alias.card.basicData", "state": "account.alias.card.basicData",
"component": "vn-alias-basic-data", "component": "vn-alias-basic-data",
@ -163,50 +191,62 @@
"params": { "params": {
"alias": "$ctrl.alias" "alias": "$ctrl.alias"
} }
}, { },
{
"url": "/users", "url": "/users",
"state": "account.alias.card.users", "state": "account.alias.card.users",
"component": "vn-alias-users", "component": "vn-alias-users",
"description": "Users" "description": "Users",
}, { "acl": ["it"]
},
{
"url": "/accounts", "url": "/accounts",
"state": "account.accounts", "state": "account.accounts",
"component": "vn-account-accounts", "component": "vn-account-accounts",
"description": "Accounts", "description": "Accounts",
"acl": ["developer"] "acl": ["sysadmin"]
}, { },
{
"url": "/ldap", "url": "/ldap",
"state": "account.ldap", "state": "account.ldap",
"component": "vn-account-ldap", "component": "vn-account-ldap",
"description": "LDAP", "description": "LDAP",
"acl": ["developer"] "acl": ["sysadmin"]
}, { },
{
"url": "/samba", "url": "/samba",
"state": "account.samba", "state": "account.samba",
"component": "vn-account-samba", "component": "vn-account-samba",
"description": "Samba", "description": "Samba",
"acl": ["developer"] "acl": ["sysadmin"]
}, { },
{
"url": "/acl?q", "url": "/acl?q",
"state": "account.acl", "state": "account.acl",
"component": "vn-acl-component", "component": "vn-acl-component",
"description": "ACLs", "description": "ACLs",
"acl": ["developer"] "acl": ["developer"]
}, { },
{
"url": "/create", "url": "/create",
"state": "account.acl.create", "state": "account.acl.create",
"component": "vn-acl-create", "component": "vn-acl-create",
"description": "New ACL" "description": "New ACL",
}, { "acl": ["developer"]
},
{
"url": "/:id/edit", "url": "/:id/edit",
"state": "account.acl.edit", "state": "account.acl.edit",
"component": "vn-acl-create", "component": "vn-acl-create",
"description": "Edit ACL" "description": "Edit ACL",
}, { "acl": ["developer"]
},
{
"url": "/connections", "url": "/connections",
"state": "account.connections", "state": "account.connections",
"component": "vn-connections", "component": "vn-connections",
"description": "Connections" "description": "Connections",
"acl": ["developer"]
} }
] ]
} }

View File

@ -82,7 +82,7 @@
<vn-quick-link <vn-quick-link
tooltip="Client invoices list" tooltip="Client invoices list"
state="['invoiceOut.index', {q: $ctrl.filter}]" state="['invoiceOut.index', {q: $ctrl.filter}]"
icon="icon-invoices"> icon="icon-invoice">
</vn-quick-link> </vn-quick-link>
</div> </div>
<div ng-transclude="btnThree"> <div ng-transclude="btnThree">

View File

@ -16,7 +16,7 @@
{"state": "client.card.note.index", "icon": "insert_drive_file"}, {"state": "client.card.note.index", "icon": "insert_drive_file"},
{"state": "client.card.credit.index", "icon": "credit_card"}, {"state": "client.card.credit.index", "icon": "credit_card"},
{"state": "client.card.greuge.index", "icon": "work"}, {"state": "client.card.greuge.index", "icon": "work"},
{"state": "client.card.balance.index", "icon": "icon-invoices"}, {"state": "client.card.balance.index", "icon": "icon-invoice"},
{"state": "client.card.recovery.index", "icon": "icon-recovery"}, {"state": "client.card.recovery.index", "icon": "icon-recovery"},
{"state": "client.card.webAccess", "icon": "cloud"}, {"state": "client.card.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"}, {"state": "client.card.log", "icon": "history"},

View File

@ -3,7 +3,7 @@
url="Tickets" url="Tickets"
link="{clientFk: $ctrl.client.id}" link="{clientFk: $ctrl.client.id}"
filter="::$ctrl.ticketFilter" filter="::$ctrl.ticketFilter"
limit="5" limit="10"
data="tickets" data="tickets"
order="shipped DESC, id"> order="shipped DESC, id">
</vn-crud-model> </vn-crud-model>
@ -287,7 +287,7 @@
<vn-horizontal> <vn-horizontal>
<vn-one> <vn-one>
<h4 translate>Latest tickets</h4> <h4 translate>Latest tickets</h4>
<vn-table model="ticketsModel" class="scrollable sm"> <vn-table model="ticketsModel">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th field="id" number>Id</vn-th> <vn-th field="id" number>Id</vn-th>

View File

@ -113,7 +113,6 @@
<vn-dialog <vn-dialog
vn-id="filterDialog" vn-id="filterDialog"
on-accept="$ctrl.addTime()"
message="Filter item"> message="Filter item">
<tpl-body class="itemFilter"> <tpl-body class="itemFilter">
<vn-horizontal> <vn-horizontal>

View File

@ -133,7 +133,7 @@ class Controller extends Section {
case 'producerFk': case 'producerFk':
case 'typeFk': case 'typeFk':
case 'size': case 'size':
case 'ink': case 'inkFk':
where[key] = value; where[key] = value;
} }
} }

View File

@ -43,7 +43,7 @@
</div> </div>
<div ng-transclude="btnThree"> <div ng-transclude="btnThree">
<vn-quick-link tooltip="Invoice list" state="['invoiceIn.index', {q: $ctrl.invoiceInFilter}]" <vn-quick-link tooltip="Invoice list" state="['invoiceIn.index', {q: $ctrl.invoiceInFilter}]"
icon="icon-invoiceIn"> icon="icon-invoice-in">
</vn-quick-link> </vn-quick-link>
</div> </div>
</div> </div>

View File

@ -1,7 +1,7 @@
{ {
"module": "invoiceIn", "module": "invoiceIn",
"name": "Invoices in", "name": "Invoices in",
"icon": "icon-invoiceIn", "icon": "icon-invoice-in",
"validations": true, "validations": true,
"dependencies": [ "dependencies": [
"worker", "worker",
@ -11,7 +11,7 @@
"main": [ "main": [
{ {
"state": "invoiceIn.index", "state": "invoiceIn.index",
"icon": "icon-invoiceIn" "icon": "icon-invoice-in"
} }
], ],
"card": [ "card": [

View File

@ -76,7 +76,7 @@
ng-click="$ctrl.showExportationLetter()" ng-click="$ctrl.showExportationLetter()"
ng-show="$ctrl.invoiceOut.serial == 'E'" ng-show="$ctrl.invoiceOut.serial == 'E'"
translate> translate>
Show CIES letter Show CITES letter
</vn-item> </vn-item>
</vn-list> </vn-list>
</vn-menu> </vn-menu>

View File

@ -6,7 +6,7 @@ Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura Clone Invoice: Clonar factura
Book invoice: Asentar factura Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES Show CITES letter: Ver carta CITES
InvoiceOut deleted: Factura eliminada InvoiceOut deleted: Factura eliminada
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura? Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura? Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?

View File

@ -1,12 +1,12 @@
{ {
"module": "invoiceOut", "module": "invoiceOut",
"name": "Invoices out", "name": "Invoices out",
"icon": "icon-invoices", "icon": "icon-invoice-out",
"validations" : true, "validations" : true,
"dependencies": ["worker", "client", "ticket"], "dependencies": ["worker", "client", "ticket"],
"menus": { "menus": {
"main": [ "main": [
{"state": "invoiceOut.index", "icon": "icon-invoices"} {"state": "invoiceOut.index", "icon": "icon-invoice-out"}
] ]
}, },
"routes": [ "routes": [

View File

@ -176,6 +176,11 @@
"model": "Expense", "model": "Expense",
"foreignKey": "expenseFk" "foreignKey": "expenseFk"
}, },
"generic": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "genericFk"
},
"tags": { "tags": {
"type": "hasMany", "type": "hasMany",
"model": "ItemTag", "model": "ItemTag",

View File

@ -137,6 +137,29 @@
ng-model="$ctrl.item.compression" ng-model="$ctrl.item.compression"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-autocomplete
vn-one
label="Generic"
url="Items/withName"
ng-model="$ctrl.item.genericFk"
show-field="name"
value-field="id"
search-function="$ctrl.itemSearchFunc($search)"
order="id DESC"
tabindex="1">
<tpl-item>
{{::id}} - {{::name}}
</tpl-item>
<append>
<vn-icon-button
icon="filter_alt"
vn-click-stop="$ctrl.showFilterDialog($ctrl.item)"
vn-tooltip="Filter...">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea <vn-textarea
vn-one vn-one
label="Description" label="Description"
@ -203,3 +226,92 @@
<button response="accept" translate>Create</button> <button response="accept" translate>Create</button>
</tpl-buttons> </tpl-buttons>
</vn-dialog> </vn-dialog>
<!-- Filter item dialog -->
<vn-dialog
vn-id="filterDialog"
message="Filter item">
<tpl-body class="itemFilter">
<vn-horizontal>
<vn-textfield
label="Name"
ng-model="$ctrl.itemFilterParams.name"
vn-focus>
</vn-textfield>
<vn-textfield
label="Size"
ng-model="$ctrl.itemFilterParams.size">
</vn-textfield>
<vn-autocomplete
label="Producer"
ng-model="$ctrl.itemFilterParams.producerFk"
url="Producers"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Type"
ng-model="$ctrl.itemFilterParams.typeFk"
url="ItemTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete
label="Color"
ng-model="$ctrl.itemFilterParams.inkFk"
url="Inks"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal class="vn-mb-md">
<vn-button vn-none
label="Search"
ng-click="$ctrl.filter()">
</vn-button>
</vn-horizontal>
<vn-crud-model
vn-id="itemsModel"
url="Items/withName"
filter="$ctrl.itemFilter"
data="items"
limit="10">
</vn-crud-model>
<vn-data-viewer
model="itemsModel"
class="vn-w-lg">
<vn-table class="scrollable">
<vn-thead>
<vn-tr>
<vn-th shrink>ID</vn-th>
<vn-th expand>Item</vn-th>
<vn-th number>Size</vn-th>
<vn-th expand>Producer</vn-th>
<vn-th>Color</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<a ng-repeat="item in items"
class="clickable vn-tr search-result"
ng-click="$ctrl.selectItem(item.id)">
<vn-td shrink>
<span
ng-click="itemDescriptor.show($event, item.id)"
class="link">
{{::item.id}}
</span>
</vn-td>
<vn-td expand>{{::item.name}}</vn-td>
<vn-td number>{{::item.size}}</vn-td>
<vn-td expand>{{::item.producer.name}}</vn-td>
<vn-td>{{::item.ink.name}}</vn-td>
</a>
</vn-tbody>
</vn-table>
</vn-data-viewer>
<vn-item-descriptor-popover
vn-id="item-descriptor"
warehouse-fk="$ctrl.vnConfig.warehouseFk">
</vn-item-descriptor-popover>
</tpl-body>
</vn-dialog>

View File

@ -17,6 +17,65 @@ class Controller extends Section {
return this.$http.patch(query, this.newIntrastat) return this.$http.patch(query, this.newIntrastat)
.then(res => this.item.intrastatFk = res.data.id); .then(res => this.item.intrastatFk = res.data.id);
} }
itemSearchFunc($search) {
return /^\d+$/.test($search)
? {id: $search}
: {name: {like: '%' + $search + '%'}};
}
showFilterDialog(item) {
this.activeItem = item;
this.itemFilterParams = {};
this.itemFilter = {
include: [
{
relation: 'producer',
scope: {
fields: ['name']
}
},
{
relation: 'ink',
scope: {
fields: ['name']
}
}
]
};
this.$.filterDialog.show();
}
selectItem(id) {
this.activeItem['genericFk'] = id;
this.$.filterDialog.hide();
}
filter() {
const filter = this.itemFilter;
const params = this.itemFilterParams;
const where = {};
for (let key in params) {
const value = params[key];
if (!value) continue;
switch (key) {
case 'name':
where[key] = {like: `%${value}%`};
break;
case 'producerFk':
case 'typeFk':
case 'size':
case 'inkFk':
where[key] = value;
}
}
filter.where = where;
this.$.itemsModel.applyFilter(filter);
}
} }
ngModule.vnComponent('vnItemBasicData', { ngModule.vnComponent('vnItemBasicData', {

View File

@ -11,3 +11,4 @@ Identifier: Identificador
Fragile: Frágil Fragile: Frágil
Is shown at website, app that this item cannot travel (wreath, palms, ...): Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...) Is shown at website, app that this item cannot travel (wreath, palms, ...): Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
Multiplier: Multiplicador Multiplier: Multiplicador
Generic: Genérico

View File

@ -7,7 +7,7 @@
"menus": { "menus": {
"main": [ "main": [
{"state": "item.index", "icon": "icon-item"}, {"state": "item.index", "icon": "icon-item"},
{"state": "item.request", "icon": "pan_tool"}, {"state": "item.request", "icon": "icon-buyrequest"},
{"state": "item.waste.index", "icon": "icon-claims"}, {"state": "item.waste.index", "icon": "icon-claims"},
{"state": "item.fixedPrice", "icon": "icon-fixedPrice"} {"state": "item.fixedPrice", "icon": "icon-fixedPrice"}
], ],

View File

@ -93,7 +93,7 @@
ng-show="::ticket.hasTicketRequest" ng-show="::ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}" translate-attr="{title: 'Purchase request'}"
class="bright" class="bright"
icon="icon-100"> icon="icon-buyrequest">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isAvailable === 0" ng-show="::ticket.isAvailable === 0"

View File

@ -55,7 +55,13 @@ module.exports = Self => {
{ {
relation: 'address', relation: 'address',
scope: { scope: {
fields: ['id', 'street', 'postalCode', 'city'], fields: ['street', 'city', 'provinceFk', 'phone', 'nickname', 'postalCode'],
include: {
relation: 'province',
scope: {
fields: ['name']
}
}
} }
}, },
] ]

View File

@ -121,11 +121,17 @@
data="$ctrl.possibleTickets"> data="$ctrl.possibleTickets">
</vn-crud-model> </vn-crud-model>
<vn-dialog <vn-dialog
vn-id="possibleTicketsDialog" vn-id="possibleTicketsDialog">
on-accept="$ctrl.setTicketsRoute()" <tpl-title>
message="Tickets to add"> <vn-horizontal>
<span translate>Tickets to add</span>
<div class="button-right">
<vn-button label="Add" ng-click="$ctrl.setTicketsRoute()"></vn-button>
</div>
</vn-horizontal>
</tpl-title>
<tpl-body> <tpl-body>
<vn-data-viewer class="vn-pa-md" model="possibleTicketsModel"> <vn-data-viewer model="possibleTicketsModel">
<vn-table model="possibleTicketsModel" auto-load="false"> <vn-table model="possibleTicketsModel" auto-load="false">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
@ -136,10 +142,15 @@
</vn-th> </vn-th>
<vn-th number>Ticket</vn-th> <vn-th number>Ticket</vn-th>
<vn-th>Client</vn-th> <vn-th>Client</vn-th>
<vn-th number shrink>Packages</vn-th> <vn-th shrink>Province</vn-th>
<vn-th shrink>Warehouse</vn-th> <vn-th shrink>
<vn-th expand>Postcode</vn-th> <span translate>
Population
</span>
</vn-th>
<vn-th expand>PC</vn-th>
<vn-th>Address</vn-th> <vn-th>Address</vn-th>
<vn-th shrink>Warehouse</vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
@ -154,24 +165,21 @@
{{::ticket.id}} {{::ticket.id}}
</span> </span>
</vn-td> </vn-td>
<vn-td number> <vn-td expand>
<span class="link" ng-click="clientDescriptor.show($event, ticket.clientFk)"> <span class="link" ng-click="clientDescriptor.show($event, ticket.clientFk)">
{{::ticket.nickname}} {{::ticket.nickname}}
</span> </span>
</vn-td> </vn-td>
<vn-td number shrink>{{::ticket.packages}}</vn-td> <vn-td shrink>{{::ticket.address.province.name}}</vn-td>
<vn-td expand>{{::ticket.warehouse.name}}</vn-td> <vn-td shrink>{{::ticket.address.city}}</vn-td>
<vn-td number shrink>{{::ticket.address.postalCode}}</vn-td> <vn-td number shrink>{{::ticket.address.postalCode}}</vn-td>
<vn-td expand title="{{::ticket.address.street}}">{{::ticket.address.street}}</vn-td> <vn-td expand title="{{::ticket.address.street}}">{{::ticket.address.street}}</vn-td>
<vn-td expand>{{::ticket.warehouse.name}}</vn-td>
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
</vn-data-viewer> </vn-data-viewer>
</tpl-body> </tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Add</button>
</tpl-buttons>
</vn-dialog> </vn-dialog>
<vn-float-button <vn-float-button
icon="add" icon="add"

View File

@ -56,7 +56,7 @@ class Controller extends Section {
this.$http.get(query).then(response => { this.$http.get(query).then(response => {
if (!response.data) if (!response.data)
throw new UserError(`The route's vehicle doesn't have a departing warehouse`); throw new UserError(`The route's vehicle doesn't have a delivery point`);
return response.data; return response.data;
}).then(address => { }).then(address => {
@ -132,6 +132,7 @@ class Controller extends Section {
this.$.model.data = this.$.model.data.concat(tickets); this.$.model.data = this.$.model.data.concat(tickets);
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
this.updateVolume(); this.updateVolume();
this.$.possibleTicketsDialog.hide();
}); });
} }

View File

@ -197,6 +197,9 @@ describe('Route', () => {
describe('setTicketsRoute()', () => { describe('setTicketsRoute()', () => {
it('should perform a POST query to add tickets to the route', () => { it('should perform a POST query to add tickets to the route', () => {
controller.$.possibleTicketsDialog = {hide: () => {}};
jest.spyOn(controller.$.possibleTicketsDialog, 'hide');
controller.$params = {id: 1101}; controller.$params = {id: 1101};
controller.$.model.data = [{id: 1, checked: false}]; controller.$.model.data = [{id: 1, checked: false}];
@ -223,6 +226,7 @@ describe('Route', () => {
$httpBackend.flush(); $httpBackend.flush();
expect(controller.$.model.data).toEqual(expectedResult); expect(controller.$.model.data).toEqual(expectedResult);
expect(controller.$.possibleTicketsDialog.hide).toHaveBeenCalledWith();
}); });
}); });

View File

@ -9,5 +9,6 @@ Tickets to add: Tickets a añadir
Ticket not found: No se ha encontrado el ticket Ticket not found: No se ha encontrado el ticket
The selected ticket is not suitable for this route: El ticket seleccionado no es apto para esta ruta The selected ticket is not suitable for this route: El ticket seleccionado no es apto para esta ruta
PC: CP PC: CP
The route's vehicle doesn't have a departing warehouse: El vehículo de la ruta no tiene un almacén de salida The route's vehicle doesn't have a delivery point: El vehículo de la ruta no tiene un punto de entrega
The route doesn't have a vehicle: La ruta no tiene un vehículo The route doesn't have a vehicle: La ruta no tiene un vehículo
Population: Población

View File

@ -8,3 +8,9 @@ vn-route-tickets form{
max-width: 30px; max-width: 30px;
} }
} }
.button-right{
display: block;
padding-right: 50px;
text-align: right;
}

View File

@ -16,7 +16,6 @@
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="supplierAccount in $ctrl.supplierAccounts"> <vn-horizontal ng-repeat="supplierAccount in $ctrl.supplierAccounts">
<vn-textfield <vn-textfield
ng-show="supplierAccount.iban || supplierAccount.iban == undefined"
label="Iban" label="Iban"
ng-model="supplierAccount.iban" ng-model="supplierAccount.iban"
on-change="supplierAccount.bankEntityFk = supplierAccount.iban.slice(4,8)" on-change="supplierAccount.bankEntityFk = supplierAccount.iban.slice(4,8)"
@ -30,10 +29,10 @@
rule> rule>
<append> <append>
<vn-icon-button <vn-icon-button
vn-auto
icon="add_circle" icon="add_circle"
vn-tooltip="New bank entity" vn-click-stop="bankEntity.show({index: $index})"
ng-click="$ctrl.showBankEntity($event, $index)" vn-tooltip="New bank entity">
tabindex="-1">
</vn-icon-button> </vn-icon-button>
</append> </append>
</vn-autocomplete> </vn-autocomplete>
@ -41,14 +40,6 @@
label="Beneficiary" label="Beneficiary"
ng-model="supplierAccount.beneficiary" ng-model="supplierAccount.beneficiary"
info="Beneficiary information"> info="Beneficiary information">
<append>
<vn-icon-button
vn-auto
icon="add_circle"
vn-click-stop="bankEntity.show({index: $index})"
vn-tooltip="New bank entity">
</vn-icon-button>
</append>
</vn-textfield> </vn-textfield>
<vn-none> <vn-none>
<vn-icon-button <vn-icon-button

View File

@ -13,7 +13,7 @@
{"state": "supplier.card.fiscalData", "icon": "account_balance"}, {"state": "supplier.card.fiscalData", "icon": "account_balance"},
{"state": "supplier.card.billingData", "icon": "icon-payment"}, {"state": "supplier.card.billingData", "icon": "icon-payment"},
{"state": "supplier.card.address.index", "icon": "icon-delivery"}, {"state": "supplier.card.address.index", "icon": "icon-delivery"},
{"state": "supplier.card.account", "icon": "icon-accounts"}, {"state": "supplier.card.account", "icon": "icon-account"},
{"state": "supplier.card.contact", "icon": "contact_phone"}, {"state": "supplier.card.contact", "icon": "contact_phone"},
{"state": "supplier.card.log", "icon": "history"}, {"state": "supplier.card.log", "icon": "history"},
{"state": "supplier.card.consumption", "icon": "show_chart"} {"state": "supplier.card.consumption", "icon": "show_chart"}

View File

@ -1,26 +1,26 @@
const UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('recalculatePrice', { Self.remoteMethodCtx('recalculatePrice', {
description: 'Calculates the price of a sale and its components', description: 'Calculates the price of sales and its components',
accessType: 'WRITE', accessType: 'WRITE',
accepts: [{ accepts: [{
arg: 'id', arg: 'sales',
description: 'The sale id', description: 'The sales',
type: 'number', type: ['object'],
required: true, required: true,
http: {source: 'path'} http: {source: 'body'}
}], }],
returns: { returns: {
type: 'Number', type: 'number',
root: true root: true
}, },
http: { http: {
path: `/:id/recalculatePrice`, path: `/recalculatePrice`,
verb: 'post' verb: 'post'
} }
}); });
Self.recalculatePrice = async(ctx, id, options) => { Self.recalculatePrice = async(ctx, sales, options) => {
const models = Self.app.models; const models = Self.app.models;
const myOptions = {}; const myOptions = {};
let tx; let tx;
@ -34,18 +34,33 @@ module.exports = Self => {
} }
try { try {
const sale = await Self.findById(id, null, myOptions); const salesIds = [];
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions); const params = [];
sales.forEach(sale => {
salesIds.push(sale.id);
params.push('?');
});
const isEditable = await models.Ticket.isEditable(ctx, sales[0].ticketFk, myOptions);
if (!isEditable) if (!isEditable)
throw new UserError(`The sales of this ticket can't be modified`); throw new UserError(`The sales of this ticket can't be modified`);
const canEditSale = await models.Sale.canEdit(ctx, [id], myOptions); const canEditSale = await models.Sale.canEdit(ctx, sales, myOptions);
if (!canEditSale) if (!canEditSale)
throw new UserError(`Sale(s) blocked, please contact production`); throw new UserError(`Sale(s) blocked, please contact production`);
const recalculation = await Self.rawSql('CALL vn.sale_calculateComponent(?, null)', [id], myOptions); const paramsString = params.join();
const query = `
DROP TEMPORARY TABLE IF EXISTS tmp.recalculateSales;
CREATE TEMPORARY TABLE tmp.recalculateSales
SELECT s.id
FROM sale s
WHERE s.id IN (${paramsString});
CALL vn.sale_recalcComponent(null);
DROP TEMPORARY TABLE tmp.recalculateSales;`;
const recalculation = await Self.rawSql(query, salesIds, myOptions);
if (tx) await tx.commit(); if (tx) await tx.commit();

View File

@ -1,18 +1,20 @@
const models = require('vn-loopback/server/server').models; const models = require('vn-loopback/server/server').models;
describe('sale recalculatePrice()', () => { describe('sale recalculatePrice()', () => {
const saleId = 7;
it('should update the sale price', async() => { it('should update the sale price', async() => {
const tx = await models.Sale.beginTransaction({}); const tx = await models.Sale.beginTransaction({});
const sales = [
{id: 7, ticketFk: 11},
{id: 8, ticketFk: 11}
];
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}}; const ctx = {req: {accessToken: {userId: 9}}};
const response = await models.Sale.recalculatePrice(ctx, saleId, options); const response = await models.Sale.recalculatePrice(ctx, sales, options);
expect(response.affectedRows).toBeDefined(); expect(response[0].affectedRows).toBeDefined();
expect(response[1].affectedRows).toBeDefined();
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {
@ -29,8 +31,8 @@ describe('sale recalculatePrice()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}}; const ctx = {req: {accessToken: {userId: 9}}};
const immutableSaleId = 1; const immutableSale = [{id: 1, ticketFk: 1}];
await models.Sale.recalculatePrice(ctx, immutableSaleId, options); await models.Sale.recalculatePrice(ctx, immutableSale, options);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -85,9 +85,10 @@ module.exports = Self => {
}; };
if (sale.quantity == originalSale.quantity) { if (sale.quantity == originalSale.quantity) {
await models.Sale.updateAll({ query = `UPDATE sale
id: sale.id SET ticketFk = ?
}, {ticketFk: ticketId}, myOptions); WHERE id = ?`;
await Self.rawSql(query, [ticketId, sale.id], myOptions);
} else if (sale.quantity != originalSale.quantity) { } else if (sale.quantity != originalSale.quantity) {
await transferPartialSale( await transferPartialSale(
ticketId, originalSale, sale, myOptions); ticketId, originalSale, sale, myOptions);
@ -170,29 +171,31 @@ module.exports = Self => {
// Update original sale // Update original sale
const rest = originalSale.quantity - sale.quantity; const rest = originalSale.quantity - sale.quantity;
const originalInstance = await models.Sale.findById(sale.id, null, options); query = `UPDATE sale
await originalInstance.updateAttribute('quantity', rest, options); SET quantity = ?
WHERE id = ?`;
await Self.rawSql(query, [rest, sale.id], options);
// Clone sale with new quantity // Clone sale with new quantity
const newSale = originalSale; query = `INSERT INTO sale (itemFk, ticketFk, concept, quantity, originalQuantity, price, discount, priceFixed,
newSale.id = undefined; reserved, isPicked, isPriceFixed, isAdded)
newSale.ticketFk = ticketId; SELECT itemFk, ?, concept, ?, originalQuantity, price, discount, priceFixed,
newSale.quantity = sale.quantity; reserved, isPicked, isPriceFixed, isAdded
FROM sale
const createdSale = await models.Sale.create(newSale, options); WHERE id = ?`;
await Self.rawSql(query, [ticketId, sale.quantity, sale.id], options);
const [lastInsertedSale] = await Self.rawSql('SELECT LAST_INSERT_ID() AS id', null, options);
// Clone sale components // Clone sale components
const saleComponents = await models.SaleComponent.find({ const saleComponents = await models.SaleComponent.find({
where: {saleFk: sale.id} where: {saleFk: sale.id}
}, options); }, options);
const newComponents = saleComponents.map(component => { const newComponents = saleComponents.map(component => {
component.saleFk = createdSale.id; component.saleFk = lastInsertedSale.id;
return component; return component;
}); });
await models.SaleComponent.create(newComponents, options); await models.SaleComponent.create(newComponents, options);
return originalInstance;
} }
}; };

View File

@ -49,7 +49,7 @@
ng-show="::ticket.hasTicketRequest" ng-show="::ticket.hasTicketRequest"
translate-attr="{title: 'Purchase request'}" translate-attr="{title: 'Purchase request'}"
class="bright" class="bright"
icon="icon-100"> icon="icon-buyrequest">
</vn-icon> </vn-icon>
<vn-icon <vn-icon
ng-show="::ticket.isAvailable === 0" ng-show="::ticket.isAvailable === 0"
@ -167,7 +167,7 @@
tooltip-position="left"> tooltip-position="left">
</vn-button> </vn-button>
<vn-button class="round sm vn-mb-sm" <vn-button class="round sm vn-mb-sm"
icon="icon-invoices" icon="icon-invoice"
ng-click="makeInvoiceConfirmation.show()" ng-click="makeInvoiceConfirmation.show()"
ng-show="$ctrl.totalChecked > 0" ng-show="$ctrl.totalChecked > 0"
vn-tooltip="Make invoice..." vn-tooltip="Make invoice..."

View File

@ -12,7 +12,7 @@
"card": [ "card": [
{"state": "ticket.card.basicData.stepOne", "icon": "settings"}, {"state": "ticket.card.basicData.stepOne", "icon": "settings"},
{"state": "ticket.card.sale", "icon": "icon-lines"}, {"state": "ticket.card.sale", "icon": "icon-lines"},
{"state": "ticket.card.request.index", "icon": "icon-100"}, {"state": "ticket.card.request.index", "icon": "icon-buyrequest"},
{"state": "ticket.card.tracking.index", "icon": "remove_red_eye"}, {"state": "ticket.card.tracking.index", "icon": "remove_red_eye"},
{"state": "ticket.card.log", "icon": "history"}, {"state": "ticket.card.log", "icon": "history"},
{"state": "ticket.card.observation", "icon": "insert_drive_file"}, {"state": "ticket.card.observation", "icon": "insert_drive_file"},

View File

@ -464,7 +464,7 @@
<vn-item translate <vn-item translate
name="calculatePrice" name="calculatePrice"
ng-click="$ctrl.calculateSalePrice()" ng-click="$ctrl.calculateSalePrice()"
ng-if="$ctrl.isEditable && $ctrl.hasOneSaleSelected()"> ng-if="$ctrl.isEditable">
Recalculate price Recalculate price
</vn-item> </vn-item>
<vn-item translate <vn-item translate

View File

@ -450,10 +450,11 @@ class Controller extends Section {
} }
calculateSalePrice() { calculateSalePrice() {
const sale = this.selectedValidSales()[0]; const sales = this.selectedValidSales();
if (!sale) return; if (!sales) return;
const query = `Sales/${sale.id}/recalculatePrice`;
this.$http.post(query).then(() => { const query = `Sales/recalculatePrice`;
this.$http.post(query, sales).then(() => {
this.vnApp.showSuccess(this.$t('Data saved!')); this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.model.refresh(); this.$.model.refresh();
}); });

View File

@ -688,10 +688,11 @@ describe('Ticket', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis(); jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller.$.model, 'refresh').mockReturnThis(); jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
const selectedSale = controller.sales[0]; controller.sales.forEach(sale => {
selectedSale.checked = true; sale.checked = true;
});
$httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200); $httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200);
controller.calculateSalePrice(); controller.calculateSalePrice();
$httpBackend.flush(); $httpBackend.flush();

View File

@ -46,12 +46,29 @@ module.exports = Self => {
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss)) if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`); throw new UserError(`You don't have enough privileges`);
const timed = new Date(args.timed); const minTime = new Date(args.timed);
minTime.setHours(0, 0, 0, 0);
query = `SELECT * FROM vn.workerLabour WHERE workerFk = ? AND (ended >= ? OR ended IS NULL);`;
const [workerLabour] = await Self.rawSql(query, [workerId, minTime]);
const absence = await models.Calendar.findOne({
where: {
businessFk: workerLabour.businessFk,
dated: minTime
}
});
if (absence) {
const absenceType = await models.AbsenceType.findById(absence.dayOffTypeFk, null, myOptions);
const isNotHalfAbsence = absenceType.code != 'halfHoliday'
&& absenceType.code != 'halfPaidLeave'
&& absenceType.code != 'halfFurlough';
if (isNotHalfAbsence)
throw new UserError(`The worker has a marked absence that day`);
}
return models.WorkerTimeControl.create({ return models.WorkerTimeControl.create({
userFk: workerId, userFk: workerId,
direction: args.direction, direction: args.direction,
timed: timed, timed: args.timed,
manual: true manual: true
}, myOptions); }, myOptions);
}; };

View File

@ -77,6 +77,23 @@ describe('workerTimeControl add/delete timeEntry()', () => {
} }
}); });
it('should fail to add a time entry if the target user has absent that day', async() => {
activeCtx.accessToken.userId = salesBossId;
const workerId = salesPersonId;
let error;
let calendar = await app.models.Calendar.findById(3);
try {
ctx.args = {timed: new Date(calendar.dated), direction: 'in'};
await models.WorkerTimeControl.addTimeEntry(ctx, workerId);
} catch (e) {
error = e;
}
expect(error.message).toBe(`The worker has a marked absence that day`);
});
it('should try but fail to delete his own time entry', async() => { it('should try but fail to delete his own time entry', async() => {
activeCtx.accessToken.userId = salesBossId; activeCtx.accessToken.userId = salesBossId;
const workerId = salesBossId; const workerId = salesBossId;

View File

@ -65,7 +65,22 @@ module.exports = Self => {
if (args.dated < labour.started || (labour.ended != null && args.dated > labour.ended)) if (args.dated < labour.started || (labour.ended != null && args.dated > labour.ended))
throw new UserError(`The contract was not active during the selected date`); throw new UserError(`The contract was not active during the selected date`);
const result = await Self.rawSql( query = `SELECT *
FROM vn.workerTimeControl
WHERE userFk = ? AND timed BETWEEN DATE(?) AND CONCAT(DATE(?), ' 23:59:59')
LIMIT 1;`;
const [hasHoursRecorded] = await Self.rawSql(query, [id, args.dated, args.dated]);
const absenceType = await models.AbsenceType.findById(args.absenceTypeId, null, myOptions);
const isNotHalfAbsence = absenceType.code != 'halfHoliday'
&& absenceType.code != 'halfPaidLeave'
&& absenceType.code != 'halfFurlough';
if (hasHoursRecorded && isNotHalfAbsence)
throw new UserError(`The worker has hours recorded that day`);
const [result] = await Self.rawSql(
`SELECT COUNT(*) halfHolidayCounter `SELECT COUNT(*) halfHolidayCounter
FROM vn.calendar c FROM vn.calendar c
JOIN postgresql.business b ON b.business_id = c.businessFk JOIN postgresql.business b ON b.business_id = c.businessFk
@ -74,10 +89,10 @@ module.exports = Self => {
WHERE c.dayOffTypeFk = 6 WHERE c.dayOffTypeFk = 6
AND pe.workerFk = ? AND pe.workerFk = ?
AND c.dated BETWEEN util.firstDayOfYear(CURDATE()) AND c.dated BETWEEN util.firstDayOfYear(CURDATE())
AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [args.id]); AND LAST_DAY(DATE_ADD(NOW(), INTERVAL 12-MONTH(NOW()) MONTH))`, [id]);
const hasHalfHoliday = result[0].halfHolidayCounter > 0; const hasHalfHoliday = result.halfHolidayCounter > 0;
const isHalfHoliday = args.absenceTypeId == 6; const isHalfHoliday = absenceType.code === 'halfHoliday';
if (isHalfHoliday && hasHalfHoliday) if (isHalfHoliday && hasHalfHoliday)
throw new UserError(`Cannot add more than one '1/2 day vacation'`); throw new UserError(`Cannot add more than one '1/2 day vacation'`);

View File

@ -77,7 +77,7 @@ describe('Worker createAbsence()', () => {
it(`should throw an error when adding a "Half holiday" absence if there's already one`, async() => { it(`should throw an error when adding a "Half holiday" absence if there's already one`, async() => {
const ctx = { const ctx = {
req: {accessToken: {userId: 19}}, req: {accessToken: {userId: 9}},
args: { args: {
id: 1, id: 1,
businessFk: 1, businessFk: 1,
@ -85,6 +85,7 @@ describe('Worker createAbsence()', () => {
dated: new Date() dated: new Date()
} }
}; };
const workerId = 1;
const tx = await app.models.Calendar.beginTransaction({}); const tx = await app.models.Calendar.beginTransaction({});
@ -102,4 +103,33 @@ describe('Worker createAbsence()', () => {
expect(error.message).toEqual(`Cannot add more than one '1/2 day vacation'`); expect(error.message).toEqual(`Cannot add more than one '1/2 day vacation'`);
}); });
it(`should throw an error when adding a absence if the worker has hours recorded that day and not is a half absence`, async() => {
const ctx = {
req: {accessToken: {userId: 19}},
args: {
id: 1106,
businessFk: 1106,
absenceTypeId: 1,
dated: new Date()
}
};
const workerId = 1106;
const tx = await app.models.Calendar.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await app.models.Worker.createAbsence(ctx, workerId, options);
await tx.rollback();
} catch (e) {
await tx.rollback();
error = e;
}
expect(error.message).toEqual(`The worker has hours recorded that day`);
});
}); });

View File

@ -11,6 +11,9 @@
"id": true, "id": true,
"type": "Number" "type": "Number"
}, },
"workerFk": {
"type": "Number"
},
"started": { "started": {
"type": "date" "type": "date"
}, },

View File

@ -83,7 +83,7 @@
"worker": "$ctrl.worker" "worker": "$ctrl.worker"
} }
}, { }, {
"url": "/time-control", "url": "/time-control?timestamp",
"state": "worker.card.timeControl", "state": "worker.card.timeControl",
"component": "vn-worker-time-control", "component": "vn-worker-time-control",
"description": "Time control", "description": "Time control",

View File

@ -91,6 +91,7 @@
</vn-label-value> </vn-label-value>
</div> </div>
<vn-calendar <vn-calendar
vn-id="calendar"
class="vn-pt-md" class="vn-pt-md"
ng-model="$ctrl.date" ng-model="$ctrl.date"
has-events="$ctrl.hasEvents($day)"> has-events="$ctrl.hasEvents($day)">

View File

@ -15,7 +15,15 @@ class Controller extends Section {
} }
$postLink() { $postLink() {
this.date = new Date(); const timestamp = this.$params.timestamp;
let initialDate = new Date();
if (timestamp) {
initialDate = new Date(timestamp * 1000);
this.$.calendar.defaultDate = initialDate;
}
this.date = initialDate;
} }
get worker() { get worker() {

View File

@ -129,5 +129,28 @@ describe('Component vnWorkerTimeControl', () => {
expect(controller.fetchHours).toHaveBeenCalledWith(); expect(controller.fetchHours).toHaveBeenCalledWith();
}); });
}); });
describe('$postLink() ', () => {
it(`should set the controller date as today if no timestamp is defined`, () => {
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.$params = {timestamp: undefined};
controller.$postLink();
expect(controller.date).toEqual(jasmine.any(Date));
});
it(`should set the controller date using the received timestamp`, () => {
const timestamp = 1;
const date = new Date(timestamp);
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.$.calendar = {};
controller.$params = {timestamp: timestamp};
controller.$postLink();
expect(controller.date.toDateString()).toEqual(date.toDateString());
});
});
}); });
}); });