Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2563-supplier_fiscalData
This commit is contained in:
commit
15b7280222
|
@ -9,7 +9,7 @@
|
|||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"required": true
|
||||
"id": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
|
@ -56,7 +56,8 @@
|
|||
"roles": {
|
||||
"type": "hasMany",
|
||||
"model": "RoleRole",
|
||||
"foreignKey": "role"
|
||||
"foreignKey": "role",
|
||||
"primaryKey": "roleFk"
|
||||
},
|
||||
"emailUser": {
|
||||
"type": "hasOne",
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
ALTER TABLE `vn`.`observationType`
|
||||
ADD COLUMN `code` VARCHAR(45) NOT NULL AFTER `description`;
|
||||
|
||||
UPDATE `vn`.`observationType` SET `code` = 'itemPicker' WHERE (`id` = '1');
|
||||
UPDATE `vn`.`observationType` SET `code` = 'packager' WHERE (`id` = '2');
|
||||
UPDATE `vn`.`observationType` SET `code` = 'salesPerson' WHERE (`id` = '4');
|
||||
UPDATE `vn`.`observationType` SET `code` = 'administrative' WHERE (`id` = '5');
|
||||
UPDATE `vn`.`observationType` SET `code` = 'weight' WHERE (`id` = '6');
|
||||
UPDATE `vn`.`observationType` SET `code` = 'delivery' WHERE (`id` = '3');
|
|
@ -0,0 +1,7 @@
|
|||
ALTER TABLE `account`.`roleRole`
|
||||
ADD `id` INT UNSIGNED NOT NULL AUTO_INCREMENT FIRST,
|
||||
ADD PRIMARY KEY (`id`);
|
||||
|
||||
UPDATE `account`.`role` SET id = 100 WHERE `name` = 'root';
|
||||
|
||||
CALL account.role_sync;
|
|
@ -0,0 +1,25 @@
|
|||
DROP TRIGGER IF EXISTS `vn`.`ticket_afterUpdate`;
|
||||
|
||||
DELIMITER $$
|
||||
USE `vn`$$
|
||||
CREATE DEFINER=`root`@`%` TRIGGER `ticket_afterUpdate`
|
||||
AFTER UPDATE ON `ticket`
|
||||
FOR EACH ROW
|
||||
BEGIN
|
||||
IF !(NEW.id <=> OLD.id)
|
||||
OR !(NEW.warehouseFk <=> OLD.warehouseFk)
|
||||
OR !(NEW.shipped <=> OLD.shipped) THEN
|
||||
CALL stock.log_add('ticket', NEW.id, OLD.id);
|
||||
END IF;
|
||||
|
||||
IF NEW.clientFk = 2067 AND !(NEW.clientFk <=> OLD.clientFk) THEN
|
||||
-- Fallo que se insertan no se sabe como tickets en este cliente
|
||||
INSERT INTO vn.mail SET
|
||||
`sender` = 'jgallego@verdnatura.es',
|
||||
`replyTo` = 'jgallego@verdnatura.es',
|
||||
`subject` = 'Modificado ticket al cliente 2067',
|
||||
`body` = CONCAT(account.myUserGetName(), ' ha modificado el ticket ',
|
||||
NEW.id);
|
||||
END IF;
|
||||
END$$
|
||||
DELIMITER ;
|
|
@ -325,7 +325,7 @@ INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `pr
|
|||
(119, 'address 19', 'Somewhere in Alberic', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
|
||||
(120, 'address 20', 'Somewhere in Montortal', 'Silla', 46460, 1, 1111111111, 222222222, 1, 109, 2, NULL, NULL, 0, 0),
|
||||
(121, 'address 21', 'the bat cave', 'Silla', 46460, 1, 1111111111, 222222222, 1, 101, 2, NULL, NULL, 0, 0),
|
||||
(122, 'address 22', 'NY roofs', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0),
|
||||
(122, 'NY roofs', 'address 22', 'Silla', 46460, 1, 1111111111, 222222222, 1, 102, 2, NULL, NULL, 0, 0),
|
||||
(123, 'address 23', 'The phone box', 'Silla', 46460, 1, 1111111111, 222222222, 1, 103, 2, NULL, NULL, 0, 0),
|
||||
(124, 'address 24', 'Stark tower Silla', 'Silla', 46460, 1, 1111111111, 222222222, 1, 104, 2, NULL, NULL, 0, 0),
|
||||
(125, 'address 25', 'The plastic cell', 'Silla', 46460, 1, 1111111111, 222222222, 1, 105, 2, NULL, NULL, 0, 0),
|
||||
|
@ -400,18 +400,20 @@ INSERT INTO `vn`.`clientObservation`(`id`, `clientFk`, `workerFk`, `text`, `crea
|
|||
(9, 109, 18, 'HULK SMASH! ...', CURDATE()),
|
||||
(10, 110, 18, 'They say everyone is born a hero. But if you let it, life will push you over the line until you are the villain.', CURDATE());
|
||||
|
||||
INSERT INTO `vn`.`observationType`(`id`,`description`)
|
||||
INSERT INTO `vn`.`observationType`(`id`,`description`, `code`)
|
||||
VALUES
|
||||
(1,'observation one'),
|
||||
(2,'observation two'),
|
||||
(3,'observation three'),
|
||||
(4,'comercial');
|
||||
(1, 'observation one', 'observation one'),
|
||||
(2, 'observation two', 'observation two'),
|
||||
(3, 'observation three', 'observation three'),
|
||||
(4, 'comercial', 'salesPerson'),
|
||||
(5, 'delivery', 'delivery');
|
||||
|
||||
INSERT INTO `vn`.`addressObservation`(`id`,`addressFk`,`observationTypeFk`,`description`)
|
||||
VALUES
|
||||
(1, 121, 1, 'under the floor'),
|
||||
(2, 121, 2, 'wears leather and goes out at night'),
|
||||
(3, 121, 3, 'care with the dog');
|
||||
(3, 121, 3, 'care with the dog'),
|
||||
(5, 122, 5, 'Delivery after 10am');
|
||||
|
||||
INSERT INTO `vn`.`creditClassification`(`id`, `client`, `dateStart`, `dateEnd`)
|
||||
VALUES
|
||||
|
@ -605,7 +607,8 @@ INSERT INTO `vn`.`ticketObservation`(`id`, `ticketFk`, `observationTypeFk`, `des
|
|||
(8, 23, 2, 'wears leather and goes out at night'),
|
||||
(9, 23, 3, 'care with the dog'),
|
||||
(10, 23, 4, 'Reclama ticket: 8'),
|
||||
(11, 24, 4, 'Reclama ticket: 7');
|
||||
(11, 24, 4, 'Reclama ticket: 7'),
|
||||
(12, 11, 5, 'Delivery after 10am');
|
||||
|
||||
-- FIX for state hours on local, inter_afterInsert
|
||||
UPDATE vncontrol.inter SET odbc_date = DATE_ADD(CURDATE(), INTERVAL -10 SECOND);
|
||||
|
|
|
@ -123,19 +123,19 @@ export default {
|
|||
streetAddress: 'vn-textfield[ng-model="$ctrl.address.street"]',
|
||||
postcode: 'vn-datalist[ng-model="$ctrl.address.postalCode"]',
|
||||
city: 'vn-datalist[ng-model="$ctrl.address.city"]',
|
||||
province: 'vn-autocomplete[ng-model="$ctrl.address.provinceId"]',
|
||||
agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeId"]',
|
||||
province: 'vn-autocomplete[ng-model="$ctrl.address.provinceFk"]',
|
||||
agency: 'vn-autocomplete[ng-model="$ctrl.address.agencyModeFk"]',
|
||||
phone: 'vn-textfield[ng-model="$ctrl.address.phone"]',
|
||||
mobileInput: 'vn-textfield[ng-model="$ctrl.address.mobile"]',
|
||||
defaultAddress: 'vn-client-address-index div:nth-child(1) div[name="street"]',
|
||||
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsId"]',
|
||||
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentId"] vn-icon-button[icon="add_circle"]',
|
||||
incoterms: 'vn-autocomplete[ng-model="$ctrl.address.incotermsFk"]',
|
||||
addNewCustomsAgent: 'vn-client-address-create vn-autocomplete[ng-model="$ctrl.address.customsAgentFk"] vn-icon-button[icon="add_circle"]',
|
||||
newCustomsAgentFiscalID: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.nif"]',
|
||||
newCustomsAgentFiscalName: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.fiscalName"]',
|
||||
newCustomsAgentStreet: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.street"]',
|
||||
newCustomsAgentPhone: 'vn-textfield[ng-model="$ctrl.newCustomsAgent.phone"]',
|
||||
saveNewCustomsAgentButton: 'button[response="accept"]',
|
||||
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentId"]',
|
||||
customsAgent: 'vn-autocomplete[ng-model="$ctrl.address.customsAgentFk"]',
|
||||
secondMakeDefaultStar: 'vn-client-address-index vn-card div:nth-child(2) vn-icon-button[icon="star_border"]',
|
||||
firstEditAddress: 'vn-client-address-index div:nth-child(1) > a',
|
||||
secondEditAddress: 'vn-client-address-index div:nth-child(2) > a',
|
||||
|
|
|
@ -24,7 +24,7 @@ describe('Worker calendar path', () => {
|
|||
expect(result).toContain(' 5 ');
|
||||
});
|
||||
|
||||
it('should set two days as holidays on the calendar', async() => {
|
||||
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
|
||||
await page.waitToClick(selectors.workerCalendar.holidays);
|
||||
await page.waitFor(reasonableTimeBetweenClicks);
|
||||
await page.waitToClick(selectors.workerCalendar.januaryThirtyFirst);
|
||||
|
@ -50,9 +50,8 @@ describe('Worker calendar path', () => {
|
|||
await page.waitToClick(selectors.workerCalendar.halfFurlough);
|
||||
await page.waitFor(reasonableTimeBetweenClicks);
|
||||
await page.waitToClick(selectors.workerCalendar.mayEighth);
|
||||
});
|
||||
await page.waitFor(reasonableTimeBetweenClicks);
|
||||
|
||||
it('should check the total holidays increased by 1.5', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText');
|
||||
|
||||
expect(result).toContain(' 6.5 ');
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -154,7 +154,8 @@ module.exports = function(Self) {
|
|||
const showFieldNames = [
|
||||
'name',
|
||||
'description',
|
||||
'code'
|
||||
'code',
|
||||
'nickname'
|
||||
];
|
||||
for (field of showFieldNames) {
|
||||
const propField = properties && properties[field];
|
||||
|
|
|
@ -41,7 +41,7 @@ module.exports = Self => {
|
|||
attributes: ['dn'],
|
||||
filter: 'objectClass=posixGroup'
|
||||
};
|
||||
res = await client.search(ldapConfig.groupDn, opts);
|
||||
let res = await client.search(ldapConfig.groupDn, opts);
|
||||
|
||||
let reqs = [];
|
||||
await new Promise((resolve, reject) => {
|
||||
|
@ -62,31 +62,28 @@ module.exports = Self => {
|
|||
let roles = await $.Role.find({
|
||||
fields: ['id', 'name']
|
||||
});
|
||||
let roleRoles = await $.RoleRole.find({
|
||||
fields: ['role', 'inheritsFrom']
|
||||
});
|
||||
let roleMap = toMap(roleRoles, e => {
|
||||
return {key: e.inheritsFrom, val: e.role};
|
||||
});
|
||||
|
||||
let accounts = await $.UserAccount.find({
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
include: {
|
||||
relation: 'roles',
|
||||
scope: {
|
||||
fields: ['inheritsFrom']
|
||||
}
|
||||
}
|
||||
fields: ['name', 'roleFk'],
|
||||
where: {active: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let map = new Map();
|
||||
for (let account of accounts) {
|
||||
let user = account.user();
|
||||
for (let inherit of user.roles()) {
|
||||
let roleId = inherit.inheritsFrom;
|
||||
if (!map.has(roleId)) map.set(roleId, []);
|
||||
map.get(roleId).push(user.name);
|
||||
}
|
||||
}
|
||||
let accountMap = toMap(accounts, e => {
|
||||
let user = e.user();
|
||||
if (!user) return;
|
||||
return {key: user.roleFk, val: user.name};
|
||||
});
|
||||
|
||||
reqs = [];
|
||||
for (let role of roles) {
|
||||
|
@ -96,8 +93,14 @@ module.exports = Self => {
|
|||
gidNumber: accountConfig.idBase + role.id
|
||||
};
|
||||
|
||||
let memberUid = map.get(role.id);
|
||||
if (memberUid) newEntry.memberUid = memberUid;
|
||||
let memberUid = [];
|
||||
for (subrole of roleMap.get(role.id) || [])
|
||||
memberUid = memberUid.concat(accountMap.get(subrole) || []);
|
||||
|
||||
if (memberUid.length) {
|
||||
memberUid.sort((a, b) => a.localeCompare(b));
|
||||
newEntry.memberUid = memberUid;
|
||||
}
|
||||
|
||||
let dn = `cn=${role.name},${ldapConfig.groupDn}`;
|
||||
reqs.push(client.add(dn, newEntry));
|
||||
|
@ -107,8 +110,19 @@ module.exports = Self => {
|
|||
err = e;
|
||||
}
|
||||
|
||||
// FIXME: Cannot disconnect, hangs on undind() call
|
||||
// await client.unbind();
|
||||
await client.unbind();
|
||||
if (err) throw err;
|
||||
};
|
||||
};
|
||||
|
||||
function toMap(array, fn) {
|
||||
let map = new Map();
|
||||
for (let item of array) {
|
||||
let keyVal = fn(item);
|
||||
if (!keyVal) continue;
|
||||
let key = keyVal.key;
|
||||
if (!map.has(key)) map.set(key, []);
|
||||
map.get(key).push(keyVal.val);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
const SyncEngine = require('../../util/sync-engine');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('syncAll', {
|
||||
description: 'Synchronizes user database with LDAP and Samba',
|
||||
http: {
|
||||
path: `/syncAll`,
|
||||
verb: 'PATCH'
|
||||
}
|
||||
});
|
||||
|
||||
Self.syncAll = async function() {
|
||||
let $ = Self.app.models;
|
||||
|
||||
let se = new SyncEngine();
|
||||
await se.init($);
|
||||
|
||||
let usersToSync = await se.getUsers();
|
||||
usersToSync = Array.from(usersToSync.values())
|
||||
.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
for (let user of usersToSync) {
|
||||
try {
|
||||
console.log(`Synchronizing user '${user}'`);
|
||||
await se.sync(user);
|
||||
console.log(` -> '${user}' sinchronized`);
|
||||
} catch (err) {
|
||||
console.error(` -> '${user}' synchronization error:`, err.message);
|
||||
}
|
||||
}
|
||||
|
||||
await se.deinit();
|
||||
|
||||
await $.RoleInherit.sync();
|
||||
};
|
||||
};
|
|
@ -1,7 +1,5 @@
|
|||
const ldap = require('../../util/ldapjs-extra');
|
||||
const nthash = require('smbhash').nthash;
|
||||
const ssh = require('node-ssh');
|
||||
const crypto = require('crypto');
|
||||
|
||||
const SyncEngine = require('../../util/sync-engine');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('sync', {
|
||||
|
@ -19,7 +17,7 @@ module.exports = Self => {
|
|||
}
|
||||
],
|
||||
http: {
|
||||
path: `/sync`,
|
||||
path: `/:userName/sync`,
|
||||
verb: 'PATCH'
|
||||
}
|
||||
});
|
||||
|
@ -35,233 +33,19 @@ module.exports = Self => {
|
|||
|
||||
if (user && isSync) return;
|
||||
|
||||
let accountConfig;
|
||||
let mailConfig;
|
||||
let extraParams;
|
||||
let hasAccount = false;
|
||||
|
||||
if (user) {
|
||||
accountConfig = await $.AccountConfig.findOne({
|
||||
fields: ['homedir', 'shell', 'idBase']
|
||||
});
|
||||
mailConfig = await $.MailConfig.findOne({
|
||||
fields: ['domain']
|
||||
});
|
||||
|
||||
user = await $.Account.findById(user.id, {
|
||||
fields: [
|
||||
'id',
|
||||
'nickname',
|
||||
'email',
|
||||
'lang',
|
||||
'roleFk',
|
||||
'sync',
|
||||
'active',
|
||||
'created',
|
||||
'updated'
|
||||
],
|
||||
where: {name: userName},
|
||||
include: {
|
||||
relation: 'roles',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'inherits',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
extraParams = {
|
||||
corporateMail: `${userName}@${mailConfig.domain}`,
|
||||
uidNumber: accountConfig.idBase + user.id
|
||||
};
|
||||
|
||||
hasAccount = user.active
|
||||
&& await $.UserAccount.exists(user.id);
|
||||
}
|
||||
|
||||
if (user) {
|
||||
let bcryptPassword = $.User.hashPassword(password);
|
||||
|
||||
await $.Account.upsertWithWhere({id: user.id},
|
||||
{bcryptPassword}
|
||||
);
|
||||
await $.user.upsert({
|
||||
id: user.id,
|
||||
username: userName,
|
||||
password: bcryptPassword,
|
||||
email: user.email,
|
||||
created: user.created,
|
||||
updated: user.updated
|
||||
});
|
||||
}
|
||||
|
||||
// SIP
|
||||
|
||||
if (hasAccount) {
|
||||
await Self.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
||||
[user.id, password]
|
||||
);
|
||||
}
|
||||
|
||||
// LDAP
|
||||
|
||||
let ldapConfig = await $.LdapConfig.findOne({
|
||||
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
||||
});
|
||||
|
||||
if (ldapConfig) {
|
||||
let ldapClient = ldap.createClient({
|
||||
url: `ldap://${ldapConfig.host}:389`
|
||||
});
|
||||
|
||||
let ldapPassword = Buffer
|
||||
.from(ldapConfig.password, 'base64')
|
||||
.toString('ascii');
|
||||
await ldapClient.bind(ldapConfig.rdn, ldapPassword);
|
||||
|
||||
let err;
|
||||
try {
|
||||
// Deletes user
|
||||
let se = new SyncEngine();
|
||||
await se.init($);
|
||||
|
||||
try {
|
||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||
await ldapClient.del(dn);
|
||||
} catch (e) {
|
||||
if (e.name !== 'NoSuchObjectError') throw e;
|
||||
}
|
||||
|
||||
// Removes user from groups
|
||||
|
||||
let opts = {
|
||||
scope: 'sub',
|
||||
attributes: ['dn'],
|
||||
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
||||
};
|
||||
res = await ldapClient.search(ldapConfig.groupDn, opts);
|
||||
|
||||
let oldGroups = [];
|
||||
await new Promise((resolve, reject) => {
|
||||
res.on('error', reject);
|
||||
res.on('searchEntry', e => oldGroups.push(e.object));
|
||||
res.on('end', resolve);
|
||||
});
|
||||
|
||||
let reqs = [];
|
||||
for (oldGroup of oldGroups) {
|
||||
let change = new ldap.Change({
|
||||
operation: 'delete',
|
||||
modification: {memberUid: userName}
|
||||
});
|
||||
reqs.push(ldapClient.modify(oldGroup.dn, change));
|
||||
}
|
||||
await Promise.all(reqs);
|
||||
|
||||
if (hasAccount) {
|
||||
// Recreates user
|
||||
|
||||
let nameArgs = user.nickname.split(' ');
|
||||
let sshaPassword = crypto
|
||||
.createHash('sha1')
|
||||
.update(password)
|
||||
.digest('base64');
|
||||
|
||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||
let newEntry = {
|
||||
uid: userName,
|
||||
objectClass: [
|
||||
'inetOrgPerson',
|
||||
'posixAccount',
|
||||
'sambaSamAccount'
|
||||
],
|
||||
cn: user.nickname || userName,
|
||||
displayName: user.nickname,
|
||||
givenName: nameArgs[0],
|
||||
sn: nameArgs[1] || 'Empty',
|
||||
mail: extraParams.corporateMail,
|
||||
userPassword: `{SSHA}${sshaPassword}`,
|
||||
preferredLanguage: user.lang,
|
||||
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
||||
loginShell: accountConfig.shell,
|
||||
uidNumber: extraParams.uidNumber,
|
||||
gidNumber: accountConfig.idBase + user.roleFk,
|
||||
sambaSID: '-',
|
||||
sambaNTPassword: nthash(password)
|
||||
};
|
||||
await ldapClient.add(dn, newEntry);
|
||||
|
||||
// Adds user to groups
|
||||
|
||||
let reqs = [];
|
||||
for (let role of user.roles()) {
|
||||
let change = new ldap.Change({
|
||||
operation: 'add',
|
||||
modification: {memberUid: userName}
|
||||
});
|
||||
let roleName = role.inherits().name;
|
||||
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
|
||||
reqs.push(ldapClient.modify(dn, change));
|
||||
}
|
||||
await Promise.all(reqs);
|
||||
}
|
||||
await se.sync(userName, password, true);
|
||||
await $.UserSync.destroyById(userName);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
||||
// FIXME: Cannot disconnect, hangs on undind() call
|
||||
// await ldapClient.unbind();
|
||||
await se.deinit();
|
||||
if (err) throw err;
|
||||
}
|
||||
|
||||
// Samba
|
||||
|
||||
let sambaConfig = await $.SambaConfig.findOne({
|
||||
fields: ['host', 'sshUser', 'sshPass']
|
||||
});
|
||||
|
||||
if (sambaConfig) {
|
||||
let sshPassword = Buffer
|
||||
.from(sambaConfig.sshPass, 'base64')
|
||||
.toString('ascii');
|
||||
|
||||
let sshClient = new ssh.NodeSSH();
|
||||
await sshClient.connect({
|
||||
host: sambaConfig.host,
|
||||
username: sambaConfig.sshUser,
|
||||
password: sshPassword
|
||||
});
|
||||
|
||||
let commands;
|
||||
|
||||
if (hasAccount) {
|
||||
commands = [
|
||||
`samba-tool user create "${userName}" `
|
||||
+ `--uid-number=${extraParams.uidNumber} `
|
||||
+ `--mail-address="${extraParams.corporateMail}" `
|
||||
+ `--random-password`,
|
||||
`samba-tool user setexpiry "${userName}" `
|
||||
+ `--noexpiry`,
|
||||
`samba-tool user setpassword "${userName}" `
|
||||
+ `--newpassword="${password}"`,
|
||||
`mkhomedir_helper "${userName}" 0027`
|
||||
];
|
||||
} else {
|
||||
commands = [
|
||||
`samba-tool user delete "${userName}"`
|
||||
];
|
||||
}
|
||||
|
||||
for (let command of commands)
|
||||
await sshClient.execCommand(command);
|
||||
|
||||
await sshClient.dispose();
|
||||
}
|
||||
|
||||
// Mark as synchronized
|
||||
|
||||
await $.UserSync.destroyById(userName);
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -5,6 +5,9 @@
|
|||
"LdapConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Mail": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MailAlias": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
|
@ -34,8 +37,5 @@
|
|||
},
|
||||
"UserSync": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Mail": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
},
|
||||
"properties": {
|
||||
"role": {
|
||||
"id": {
|
||||
"id": true
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,4 +2,5 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/user-account/sync')(Self);
|
||||
require('../methods/user-account/sync-by-id')(Self);
|
||||
require('../methods/user-account/sync-all')(Self);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
/**
|
||||
* Base class for user synchronizators.
|
||||
*
|
||||
* @property {Array<Model>} $
|
||||
* @property {Object} accountConfig
|
||||
* @property {Object} mailConfig
|
||||
*/
|
||||
class SyncConnector {
|
||||
/**
|
||||
* Initalizes the connector.
|
||||
*/
|
||||
async init() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get users to synchronize.
|
||||
*
|
||||
* @param {Set} usersToSync Set where users are added
|
||||
*/
|
||||
async getUsers(usersToSync) {}
|
||||
|
||||
/**
|
||||
* Synchronizes a user.
|
||||
*
|
||||
* @param {Object} info User information
|
||||
* @param {String} userName The user name
|
||||
* @param {String} password Thepassword
|
||||
*/
|
||||
async sync(info, userName, password) {}
|
||||
|
||||
/**
|
||||
* Synchronizes user groups.
|
||||
*
|
||||
* @param {User} user Instace of user
|
||||
* @param {String} userName The user name
|
||||
*/
|
||||
async syncGroups(user, userName) {}
|
||||
|
||||
/**
|
||||
* Deinitalizes the connector.
|
||||
*/
|
||||
async deinit() {}
|
||||
}
|
||||
|
||||
SyncConnector.connectors = [];
|
||||
module.exports = SyncConnector;
|
|
@ -0,0 +1,57 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
|
||||
class SyncDb extends SyncConnector {
|
||||
async sync(info, userName, password) {
|
||||
let {$} = this;
|
||||
let {user} = info;
|
||||
|
||||
if (user && user.active) {
|
||||
let bcryptPassword = password
|
||||
? $.User.hashPassword(password)
|
||||
: user.bcryptPassword;
|
||||
|
||||
await $.Account.upsertWithWhere({id: user.id},
|
||||
{bcryptPassword}
|
||||
);
|
||||
|
||||
let dbUser = {
|
||||
id: user.id,
|
||||
username: userName,
|
||||
email: user.email,
|
||||
created: user.created,
|
||||
updated: user.updated
|
||||
};
|
||||
if (bcryptPassword)
|
||||
dbUser.password = bcryptPassword;
|
||||
|
||||
if (await $.user.exists(user.id))
|
||||
await $.user.replaceById(user.id, dbUser);
|
||||
else
|
||||
await $.user.create(dbUser);
|
||||
} else
|
||||
await $.user.destroyAll({username: userName});
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let accounts = await this.$.UserAccount.find({
|
||||
fields: ['id'],
|
||||
include: {
|
||||
relation: 'user',
|
||||
scope: {
|
||||
fields: ['name'],
|
||||
where: {active: true}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
for (let account of accounts) {
|
||||
let user = account.user();
|
||||
if (!user) continue;
|
||||
usersToSync.add(user.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncDb);
|
||||
module.exports = SyncDb;
|
|
@ -0,0 +1,127 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
require('./sync-db');
|
||||
require('./sync-sip');
|
||||
require('./sync-ldap');
|
||||
require('./sync-samba');
|
||||
|
||||
module.exports = class SyncEngine {
|
||||
async init($) {
|
||||
let accountConfig = await $.AccountConfig.findOne({
|
||||
fields: ['homedir', 'shell', 'idBase']
|
||||
});
|
||||
let mailConfig = await $.MailConfig.findOne({
|
||||
fields: ['domain']
|
||||
});
|
||||
|
||||
let connectors = [];
|
||||
|
||||
for (let ConnectorClass of SyncConnector.connectors) {
|
||||
let connector = new ConnectorClass();
|
||||
Object.assign(connector, {
|
||||
se: this,
|
||||
$
|
||||
});
|
||||
if (!await connector.init()) continue;
|
||||
connectors.push(connector);
|
||||
}
|
||||
|
||||
Object.assign(this, {
|
||||
connectors,
|
||||
$,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
});
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
for (let connector of this.connectors)
|
||||
await connector.deinit();
|
||||
}
|
||||
|
||||
async sync(userName, password, syncGroups) {
|
||||
let {
|
||||
$,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
} = this;
|
||||
|
||||
if (!userName) return;
|
||||
userName = userName.toLowerCase();
|
||||
|
||||
// Skip conflicting users
|
||||
if (['administrator', 'root'].indexOf(userName) >= 0)
|
||||
return;
|
||||
|
||||
let user = await $.Account.findOne({
|
||||
where: {name: userName},
|
||||
fields: [
|
||||
'id',
|
||||
'nickname',
|
||||
'email',
|
||||
'lang',
|
||||
'roleFk',
|
||||
'sync',
|
||||
'active',
|
||||
'created',
|
||||
'bcryptPassword',
|
||||
'updated'
|
||||
],
|
||||
include: {
|
||||
relation: 'roles',
|
||||
scope: {
|
||||
include: {
|
||||
relation: 'inherits',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let extraParams;
|
||||
let hasAccount = false;
|
||||
|
||||
if (user) {
|
||||
hasAccount = user.active
|
||||
&& await $.UserAccount.exists(user.id);
|
||||
|
||||
extraParams = {
|
||||
corporateMail: `${userName}@${mailConfig.domain}`,
|
||||
uidNumber: accountConfig.idBase + user.id
|
||||
};
|
||||
}
|
||||
|
||||
let info = {
|
||||
user,
|
||||
extraParams,
|
||||
hasAccount,
|
||||
accountConfig,
|
||||
mailConfig
|
||||
};
|
||||
|
||||
let errs = [];
|
||||
|
||||
for (let connector of this.connectors) {
|
||||
try {
|
||||
await connector.sync(info, userName, password);
|
||||
if (syncGroups)
|
||||
await connector.syncGroups(info, userName);
|
||||
} catch (err) {
|
||||
errs.push(err);
|
||||
}
|
||||
}
|
||||
|
||||
if (errs.length) throw errs[0];
|
||||
}
|
||||
|
||||
async getUsers() {
|
||||
let usersToSync = new Set();
|
||||
|
||||
for (let connector of this.connectors)
|
||||
await connector.getUsers(usersToSync);
|
||||
|
||||
return usersToSync;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,204 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
const nthash = require('smbhash').nthash;
|
||||
const ldap = require('./ldapjs-extra');
|
||||
const crypto = require('crypto');
|
||||
|
||||
class SyncLdap extends SyncConnector {
|
||||
async init() {
|
||||
let ldapConfig = await this.$.LdapConfig.findOne({
|
||||
fields: ['host', 'rdn', 'password', 'baseDn', 'groupDn']
|
||||
});
|
||||
if (!ldapConfig) return false;
|
||||
|
||||
let client = ldap.createClient({
|
||||
url: `ldap://${ldapConfig.host}:389`
|
||||
});
|
||||
|
||||
let ldapPassword = Buffer
|
||||
.from(ldapConfig.password, 'base64')
|
||||
.toString('ascii');
|
||||
await client.bind(ldapConfig.rdn, ldapPassword);
|
||||
|
||||
Object.assign(this, {
|
||||
ldapConfig,
|
||||
client
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
if (this.client)
|
||||
await this.client.unbind();
|
||||
}
|
||||
|
||||
async sync(info, userName, password) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client,
|
||||
} = this;
|
||||
|
||||
let {
|
||||
user,
|
||||
hasAccount,
|
||||
extraParams,
|
||||
accountConfig
|
||||
} = info;
|
||||
|
||||
let res = await client.search(ldapConfig.baseDn, {
|
||||
scope: 'sub',
|
||||
attributes: ['userPassword', 'sambaNTPassword'],
|
||||
filter: `&(uid=${userName})`
|
||||
});
|
||||
|
||||
let oldUser;
|
||||
await new Promise((resolve, reject) => {
|
||||
res.on('error', reject);
|
||||
res.on('searchEntry', e => oldUser = e.object);
|
||||
res.on('end', resolve);
|
||||
});
|
||||
|
||||
try {
|
||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||
await client.del(dn);
|
||||
} catch (e) {
|
||||
if (e.name !== 'NoSuchObjectError') throw e;
|
||||
}
|
||||
|
||||
if (!hasAccount) {
|
||||
if (oldUser)
|
||||
console.log(` -> '${userName}' removed from LDAP`);
|
||||
return;
|
||||
}
|
||||
|
||||
let nickname = user.nickname || userName;
|
||||
let nameArgs = nickname.trim().split(' ');
|
||||
let sn = nameArgs.length > 1
|
||||
? nameArgs.splice(1).join(' ')
|
||||
: '-';
|
||||
|
||||
let dn = `uid=${userName},${ldapConfig.baseDn}`;
|
||||
let newEntry = {
|
||||
uid: userName,
|
||||
objectClass: [
|
||||
'inetOrgPerson',
|
||||
'posixAccount',
|
||||
'sambaSamAccount'
|
||||
],
|
||||
cn: nickname,
|
||||
displayName: nickname,
|
||||
givenName: nameArgs[0],
|
||||
sn,
|
||||
mail: extraParams.corporateMail,
|
||||
preferredLanguage: user.lang,
|
||||
homeDirectory: `${accountConfig.homedir}/${userName}`,
|
||||
loginShell: accountConfig.shell,
|
||||
uidNumber: extraParams.uidNumber,
|
||||
gidNumber: accountConfig.idBase + user.roleFk,
|
||||
sambaSID: '-'
|
||||
};
|
||||
|
||||
if (password) {
|
||||
let salt = crypto
|
||||
.randomBytes(8)
|
||||
.toString('base64');
|
||||
|
||||
let hash = crypto.createHash('sha1');
|
||||
hash.update(password);
|
||||
hash.update(salt, 'binary');
|
||||
let digest = hash.digest('binary');
|
||||
|
||||
let ssha = Buffer
|
||||
.from(digest + salt, 'binary')
|
||||
.toString('base64');
|
||||
|
||||
Object.assign(newEntry, {
|
||||
userPassword: `{SSHA}${ssha}`,
|
||||
sambaNTPassword: nthash(password)
|
||||
});
|
||||
} else if (oldUser) {
|
||||
Object.assign(newEntry, {
|
||||
userPassword: oldUser.userPassword,
|
||||
sambaNTPassword: oldUser.sambaNTPassword
|
||||
});
|
||||
}
|
||||
|
||||
for (let prop in newEntry) {
|
||||
if (newEntry[prop] == null)
|
||||
delete newEntry[prop];
|
||||
}
|
||||
|
||||
await client.add(dn, newEntry);
|
||||
}
|
||||
|
||||
async syncGroups(info, userName) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client
|
||||
} = this;
|
||||
|
||||
let {
|
||||
user,
|
||||
hasAccount
|
||||
} = info;
|
||||
|
||||
let res = await client.search(ldapConfig.groupDn, {
|
||||
scope: 'sub',
|
||||
attributes: ['dn'],
|
||||
filter: `&(memberUid=${userName})(objectClass=posixGroup)`
|
||||
});
|
||||
|
||||
let oldGroups = [];
|
||||
await new Promise((resolve, reject) => {
|
||||
res.on('error', reject);
|
||||
res.on('searchEntry', e => oldGroups.push(e.object));
|
||||
res.on('end', resolve);
|
||||
});
|
||||
|
||||
let reqs = [];
|
||||
for (let oldGroup of oldGroups) {
|
||||
let change = new ldap.Change({
|
||||
operation: 'delete',
|
||||
modification: {memberUid: userName}
|
||||
});
|
||||
reqs.push(client.modify(oldGroup.dn, change));
|
||||
}
|
||||
await Promise.all(reqs);
|
||||
|
||||
if (!hasAccount) return;
|
||||
|
||||
reqs = [];
|
||||
for (let role of user.roles()) {
|
||||
let change = new ldap.Change({
|
||||
operation: 'add',
|
||||
modification: {memberUid: userName}
|
||||
});
|
||||
let roleName = role.inherits().name;
|
||||
let dn = `cn=${roleName},${ldapConfig.groupDn}`;
|
||||
reqs.push(client.modify(dn, change));
|
||||
}
|
||||
await Promise.all(reqs);
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let {
|
||||
ldapConfig,
|
||||
client
|
||||
} = this;
|
||||
|
||||
let res = await client.search(ldapConfig.baseDn, {
|
||||
scope: 'sub',
|
||||
attributes: ['uid'],
|
||||
filter: `uid=*`
|
||||
});
|
||||
|
||||
await new Promise((resolve, reject) => {
|
||||
res.on('error', reject);
|
||||
res.on('searchEntry', e => usersToSync.add(e.object.uid));
|
||||
res.on('end', resolve);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncLdap);
|
||||
module.exports = SyncLdap;
|
|
@ -0,0 +1,93 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
const ssh = require('node-ssh');
|
||||
|
||||
class SyncSamba extends SyncConnector {
|
||||
async init() {
|
||||
let sambaConfig = await this.$.SambaConfig.findOne({
|
||||
fields: ['host', 'sshUser', 'sshPass']
|
||||
});
|
||||
if (!sambaConfig) return false;
|
||||
|
||||
let sshPassword = Buffer
|
||||
.from(sambaConfig.sshPass, 'base64')
|
||||
.toString('ascii');
|
||||
|
||||
let client = new ssh.NodeSSH();
|
||||
await client.connect({
|
||||
host: sambaConfig.host,
|
||||
username: sambaConfig.sshUser,
|
||||
password: sshPassword
|
||||
});
|
||||
|
||||
Object.assign(this, {
|
||||
sambaConfig,
|
||||
client
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
async deinit() {
|
||||
if (this.client)
|
||||
await this.client.dispose();
|
||||
}
|
||||
|
||||
async sync(info, userName, password) {
|
||||
let {
|
||||
client
|
||||
} = this;
|
||||
|
||||
let {
|
||||
hasAccount,
|
||||
extraParams
|
||||
} = info;
|
||||
|
||||
if (hasAccount) {
|
||||
try {
|
||||
await client.exec('samba-tool user create', [
|
||||
userName,
|
||||
'--uid-number', `${extraParams.uidNumber}`,
|
||||
'--mail-address', extraParams.corporateMail,
|
||||
'--random-password'
|
||||
]);
|
||||
} catch (e) {}
|
||||
|
||||
await client.exec('samba-tool user setexpiry', [
|
||||
userName,
|
||||
'--noexpiry'
|
||||
]);
|
||||
|
||||
if (password) {
|
||||
await client.exec('samba-tool user setpassword', [
|
||||
userName,
|
||||
'--newpassword', password
|
||||
]);
|
||||
await client.exec('samba-tool user enable', [
|
||||
userName
|
||||
]);
|
||||
}
|
||||
|
||||
await client.exec('mkhomedir_helper', [
|
||||
userName,
|
||||
'0027'
|
||||
]);
|
||||
} else {
|
||||
try {
|
||||
await client.exec('samba-tool user disable', [
|
||||
userName
|
||||
]);
|
||||
console.log(` -> '${userName}' disabled on Samba`);
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
async getUsers(usersToSync) {
|
||||
let {client} = this;
|
||||
let res = await client.execCommand('samba-tool user list');
|
||||
let users = res.stdout.split('\n');
|
||||
for (let user of users) usersToSync.add(user.trim());
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncSamba);
|
||||
module.exports = SyncSamba;
|
|
@ -0,0 +1,15 @@
|
|||
|
||||
const SyncConnector = require('./sync-connector');
|
||||
|
||||
class SyncSip extends SyncConnector {
|
||||
async sync(info, userName, password) {
|
||||
if (!info.hasAccount || !password) return;
|
||||
|
||||
await this.$.Account.rawSql('CALL pbx.sip_setPassword(?, ?)',
|
||||
[info.user.id, password]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
SyncConnector.connectors.push(SyncSip);
|
||||
module.exports = SyncSip;
|
|
@ -15,3 +15,6 @@ import './basic-data';
|
|||
import './mail-forwarding';
|
||||
import './aliases';
|
||||
import './roles';
|
||||
import './ldap';
|
||||
import './samba';
|
||||
import './posix';
|
||||
|
|
|
@ -0,0 +1,93 @@
|
|||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
url="LdapConfigs"
|
||||
data="$ctrl.config"
|
||||
id-value="1"
|
||||
form="form">
|
||||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="watcher.submit()"
|
||||
class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-textfield
|
||||
label="Host"
|
||||
ng-model="$ctrl.config.host"
|
||||
rule="LdapConfig"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="RDN"
|
||||
ng-model="$ctrl.config.rdn"
|
||||
rule="LdapConfig">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Password"
|
||||
ng-model="$ctrl.config.password"
|
||||
info="Password should be base64 encoded"
|
||||
type="password"
|
||||
rule="LdapConfig">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Base DN"
|
||||
ng-model="$ctrl.config.baseDn"
|
||||
rule="LdapConfig">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Filter"
|
||||
ng-model="$ctrl.config.filter"
|
||||
rule="LdapConfig">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Group DN"
|
||||
ng-model="$ctrl.config.groupDn"
|
||||
rule="LdapConfig">
|
||||
</vn-textfield>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-button
|
||||
label="Undo changes"
|
||||
ng-if="watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
label="Synchronize now"
|
||||
ng-if="watcher.hasData"
|
||||
ng-click="$ctrl.onSynchronizeAll()">
|
||||
</vn-button>
|
||||
<vn-button
|
||||
label="Synchronize user"
|
||||
ng-if="watcher.hasData"
|
||||
ng-click="syncUser.show()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
<vn-submit
|
||||
icon="save"
|
||||
vn-tooltip="Save"
|
||||
class="round"
|
||||
fixed-bottom-right>
|
||||
</vn-submit>
|
||||
</form>
|
||||
<vn-dialog
|
||||
vn-id="syncUser"
|
||||
on-accept="$ctrl.onUserSync()"
|
||||
on-close="$ctrl.onPassClose()">
|
||||
<tpl-body>
|
||||
<vn-textfield
|
||||
label="Username"
|
||||
ng-model="$ctrl.syncUser">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Password"
|
||||
ng-model="$ctrl.syncPassword"
|
||||
type="password"
|
||||
info="If password is not specified, just user attributes are synchronized">
|
||||
</vn-textfield>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button response="accept" translate>Synchronize</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -0,0 +1,30 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import UserError from 'core/lib/user-error';
|
||||
|
||||
export default class Controller extends Section {
|
||||
onSynchronizeAll() {
|
||||
this.vnApp.showSuccess(this.$t('Synchronizing in the background'));
|
||||
this.$http.patch(`UserAccounts/syncAll`)
|
||||
.then(() => this.vnApp.showSuccess(this.$t('LDAP users synchronized')));
|
||||
}
|
||||
|
||||
onUserSync() {
|
||||
if (!this.syncUser)
|
||||
throw new UserError('Please enter the username');
|
||||
|
||||
let params = {password: this.syncPassword};
|
||||
return this.$http.patch(`UserAccounts/${this.syncUser}/sync`, params)
|
||||
.then(() => this.vnApp.showSuccess(this.$t('User synchronized')));
|
||||
}
|
||||
|
||||
onSyncClose() {
|
||||
this.syncUser = '';
|
||||
this.syncPassword = '';
|
||||
}
|
||||
}
|
||||
|
||||
ngModule.component('vnAccountLdap', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
Host: Host
|
||||
RDN: RDN
|
||||
Base DN: DN base
|
||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
||||
Filter: Filtro
|
||||
Group DN: DN grupos
|
||||
Synchronize now: Sincronizar ahora
|
||||
Synchronize user: Sincronizar usuario
|
||||
If password is not specified, just user attributes are synchronized: >-
|
||||
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||
Synchronizing in the background: Sincronizando en segundo plano
|
||||
LDAP users synchronized: Usuarios LDAP sincronizados
|
||||
Username: Nombre de usuario
|
||||
Synchronize: Sincronizar
|
||||
Please enter the username: Por favor introduce el nombre de usuario
|
||||
User synchronized: Usuario sincronizado
|
|
@ -0,0 +1,69 @@
|
|||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
url="AccountConfigs"
|
||||
data="$ctrl.config"
|
||||
id-value="1"
|
||||
form="form">
|
||||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="watcher.submit()"
|
||||
class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-textfield
|
||||
label="Homedir"
|
||||
ng-model="$ctrl.config.homedir"
|
||||
rule="AccountConfig"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Shell"
|
||||
ng-model="$ctrl.config.shell"
|
||||
rule="AccountConfig">
|
||||
</vn-textfield>
|
||||
<vn-input-number
|
||||
label="Id base"
|
||||
ng-model="$ctrl.config.idBase"
|
||||
rule="AccountConfig">
|
||||
</vn-input-number>
|
||||
<vn-horizontal>
|
||||
<vn-input-number
|
||||
label="Min"
|
||||
ng-model="$ctrl.config.min"
|
||||
rule="AccountConfig">
|
||||
</vn-input-number>
|
||||
<vn-input-number
|
||||
label="Max"
|
||||
ng-model="$ctrl.config.max"
|
||||
rule="AccountConfig">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-input-number
|
||||
label="Warn"
|
||||
ng-model="$ctrl.config.warn"
|
||||
rule="AccountConfig">
|
||||
</vn-input-number>
|
||||
<vn-input-number
|
||||
label="Inact"
|
||||
ng-model="$ctrl.config.inact"
|
||||
rule="AccountConfig">
|
||||
</vn-input-number>
|
||||
</vn-horizontal>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-button
|
||||
label="Undo changes"
|
||||
ng-if="watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
<vn-submit
|
||||
icon="save"
|
||||
vn-tooltip="Save"
|
||||
class="round"
|
||||
fixed-bottom-right>
|
||||
</vn-submit>
|
||||
</form>
|
|
@ -0,0 +1,9 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
export default class Controller extends Section {}
|
||||
|
||||
ngModule.component('vnAccountPosix', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
Host: Host
|
||||
RDN: RDN
|
||||
Base DN: DN base
|
||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
||||
Filter: Filtro
|
||||
Group DN: DN grupos
|
||||
Synchronize now: Sincronizar ahora
|
||||
Synchronize user: Sincronizar usuario
|
||||
If password is not specified, just user attributes are synchronized: >-
|
||||
Si la contraseña no se especifica solo se sincronizarán lo atributos del usuario
|
||||
Synchronizing in the background: Sincronizando en segundo plano
|
||||
LDAP users synchronized: Usuarios LDAP sincronizados
|
||||
Username: Nombre de usuario
|
||||
Synchronize: Sincronizar
|
||||
Please enter the username: Por favor introduce el nombre de usuario
|
||||
User synchronized: Usuario sincronizado
|
|
@ -9,6 +9,9 @@
|
|||
{"state": "account.index", "icon": "face"},
|
||||
{"state": "account.role", "icon": "group"},
|
||||
{"state": "account.alias", "icon": "email"},
|
||||
{"state": "account.posix", "icon": "accessibility"},
|
||||
{"state": "account.ldap", "icon": "account_tree"},
|
||||
{"state": "account.samba", "icon": "desktop_windows"},
|
||||
{"state": "account.acl", "icon": "check"},
|
||||
{"state": "account.connections", "icon": "share"}
|
||||
],
|
||||
|
@ -174,6 +177,24 @@
|
|||
"state": "account.alias.card.users",
|
||||
"component": "vn-alias-users",
|
||||
"description": "Users"
|
||||
}, {
|
||||
"url": "/posix",
|
||||
"state": "account.posix",
|
||||
"component": "vn-account-posix",
|
||||
"description": "Posix",
|
||||
"acl": ["developer"]
|
||||
}, {
|
||||
"url": "/ldap",
|
||||
"state": "account.ldap",
|
||||
"component": "vn-account-ldap",
|
||||
"description": "LDAP",
|
||||
"acl": ["developer"]
|
||||
}, {
|
||||
"url": "/samba",
|
||||
"state": "account.samba",
|
||||
"component": "vn-account-samba",
|
||||
"description": "Samba",
|
||||
"acl": ["developer"]
|
||||
}, {
|
||||
"url": "/acl?q",
|
||||
"state": "account.acl",
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
url="SambaConfigs"
|
||||
data="$ctrl.config"
|
||||
id-value="1"
|
||||
form="form">
|
||||
</vn-watcher>
|
||||
<form
|
||||
name="form"
|
||||
ng-submit="watcher.submit()"
|
||||
class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-vertical>
|
||||
<vn-textfield
|
||||
label="SSH host"
|
||||
ng-model="$ctrl.config.host"
|
||||
rule="SambaConfig"
|
||||
vn-focus>
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="User"
|
||||
ng-model="$ctrl.config.sshUser"
|
||||
rule="SambaConfig">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
label="Password"
|
||||
ng-model="$ctrl.config.sshPass"
|
||||
info="Password should be base64 encoded"
|
||||
type="password"
|
||||
rule="SambaConfig">
|
||||
</vn-textfield>
|
||||
</vn-vertical>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-button
|
||||
label="Undo changes"
|
||||
ng-if="watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
<vn-submit
|
||||
icon="save"
|
||||
vn-tooltip="Save"
|
||||
class="round"
|
||||
fixed-bottom-right>
|
||||
</vn-submit>
|
||||
</form>
|
|
@ -0,0 +1,9 @@
|
|||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
|
||||
export default class Controller extends Section {}
|
||||
|
||||
ngModule.component('vnAccountSamba', {
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,2 @@
|
|||
SSH host: Host SSH
|
||||
Password should be base64 encoded: La contraseña debe estar codificada en base64
|
|
@ -4,7 +4,7 @@ module.exports = function(Self) {
|
|||
Self.remoteMethodCtx('createAddress', {
|
||||
description: 'Creates client address updating default address',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
arg: 'clientFk',
|
||||
type: 'number',
|
||||
description: 'The client id',
|
||||
http: {source: 'path'}
|
||||
|
@ -37,19 +37,19 @@ module.exports = function(Self) {
|
|||
type: 'string'
|
||||
},
|
||||
{
|
||||
arg: 'provinceId',
|
||||
arg: 'provinceFk',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
arg: 'agencyModeId',
|
||||
arg: 'agencyModeFk',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
arg: 'incotermsId',
|
||||
arg: 'incotermsFk',
|
||||
type: 'string'
|
||||
},
|
||||
{
|
||||
arg: 'customsAgentId',
|
||||
arg: 'customsAgentFk',
|
||||
type: 'number'
|
||||
},
|
||||
{
|
||||
|
@ -66,44 +66,33 @@ module.exports = function(Self) {
|
|||
},
|
||||
http: {
|
||||
verb: 'post',
|
||||
path: '/:id/createAddress'
|
||||
path: '/:clientFk/createAddress'
|
||||
}
|
||||
});
|
||||
|
||||
Self.createAddress = async(ctx, clientId) => {
|
||||
Self.createAddress = async(ctx, clientFk) => {
|
||||
const models = Self.app.models;
|
||||
const args = ctx.args;
|
||||
const tx = await models.Address.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const province = await models.Province.findById(args.provinceId, {
|
||||
const province = await models.Province.findById(args.provinceFk, {
|
||||
include: {
|
||||
relation: 'country'
|
||||
}
|
||||
}, options);
|
||||
|
||||
const isUeeMember = province.country().isUeeMember;
|
||||
if (!isUeeMember && !args.incotermsId)
|
||||
if (!isUeeMember && !args.incotermsFk)
|
||||
throw new UserError(`Incoterms is required for a non UEE member`);
|
||||
|
||||
if (!isUeeMember && !args.customsAgentId)
|
||||
if (!isUeeMember && !args.customsAgentFk)
|
||||
throw new UserError(`Customs agent is required for a non UEE member`);
|
||||
|
||||
const newAddress = await models.Address.create({
|
||||
clientFk: clientId,
|
||||
nickname: args.nickname,
|
||||
incotermsFk: args.incotermsId,
|
||||
customsAgentFk: args.customsAgentId,
|
||||
city: args.city,
|
||||
street: args.street,
|
||||
phone: args.phone,
|
||||
postalCode: args.postalCode,
|
||||
provinceFk: args.provinceId,
|
||||
agencyModeFk: args.agencyModeId,
|
||||
isActive: args.isActive
|
||||
}, options);
|
||||
const client = await Self.findById(clientId, null, options);
|
||||
delete args.ctx; // Remove unwanted properties
|
||||
const newAddress = await models.Address.create(args, options);
|
||||
const client = await Self.findById(clientFk, null, options);
|
||||
|
||||
if (args.isDefaultAddress) {
|
||||
await client.updateAttributes({
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('Address createAddress', () => {
|
||||
const clientId = 101;
|
||||
const provinceId = 5;
|
||||
const incotermsId = 'FAS';
|
||||
const clientFk = 101;
|
||||
const provinceFk = 5;
|
||||
const incotermsFk = 'FAS';
|
||||
const customAgentOneId = 1;
|
||||
|
||||
it('should throw a non uee member error if no incoterms is defined', async() => {
|
||||
const expectedResult = 'My edited address';
|
||||
const ctx = {
|
||||
args: {
|
||||
provinceId: provinceId,
|
||||
provinceFk: provinceFk,
|
||||
nickname: expectedResult,
|
||||
street: 'Wall Street',
|
||||
city: 'New York',
|
||||
customsAgentId: customAgentOneId
|
||||
customsAgentFk: customAgentOneId
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Client.createAddress(ctx, clientId);
|
||||
await app.models.Client.createAddress(ctx, clientFk);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
@ -32,16 +32,16 @@ describe('Address createAddress', () => {
|
|||
const expectedResult = 'My edited address';
|
||||
const ctx = {
|
||||
args: {
|
||||
provinceId: provinceId,
|
||||
provinceFk: provinceFk,
|
||||
nickname: expectedResult,
|
||||
street: 'Wall Street',
|
||||
city: 'New York',
|
||||
incotermsId: incotermsId
|
||||
incotermsFk: incotermsFk
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
await app.models.Client.createAddress(ctx, clientId);
|
||||
await app.models.Client.createAddress(ctx, clientFk);
|
||||
} catch (e) {
|
||||
err = e;
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ describe('Address createAddress', () => {
|
|||
});
|
||||
|
||||
it('should verify that client defaultAddressFk is untainted', async() => {
|
||||
const client = await app.models.Client.findById(clientId);
|
||||
const client = await app.models.Client.findById(clientFk);
|
||||
|
||||
expect(client.defaultAddressFk).toEqual(1);
|
||||
});
|
||||
|
@ -59,18 +59,23 @@ describe('Address createAddress', () => {
|
|||
it('should create a new address and set as a client default address', async() => {
|
||||
const ctx = {
|
||||
args: {
|
||||
provinceId: 1,
|
||||
clientFk: 101,
|
||||
provinceFk: 1,
|
||||
nickname: 'My address',
|
||||
street: 'Wall Street',
|
||||
city: 'New York',
|
||||
incotermsId: incotermsId,
|
||||
customsAgentId: customAgentOneId,
|
||||
phone: 678678678,
|
||||
mobile: 678678678,
|
||||
postalCode: 46680,
|
||||
agencyModeFk: 1,
|
||||
incotermsFk: incotermsFk,
|
||||
customsAgentFk: customAgentOneId,
|
||||
isDefaultAddress: true
|
||||
}
|
||||
};
|
||||
|
||||
const address = await app.models.Client.createAddress(ctx, clientId);
|
||||
const client = await app.models.Client.findById(clientId);
|
||||
const address = await app.models.Client.createAddress(ctx, clientFk);
|
||||
const client = await app.models.Client.findById(clientFk);
|
||||
|
||||
expect(client.defaultAddressFk).toEqual(address.id);
|
||||
|
||||
|
@ -78,4 +83,31 @@ describe('Address createAddress', () => {
|
|||
await client.updateAttributes({defaultAddressFk: 1});
|
||||
await address.destroy();
|
||||
});
|
||||
|
||||
it('should create a new address and set all properties', async() => {
|
||||
const ctx = {
|
||||
args: {
|
||||
clientFk: 101,
|
||||
provinceFk: 1,
|
||||
nickname: 'My address',
|
||||
street: 'Wall Street',
|
||||
city: 'New York',
|
||||
phone: '678678678',
|
||||
mobile: '678678678',
|
||||
postalCode: '46680',
|
||||
agencyModeFk: 1,
|
||||
incotermsFk: incotermsFk,
|
||||
customsAgentFk: customAgentOneId,
|
||||
isDefaultAddress: true
|
||||
}
|
||||
};
|
||||
|
||||
address = await app.models.Client.createAddress(ctx, clientFk);
|
||||
|
||||
expect(address).toEqual(jasmine.objectContaining(ctx.args));
|
||||
// restores
|
||||
const client = await app.models.Client.findById(clientFk);
|
||||
await client.updateAttributes({defaultAddressFk: 1});
|
||||
await address.destroy();
|
||||
});
|
||||
});
|
||||
|
|
|
@ -15,6 +15,10 @@
|
|||
"description": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"code": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
|
|
|
@ -88,7 +88,7 @@
|
|||
</vn-datalist>
|
||||
<vn-autocomplete vn-id="province" vn-one
|
||||
label="Province"
|
||||
ng-model="$ctrl.address.provinceId"
|
||||
ng-model="$ctrl.address.provinceFk"
|
||||
data="provincesLocation"
|
||||
fields="['id', 'name', 'countryFk']"
|
||||
show-field="name"
|
||||
|
@ -100,7 +100,7 @@
|
|||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
ng-model="$ctrl.address.agencyModeId"
|
||||
ng-model="$ctrl.address.agencyModeFk"
|
||||
url="AgencyModes/isActive"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
|
@ -121,14 +121,14 @@
|
|||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.address.incotermsId"
|
||||
ng-model="$ctrl.address.incotermsFk"
|
||||
data="incoterms"
|
||||
show-field="name"
|
||||
value-field="code"
|
||||
label="Incoterms">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.address.customsAgentId"
|
||||
ng-model="$ctrl.address.customsAgentFk"
|
||||
data="customsAgents"
|
||||
show-field="fiscalName"
|
||||
value-field="id"
|
||||
|
|
|
@ -29,7 +29,7 @@ export default class Controller extends Section {
|
|||
|
||||
onCustomAgentAccept() {
|
||||
return this.$http.post(`CustomsAgents`, this.newCustomsAgent)
|
||||
.then(res => this.address.customsAgentId = res.data.id);
|
||||
.then(res => this.address.customsAgentFk = res.data.id);
|
||||
}
|
||||
|
||||
get town() {
|
||||
|
@ -45,8 +45,8 @@ export default class Controller extends Section {
|
|||
const province = selection.province;
|
||||
const postcodes = selection.postcodes;
|
||||
|
||||
if (!this.address.provinceI)
|
||||
this.address.provinceId = province.id;
|
||||
if (!this.address.provinceFk)
|
||||
this.address.provinceFk = province.id;
|
||||
|
||||
if (postcodes.length === 1)
|
||||
this.address.postalCode = postcodes[0].code;
|
||||
|
@ -68,8 +68,8 @@ export default class Controller extends Section {
|
|||
if (!this.address.city)
|
||||
this.address.city = town.name;
|
||||
|
||||
if (!this.address.provinceId)
|
||||
this.address.provinceId = province.id;
|
||||
if (!this.address.provinceFk)
|
||||
this.address.provinceFk = province.id;
|
||||
}
|
||||
|
||||
onResponse(response) {
|
||||
|
|
|
@ -54,7 +54,7 @@ describe('Client', () => {
|
|||
});
|
||||
|
||||
describe('town() setter', () => {
|
||||
it(`should set provinceId property`, () => {
|
||||
it(`should set provinceFk property`, () => {
|
||||
controller.town = {
|
||||
provinceFk: 1,
|
||||
code: 46001,
|
||||
|
@ -69,10 +69,10 @@ describe('Client', () => {
|
|||
postcodes: []
|
||||
};
|
||||
|
||||
expect(controller.address.provinceId).toEqual(1);
|
||||
expect(controller.address.provinceFk).toEqual(1);
|
||||
});
|
||||
|
||||
it(`should set provinceId property and fill the postalCode if there's just one`, () => {
|
||||
it(`should set provinceFk property and fill the postalCode if there's just one`, () => {
|
||||
controller.town = {
|
||||
provinceFk: 1,
|
||||
code: 46001,
|
||||
|
@ -87,7 +87,7 @@ describe('Client', () => {
|
|||
postcodes: [{code: '46001'}]
|
||||
};
|
||||
|
||||
expect(controller.address.provinceId).toEqual(1);
|
||||
expect(controller.address.provinceFk).toEqual(1);
|
||||
expect(controller.address.postalCode).toEqual('46001');
|
||||
});
|
||||
});
|
||||
|
@ -112,7 +112,7 @@ describe('Client', () => {
|
|||
};
|
||||
|
||||
expect(controller.address.city).toEqual('New York');
|
||||
expect(controller.address.provinceId).toEqual(1);
|
||||
expect(controller.address.provinceFk).toEqual(1);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -123,7 +123,7 @@ describe('Client', () => {
|
|||
controller.onCustomAgentAccept();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.address.customsAgentId).toEqual(1);
|
||||
expect(controller.address.customsAgentFk).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -190,8 +190,6 @@ module.exports = Self => {
|
|||
item.prices.push(price);
|
||||
else
|
||||
item.prices = [price];
|
||||
|
||||
item.available = price.grouping;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,82 +1,84 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket-request filter()', () => {
|
||||
it('should now return all ticket requests', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {}};
|
||||
const userId = 9;
|
||||
let ctx = {req: {accessToken: {userId: userId}}};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
it('should now return all ticket requests', async() => {
|
||||
ctx.args = {};
|
||||
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching a generic search value which is the ticket ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {search: 11}};
|
||||
ctx.args = {search: 11};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(4);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching a generic search value which is the client address alias', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {search: 'NY roofs'}};
|
||||
ctx.args = {search: 'NY roofs'};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(4);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the ticket ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {ticketFk: 11}};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
ctx.args = {ticketFk: 11};
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(4);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the atender ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {attenderFk: 35}};
|
||||
ctx.args = {attenderFk: 35};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the isOk triple-state', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {isOk: null}};
|
||||
ctx.args = {isOk: null};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the client ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {clientFk: 102}};
|
||||
ctx.args = {clientFk: 102};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(4);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the warehouse ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {warehouse: 1}};
|
||||
ctx.args = {warehouse: 1};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx, {order: 'id'});
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx, {order: 'id'});
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(3);
|
||||
});
|
||||
|
||||
it('should return the ticket request matching the salesPerson ID', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 9}}, args: {salesPersonFk: 18}};
|
||||
ctx.args = {salesPersonFk: 18};
|
||||
|
||||
let result = await app.models.TicketRequest.filter(ctx);
|
||||
let requestId = result[0].id;
|
||||
const result = await app.models.TicketRequest.filter(ctx);
|
||||
const requestId = result[0].id;
|
||||
|
||||
expect(requestId).toEqual(3);
|
||||
});
|
||||
|
|
|
@ -89,15 +89,21 @@ module.exports = Self => {
|
|||
if (!zoneShipped || zoneShipped.zoneFk != zoneFk)
|
||||
throw new UserError(`You don't have privileges to change the zone`);
|
||||
}
|
||||
const originalTicket = await models.Ticket.findById(id, {
|
||||
include: {
|
||||
const observationTypeDelivery = await models.ObservationType.findOne({
|
||||
where: {code: 'delivery'}
|
||||
});
|
||||
|
||||
const originalTicket = await models.Ticket.findOne({
|
||||
where: {id: id},
|
||||
fields: ['id', 'clientFk', 'agencyModeFk', 'addressFk', 'zoneFk',
|
||||
'warehouseFk', 'companyFk', 'shipped', 'landed', 'isDeleted'],
|
||||
include: [
|
||||
{
|
||||
relation: 'client',
|
||||
scope: {
|
||||
fields: 'salesPersonFk'
|
||||
}
|
||||
},
|
||||
fields: ['id', 'clientFk', 'agencyModeFk', 'addressFk', 'zoneFk',
|
||||
'warehouseFk', 'companyFk', 'shipped', 'landed', 'isDeleted']
|
||||
}]
|
||||
});
|
||||
const updatedTicket = Object.assign({}, ctx.args);
|
||||
delete updatedTicket.ctx;
|
||||
|
@ -121,6 +127,39 @@ module.exports = Self => {
|
|||
option
|
||||
]);
|
||||
|
||||
if (originalTicket.addressFk != updatedTicket.addressFk) {
|
||||
const ticketObservation = await models.TicketObservation.findOne({
|
||||
where: {
|
||||
ticketFk: id,
|
||||
observationTypeFk: observationTypeDelivery.id}
|
||||
});
|
||||
|
||||
if (ticketObservation)
|
||||
await ticketObservation.destroy();
|
||||
|
||||
const address = await models.Address.findOne({
|
||||
where: {id: addressFk},
|
||||
include: {
|
||||
relation: 'observations',
|
||||
scope: {
|
||||
where: {observationTypeFk: observationTypeDelivery.id},
|
||||
include: {
|
||||
relation: 'observationType'
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
const [observation] = address.observations();
|
||||
if (observation) {
|
||||
await models.TicketObservation.create({
|
||||
ticketFk: id,
|
||||
observationTypeFk: observation.observationTypeFk,
|
||||
description: observation.description
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const changes = loggable.getChanges(originalTicket, updatedTicket);
|
||||
const oldProperties = await loggable.translateValues(Self, changes.old);
|
||||
const newProperties = await loggable.translateValues(Self, changes.new);
|
||||
|
|
|
@ -5,6 +5,7 @@ describe('ticket componentUpdate()', () => {
|
|||
const ticketID = 11;
|
||||
const today = new Date();
|
||||
const tomorrow = new Date();
|
||||
|
||||
tomorrow.setDate(tomorrow.getDate() + 1);
|
||||
|
||||
let deliveryComponentId;
|
||||
|
@ -97,4 +98,62 @@ describe('ticket componentUpdate()', () => {
|
|||
expect(firstvalueBeforeChange).toEqual(firstvalueAfterChange);
|
||||
expect(secondvalueBeforeChange).toEqual(secondvalueAfterChange);
|
||||
});
|
||||
|
||||
it('should change the addressFk and check that delivery observations have been changed and then undo the changes', async() => {
|
||||
const clientID = 102;
|
||||
const addressID = 122;
|
||||
const newAddressID = 2;
|
||||
const agencyModeID = 8;
|
||||
const warehouseID = 1;
|
||||
const zoneID = 5;
|
||||
const shipped = today;
|
||||
const companyID = 442;
|
||||
const isDeleted = false;
|
||||
const landed = tomorrow;
|
||||
const option = 1;
|
||||
const ctx = {
|
||||
args: {clientFk: clientID,
|
||||
agencyModeFk: agencyModeID},
|
||||
req: {
|
||||
accessToken: {userId: userID},
|
||||
headers: {origin: 'http://localhost'},
|
||||
__: value => {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
};
|
||||
const observationTypeDelivery = await app.models.ObservationType.findOne({
|
||||
where: {code: 'delivery'}
|
||||
});
|
||||
const originalTicketObservation = await app.models.TicketObservation.findOne({
|
||||
where: {
|
||||
ticketFk: ticketID,
|
||||
observationTypeFk: observationTypeDelivery.id}
|
||||
});
|
||||
|
||||
expect(originalTicketObservation).toBeDefined();
|
||||
|
||||
await app.models.Ticket.componentUpdate(ctx, ticketID, clientID, agencyModeID, newAddressID,
|
||||
zoneID, warehouseID, companyID, shipped, landed, isDeleted, option);
|
||||
|
||||
const removedTicketObservation = await app.models.TicketObservation.findOne({
|
||||
where: {
|
||||
ticketFk: ticketID,
|
||||
observationTypeFk: observationTypeDelivery.id}
|
||||
});
|
||||
|
||||
expect(removedTicketObservation).toBeNull();
|
||||
|
||||
// restores
|
||||
await app.models.Ticket.componentUpdate(ctx, ticketID, clientID, agencyModeID, addressID,
|
||||
zoneID, warehouseID, companyID, shipped, landed, isDeleted, option);
|
||||
|
||||
const restoredTicketObservation = await app.models.TicketObservation.findOne({
|
||||
where: {
|
||||
ticketFk: ticketID,
|
||||
observationTypeFk: observationTypeDelivery.id}
|
||||
});
|
||||
|
||||
expect(restoredTicketObservation.description).toEqual(originalTicketObservation.description);
|
||||
});
|
||||
});
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -17,7 +17,7 @@
|
|||
"helmet": "^3.21.2",
|
||||
"i18n": "^0.8.4",
|
||||
"imap": "^0.8.19",
|
||||
"ldapjs": "^1.0.2",
|
||||
"ldapjs": "^2.2.0",
|
||||
"loopback": "^3.26.0",
|
||||
"loopback-boot": "^2.27.1",
|
||||
"loopback-component-explorer": "^6.5.0",
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue