Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 3377-modifyStyles
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Vicent Llopis 2021-11-25 14:07:14 +01:00
commit 6afc694f6c
145 changed files with 2504 additions and 1344 deletions

View File

@ -27,7 +27,7 @@ module.exports = Self => {
Self.sendCheckingPresence = async(ctx, recipientId, message, options) => {
if (!recipientId) return false;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

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

@ -60,7 +60,7 @@ module.exports = Self => {
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -54,7 +54,7 @@ module.exports = Self => {
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -16,7 +16,7 @@ module.exports = function(Self) {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -31,7 +31,7 @@ module.exports = function(Self) {
const userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -23,7 +23,7 @@ module.exports = function(Self) {
const userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

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,5 @@
UPDATE `salix`.`defaultViewConfig`
SET `columns` = '{"intrastat":false,"description":false,"density":false,"isActive":false,
"freightValue":false,"packageValue":false,"isIgnored":false,"price2":false,"ektFk":false,"weight":false,
"size":false,"comissionValue":false,"landing":false}'
WHERE tableCode = 'latestBuys'

View File

@ -1,9 +1,8 @@
DROP PROCEDURE IF EXISTS vn.item_getBalance;
DROP PROCEDURE IF EXISTS `vn`.`item_getBalance`;
DELIMITER $$
$$
CREATE
definer = root@`%` procedure vn.item_getBalance(IN vItemId int, IN vWarehouse int)
CREATE DEFINER=`root`@`%` PROCEDURE `vn`.`item_getBalance`(IN vItemId int, IN vWarehouse int)
BEGIN
DECLARE vDateInventory DATETIME;
DECLARE vCurdate DATE DEFAULT CURDATE();
@ -116,7 +115,7 @@ BEGIN
s.id,
st.`order`,
ct.code,
cl.id
cb.claimFk
FROM sale s
JOIN ticket t ON t.id = s.ticketFk
LEFT JOIN ticketState ts ON ts.ticket = t.id
@ -132,6 +131,7 @@ BEGIN
LEFT JOIN state stPrep ON stPrep.`code` = 'PREPARED'
LEFT JOIN saleTracking stk ON stk.saleFk = s.id AND stk.stateFk = stPrep.id
LEFT JOIN claim cl ON cl.ticketFk = t.id
LEFT JOIN claimBeginning cb ON cl.id = cb.claimFk AND s.id = cb.saleFk
WHERE t.shipped >= vDateInventory
AND s.itemFk = vItemId
AND vWarehouse =t.warehouseFk
@ -141,4 +141,3 @@ BEGIN
END;
$$
DELIMITER ;

View File

@ -0,0 +1,2 @@
ALTER TABLE vn.payMethod CHANGE ibanRequiredForClients isIbanRequiredForClients tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers isIbanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;

File diff suppressed because one or more lines are too long

View File

@ -217,7 +217,7 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequiredForClients`, `ibanRequiredForSuppliers`)
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `isIbanRequiredForClients`, `isIbanRequiredForSuppliers`)
VALUES
(1, NULL, 'PayMethod one', 0, 001, 0, 0),
(2, NULL, 'PayMethod two', 10, 001, 0, 0),
@ -739,7 +739,7 @@ INSERT INTO `vn`.`itemType`(`id`, `code`, `name`, `categoryFk`, `warehouseFk`, `
(3, 'WPN', 'Paniculata', 2, 1, 31, 35, 0),
(4, 'PRT', 'Delivery ports', 3, 1, NULL, 35, 1),
(5, 'CON', 'Container', 3, 1, NULL, 35, 1),
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 35, 0);
(6, 'ALS', 'Alstroemeria', 1, 1, 31, 16, 0);
INSERT INTO `vn`.`ink`(`id`, `name`, `picture`, `showOrder`, `hex`)
VALUES
@ -796,25 +796,25 @@ 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`)
INSERT INTO `vn`.`item`(`id`, `typeFk`, `size`, `inkFk`, `stems`, `originFk`, `description`, `producerFk`, `intrastatFk`, `expenceFk`, `comment`, `relevancy`, `image`, `subName`, `minPrice`, `stars`, `family`, `isFloramondo`)
VALUES
(1, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '1', NULL, 0, 1, 'VT'),
(2, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '2', NULL, 0, 2, 'VT'),
(3, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '3', NULL, 0, 5, 'VT'),
(4, 1, 60, 'YEL', 1, 1, 'Increases block', 1, 05080000, 4751000000, NULL, 0, '4', NULL, 0, 3, 'VT'),
(5, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '5', NULL, 0, 3, 'VT'),
(6, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '6', NULL, 0, 4, 'VT'),
(7, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '7', NULL, 0, 4, 'VT'),
(8, 2, 70, 'YEL', 1, 1, NULL, 1, 06021010, 2000000000, NULL, 0, '8', NULL, 0, 5, 'VT'),
(9, 2, 70, 'BLU', 1, 2, NULL, 1, 06021010, 2000000000, NULL, 0, '9', NULL, 0, 4, 'VT'),
(10, 1, 60, 'YEL', 1, 3, NULL, 1, 05080000, 4751000000, NULL, 0, '10', NULL, 0, 4, 'VT'),
(11, 1, 60, 'YEL', 1, 1, NULL, 1, 05080000, 4751000000, NULL, 0, '11', NULL, 0, 4, 'VT'),
(12, 3, 30, 'RED', 1, 2, NULL, 2, 06021010, 4751000000, NULL, 0, '12', NULL, 0, 3, 'VT'),
(13, 5, 30, 'RED', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '13', NULL, 0, 2, 'VT'),
(14, 5, 90, 'BLU', 1, 2, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 4, 'VT'),
(15, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(16, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'EMB'),
(71, 4, NULL, NULL, NULL, 1, NULL, NULL, 06021010, 4751000000, NULL, 0, '', NULL, 0, 0, 'VT');
(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);
-- Update the taxClass after insert of the items
UPDATE `vn`.`itemTaxCountry` SET `taxClassFk` = 2
@ -1882,6 +1882,7 @@ INSERT INTO `postgresql`.`calendar_state` (`calendar_state_id`, `type`, `rgb`, `
INSERT INTO `postgresql`.`calendar_employee` (`business_id`, `calendar_state_id`, `date`)
VALUES
(1, 6, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -10 DAY), DATE_ADD(CURDATE(), INTERVAL 10 DAY))),
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -11 DAY), DATE_ADD(CURDATE(), INTERVAL 11 DAY))),
(1106, 1, IF(MONTH(CURDATE()) = 12 AND DAY(CURDATE()) > 10, DATE_ADD(CURDATE(), INTERVAL -12 DAY), DATE_ADD(CURDATE(), INTERVAL 12 DAY))),

File diff suppressed because it is too large Load Diff

View File

@ -33,6 +33,7 @@ TABLES=(
ACL
fieldAcl
module
defaultViewConfig
)
dump_tables ${TABLES[@]}
@ -47,19 +48,20 @@ TABLES=(
cplusSubjectOp
cplusTaxBreak
cplusTrascendency472
pgc
time
claimResponsible
claimReason
claimRedelivery
claimResult
ticketUpdateAction
state
sample
department
component
componentType
continent
department
itemPackingType
pgc
sample
state
ticketUpdateAction
time
volumeConfig
)
dump_tables ${TABLES[@]}

View File

