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

This commit is contained in:
Alex Moreno 2023-07-06 10:42:53 +02:00
commit 63a8f40443
37 changed files with 838 additions and 109 deletions

View File

@ -0,0 +1,89 @@
DROP PROCEDURE IF EXISTS vn.clientCreate;
DELIMITER $$
CREATE OR REPLACE DEFINER=`root`@`localhost` PROCEDURE `vn`.`client_create`(
vFirstname VARCHAR(50),
vSurnames VARCHAR(50),
vFi VARCHAR(9),
vAddress TEXT,
vPostcode CHAR(5),
vCity VARCHAR(25),
vProvinceFk SMALLINT(5),
vCompanyFk SMALLINT(5),
vPhone VARCHAR(11),
vEmail VARCHAR(255),
vUserFk INT
)
BEGIN
/**
* Create new client
*
* @params vFirstname firstName
* @params vSurnames surnames
* @params vFi company code from accounting transactions
* @params vAddress address
* @params vPostcode postCode
* @params vCity city
* @params vProvinceFk province
* @params vCompanyFk company in which he has become a client
* @params vPhone telephone number
* @params vEmail email address
* @params vUserFk user id
*/
DECLARE vPayMethodFk INT;
DECLARE vDueDay INT;
DECLARE vDefaultCredit DECIMAL(10, 2);
DECLARE vIsTaxDataChecked TINYINT(1);
DECLARE vHasCoreVnl BOOLEAN;
DECLARE vMandateTypeFk INT;
SELECT defaultPayMethodFk,
defaultDueDay,
defaultCredit,
defaultIsTaxDataChecked,
defaultHasCoreVnl,
defaultMandateTypeFk
INTO vPayMethodFk,
vDueDay,
vDefaultCredit,
vIsTaxDataChecked,
vHasCoreVnl,
vMandateTypeFk
FROM clientConfig;
INSERT INTO `client`
SET id = vUserFk,
name = CONCAT(vFirstname, ' ', vSurnames),
street = vAddress,
fi = TRIM(vFi),
phone = vPhone,
email = vEmail,
provinceFk = vProvinceFk,
city = vCity,
postcode = vPostcode,
socialName = CONCAT(vSurnames, ' ', vFirstname),
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isEqualizated = FALSE
ON duplicate KEY UPDATE
payMethodFk = vPayMethodFk,
dueDay = vDueDay,
credit = vDefaultCredit,
isTaxDataChecked = vIsTaxDataChecked,
hasCoreVnl = vHasCoreVnl,
isActive = TRUE;
INSERT INTO mandate (clientFk, companyFk, mandateTypeFk)
SELECT vUserFk, vCompanyFk, vMandateTypeFk
WHERE NOT EXISTS (
SELECT id
FROM mandate
WHERE clientFk = vUserFk
AND companyFk = vCompanyFk
AND mandateTypeFk = vMandateTypeFk
);
END$$
DELIMITER ;

View File

@ -0,0 +1,17 @@
ALTER TABLE vn.clientConfig ADD defaultPayMethodFk tinyint(3) unsigned NULL;
ALTER TABLE vn.clientConfig ADD defaultDueDay int unsigned NULL;
ALTER TABLE vn.clientConfig ADD defaultCredit decimal(10, 2) NULL;
ALTER TABLE vn.clientConfig ADD defaultIsTaxDataChecked tinyint(1) NULL;
ALTER TABLE vn.clientConfig ADD defaultHasCoreVnl boolean NULL;
ALTER TABLE vn.clientConfig ADD defaultMandateTypeFk smallint(5) NULL;
ALTER TABLE vn.clientConfig ADD CONSTRAINT clientNewConfigPayMethod_FK FOREIGN KEY (dafaultPayMethodFk) REFERENCES vn.payMethod(id);
ALTER TABLE vn.clientConfig ADD CONSTRAINT clientNewConfigMandateType_FK FOREIGN KEY (defaultMandateTypeFk) REFERENCES vn.mandateType(id);
UPDATE vn.clientConfig
SET defaultPayMethodFk = 4,
defaultDueDay = 5,
defaultCredit = 300.0,
defaultIsTaxDataChecked = 1,
defaultHasCoreVnl = 1,
defaultMandateTypeFk = 2
WHERE id = 1;

