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');
describe('newCollection()', () => {
// #3400 analizar que hacer con rutas de back colletion
xdescribe('newCollection()', () => {
it('return a new collection', async() => {
let ctx = {req: {accessToken: {userId: 1106}}};
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'),
('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
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT', 0),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT', 0),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT', 0),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT', 0),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT', 0),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT', 0),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT', 0),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT', 0),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT', 1),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT', 0),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT', 0),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT', 0),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT', 1),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT', 1),
(15, 4, 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),
(71, 6, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, '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, NULL),
(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, NULL),
(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, NULL),
(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, NULL),
(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, NULL),
(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, NULL),
(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, NULL),
(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, NULL),
(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 `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1877,6 +1878,7 @@ INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `
(1, 'Holidays', '#FF4444', 'holiday', 0),
(2, 'Leave of absence', '#C71585', 'absence', 0),
(6, 'Half holiday', '#E65F00', 'halfHoliday', 0),
(15, 'Half Paid Leave', '#5151c0', 'halfPaidLeave', 0),
(20, 'Furlough', '#97B92F', 'furlough', 1),
(21, 'Furlough half day', '#778899', 'halfFurlough', 0.5);
@ -2419,3 +2421,6 @@ INSERT INTO `vn`.`expeditionScan` (`id`, `expeditionFk`, `scanned`, `palletFk`)
(10, 10, CURDATE(), 1);
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
fieldAcl
module
defaultViewConfig
)
dump_tables ${TABLES[@]}

View File

@ -27,7 +27,7 @@ export async function getBrowser() {
args,
defaultViewport: null,
headless: headless,
slowMo: 5, // slow down by ms
slowMo: 1, // slow down by ms
// ignoreDefaultArgs: ['--disable-extensions'],
// executablePath: '/usr/bin/google-chrome-stable',
// 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"]',
origin: 'vn-autocomplete[ng-model="$ctrl.item.originFk"]',
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"]',
longName: 'vn-textfield[ng-model="$ctrl.item.longName"]',
isActiveCheckbox: 'vn-check[label="Active"]',
@ -556,6 +557,7 @@ export default {
moreMenuReserve: 'vn-item[name="reserve"]',
moreMenuUnmarkReseved: 'vn-item[name="unreserve"]',
moreMenuUpdateDiscount: 'vn-item[name="discount"]',
moreMenuRecalculatePrice: 'vn-item[name="calculatePrice"]',
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',
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() => {
await page.clearInput(selectors.itemBasicData.name);
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.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.priceInKgCheckbox);
await page.waitToClick(selectors.itemBasicData.isFragile);
@ -101,6 +103,13 @@ describe('Item Edit basic data path', () => {
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() => {
const result = await page
.waitToGetProperty(selectors.itemBasicData.longName, 'value');

View File

@ -195,6 +195,17 @@ describe('Ticket Edit sale path', () => {
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() => {
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.waitToClick(selectors.ticketSales.moreMenu);

View File

@ -8,7 +8,7 @@ describe('Entry observations path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('developer', 'entry');
await page.loginAndModule('buyer', 'entry');
await page.accessToSearchResult('2');
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');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult);
expect(rolesCount).toEqual(4);
expect(rolesCount).toEqual(3);
});
describe('Descriptor option', () => {
describe('Edit role', () => {
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.changeRole);
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() => {
// 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.reloadSection('account.card.roles');
const rolesCount = await page.countElement(selectors.accountRoles.anyResult);

View File

@ -8,7 +8,7 @@ describe('Account Role create and basic data path', () => {
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('developer', 'account');
await page.loginAndModule('it', 'account');
await page.accessToSection('account.role');
});
@ -76,11 +76,11 @@ describe('Account Role create and basic data path', () => {
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.accessToSection('account.role.card.inherited');
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;
}
.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 {
content: "\e940";
content: "\e976";
}
.icon-accessory:before {
content: "\e90a";
}
.icon-actions:before {
.icon-account:before {
content: "\e900";
}
.icon-addperson:before {
.icon-actions:before {
content: "\e901";
}
.icon-albaran:before {
.icon-addperson:before {
content: "\e902";
}
.icon-apps:before {
content: "\e948";
}
.icon-artificial:before {
.icon-agency:before {
content: "\e903";
}
.icon-barcode:before {
.icon-albaran:before {
content: "\e904";
}
.icon-basket:before {
content: "\e942";
}
.icon-bin:before {
.icon-anonymous:before {
content: "\e905";
}
.icon-botanical:before {
.icon-apps:before {
content: "\e906";
}
.icon-bucket:before {
.icon-artificial:before {
content: "\e907";
}
.icon-claims:before {
.icon-attach:before {
content: "\e908";
}
.icon-clone:before {
.icon-barcode:before {
content: "\e909";
}
.icon-columnadd:before {
content: "\e944";
.icon-basket:before {
content: "\e90a";
}
.icon-columndelete:before {
content: "\e90f";
}
.icon-components:before {
.icon-basketadd:before {
content: "\e90b";
}
.icon-consignatarios: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 {
.icon-bin:before {
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";
}
.icon-info:before {
content: "\e949";
.icon-clone:before {
content: "\e91b";
}
.icon-item:before {
content: "\e941";
.icon-columnadd:before {
content: "\e91c";
}
.icon-languaje:before {
.icon-columndelete:before {
content: "\e91d";
}
.icon-linedelete:before {
content: "\e946";
}
.icon-lines:before {
.icon-accessory:before {
content: "\e91e";
}
.icon-linesprepaired:before {
content: "\e94b";
}
.icon-logout:before {
.icon-components:before {
content: "\e91f";
}
.icon-mana:before {
.icon-handmade:before {
content: "\e920";
}
.icon-mandatory:before {
.icon-consignatarios:before {
content: "\e921";
}
.icon-niche:before {
.icon-control:before {
content: "\e922";
}
.icon-no036:before {
.icon-credit:before {
content: "\e923";
}
.icon-notes:before {
.icon-deletedTicketCross:before {
content: "\e924";
}
.icon-noweb:before {
.icon-deleteline:before {
content: "\e925";
}
.icon-onlinepayment:before {
.icon-delivery:before {
content: "\e926";
}
.icon-package:before {
.icon-deliveryprices:before {
content: "\e927";
}
.icon-payment:before {
.icon-details:before {
content: "\e928";
}
.icon-person:before {
.icon-dfiscales:before {
content: "\e929";
}
.icon-photo:before {
.icon-doc:before {
content: "\e92a";
}
.icon-plant:before {
.icon-entry:before {
content: "\e92b";
}
.icon-recovery: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 {
.icon-exit:before {
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";
}
.icon-tags:before {
.icon-history:before {
content: "\e937";
}
.icon-tax:before {
.icon-disabled:before {
content: "\e938";
}
.icon-ticket:before {
.icon-info:before {
content: "\e939";
}
.icon-traceability:before {
.icon-inventory:before {
content: "\e93a";
}
.icon-transaction:before {
.icon-invoice:before {
content: "\e93b";
}
.icon-volume:before {
.icon-invoice-in:before {
content: "\e93c";
}
.icon-invoice-in-create:before {
content: "\e93d";
}
.icon-web:before {
.icon-invoice-out:before {
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";
}
.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",
"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",
"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",
"property": "id"
}
}
},
"acls": [{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}]
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -16,7 +16,7 @@
{"state": "client.card.note.index", "icon": "insert_drive_file"},
{"state": "client.card.credit.index", "icon": "credit_card"},
{"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.webAccess", "icon": "cloud"},
{"state": "client.card.log", "icon": "history"},

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -6,7 +6,7 @@ Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES
Show CITES letter: Ver carta CITES
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 clone this invoice?: Estas seguro de clonar esta factura?

View File

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

View File

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

View File

@ -137,6 +137,29 @@
ng-model="$ctrl.item.compression"
rule>
</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-one
label="Description"
@ -203,3 +226,92 @@
<button response="accept" translate>Create</button>
</tpl-buttons>
</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)
.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', {

View File

@ -11,3 +11,4 @@ Identifier: Identificador
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, ...)
Multiplier: Multiplicador
Generic: Genérico

View File

@ -7,7 +7,7 @@
"menus": {
"main": [
{"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.fixedPrice", "icon": "icon-fixedPrice"}
],

View File

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

View File

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

View File

@ -56,7 +56,7 @@ class Controller extends Section {
this.$http.get(query).then(response => {
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;
}).then(address => {
@ -132,6 +132,7 @@ class Controller extends Section {
this.$.model.data = this.$.model.data.concat(tickets);
this.vnApp.showSuccess(this.$t('Data saved!'));
this.updateVolume();
this.$.possibleTicketsDialog.hide();
});
}

View File

@ -197,6 +197,9 @@ describe('Route', () => {
describe('setTicketsRoute()', () => {
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.$.model.data = [{id: 1, checked: false}];
@ -223,6 +226,7 @@ describe('Route', () => {
$httpBackend.flush();
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
The selected ticket is not suitable for this route: El ticket seleccionado no es apto para esta ruta
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
Population: Población

View File

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

View File

@ -13,7 +13,7 @@
{"state": "supplier.card.fiscalData", "icon": "account_balance"},
{"state": "supplier.card.billingData", "icon": "icon-payment"},
{"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.log", "icon": "history"},
{"state": "supplier.card.consumption", "icon": "show_chart"}

View File

@ -1,26 +1,26 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('recalculatePrice', {
description: 'Calculates the price of a sale and its components',
description: 'Calculates the price of sales and its components',
accessType: 'WRITE',
accepts: [{
arg: 'id',
description: 'The sale id',
type: 'number',
arg: 'sales',
description: 'The sales',
type: ['object'],
required: true,
http: {source: 'path'}
http: {source: 'body'}
}],
returns: {
type: 'Number',
type: 'number',
root: true
},
http: {
path: `/:id/recalculatePrice`,
path: `/recalculatePrice`,
verb: 'post'
}
});
Self.recalculatePrice = async(ctx, id, options) => {
Self.recalculatePrice = async(ctx, sales, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
@ -34,18 +34,33 @@ module.exports = Self => {
}
try {
const sale = await Self.findById(id, null, myOptions);
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk, myOptions);
const salesIds = [];
const params = [];
sales.forEach(sale => {
salesIds.push(sale.id);
params.push('?');
});
const isEditable = await models.Ticket.isEditable(ctx, sales[0].ticketFk, myOptions);
if (!isEditable)
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)
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();

View File

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

View File

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

View File

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

View File

@ -12,7 +12,7 @@
"card": [
{"state": "ticket.card.basicData.stepOne", "icon": "settings"},
{"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.log", "icon": "history"},
{"state": "ticket.card.observation", "icon": "insert_drive_file"},

View File

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

View File

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

View File

@ -684,14 +684,15 @@ describe('Ticket', () => {
});
describe('calculateSalePrice()', () => {
it('should make an HTTP post query ', () => {
it('should make an HTTP post query', () => {
jest.spyOn(controller.vnApp, 'showSuccess').mockReturnThis();
jest.spyOn(controller.$.model, 'refresh').mockReturnThis();
const selectedSale = controller.sales[0];
selectedSale.checked = true;
controller.sales.forEach(sale => {
sale.checked = true;
});
$httpBackend.expect('POST', `Sales/1/recalculatePrice`).respond(200);
$httpBackend.expect('POST', `Sales/recalculatePrice`).respond(200);
controller.calculateSalePrice();
$httpBackend.flush();

View File

@ -46,12 +46,29 @@ module.exports = Self => {
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
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({
userFk: workerId,
direction: args.direction,
timed: timed,
timed: args.timed,
manual: true
}, 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() => {
activeCtx.accessToken.userId = 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))
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
FROM vn.calendar c
JOIN postgresql.business b ON b.business_id = c.businessFk
@ -74,10 +89,10 @@ module.exports = Self => {
WHERE c.dayOffTypeFk = 6
AND pe.workerFk = ?
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 isHalfHoliday = args.absenceTypeId == 6;
const hasHalfHoliday = result.halfHolidayCounter > 0;
const isHalfHoliday = absenceType.code === 'halfHoliday';
if (isHalfHoliday && hasHalfHoliday)
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() => {
const ctx = {
req: {accessToken: {userId: 19}},
req: {accessToken: {userId: 9}},
args: {
id: 1,
businessFk: 1,
@ -85,6 +85,7 @@ describe('Worker createAbsence()', () => {
dated: new Date()
}
};
const workerId = 1;
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'`);
});
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,
"type": "Number"
},
"workerFk": {
"type": "Number"
},
"started": {
"type": "date"
},

View File

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

View File

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

View File

@ -15,7 +15,15 @@ class Controller extends Section {
}
$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() {

View File

@ -129,5 +129,28 @@ describe('Component vnWorkerTimeControl', () => {
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());
});
});
});
});