@ -29,11 +29,11 @@ describe('Client create path', () => {
it('should receive an error when clicking the create button having name and Business name fields empty',
async() => {
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.write(selectors.createClientView.taxNumber, '74451390E');
await page.write(selectors.createClientView.userName, 'CaptainMarvel');
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
@ -83,7 +83,6 @@ describe('Client create path', () => {
expect(message.text).toContain('Some fields are invalid');
});
/* Tarea #3370
it(`should attempt to create a new user with all it's data but wrong business type`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
@ -91,13 +90,10 @@ describe('Client create path', () => {
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});*/
expect(message.text).toContain('The type of business must be filled in basic data');
});
it(`should attempt to create a new user with all it's data but wrong postal code`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.clearInput(selectors.createClientView.postcode);
await page.write(selectors.createClientView.postcode, '479999');

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

@ -86,12 +86,12 @@
align-items: center;
&.today {
color: $color-font-bg;
color: $color-font;
& > .day-number {
border: 2px solid $color-font-link;
&:hover {
background-color: lighten($color-font-link, 20%);
background-color: $color-marginal;
opacity: .8
}
}

View File

@ -50,7 +50,7 @@ vn-chip {
&.alert,
&.alert.clickable:hover,
&.alert.clickable:focus {
background-color: $color-alert-medium;
background-color: lighten($color-alert, 5%);
}
&.message,
&.message.clickable:hover,

View File

@ -223,26 +223,29 @@ export default class Contextmenu {
delete userFilter.where;
}
function removeProp(instance, findProp, prop) {
if (prop == findProp)
delete instance[prop];
function removeProp(obj, targetProp, prop) {
if (prop == targetProp)
delete obj[prop];
if (prop === 'and' || prop === 'or') {
const instanceCopy = instance[prop].slice();
for (let param of instanceCopy) {
const arrayCopy = obj[prop].slice();
for (let param of arrayCopy) {
const [key] = Object.keys(param);
const index = instance[prop].findIndex(param => {
const index = obj[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == findProp)
instance[prop].splice(index, 1);
if (key == targetProp)
obj[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, filterKey, key);
if (Object.keys(param).length == 0)
obj[prop].splice(index, 1);
}
if (instance[prop].length == 0)
delete instance[prop];
if (obj[prop].length == 0)
delete obj[prop];
}
}

View File

@ -71,7 +71,7 @@
color: $color-font;
&::placeholder {
color: $color-font-bg;
color: $color-font-bg-marginal;
}
&[type=time],
&[type=date],
@ -116,6 +116,7 @@
&:active,
&:valid {
box-shadow: 0 0 0 40px $color-bg-panel inset;
-webkit-text-fill-color: $color-primary-medium
}
}
}
@ -198,7 +199,7 @@
}
&.standout {
border-radius: 1px;
background-color: rgba(255, 255, 255, .1);
background-color: rgba(161, 161, 161, 0.1);
padding: 0 12px;
transition-property: background-color, color;
transition-duration: 200ms;
@ -208,6 +209,17 @@
& > .underline {
display: none;
}
& > .infix > .control > input {
&:-internal-autofill-selected {
&,
&:hover,
&:active,
&:valid {
box-shadow: 0 0 0 40px #474747 inset;
-webkit-text-fill-color: $color-font-dark
}
}
}
& > .infix > .control > * {
color: $color-font-dark;
@ -222,11 +234,22 @@
}
}
&.focused {
background-color: $color-bg-panel;
background-color: $color-font-dark;
& > .container {
& > .infix > .control > input {
&:-internal-autofill-selected {
&,
&:hover,
&:active,
&:valid {
box-shadow: 0 0 0 40px $color-font-dark inset;
-webkit-text-fill-color: $color-font-bg
}
}
}
& > .infix > .control > * {
color: $color-font;
color: $color-marginal;
&::placeholder {
color: $color-font-bg;
@ -235,7 +258,7 @@
& > .prepend,
& > .append,
& > .icons {
color: $color-font-bg-marginal;
color: $color-marginal;
}
}
}

View File

@ -15,6 +15,7 @@
<div class="icons pre">
<vn-icon
icon="clear"
ng-show="::$ctrl.clearDisabled != true"
translate-attr="{title: 'Clear'}"
ng-click="$ctrl.onClear($event)">
</vn-icon>

View File

@ -85,6 +85,7 @@ ngModule.vnComponent('vnInputNumber', {
min: '<?',
max: '<?',
step: '<?',
displayControls: '<?'
displayControls: '<?',
clearDisabled: '<?'
}
});

View File

@ -54,20 +54,6 @@ export default class SmartTable extends Component {
set viewConfigId(value) {
this._viewConfigId = value;
/* if (value) {
this.defaultViewConfig = {};
const url = 'DefaultViewConfigs';
const filter = {where: {tableCode: value}};
this.$http.get(url, {filter})
.then(res => {
if (res && res.data.length) {
const columns = res.data[0].columns;
this.defaultViewConfig = columns;
}
});
} */
}
getDefaultViewConfig() {
@ -163,7 +149,7 @@ export default class SmartTable extends Component {
}
}
let styleElement = document.querySelector('style[id="smart-table"]');
const styleElement = document.querySelector('style[id="smart-table"]');
if (styleElement)
styleElement.parentNode.removeChild(styleElement);
@ -379,23 +365,33 @@ export default class SmartTable extends Component {
for (let key of whereKeys) {
removeProp(where, field, key);
if (!Object.keys(where))
if (Object.keys(where).length == 0)
delete userFilter.where;
}
function removeProp(instance, findProp, prop) {
if (prop == findProp)
delete instance[prop];
function removeProp(obj, targetProp, prop) {
if (prop == targetProp)
delete obj[prop];
if (prop === 'and') {
for (let [index, param] of instance[prop].entries()) {
if (prop === 'and' || prop === 'or') {
const arrayCopy = obj[prop].slice();
for (let param of arrayCopy) {
const [key] = Object.keys(param);
if (key == findProp)
instance[prop].splice(index, 1);
const index = obj[prop].findIndex(param => {
return Object.keys(param)[0] == key;
});
if (key == targetProp)
obj[prop].splice(index, 1);
if (param[key] instanceof Array)
removeProp(param, field, key);
if (Object.keys(param).length == 0)
obj[prop].splice(index, 1);
}
if (obj[prop].length == 0)
delete obj[prop];
}
}
@ -429,7 +425,7 @@ export default class SmartTable extends Component {
if (!model.isChanged)
return this.vnApp.showError(this.$t('No changes to save'));
this.model.save()
return this.model.save()
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
}
}

View File

@ -0,0 +1,229 @@
describe('Component smartTable', () => {
let $element;
let controller;
let $httpBackend;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($compile, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$element = $compile(`<smart-table></smart-table>`)($rootScope);
controller = $element.controller('smartTable');
}));
afterEach(() => {
$element.remove();
});
describe('options setter()', () => {
it(`should throw an error if the table doesn't have an identifier`, () => {
const options = {activeButtons: {shownColumns: []}};
expect(() => controller.options = options).toThrowError(/View identifier not defined/);
});
it('should not throw an error if the table does have an identifier', () => {
const options = {activeButtons: {shownColumns: []}};
controller.viewConfigId = 'test';
expect(() => controller.options = options).not.toThrow();
});
});
describe('getDefaultViewConfig()', () => {
it('should perform a query and return the default view columns', done => {
const expectedResponse = [{
columns: {}
}];
$httpBackend.expectGET('DefaultViewConfigs').respond(expectedResponse);
controller.getDefaultViewConfig().then(columns => {
expect(columns).toEqual(expectedResponse[0].columns);
done();
}).catch(done.fail);
$httpBackend.flush();
});
});
describe('viewConfig setter', () => {
it('should just call applyViewConfig() if a viewConfig was provided', () => {
spyOn(controller, 'applyViewConfig');
controller.viewConfig = [{}];
expect(controller.applyViewConfig).toHaveBeenCalled();
});
it('should not get a defaultConfig then insert a new one', () => {
spyOn(controller, 'applyViewConfig');
controller.$.userViewModel = {
insert: jest.fn()
};
const emptyResponse = [{
columns: {}
}];
controller.columns = [
{field: 'test1'},
{field: 'test2'}
];
$httpBackend.expectGET('DefaultViewConfigs').respond(emptyResponse);
controller.viewConfig = [];
$httpBackend.flush();
const expectedObject = {configuration: {'test1': true, 'test2': true}};
expect(controller.$.userViewModel.insert).toHaveBeenCalledWith(expect.objectContaining(expectedObject));
expect(controller.applyViewConfig).toHaveBeenCalled();
});
});
describe('addFilter()', () => {
it('should call the model addFilter() with a basic where filter if exprBuilder() was not received', () => {
controller.model = {addFilter: jest.fn()};
controller.addFilter('myField', 'myValue');
const expectedFilter = {
where: {
myField: 'myValue'
}
};
expect(controller.model.addFilter).toHaveBeenCalledWith(expectedFilter);
});
it('should call the model addFilter() with a built where filter resultant of exprBuilder()', () => {
controller.exprBuilder = jest.fn().mockReturnValue({builtField: 'builtValue'});
controller.model = {addFilter: jest.fn()};
controller.addFilter('myField', 'myValue');
const expectedFilter = {
where: {
builtField: 'builtValue'
}
};
expect(controller.model.addFilter).toHaveBeenCalledWith(expectedFilter);
});
});
describe('applySort()', () => {
it('should call the model refresh() without making changes on the model order', () => {
controller.model = {refresh: jest.fn()};
controller.applySort();
expect(controller.model.order).toBeUndefined();
expect(controller.model.refresh).toHaveBeenCalled();
});
it('should call the model.refresh() after setting model order according to the controller sortCriteria', () => {
controller.model = {refresh: jest.fn()};
const orderBy = {field: 'myField', sortType: 'ASC'};
controller.sortCriteria = [orderBy];
controller.applySort();
expect(controller.model.order).toEqual(`${orderBy.field} ${orderBy.sortType}`);
expect(controller.model.refresh).toHaveBeenCalled();
});
});
describe('filterSanitizer()', () => {
it('should remove the where filter after leaving no fields in it', () => {
controller.model = {
userFilter: {
where: {fieldToRemove: 'valueToRemove'}
},
userParams: {}
};
const result = controller.filterSanitizer('fieldToRemove');
const exectedObject = {userFilter: {}, userParams: {}};
expect(result).toEqual(exectedObject);
});
it('should remove the where filter after leaving no fields and "empty ands/ors" in it', () => {
controller.model = {
userFilter: {
where: {
and: [
{aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'},
{
or: [
{aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'},
]
}
]
}
},
userParams: {}
};
const result = controller.filterSanitizer('aFieldToRemove');
const exectedObject = {userFilter: {}, userParams: {}};
expect(result).toEqual(exectedObject);
});
it('should not remove the where filter after leaving no empty "ands/ors" in it', () => {
controller.model = {
userFilter: {
where: {
and: [
{aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'},
{
or: [
{aFieldToRemove: 'aValueToRemove'},
{aFieldToRemove: 'aValueToRemove'},
]
}
],
or: [{dontKillMe: 'thanks'}]
}
},
userParams: {}
};
const result = controller.filterSanitizer('aFieldToRemove');
const exectedObject = {userFilter: {where: {or: [{dontKillMe: 'thanks'}]}}, userParams: {}};
expect(result).toEqual(exectedObject);
});
});
describe('saveAll()', () => {
it('should throw an error if there are no changes to save in the model', () => {
jest.spyOn(controller.vnApp, 'showError');
controller.model = {isChanged: false};
controller.saveAll();
expect(controller.vnApp.showError).toHaveBeenCalledWith('No changes to save');
});
it('should call the showSuccess() if there are changes to save in the model', done => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.model = {
save: jest.fn().mockReturnValue(Promise.resolve()),
isChanged: true
};
controller.saveAll().then(() => {
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Data saved!');
done();
}).catch(done.fail);
});
});
});

View File

@ -6,14 +6,14 @@ smart-table table {
border-collapse: collapse;
& > thead {
border-bottom: 2px solid $color-spacer;
border-bottom: $border;
& > * > th {
font-weight: normal;
}
}
& > tfoot {
border-top: 2px solid $color-spacer;
border-top: $border;
}
thead, tbody, tfoot {
& > * {
@ -27,7 +27,7 @@ smart-table table {
& > th,
& > td {
text-align: left;
padding: 9px 5px;
padding: 5px;
white-space: nowrap;
text-overflow: ellipsis;
@ -67,7 +67,7 @@ smart-table table {
}
}
tbody > * {
border-bottom: 1px solid $color-spacer-light;
border-bottom: $border-thin;
&:last-child {
border-bottom: none;
@ -76,23 +76,26 @@ smart-table table {
.chip {
padding: 4px;
border-radius: 4px;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium
background-color: $color-notice-medium;
color: $color-font-bg;
}
&.success {
background-color: $color-success-medium;
color: $color-font-bg;
}
&.warning {
background-color: $color-main-medium;
color: $color-font-bg;
}
&.alert {
background-color: $color-alert-medium;
background-color: lighten($color-alert, 5%);
color: $color-font-bg;
}
&.message {
background-color: $color-bg-dark;
color: $color-font-dark;
background-color: $color-bg-dark
}
}
}

View File

@ -14,7 +14,7 @@ vn-table {
& > vn-thead,
& > thead {
display: table-header-group;
border-bottom: 2px solid $color-spacer;
border-bottom: $border;
& > * > th {
font-weight: normal;
@ -32,7 +32,7 @@ vn-table {
& > vn-tfoot,
& > .vn-tfoot,
& > tfoot {
border-top: 2px solid $color-spacer;
border-top: $border;
display: table-footer-group
}
& > * > vn-tr,
@ -119,7 +119,7 @@ vn-table {
vn-tbody > *,
.vn-tbody > *,
tbody > * {
border-bottom: 1px solid $color-spacer-light;
border-bottom: $border-thin;
&:last-child {
border-bottom: none;
@ -132,23 +132,26 @@ vn-table {
.chip {
padding: 4px;
border-radius: 4px;
color: $color-font-bg;
&.notice {
background-color: $color-notice-medium
background-color: $color-notice-medium;
color: $color-font-bg;
}
&.success {
background-color: $color-success-medium;
color: $color-font-bg;
}
&.warning {
background-color: $color-main-medium;
color: $color-font-bg;
}
&.alert {
background-color: $color-alert-medium;
background-color: lighten($color-alert, 5%);
color: $color-font-bg;
}
&.message {
background-color: $color-bg-dark;
color: $color-font-dark;
background-color: $color-bg-dark
}
}
vn-icon-menu {
@ -194,8 +197,8 @@ vn-table.scrollable > .vn-table,
vn-thead vn-th,
thead vn-th,
thead th {
border-bottom: 2px solid $color-spacer;
background-color: #FFF;
border-bottom: $border;
background-color: $color-bg-panel;
position: sticky;
z-index: 9;
top: 0

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View File

@ -1,5 +1,5 @@
import ngModule from '../module';
import noImage from './no-image.png';
import noImage from './no-image-dark.png';
/**
* Sets a default image when there is an error loading

View File

@ -23,30 +23,30 @@
-moz-osx-font-smoothing: grayscale;
}
.icon-basketadd:before {
content: "\e901";
.icon-preserved:before {
content: "\e963";
}
.icon-addperson:before {
content: "\e955";
.icon-treatments:before {
content: "\e964";
}
.icon-supplierfalse:before {
.icon-funeral:before {
content: "\e965";
}
.icon-handmadeArtificial:before {
content: "\e966";
}
.icon-fixedPrice:before {
content: "\e962";
}
.icon-invoice-in-create:before {
content: "\e948";
.icon-accounts:before {
content: "\e95f";
}
.icon-invoiceOut:before {
.icon-clientConsumption:before {
content: "\e960";
}
.icon-invoiceIn:before {
.icon-lastBuy:before {
content: "\e961";
}
.icon-supplier:before {
content: "\e936";
}
.icon-latestBuy:before {
content: "\e95f";
}
.icon-zone:before {
content: "\e95d";
}
@ -86,6 +86,9 @@
.icon-deliveryprices:before {
content: "\e956";
}
.icon-basketadd:before {
content: "\e955";
}
.icon-catalog:before {
content: "\e952";
}
@ -128,9 +131,15 @@
.icon-actions:before {
content: "\e900";
}
.icon-addperson:before {
content: "\e901";
}
.icon-albaran:before {
content: "\e902";
}
.icon-apps:before {
content: "\e948";
}
.icon-artificial:before {
content: "\e903";
}
@ -239,6 +248,9 @@
.icon-mandatory:before {
content: "\e921";
}
.icon-niche:before {
content: "\e922";
}
.icon-no036:before {
content: "\e923";
}
@ -302,6 +314,9 @@
.icon-stowaway:before {
content: "\e92c";
}
.icon-supplier:before {
content: "\e936";
}
.icon-tags:before {
content: "\e937";
}

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 112 KiB

After

Width:  |  Height:  |  Size: 136 KiB

View File

@ -1,6 +1,6 @@
@import "./util";
$font-size: 12pt;
$font-size: 11pt;
$menu-width: 256px;
$topbar-height: 56px;
$mobile-width: 800px;
@ -24,7 +24,7 @@ $spacing-xl: 70px;
// Light theme
$color-primary: #f7931e;
/* $color-primary: #f7931e;
$color-secondary: $color-primary;
$color-font: #222;
@ -72,52 +72,65 @@ $color-success-light: lighten($color-success, 35%);
$color-notice-medium: lighten($color-notice, 20%);
$color-notice-light: lighten($color-notice, 35%);
$color-alert-medium: lighten($color-alert, 20%);
$color-alert-light: lighten($color-alert, 35%);
$color-alert-light: lighten($color-alert, 35%); */
/**/
// Dark theme
/*
$color-header: #3d3d3d;
$color-bg: #222;
$color-bg-dark: #222;
$color-primary: #ec8916;
$color-secondary: $color-primary;
$color-font: #eee;
$color-font-light: #aaa;
$color-font-secondary: #777;
$color-font-dark: white;
$color-font-link: #66bfff;
$color-font-bg: rgba(0, 0, 0, .8);
$color-font-link: #005a9a;
$color-font-bg-marginal: rgba(255, 255, 255, .4);
$color-font-bg-dark: rgba(255, 255, 255, .7);
$color-font-bg-dark-marginal: rgba(255, 255, 255, .4);
$color-header: #3d3d3d;
$color-menu-header: #3d3d3d;
$color-bg: #222;
$color-bg-dark: #222;
$color-active: #666;
$color-active-font: white;
$color-bg-panel: #3c3b3b;
$color-main: #f7931e;
$color-marginal: #ccc;
$color-main: $color-primary;
$color-marginal: #222;
$color-success: #a3d131;
$color-notice: #32b1ce;
$color-alert: #f42121;
$color-alert: #fa3939;
$color-button: $color-secondary;
$color-spacer: rgba(255, 255, 255, .3);
$color-spacer-light: rgba(255, 255, 255, .12);
$color-input-underline: rgba(255, 255, 255, .12);
$color-input-underline-hover: rgba(255, 255, 255, .6);
$color-shadow: rgba(0, 0, 0, .2);
$color-border: rgba(0, 0, 0, .3);
$color-hightlight: rgba(255, 255, 255, .15);
$color-hover-cd: rgba(255, 255, 255, .1);
$color-hover-dc: .7;
$color-disabled: .6;
$color-font-link: lighten($color-main, 10%);
$color-main-medium: darken($color-main, 20%);
$color-main-light: darken($color-main, 35%);
$color-success-medium: darken($color-success, 20%);
$color-success-light: darken($color-success, 35%);
$color-notice-medium: darken($color-notice, 20%);
$color-notice-light: darken($color-notice, 35%);
$color-alert-medium: darken($color-alert, 20%);
$color-alert-light: darken($color-alert, 35%);
$color-primary-medium: lighten($color-primary, 20%);
$color-primary-light: lighten($color-primary, 35%);
$color-font-link-medium: lighten($color-font-link, 20%);
$color-font-link-light: lighten($color-font-link, 35%);
$color-main-medium: lighten($color-main, 20%);
$color-main-light: lighten($color-main, 35%);
$color-success-medium: lighten($color-success, 20%);
$color-success-light: lighten($color-success, 35%);
$color-notice-medium: lighten($color-notice, 20%);
$color-notice-light: lighten($color-notice, 35%);
$color-alert-medium: lighten($color-alert, 20%);
$color-alert-light: lighten($color-alert, 35%);
/**/
// Border
$border-thin: 1px solid $color-spacer;
$border-thin: 1px solid $color-border;
$border-thin-light: 1px solid $color-spacer-light;
$border: 2px solid $color-border;
$shadow: 0 2px 2px 0 rgba(0, 0, 0, .3);

View File

@ -23,9 +23,12 @@ vn-layout {
padding-right: 16px;
overflow: hidden;
& > .logo > img {
height: 32px;
display: block;
& > .logo {
outline: 0;
& > img {
height: 32px;
display: block;
}
}
& > .main-title {
font-size: 1.56rem;

View File

@ -65,11 +65,10 @@ ui-view > .vn-summary {
text-transform: uppercase;
font-size: 1.25rem;
line-height: 1;
padding: 7px;
padding-bottom: 4px; /* Bottom line-height fix */
padding: 7px 0;
padding-bottom: 5px; /* Bottom line-height fix */
font-weight: lighter;
background-color: $color-main-light;
border-bottom: 1px solid $color-main;
border-bottom: 2px solid $color-main;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@ -78,7 +77,7 @@ ui-view > .vn-summary {
display: block;
}
a {
color: $color-font;
color: $color-font-link;
}
}
h4 span:after {
@ -87,7 +86,7 @@ ui-view > .vn-summary {
position: absolute;
right: 5px;
text-transform: none;
color: $color-spacer
color: $color-font-link;
}
& > * {
margin: $spacing-sm;

View File

@ -60,7 +60,7 @@ vn-bg-title {
font-size: 1.25rem;
}
.totalBox {
border: 1px solid #CCC;
border: $border-thin;
text-align: right;
justify-content: center;
align-items: center;

View File

@ -118,5 +118,6 @@
"reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option",
"This item is not available": "This item is not available",
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}"
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}",
"The type of business must be filled in basic data": "The type of business must be filled in basic data"
}

View File

@ -133,7 +133,7 @@
"reserved": "reservado",
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
"Deny buy request":"Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}",
"Deny buy request": "Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
@ -193,6 +193,7 @@
"Client assignment has changed": "He cambiado el comercial ~*\"<{{previousWorkerName}}>\"*~ por *\"<{{currentWorkerName}}>\"* del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"None": "Ninguno",
"The contract was not active during the selected date": "El contrato no estaba activo durante la fecha seleccionada",
"Cannot add more than one '1/2 day vacation'": "No puedes añadir más de un 'Vacaciones 1/2 dia'",
"This document already exists on this ticket": "Este documento ya existe en el ticket",
"Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables",
"You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes",
@ -211,5 +212,7 @@
"Can't verify data unless the client has a business type": "No se puede verificar datos de un cliente que no tiene tipo de negocio",
"You don't have enough privileges to set this credit amount": "No tienes suficientes privilegios para establecer esta cantidad de crédito",
"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",
"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"
}

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

@ -5,40 +5,43 @@
model="model"
class="vn-w-sm">
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="user in model.data track by user.id"
ui-sref="account.card.summary(::{id: user.id})"
translate-attr="{title: 'View user'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::user.nickname}}</h6>
<vn-label-value
label="Id"
value="{{::user.id}}">
</vn-label-value>
<vn-label-value
label="User"
value="{{::user.name}}">
</vn-label-value>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
vn-click-stop="$ctrl.preview(user)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
<div class="vn-list separated">
<a
ng-repeat="user in model.data track by user.id"
ui-sref="account.card.summary(::{id: user.id})"
translate-attr="{title: 'View user'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::user.nickname}}</h6>
<vn-label-value
label="Id"
value="{{::user.id}}">
</vn-label-value>
<vn-label-value
label="User"
value="{{::user.name}}">
</vn-label-value>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
vn-click-stop="$ctrl.preview(user)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-card>
</vn-data-viewer>
<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

@ -6,26 +6,26 @@
model="model"
class="vn-w-sm">
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="role in model.data track by role.id"
ui-sref="account.role.card.summary(::{id: role.id})"
ui-sref-opts="{inherit: false}"
translate-attr="{title: 'View role'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::role.name}}</h6>
<div>{{::role.description}}</div>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
vn-click-stop="$ctrl.preview(role)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
<div class="vn-list separated">
<a
ng-repeat="role in model.data track by role.id"
ui-sref="account.role.card.summary(::{id: role.id})"
ui-sref-opts="{inherit: false}"
translate-attr="{title: 'View role'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::role.name}}</h6>
<div>{{::role.description}}</div>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
vn-click-stop="$ctrl.preview(role)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-card>
</vn-data-viewer>
<vn-popup vn-id="summary">
@ -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

@ -63,7 +63,7 @@ module.exports = Self => {
};
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -20,7 +20,7 @@ module.exports = Self => {
Self.removeFile = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -22,7 +22,7 @@ module.exports = Self => {
let userId = ctx.req.accessToken.userId;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -57,8 +57,14 @@ module.exports = Self => {
}
}, myOptions);
const landedPlusWeek = new Date(ticket.landed);
landedPlusWeek.setDate(landedPlusWeek.getDate() + 7);
const isClaimable = landedPlusWeek >= new Date();
if (ticket.isDeleted)
throw new UserError(`You can't create a claim for a removed ticket`);
if (!isClaimable)
throw new UserError(`You can't create a claim from a ticket delivered more than seven days ago`);
const newClaim = await Self.create({
ticketFk: ticketId,

View File

@ -24,7 +24,7 @@ module.exports = Self => {
const resolvedState = 3;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -1,31 +1,40 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('Claim createFromSales()', () => {
const ticketId = 2;
const ticketId = 16;
const newSale = [{
id: 3,
instance: 0,
quantity: 10
}];
const ctx = {
req: {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
}
const activeCtx = {
accessToken: {userId: 1},
headers: {origin: 'localhost:5000'},
__: () => {}
};
const ctx = {
req: activeCtx
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create a new claim', async() => {
const tx = await app.models.Claim.beginTransaction({});
const tx = await models.Claim.beginTransaction({});
try {
const options = {transaction: tx};
const claim = await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
const claim = await models.Claim.createFromSales(ctx, ticketId, newSale, options);
expect(claim.ticketFk).toEqual(ticketId);
let claimBeginning = await app.models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
let claimBeginning = await models.ClaimBeginning.findOne({where: {claimFk: claim.id}}, options);
expect(claimBeginning.saleFk).toEqual(newSale[0].id);
expect(claimBeginning.quantity).toEqual(newSale[0].quantity);
@ -37,17 +46,42 @@ describe('Claim createFromSales()', () => {
}
});
it('should not be able to create a claim if exists that sale', async() => {
const tx = await app.models.Claim.beginTransaction({});
it('should not be able to create a claim for a ticket delivered more than seven days ago', async() => {
const tx = await models.Claim.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
const todayMinusEightDays = new Date();
todayMinusEightDays.setDate(todayMinusEightDays.getDate() - 8);
await app.models.Claim.createFromSales(ctx, ticketId, newSale, options);
const ticket = await models.Ticket.findById(ticketId, options);
await ticket.updateAttribute('landed', todayMinusEightDays, options);
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
await tx.rollback();
} catch (e) {
error = e;
await tx.rollback();
}
expect(error.toString()).toContain(`You can't create a claim from a ticket delivered more than seven days ago`);
});
it('should not be able to create a claim if exists that sale', async() => {
const tx = await models.Claim.beginTransaction({});
let error;
try {
const options = {transaction: tx};
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
await models.Claim.createFromSales(ctx, ticketId, newSale, options);
await tx.rollback();
} catch (e) {

View File

@ -44,7 +44,7 @@ module.exports = Self => {
const userId = ctx.req.accessToken.userId;
const args = ctx.args;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -30,7 +30,7 @@ module.exports = Self => {
Self.updateClaimAction = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -54,7 +54,7 @@ module.exports = Self => {
Self.uploadFile = async(ctx, id, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -25,7 +25,7 @@ module.exports = function(Self) {
Self.canBeInvoiced = async(id, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -55,6 +55,15 @@ module.exports = Self => {
with: /^[\w|.|-]+@[\w|-]+(\.[\w|-]+)*(,[\w|.|-]+@[\w|-]+(\.[\w|-]+)*)*$/
});
Self.validate('businessTypeFk', hasBusinessType, {
message: `The type of business must be filled in basic data`
});
function hasBusinessType(err) {
if (!this.businessTypeFk)
err();
}
Self.validatesLengthOf('postcode', {
allowNull: true,
allowBlank: true,
@ -129,7 +138,8 @@ module.exports = Self => {
function hasIban(err, done) {
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequiredForClients && !this.iban)
const isMissingIban = instance && instance.isIbanRequiredForClients && !this.iban;
if (isMissingIban)
err();
done();
});

View File

@ -25,10 +25,10 @@
"outstandingDebt": {
"type": "Number"
},
"ibanRequiredForClients": {
"isIbanRequiredForClients": {
"type": "boolean"
},
"ibanRequiredForSuppliers": {
"isIbanRequiredForSuppliers": {
"type": "boolean"
}
}

View File

@ -19,7 +19,7 @@
vn-acl="salesAssistant"
ng-model="$ctrl.client.payMethodFk"
data="paymethods"
fields="['ibanRequiredForClients']"
fields="['isIbanRequiredForClients']"
initial-data="$ctrl.client.payMethod">
</vn-autocomplete>
<vn-input-number

View File

@ -5,7 +5,7 @@
limit="20"
user-params="::$ctrl.filterParams"
data="sales"
order="itemTypeFk, itemName, itemSize">
order="itemTypeFk, itemName, itemSize, description">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
@ -42,7 +42,7 @@
<vn-th field="itemFk" number>Item</vn-th>
<vn-th field="ticketFk" number>Ticket</vn-th>
<vn-th field="shipped" expand>Fecha</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="description" expand>Description</vn-th>
<vn-th field="quantity" number>Quantity</vn-th>
</vn-tr>
</vn-thead>

View File

@ -7,4 +7,5 @@ Create and edit: Crear y editar
You can save multiple emails: >-
Puede guardar varios correos electrónicos encadenándolos mediante comas
sin espacios, ejemplo: user@dominio.com, user2@dominio.com siendo el primer
correo electrónico el principal
correo electrónico el principal
The type of business must be filled in basic data: El tipo de negocio debe estar rellenado en datos básicos

View File

@ -17,8 +17,8 @@
<slot-body>
<div class="attributes">
<vn-label-value
label="Phone"
value="{{$ctrl.client.phone | phone}}">
label="Pay method"
value="{{$ctrl.client.payMethod.name}}">
</vn-label-value>
<vn-label-value
label="Credit"

View File

@ -3,4 +3,5 @@ View consumer report: Ver informe de consumo
From date: Fecha desde
To date: Fecha hasta
Go to user: Ir al usuario
Client invoices list: Listado de facturas del cliente
Client invoices list: Listado de facturas del cliente
Pay method: Forma de pago

View File

@ -68,7 +68,7 @@ module.exports = Self => {
Self.addBuy = async(ctx, options) => {
const conn = Self.dataSource.connector;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.deleteBuys = async(ctx, options) => {
const models = Self.app.models;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -32,7 +32,7 @@ module.exports = Self => {
Self.editLatestBuys = async(field, newValue, lines, options) => {
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -108,7 +108,7 @@ module.exports = Self => {
});
Self.filter = async(ctx, filter, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -29,7 +29,7 @@ module.exports = Self => {
Self.getBuys = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.getEntry = async(id, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -26,7 +26,7 @@ module.exports = Self => {
Self.importBuysPreview = async(id, buys, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -39,6 +39,11 @@ module.exports = Self => {
type: 'integer',
description: 'The buyer of the item',
},
{
arg: 'supplierFk',
type: 'integer',
description: 'The supplier of the item',
},
{
arg: 'active',
type: 'boolean',
@ -49,6 +54,11 @@ module.exports = Self => {
type: 'boolean',
description: 'Whether the item is or not visible',
},
{
arg: 'floramondo',
type: 'boolean',
description: 'Whether the item is or not floramondo',
},
{
arg: 'typeFk',
type: 'integer',
@ -63,6 +73,16 @@ module.exports = Self => {
arg: 'packingOut',
type: 'integer',
description: 'the packingOut',
},
{
arg: 'from',
type: 'date',
description: `The from date filter`
},
{
arg: 'to',
type: 'date',
description: `The to date filter`
}
],
returns: {
@ -76,7 +96,7 @@ module.exports = Self => {
});
Self.latestBuysFilter = async(ctx, filter, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
@ -99,10 +119,18 @@ module.exports = Self => {
return {'ic.id': value};
case 'salesPersonFk':
return {'it.workerFk': value};
case 'supplierFk':
return {'s.id': value};
case 'code':
return {'it.code': value};
case 'active':
return {'i.isActive': value};
case 'floramondo':
return {'i.isFloramondo': value};
case 'from':
return {'lb.landing': {gte: value}};
case 'to':
return {'lb.landing': {lte: value}};
case 'visible':
if (value)
return {'v.visible': {gt: 0}};
@ -162,7 +190,8 @@ module.exports = Self => {
b.price3,
b.ektFk,
b.weight,
b.packageFk
b.packageFk,
lb.landing
FROM cache.last_buy lb
LEFT JOIN cache.visible v ON v.item_id = lb.item_id
AND v.calc_id = @calc_id
@ -172,7 +201,9 @@ module.exports = Self => {
LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
LEFT JOIN itemType t ON t.id = i.typeFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN origin ori ON ori.id = i.originFk`
LEFT JOIN origin ori ON ori.id = i.originFk
LEFT JOIN entry e ON e.id = b.entryFk AND e.created >= DATE_SUB(CURDATE(),INTERVAL 1 YEAR)
LEFT JOIN supplier s ON s.id = e.supplierFk`
);
if (ctx.args.tags) {

View File

@ -113,6 +113,30 @@ describe('Buy latests buys filter()', () => {
expect(results.length).toBe(0);
});
it('should return results matching "floramondo"', async() => {
let ctx = {
args: {
floramondo: true
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(1);
});
it('should return results matching "not floramondo"', async() => {
let ctx = {
args: {
floramondo: false
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(5);
});
it('should return results matching "salesPersonFk"', async() => {
let ctx = {
args: {
@ -136,5 +160,36 @@ describe('Buy latests buys filter()', () => {
expect(results.length).toBe(1);
});
it('should return results matching "supplierFk"', async() => {
let ctx = {
args: {
supplierFk: 1
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(2);
});
it('should return results matching "from" and "to"', async() => {
const from = new Date();
from.setHours(0, 0, 0, 0);
const to = new Date();
to.setHours(23, 59, 59, 999);
let ctx = {
args: {
from: from,
to: to
}
};
let results = await app.models.Buy.latestBuysFilter(ctx);
expect(results.length).toBe(2);
});
});

View File

@ -15,16 +15,16 @@ vn-entry-buy-index vn-card {
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-marginal;
border-right: 1px solid $color-marginal;
border-left: 1px solid $color-spacer;
border-right: 1px solid $color-spacer;
}
tbody tr:nth-child(2) {
border-bottom: 1px solid $color-marginal;
border-bottom: 1px solid $color-spacer;
}
tbody{
border-bottom: 1px solid $color-marginal;
border-bottom: 1px solid $color-spacer;
}
tbody:last-child {

View File

@ -46,6 +46,29 @@
where="{role: {inq: ['logistic', 'buyer']}}"
label="Buyer">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Supplier"
ng-model="filter.supplierFk"
url="Suppliers"
fields="['name','nickname']"
search-function="{or: [{nickname: {like: '%'+ $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
show-field="name"
value-field="id">
<tpl-item>{{name}}: {{nickname}}</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-date-picker
vn-one
label="From"
ng-model="filter.from">
</vn-date-picker>
<vn-date-picker
vn-one
label="To"
ng-model="filter.to">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-check
@ -58,6 +81,11 @@
ng-model="filter.visible"
triple-state="true">
</vn-check>
<vn-check
label="Is floramondo"
ng-model="filter.floramondo"
triple-state="true">
</vn-check>
</vn-horizontal>
<vn-horizontal class="vn-pt-sm">
<vn-one class="text-subtitle1" translate>
@ -81,18 +109,21 @@
on-change="itemTag.value = null">
</vn-autocomplete>
<vn-textfield
ng-show="tag.selection.isFree !== false"
ng-show="tag.selection.isFree || tag.selection.isFree == undefined"
vn-id="text"
label="Value"
ng-model="itemTag.value">
</vn-textfield>
<vn-autocomplete
<vn-autocomplete
vn-one
ng-show="tag.selection.isFree === false"
url="{{$ctrl.getSourceTable(tag.selection)}}"
url="{{'Tags/' + itemTag.tagFk + '/filterValue'}}"
search-function="{value: $search}"
label="Value"
ng-model="itemTag.value"
show-field="name"
value-field="name">
show-field="value"
value-field="value"
rule>
</vn-autocomplete>
<vn-icon-button
vn-none

View File

@ -55,21 +55,9 @@ class Controller extends SearchPanel {
this.$.filter = value;
}
getSourceTable(selection) {
if (!selection || selection.isFree === true)
return null;
if (selection.sourceTable) {
return ''
+ selection.sourceTable.charAt(0).toUpperCase()
+ selection.sourceTable.substring(1) + 's';
} else if (selection.sourceTable == null)
return `ItemTags/filterItemTags/${selection.id}`;
}
removeField(index, field) {
this.fieldFilters.splice(index, 1);
this.$.filter[field] = undefined;
delete this.$.filter[field];
}
}

View File

@ -12,33 +12,48 @@ describe('Entry', () => {
controller = $componentController('vnLatestBuysSearchPanel', {$element});
}));
describe('getSourceTable()', () => {
it(`should return null if there's no selection`, () => {
let selection = null;
let result = controller.getSourceTable(selection);
describe('filter() setter', () => {
it(`should set the tags property to the scope filter with an empty array`, () => {
const expectedFilter = {
tags: [{}]
};
controller.filter = null;
expect(result).toBeNull();
expect(controller.filter).toEqual(expectedFilter);
});
it(`should return null if there's a selection but its isFree property is truthy`, () => {
let selection = {isFree: true};
let result = controller.getSourceTable(selection);
it(`should set the tags property to the scope filter with an array of tags`, () => {
const expectedFilter = {
description: 'My item',
tags: [{}]
};
const expectedFieldFilter = [{
info: {
label: 'description',
name: 'description',
type: null
},
name: 'description',
value: 'My item'
}];
controller.filter = {
description: 'My item'
};
expect(result).toBeNull();
expect(controller.filter).toEqual(expectedFilter);
expect(controller.fieldFilters).toEqual(expectedFieldFilter);
});
});
it(`should return the formated sourceTable concatenated to a path`, () => {
let selection = {sourceTable: 'hello guy'};
let result = controller.getSourceTable(selection);
describe('removeField()', () => {
it(`should remove the description property from the fieldFilters and from the scope filter`, () => {
const expectedFilter = {tags: [{}]};
controller.filter = {description: 'My item'};
expect(result).toEqual('Hello guys');
});
controller.removeField(0, 'description');
it(`should return a path if there's no sourceTable and the selection has an id`, () => {
let selection = {id: 99};
let result = controller.getSourceTable(selection);
expect(result).toEqual(`ItemTags/filterItemTags/${selection.id}`);
expect(controller.filter).toEqual(expectedFilter);
expect(controller.fieldFilters).toEqual([]);
});
});
});

View File

@ -112,6 +112,9 @@
<th field="packingOut">
<span translate>Package out</span>
</th>
<th field="landing">
<span translate>Landing</span>
</th>
</tr>
</thead>
<tbody>
@ -207,6 +210,7 @@
<td>{{::buy.weight}}</td>
<td>{{::buy.packageFk}}</td>
<td>{{::buy.packingOut}}</td>
<td>{{::buy.landing | date: 'dd/MM/yyyy'}}</td>
</tr>
</tbody>
</table>

View File

@ -61,6 +61,10 @@ export default class Controller extends Section {
field: 'isIgnored',
searchable: false
},
{
field: 'landing',
searchable: false
}
]
};
}
@ -76,7 +80,8 @@ export default class Controller extends Section {
{field: 'description', displayName: this.$t('Description')},
{field: 'size', displayName: this.$t('Size')},
{field: 'density', displayName: this.$t('Density')},
{field: 'packingOut', displayName: this.$t('PackingOut')}
{field: 'packingOut', displayName: this.$t('PackingOut')},
{field: 'landing', displayName: this.$t('Landing')}
];
return this._columns;
@ -112,6 +117,8 @@ export default class Controller extends Section {
return {'intr.description': value};
case 'origin':
return {'ori.code': value};
case 'landing':
return {[`lb.${param}`]: value};
case 'packing':
case 'grouping':
case 'quantity':

View File

@ -4,6 +4,8 @@ Freight value: Porte
Commission value: Comisión
Package value: Embalaje
Is ignored: Ignorado
Is visible: Visible
Is floramondo: Floramondo
Grouping price: Precio grouping
Packing price: Precio packing
Min price: Precio min

View File

@ -2,7 +2,8 @@
vn-id="model"
url="Entries/filter"
limit="20"
auto-load="true">
auto-load="true"
order="landed DESC, id DESC">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar

View File

@ -7,7 +7,7 @@
"menus": {
"main": [
{"state": "entry.index", "icon": "icon-entry"},
{"state": "entry.latestBuys", "icon": "contact_support"}
{"state": "entry.latestBuys", "icon": "icon-lastBuy"}
],
"card": [
{"state": "entry.card.basicData", "icon": "settings"},

View File

@ -9,13 +9,13 @@ vn-entry-summary .summary {
}
tbody tr:nth-child(1) {
border-top: 1px solid $color-marginal;
border-top: $border-thin;
}
tbody tr:nth-child(1),
tbody tr:nth-child(2) {
border-left: 1px solid $color-marginal;
border-right: 1px solid $color-marginal
border-left: $border-thin;
border-right: $border-thin
}
tbody tr:nth-child(3) {

View File

@ -78,7 +78,7 @@ module.exports = Self => {
{
arg: 'isBooked',
type: 'boolean',
description: 'Whether the the invoice is booked or not',
description: 'Whether the invoice is booked or not',
},
],
returns: {
@ -95,7 +95,7 @@ module.exports = Self => {
const conn = Self.dataSource.connector;
const args = ctx.args;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -20,7 +20,7 @@ module.exports = Self => {
});
Self.summary = async(id, options) => {
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -23,7 +23,7 @@ module.exports = Self => {
Self.book = async(ref, options) => {
const models = Self.app.models;
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -35,7 +35,7 @@ module.exports = Self => {
throw new UserError(`Action not allowed on the test environment`);
let tx;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -35,7 +35,7 @@ module.exports = Self => {
{
arg: 'hasPdf',
type: 'boolean',
description: 'Whether the the invoiceOut has PDF or not',
description: 'Whether the invoiceOut has PDF or not',
http: {source: 'query'}
},
{
@ -87,7 +87,7 @@ module.exports = Self => {
Self.filter = async(ctx, filter, options) => {
const conn = Self.dataSource.connector;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -29,7 +29,7 @@ module.exports = Self => {
Self.getTickets = async(id, filter, options) => {
const models = Self.app.models;
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -21,7 +21,7 @@ module.exports = Self => {
Self.summary = async(id, options) => {
let summary = {};
let myOptions = {};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);

View File

@ -0,0 +1,44 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethod('activeBuyers', {
description: 'Returns a list of buyers for the given item type',
accepts: [{
arg: 'filter',
type: 'object',
description: `Filter defining where, order, offset, and limit - must be a JSON-encoded string`
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/activeBuyers`,
verb: 'GET'
}
});
Self.activeBuyers = async(filter, options) => {
const conn = Self.dataSource.connector;
const where = {isActive: true};
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
filter = mergeFilters(filter, {where});
let stmt = new ParameterizedSQL(
`SELECT DISTINCT w.id workerFk, w.firstName, w.lastName, u.name, u.nickname
FROM worker w
JOIN itemType it ON it.workerFk = w.id
JOIN account.user u ON u.id = w.id
JOIN item i ON i.typeFk = it.id`,
null, myOptions);
stmt.merge(conn.makeSuffix(filter));
return conn.executeStmt(stmt);
};
};

View File

@ -41,13 +41,18 @@ module.exports = Self => {
{
arg: 'isActive',
type: 'boolean',
description: 'Whether the the item is or not active',
description: 'Whether the item is or not active',
},
{
arg: 'salesPersonFk',
arg: 'buyerFk',
type: 'integer',
description: 'The buyer of the item',
},
{
arg: 'supplierFk',
type: 'integer',
description: 'The supplier of the item',
},
{
arg: 'description',
type: 'string',
@ -62,6 +67,11 @@ module.exports = Self => {
arg: 'landed',
type: 'date',
description: 'The item last buy landed date',
},
{
arg: 'isFloramondo',
type: 'boolean',
description: 'Whether the the item is or not floramondo',
}
],
returns: {
@ -104,17 +114,18 @@ module.exports = Self => {
? {or: [{'i.id': value}, codeWhere]}
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]};
case 'id':
return {'i.id': value};
case 'isActive':
return {'i.isActive': value};
case 'typeFk':
case 'isFloramondo':
return {[`i.${param}`]: value};
case 'multiplier':
return {'i.stemMultiplier': value};
case 'typeFk':
return {'i.typeFk': value};
case 'categoryFk':
return {'ic.id': value};
case 'buyerFk':
return {'it.workerFk': value};
case 'supplierFk':
return {'s.id': value};
case 'origin':
return {'ori.code': value};
case 'intrastat':
@ -146,6 +157,7 @@ module.exports = Self => {
i.density,
i.stemMultiplier,
i.typeFk,
i.isFloramondo,
it.name AS typeName,
it.workerFk AS buyerFk,
u.name AS userName,
@ -164,7 +176,9 @@ module.exports = Self => {
LEFT JOIN producer pr ON pr.id = i.producerFk
LEFT JOIN origin ori ON ori.id = i.originFk
LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = it.warehouseFk
LEFT JOIN vn.buy b ON b.id = lb.buy_id`
LEFT JOIN buy b ON b.id = lb.buy_id
LEFT JOIN entry e ON e.id = b.entryFk
LEFT JOIN supplier s ON s.id = e.supplierFk`
);
if (ctx.args.tags) {

View File

@ -0,0 +1,24 @@
const models = require('vn-loopback/server/server').models;
describe('Worker activeBuyers', () => {
it('should return the buyers in itemType as result', async() => {
const tx = await models.Item.beginTransaction({});
try {
const options = {transaction: tx};
const filter = {};
const result = await models.Item.activeBuyers(filter, options);
const firstWorker = result[0];
const secondWorker = result[1];
expect(result.length).toEqual(2);
expect(firstWorker.nickname).toEqual('logisticBossNick');
expect(secondWorker.nickname).toEqual('buyerNick');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -60,4 +60,63 @@ describe('item filter()', () => {
throw e;
}
});
it('should return 2 result filtering by isFloramondo checkbox', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const filter = {};
const ctx = {args: {filter: filter, isFloramondo: true}};
const result = await models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(3);
expect(result[0].id).toEqual(9);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return 2 result filtering by buyerFk', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const filter = {};
const ctx = {args: {filter: filter, buyerFk: 16}};
const result = await models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(2);
expect(result[0].id).toEqual(16);
expect(result[1].id).toEqual(71);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return 2 result filtering by supplierFk', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const filter = {};
const ctx = {args: {filter: filter, supplierFk: 1}};
const result = await models.Item.filter(ctx, filter, options);
expect(result.length).toEqual(2);
expect(result[0].id).toEqual(1);
expect(result[1].id).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -36,4 +36,36 @@ describe('item getBalance()', () => {
throw e;
}
});
it('should show the claimFk only on the claimed item', async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const firstFilter = {
where: {
itemFk: 1,
warehouseFk: 1
}
};
const secondFilter = {
where: {
itemFk: 2,
warehouseFk: 1
}
};
const firstItemBalance = await models.Item.getBalance(firstFilter, options);
const secondItemBalance = await models.Item.getBalance(secondFilter, options);
expect(firstItemBalance[9].claimFk).toEqual(null);
expect(secondItemBalance[5].claimFk).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -47,9 +47,14 @@ module.exports = Self => {
const where = filter.where;
if (where && where.value) {
stmt.merge(conn.makeWhere({value: {like: `%${where.value}%`}}));
stmt.merge(`
ORDER BY value LIKE '${where.value}' DESC,
value LIKE '${where.value}%' DESC`);
const orderStmt = new ParameterizedSQL(
`ORDER BY value LIKE ? DESC,
value LIKE ? DESC`, [
where.value,
`${where.value}%`
]);
ParameterizedSQL.append(stmt, orderStmt);
}
stmt.merge(conn.makeLimit(filter));

View File

@ -0,0 +1,26 @@
{
"name": "ItemPackingType",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemPackingType"
}
},
"properties": {
"code": {
"type": "string",
"id": true
},
"description": {
"type": "string"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -14,6 +14,7 @@ module.exports = Self => {
require('../methods/item/getWasteByWorker')(Self);
require('../methods/item/getWasteByItem')(Self);
require('../methods/item/createIntrastat')(Self);
require('../methods/item/activeBuyers')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});

View File

@ -140,6 +140,9 @@
},
"isFragile": {
"type": "boolean"
},
"isFloramondo": {
"type": "boolean"
}
},
"relations": {

View File

@ -1,16 +1,19 @@
<vn-crud-model
vn-id="model"
url="Items/filter"
user-params="::$ctrl.filterParams"
limit="12"
order="isActive DESC, name, id"
data="items">
data="items"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-item-search-panel"
info="Search items by id, name or barcode"
suggested-filter="{isActive: true}"
suggested-filter="$ctrl.filterParams"
filter="$ctrl.filterParams"
model="model">
</vn-searchbar>
</vn-portal>

View File

@ -1,7 +1,15 @@
import ngModule from '../module';
import ModuleMain from 'salix/components/module-main';
export default class Items extends ModuleMain {
constructor($element, $) {
super($element, $);
export default class Items extends ModuleMain {}
this.filterParams = {
isActive: true,
isFloramondo: false
};
}
}
ngModule.vnComponent('vnItems', {
controller: Items,

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