View File

@ -385,9 +385,10 @@ INSERT INTO `vn`.`clientManaCache`(`clientFk`, `mana`, `dated`)
(1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)), (1103, 0, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)),
(1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH)); (1104, -30, DATE_ADD(util.VN_CURDATE(), INTERVAL -1 MONTH));
INSERT INTO `vn`.`clientConfig`(`riskTolerance`, `maxCreditRows`) INSERT INTO `vn`.`clientConfig`(`id`, `riskTolerance`, `maxCreditRows`, `maxPriceIncreasingRatio`, `riskScope`, `defaultPayMethodFk`, `defaultDueDay`, `defaultCredit`, `defaultIsTaxDataChecked`, `defaultHasCoreVnl`, `defaultMandateTypeFk`)
VALUES VALUES
(200, 10); (1, 200, 10, 0.25, 2, 4, 5, 300.00, 1, 1, 2);
INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`) INSERT INTO `vn`.`address`(`id`, `nickname`, `street`, `city`, `postalCode`, `provinceFk`, `phone`, `mobile`, `isActive`, `clientFk`, `agencyModeFk`, `longitude`, `latitude`, `isEqualizated`, `isDefaultAddress`)
VALUES VALUES

View File

@ -894,6 +894,18 @@ export default {
extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span', extension: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(5) > section > span',
}, },
department: {
firstDepartment: 'vn-worker-department-index vn-card > vn-treeview vn-treeview-childs vn-treeview-childs vn-treeview-childs a'
},
departmentSummary: {
header: 'vn-worker-department-summary h5',
name: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(1) > section > span',
code: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(2) > section > span',
chat: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(3) > section > span',
bossDepartment: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(4) > section > span',
email: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(5) > section > span',
clientFk: 'vn-worker-department-summary vn-horizontal > vn-one > vn-vertical > vn-label-value:nth-child(6) > section > span',
},
workerBasicData: { workerBasicData: {
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]', name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]', surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
@ -901,6 +913,13 @@ export default {
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]', locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
saveButton: 'vn-worker-basic-data button[type=submit]' saveButton: 'vn-worker-basic-data button[type=submit]'
}, },
departmentBasicData: {
Name: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.name"]',
Code: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.code"]',
Chat: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.chat"]',
Email: 'vn-worker-department-basic-data vn-textfield[ng-model="$ctrl.department.notificationEmail"]',
saveButton: 'vn-worker-department-basic-data button[type=submit]'
},
workerNotes: { workerNotes: {
addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]', addNoteFloatButton: 'vn-worker-note vn-icon[icon="add"]',
note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]', note: 'vn-note-worker-create vn-textarea[ng-model="$ctrl.note.text"]',

View File

@ -0,0 +1,29 @@
import selectors from '../../../helpers/selectors.js';
import getBrowser from '../../../helpers/puppeteer';
describe('department summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSection('worker.department');
await page.doSearch('INFORMATICA');
await page.click(selectors.department.firstDepartment);
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section and check all properties', async() => {
expect(await page.waitToGetProperty(selectors.departmentSummary.header, 'innerText')).toEqual('INFORMATICA');
expect(await page.getProperty(selectors.departmentSummary.name, 'innerText')).toEqual('INFORMATICA');
expect(await page.getProperty(selectors.departmentSummary.code, 'innerText')).toEqual('it');
expect(await page.getProperty(selectors.departmentSummary.chat, 'innerText')).toEqual('informatica-cau');
expect(await page.getProperty(selectors.departmentSummary.bossDepartment, 'innerText')).toEqual('');
expect(await page.getProperty(selectors.departmentSummary.email, 'innerText')).toEqual('-');
expect(await page.getProperty(selectors.departmentSummary.clientFk, 'innerText')).toEqual('-');
});
});

View File

@ -0,0 +1,43 @@
import getBrowser from '../../../helpers/puppeteer';
import selectors from '../../../helpers/selectors.js';
const $ = {
form: 'vn-worker-department-basic-data form',
};
describe('department summary path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSection('worker.department');
await page.doSearch('INFORMATICA');
await page.click(selectors.department.firstDepartment);
});
beforeEach(async() => {
await page.accessToSection('worker.department.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it(`should edit the department basic data and confirm the department data was edited`, async() => {
const values = {
Name: 'Informatica',
Code: 'IT',
Chat: 'informatica-cau',
Email: 'it@verdnatura.es',
};
await page.fillForm($.form, values);
const formValues = await page.fetchForm($.form, Object.keys(values));
const message = await page.sendForm($.form, values);
expect(message.isSuccess).toBeTrue();
expect(formValues).toEqual(values);
});
});

View File

@ -147,14 +147,12 @@
"Receipt's bank was not found": "Receipt's bank was not found", "Receipt's bank was not found": "Receipt's bank was not found",
"This receipt was not compensated": "This receipt was not compensated", "This receipt was not compensated": "This receipt was not compensated",
"Client's email was not found": "Client's email was not found", "Client's email was not found": "Client's email was not found",
"Tickets with associated refunds": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº {{id}}", "Tickets with associated refunds": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº %d",
"It is not possible to modify tracked sales": "It is not possible to modify tracked sales", "It is not possible to modify tracked sales": "It is not possible to modify tracked sales",
"It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo", "It is not possible to modify sales that their articles are from Floramondo": "It is not possible to modify sales that their articles are from Floramondo",
"It is not possible to modify cloned sales": "It is not possible to modify cloned sales", "It is not possible to modify cloned sales": "It is not possible to modify cloned sales",
"Valid priorities: 1,2,3": "Valid priorities: 1,2,3",
"Warehouse inventory not set": "Almacén inventario no está establecido", "Warehouse inventory not set": "Almacén inventario no está establecido",
"Component cost not set": "Componente coste no está estabecido", "Component cost not set": "Componente coste no está estabecido",
"Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2": "Tickets with associated refunds can't be deleted. This ticket is associated with refund Nº 2",
"Description cannot be blank": "Description cannot be blank", "Description cannot be blank": "Description cannot be blank",
"company": "Company", "company": "Company",
"country": "Country", "country": "Country",
@ -178,5 +176,6 @@
"Failed to upload delivery note": "Error to upload delivery note {{id}}", "Failed to upload delivery note": "Error to upload delivery note {{id}}",
"Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address", "Mail not sent": "There has been an error sending the invoice to the client [{{clientId}}]({{{clientUrl}}}), please check the email address",
"The renew period has not been exceeded": "The renew period has not been exceeded", "The renew period has not been exceeded": "The renew period has not been exceeded",
"You can not use the same password": "You can not use the same password" "You can not use the same password": "You can not use the same password",
"Valid priorities": "Valid priorities: %d"
} }

View File

@ -268,7 +268,7 @@
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite", "Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
"Warehouse inventory not set": "El almacén inventario no está establecido", "Warehouse inventory not set": "El almacén inventario no está establecido",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada", "This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº %d",
"Not exist this branch": "La rama no existe", "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Collection does not exist": "La colección no existe", "Collection does not exist": "La colección no existe",
@ -301,6 +301,7 @@
"Fecha fuera de rango": "Fecha fuera de rango", "Fecha fuera de rango": "Fecha fuera de rango",
"Error while generating PDF": "Error al generar PDF", "Error while generating PDF": "Error al generar PDF",
"Error when sending mail to client": "Error al enviar el correo al cliente", "Error when sending mail to client": "Error al enviar el correo al cliente",
"Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico", "Mail not sent": "Se ha producido un fallo al enviar la factura al cliente [{{clientId}}]({{{clientUrl}}}), por favor revisa la dirección de correo electrónico",
"The renew period has not been exceeded": "El periodo de renovación no ha sido superado" "The renew period has not been exceeded": "El periodo de renovación no ha sido superado",
"Valid priorities": "Prioridades válidas: %d"
} }

View File

@ -1,4 +1,4 @@
let UserError = require('vn-loopback/util/user-error'); const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('new', { Self.remoteMethodCtx('new', {
@ -49,7 +49,7 @@ module.exports = Self => {
try { try {
const itemConfig = await models.ItemConfig.findOne({fields: ['validPriorities']}, myOptions); const itemConfig = await models.ItemConfig.findOne({fields: ['validPriorities']}, myOptions);
if (!itemConfig.validPriorities.includes(params.priority)) if (!itemConfig.validPriorities.includes(params.priority))
throw new UserError(`Valid priorities: ${[...itemConfig.validPriorities]}`); throw new UserError('Valid priorities', 'VALID_PRIORITIES', [...itemConfig.validPriorities]);
const provisionalName = params.provisionalName; const provisionalName = params.provisionalName;
delete params.provisionalName; delete params.provisionalName;

View File

@ -50,7 +50,7 @@ module.exports = Self => {
fields: ['id']} fields: ['id']}
, myOptions); , myOptions);
if (ticketRefunds.length > 0) if (ticketRefunds.length > 0)
throw new UserError($t('Tickets with associated refunds', {id: ticketRefunds[0].id})); throw new UserError('Tickets with associated refunds', 'TICKET_REFUND', ticketRefunds[0].id);
// Check if has sales with shelving // Check if has sales with shelving
const canDeleteTicketWithPartPrepared = const canDeleteTicketWithPartPrepared =

View File

@ -33,7 +33,6 @@ module.exports = Self => {
map.set(node.parentFk, []); map.set(node.parentFk, []);
map.get(node.parentFk).push(node); map.get(node.parentFk).push(node);
} }
function setLeaves(nodes) { function setLeaves(nodes) {
if (!nodes) return; if (!nodes) return;
for (let node of nodes) { for (let node of nodes) {
@ -43,6 +42,7 @@ module.exports = Self => {
} }
let leaves = map.get(parentId); let leaves = map.get(parentId);
setLeaves(leaves); setLeaves(leaves);
return leaves || []; return leaves || [];

View File

@ -0,0 +1,10 @@
const models = require('vn-loopback/server/server').models;
describe('department getLeaves()', () => {
const ctx = {req: {accessToken: {userId: 9}}};
it('should return the department and the childs containing the search value', async() => {
let result = await models.Department.getLeaves(ctx, null, 'INFORMATICA');
expect(result.length).toEqual(1);
});
});

View File

@ -171,7 +171,7 @@ module.exports = Self => {
throw new UserError(`That payment method requires an IBAN`); throw new UserError(`That payment method requires an IBAN`);
await models.Worker.rawSql( await models.Worker.rawSql(
'CALL vn.clientCreate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', 'CALL vn.client_create(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)',
[ [
args.firstName, args.firstName,
args.lastNames, args.lastNames,

View File

@ -8,8 +8,9 @@
}, },
"properties": { "properties": {
"id": { "id": {
"type": "number",
"id": true, "id": true,
"type": "number" "description": "Identifier"
}, },
"code": { "code": {
"type": "string" "type": "string"
@ -37,6 +38,30 @@
}, },
"hasToMistake": { "hasToMistake": {
"type": "number" "type": "number"
},
"isTeleworking": {
"type": "boolean"
},
"hasToRefill": {
"type": "boolean"
},
"hasToSendMail": {
"type": "boolean"
},
"isProduction": {
"type": "boolean"
}
},
"relations": {
"client": {
"type": "belongsTo",
"model": "Client",
"foreignKey": "clientFk"
},
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
} }
} }
} }

View File

@ -60,7 +60,7 @@
"user": { "user": {
"type": "belongsTo", "type": "belongsTo",
"model": "VnUser", "model": "VnUser",
"foreignKey": "userFk" "foreignKey": "id"
}, },
"boss": { "boss": {
"type": "belongsTo", "type": "belongsTo",

View File

@ -0,0 +1,102 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.department"
form="form"
url="Departments">
</vn-watcher>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
info="Search departments by id, name or code"
on-search="$ctrl.onSearch($params)"
base-state="worker.department">
</vn-searchbar>
</vn-portal>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="$ctrl.department.name"
vn-name="Name">
</vn-textfield>
<vn-textfield
vn-one
label="Code"
ng-model="$ctrl.department.code"
vn-name="Code">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Chat"
ng-model="$ctrl.department.chatName"
vn-name="Chat">
</vn-textfield>
<vn-textfield type="Email"
vn-one
label="Email"
ng-model="$ctrl.department.notificationEmail"
vn-name="Email">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
ng-model="$ctrl.department.workerFk"
url="Workers/activeWithInheritedRole"
show-field="nickname"
search-function="{firstName: $search}"
label="Boss department">
</vn-autocomplete>
<vn-autocomplete
ng-model="$ctrl.department.clientFk"
url="Clients/"
show-field="name"
search-function="{firstName: $search}"
label="Self-consumption customer">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Telework"
ng-model="$ctrl.department.isTeleworking">
</vn-check>
<vn-check
label="Notify on errors"
ng-model="$ctrl.department.hasToMistake">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="worksInProduction"
ng-model="$ctrl.department.isProduction">
</vn-check>
<vn-check
label="Fill in days without physical check-ins"
ng-model="$ctrl.department.hasToRefill">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-check
label="Send check-ins by email"
ng-model="$ctrl.department.hasToSendMail">
</vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -0,0 +1,10 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
ngModule.vnComponent('vnWorkerDepartmentBasicData', {
template: require('./index.html'),
controller: Section,
bindings: {
department: '<'
}
});

View File

@ -0,0 +1,13 @@
Name: Nombre
Code: Código
Chat: Chat
Email: Email
Boss department: Jefe del departamento
Self-consumption customer: Cliente autoconsumo
Telecommutes: Teletrabaja
Notify on errors: Notificar errores
worksInProduction: Pertenece a producción
Fill in days without physical check-ins: Completar días sin registros físicos
Send check-ins by email: Enviar fichadas por email
Save: Guardar
Undo changes: Deshacer cambios

View File

@ -0,0 +1,5 @@
<vn-portal slot="menu">
<vn-worker-department-descriptor department="$ctrl.department"></vn-worker-department-descriptor>
<vn-left-menu source="department"></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -0,0 +1,39 @@
import ngModule from '../../module';
import ModuleCard from 'salix/components/module-card';
class Controller extends ModuleCard {
reload() {
const filter = {
fields: ['id', 'name', 'code', 'workerFk', 'isProduction', 'chatName',
'isTeleworking', 'notificationEmail', 'hasToRefill', 'hasToSendMail', 'hasToMistake', 'clientFk'],
include: [
{
relation: 'client',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'worker',
scope: {
fields: ['id', 'firstName', 'lastName'],
include: {
relation: 'user',
scope: {
fields: ['name']
}
}
}
}
]
};
this.$http.get(`Departments/${this.$params.id}`, {filter})
.then(res => this.department = res.data);
}
}
ngModule.vnComponent('vnWorkerDepartmentCard', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,4 @@
<slot-descriptor>
<vn-worker-department-descriptor>
</vn-worker-department-descriptor>
</slot-descriptor>

View File

@ -0,0 +1,9 @@
import ngModule from '../../module';
import DescriptorPopover from 'salix/components/descriptor-popover';
class Controller extends DescriptorPopover {}
ngModule.vnComponent('vnWorkerDepartmentDescriptorPopover', {
slotTemplate: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,49 @@
<vn-descriptor-content
module="worker"
description="$ctrl.department.name"
base-state="worker.department">
<slot-menu>
<vn-item
ng-click="deleteConfirmation.show()"
name="deleteDepartment"
translate>
Delete
</vn-item>
</slot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
label="Chat"
value="{{$ctrl.department.chatName}}">
</vn-label-value>
<vn-label-value
label="Email"
value="{{$ctrl.department.emailNotification}}">
</vn-label-value>
<vn-label-value
label="Self-consumption customer"
value="{{$ctrl.department.client.name}}">
</vn-label-value>
<vn-label-value
label="Boss department"
value="{{$ctrl.department.worker.firstName}}">
</vn-label-value>
</div>
<div class="quicklinks">
<div ng-transclude="btnOne">
<vn-quick-link vn-anchor="{state: 'worker.index', params: {q: {departmentFk: $ctrl.$params.id} } }"
vn-tooltip="Department workers"
icon="icon-worker">
</vn-quick-link>
</div>
</div>
</slot-body>
</vn-descriptor-content>
<vn-popup vn-id="summary">
<vn-worker-summary worker="$ctrl.worker"></vn-worker-summary>
</vn-popup>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteDepartment()"
question="Are you sure you want to delete this department?">
</vn-confirm>

View File

@ -0,0 +1,45 @@
import ngModule from '../../module';
import Descriptor from 'salix/components/descriptor';
class Controller extends Descriptor {
constructor($element, $, $rootScope) {
super($element, $);
this.$rootScope = $rootScope;
}
get department() {
return this.entity;
}
set department(value) {
this.entity = value;
}
filterDepartments(department, event) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.$state.go(`worker.index`,
{q: JSON.stringify({departmentFk: department.id})});
}
deleteDepartment() {
return this.$http.delete(`Departments/${this.id}`)
.then(() => {
this.$state.go('worker.department');
this.vnApp.showSuccess(this.$t('Department deleted.'));
});
}
}
Controller.$inject = ['$element', '$scope', '$rootScope'];
ngModule.vnComponent('vnWorkerDepartmentDescriptor', {
template: require('./index.html'),
controller: Controller,
bindings: {
department: '<'
}
});

View File

@ -1,83 +1,7 @@
import ngModule from '../module'; import './main';
import Section from 'salix/components/section'; import './index/';
import './summary';
class Controller extends Section { import './card';
$postLink() { import './descriptor';
this.$.treeview.fetch(); import './basic-data';
} import './descriptor-popover';
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => {
return this.$.model.data;
});
}
onSort(a, b) {
return a.name.localeCompare(b.name);
}
onDrop(dropped, dragged) {
const params = dropped ? {parentId: dropped.id} : null;
const query = `departments/${dragged.id}/moveChild`;
this.$http.post(query, params).then(() => {
this.$.treeview.move(dragged, dropped);
});
}
onCreate(parent) {
this.newChild = {
parent: parent,
name: ''
};
this.$.createNode.show();
}
onCreateDialogOpen() {
this.newChild.name = '';
}
onCreateResponse() {
try {
if (!this.newChild.name)
throw new Error(`Name can't be empty`);
const params = {name: this.newChild.name};
const parent = this.newChild.parent;
if (parent && parent.id)
params.parentId = parent.id;
const query = `departments/createChild`;
this.$http.post(query, params).then(res => {
const item = res.data;
item.parent = parent;
this.$.treeview.create(item);
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
onRemove(item) {
this.removedChild = item;
this.$.deleteNode.show();
}
onRemoveResponse() {
const childId = this.removedChild.id;
const path = `departments/${childId}/removeChild`;
this.$http.post(path).then(() => {
this.$.treeview.remove(this.removedChild);
});
}
}
ngModule.vnComponent('vnWorkerDepartment', {
template: require('./index.html'),
controller: Controller
});

View File

@ -3,6 +3,14 @@
url="departments/getLeaves" url="departments/getLeaves"
auto-load="false"> auto-load="false">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
info="Search departments by id, name or code"
on-search="$ctrl.onSearch($params)"
base-state="worker.department">
</vn-searchbar>
</vn-portal>
<form name="form" class="vn-w-md"> <form name="form" class="vn-w-md">
<vn-card class="vn-pa-md"> <vn-card class="vn-pa-md">
<vn-treeview vn-id="treeview" root-label="Departments" read-only="false" <vn-treeview vn-id="treeview" root-label="Departments" read-only="false"
@ -13,7 +21,12 @@
on-drop="$ctrl.onDrop($dropped, $dragged)" on-drop="$ctrl.onDrop($dropped, $dragged)"
on-drag-start="$ctrl.onDragStart(item)" on-drag-start="$ctrl.onDragStart(item)"
on-drag-end="$ctrl.onDragEnd(item)"> on-drag-end="$ctrl.onDragEnd(item)">
{{::item.name}} <a
style="display: block; color: inherit;"
ui-sref="worker.department.card.summary({id: item.id})"
translate>
{{::item.name}}
</a>
</vn-treeview> </vn-treeview>
</vn-card> </vn-card>
</form> </form>

View File

@ -0,0 +1,94 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
class Controller extends Section {
$postLink() {
this.$.treeview.fetch();
if (this.$params.q) {
const search = JSON.parse(this.$params.q);
this.onSearch(search);
}
}
onSearch(params) {
this.$.model.applyFilter({}, params).then(() => {
const data = this.$.model.data;
this.$.treeview.data = data;
});
}
onFetch(item) {
const params = item ? {parentId: item.id} : null;
return this.$.model.applyFilter({}, params).then(() => {
return this.$.model.data;
});
}
onSort(a, b) {
return a.name.localeCompare(b.name);
}
onDrop(dropped, dragged) {
const params = dropped ? {parentId: dropped.id} : null;
const query = `departments/${dragged.id}/moveChild`;
this.$http.post(query, params).then(() => {
this.$.treeview.move(dragged, dropped);
});
}
onCreate(parent) {
this.newChild = {
parent: parent,
name: ''
};
this.$.createNode.show();
}
onCreateDialogOpen() {
this.newChild.name = '';
}
onCreateResponse() {
try {
if (!this.newChild.name)
throw new Error(`Name can't be empty`);
const params = {name: this.newChild.name};
const parent = this.newChild.parent;
if (parent && parent.id)
params.parentId = parent.id;
const query = `departments/createChild`;
this.$http.post(query, params).then(res => {
const item = res.data;
item.parent = parent;
this.$.treeview.create(item);
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
onRemove(item) {
this.removedChild = item;
this.$.deleteNode.show();
}
onRemoveResponse() {
const childId = this.removedChild.id;
const path = `departments/${childId}/removeChild`;
this.$http.post(path).then(() => {
this.$.treeview.remove(this.removedChild);
});
}
}
ngModule.vnComponent('vnWorkerDepartmentIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,4 +1,5 @@
New department: Nuevo departamento New department: Nuevo departamento
Delete department: Eliminar departamento Delete department: Eliminar departamento
Are you sure you want to delete it?: ¿Seguro que quieres eliminarlo? Are you sure you want to delete it?: ¿Seguro que quieres eliminarlo?
Name can't be empty: El nombre esta vacio Name can't be empty: El nombre esta vacio
Department workers: Trabajadores del departamento

View File

@ -0,0 +1,9 @@
<vn-crud-model
vn-id="model"
url="Departments/filter"
limit="20"
order="id">
</vn-crud-model>
<ui-view>
<vn-worker-department-index></vn-worker-department-index>
</ui-view>

View File

@ -0,0 +1,7 @@
import ngModule from '../../module';
import ModuleMain from 'salix/components/module-main';
ngModule.vnComponent('vnWorkerDepartment', {
controller: ModuleMain,
template: require('./index.html')
});

View File

@ -0,0 +1,91 @@
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
info="Search departments by id, name or code"
on-search="$ctrl.onSearch($params)"
base-state="worker.department">
</vn-searchbar>
</vn-portal>
<vn-card class="summary">
<h5>
<span>{{summary.name}}</span>
</h5>
<vn-horizontal class="vn-pa-md">
<vn-one class="expand">
<h4 ng-show="$ctrl.isHr">
<a
ui-sref="worker.department.card.basicData({id:summary.id})">
<span translate vn-tooltip="Go to">Basic data</span>
</a>
</h4>
<h4
translates
ng-show="!$ctrl.isHr">
Basic data
</h4>
</vn-one>
<vn-one>
<vn-vertical>
<vn-label-value label="Nombre"
value="{{summary.name}}">
</vn-label-value>
<vn-label-value label="Code"
value="{{summary.code}}">
</vn-label-value>
<vn-label-value label="Chat"
value="{{summary.chatName}}">
</vn-label-value>
<vn-label-value
label="Boss department">
<span
ng-click="workerDescriptor.show($event, summary.worker.id)"
class="link">
{{summary.worker.user.name}}
</span>
</vn-label-value>
<vn-label-value
label="Email"
value="{{summary.notificationEmail}}">
</vn-label-value>
<vn-label-value label="Self-consumption customer"
value="{{summary.client.name}}">
</vn-label-value>
</vn-vertical>
</vn-one>
<vn-one>
<vn-vertical>
<vn-check
label="Telework"
ng-model="summary.isTeleworking"
disabled="true">
</vn-check>
<vn-check
label="Notify on errors"
ng-model="summary.hasToMistake"
disabled="true">
</vn-check>
<vn-check
label="worksInProduction"
ng-model="summary.isProduction"
disabled="true">
</vn-check>
<vn-check
label="Fill in days without physical check-ins"
ng-model="summary.hasToRefill"
disabled="true">
</vn-check>
<vn-check
label="Send check-ins by email"
ng-model="summary.hasToSendMail"
disabled="true">
</vn-check>
</vn-vertical>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>
<vn-worker-department-descriptor-popover
vn-id="workerDepartmentDescriptor">
</vn-worker-department-descriptor-popover>

View File

@ -0,0 +1,27 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
set department(value) {
this._department = value;
this.$.summary = null;
if (!value) return;
this.$.summary = this.department;
}
get department() {
return this._department;
}
get isHr() {
return this.aclService.hasAny(['hr']);
}
}
ngModule.component('vnWorkerDepartmentSummary', {
template: require('./index.html'),
controller: Controller,
bindings: {
department: '<'
}
});

View File

@ -0,0 +1,12 @@
Name: Nombre
Code: Código
Chat: Chat
Boss department: Jefe de departamento
Email: Email
Self-consumption customer: Cliente autoconsumo
Telework: Teletrabaja
Notify on errors: Notificar errores
worksInProduction: Pertenece a producción
Fill in days without physical check-ins: Completar días sin registros físicos
Send check-ins by email: Enviar fichadas por mail
Are you sure you want to delete this department?: ¿Estás seguro de que quieres eliminar este departamento?

View File

@ -0,0 +1,8 @@
@import "./variables";
vn-worker-department-summary {
.expand {
min-width: 100%;
}
}

View File

@ -24,6 +24,9 @@
"description": "Wikipedia" "description": "Wikipedia"
}, },
{"state": "worker.card.workerLog", "icon": "history"} {"state": "worker.card.workerLog", "icon": "history"}
],
"department": [
{"state": "worker.department.card.basicData", "icon": "settings"}
] ]
}, },
"keybindings": [ "keybindings": [
@ -118,12 +121,35 @@
"worker": "$ctrl.worker" "worker": "$ctrl.worker"
} }
}, { }, {
"url" : "/department", "url": "/department?q",
"state": "worker.department", "state": "worker.department",
"component": "vn-worker-department", "component": "vn-worker-department",
"description": "Departments", "description":"Departments"
"acl": ["hr"]
}, { }, {
"url": "/:id",
"state": "worker.department.card",
"component": "vn-worker-department-card",
"abstract": true,
"description": "Detail"
}, {
"url" : "/summary",
"state": "worker.department.card.summary",
"component": "vn-worker-department-summary",
"description": "Summary",
"params": {
"department": "$ctrl.department"
}
},
{
"url": "/basic-data",
"state": "worker.department.card.basicData",
"component": "vn-worker-department-basic-data",
"description": "Basic data",
"params": {
"department": "$ctrl.department"
}
},
{
"url": "/dms", "url": "/dms",
"state": "worker.card.dms", "state": "worker.card.dms",
"abstract": true, "abstract": true,

View File

@ -27,15 +27,19 @@
<vn-label-value label="Email" no-ellipsize <vn-label-value label="Email" no-ellipsize
value="{{worker.user.emailUser.email}}"> value="{{worker.user.emailUser.email}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Department" <vn-label-value label="Department">
value="{{worker.department.department.name}}"> <span
ng-click="workerDepartmentDescriptor.show($event, worker.department.department.id)"
class="link">
{{worker.department.department.name}}
</span>
</vn-label-value> </vn-label-value>
<vn-label-value <vn-label-value
label="Boss"> label="Boss">
<span <span
ng-click="workerDescriptor.show($event, worker.boss.id)" ng-click="workerDescriptor.show($event, worker.boss.id)"
class="link"> class="link">
{{::worker.boss.nickname}} {{::worker.boss.name}}
</span> </span>
</vn-label-value> </vn-label-value>
<vn-label-value label="Mobile extension" <vn-label-value label="Mobile extension"
@ -71,3 +75,6 @@
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="workerDescriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<vn-worker-department-descriptor-popover
vn-id="workerDepartmentDescriptor">
</vn-worker-department-descriptor-popover>

View File

@ -38,7 +38,7 @@ class Controller extends Summary {
}, },
{ {
relation: 'boss', relation: 'boss',
scope: {fields: ['id', 'nickname']} scope: {fields: ['id', 'name']}
}, },
{ {
relation: 'sip', relation: 'sip',
@ -48,7 +48,8 @@ class Controller extends Summary {
relation: 'department', relation: 'department',
scope: { scope: {
include: { include: {
relation: 'department' relation: 'department',
scope: {fields: ['id', 'code', 'name']}
} }
} }
} }