diff --git a/CHANGELOG.md b/CHANGELOG.md index f8a04cfe8..41374b4df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,25 +8,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [2304.01] - 2023-02-09 ### Added -- +- (Trabajadores -> Nuevo trabajador) Nueva sección ### Changed -- +- ### Fixed -- +- ## [2302.01] - 2023-01-26 ### Added -- (General -> Inicio) Permite recuperar la contraseña -- (Tickets -> Opciones) Subir albarán a Docuware -- (Tickets -> Opciones) Enviar correo con PDF de Docuware -- (Artículos -> Datos Básicos) Añadido campo Unidades/Caja +- (General -> Inicio) Permite recuperar la contraseña +- (Tickets -> Opciones) Subir albarán a Docuware +- (Tickets -> Opciones) Enviar correo con PDF de Docuware +- (Artículos -> Datos Básicos) Añadido campo Unidades/Caja ### Changed -- (Reclamaciones -> Descriptor) Cambiado el campo Agencia por Zona -- (Tickets -> Líneas preparadas) Actualizada sección para que sea más visual +- (Reclamaciones -> Descriptor) Cambiado el campo Agencia por Zona +- (Tickets -> Líneas preparadas) Actualizada sección para que sea más visual ### Fixed - (General) Al utilizar el traductor de Google se descuadraban los iconos diff --git a/db/changes/230401/.gitkeep b/db/changes/230401/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/db/changes/230401/00-createWorker.sql b/db/changes/230401/00-createWorker.sql new file mode 100644 index 000000000..7ca2c41ee --- /dev/null +++ b/db/changes/230401/00-createWorker.sql @@ -0,0 +1,24 @@ +UPDATE `salix`.`ACL` +SET accessType='READ' +WHERE model='Worker' + AND property='*' + AND accessType='*' + AND permission='ALLOW' + AND principalType='ROLE' + AND principalId='employee'; + + +INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) + VALUES + ('Worker', 'updateAttributes', 'WRITE', 'ALLOW', 'ROLE', 'hr'), + ('Worker', 'createAbsence', '*', 'ALLOW', 'ROLE', 'employee'), + ('Worker', 'updateAbsence', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('Worker', 'deleteAbsence', '*', 'ALLOW', 'ROLE', 'employee'), + ('Worker', 'new', 'WRITE', 'ALLOW', 'ROLE', 'hr'), + ('Role', '*', 'READ', 'ALLOW', 'ROLE', 'hr'); + +ALTER TABLE `vn`.`workerConfig` ADD roleFk int(10) unsigned NOT NULL COMMENT 'Rol por defecto al dar de alta un trabajador nuevo'; +UPDATE `vn`.`workerConfig` + SET roleFk = 1 + WHERE id = 1; + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 27e72708f..218fcb9ca 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2629,7 +2629,7 @@ INSERT INTO `vn`.`machineWorker` (`workerFk`, `machineFk`, `inTimed`, `outTimed` INSERT INTO `vn`.`zoneExclusion` (`id`, `zoneFk`, `dated`, `created`, `userFk`) VALUES - (1, 1, DATE_ADD(CURDATE(), INTERVAL (IF(DAYOFWEEK(CURDATE())<=7, 7, 14) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100), + (1, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL (IF(DAYOFWEEK(util.VN_CURDATE())<=7, 7, 14) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100), (2, 1, DATE_ADD(util.VN_CURDATE(), INTERVAL (IF(DAYOFWEEK(util.VN_CURDATE())<=8, 8, 15) - DAYOFWEEK(util.VN_CURDATE())) DAY), util.VN_CURDATE(), 100); INSERT INTO `vn`.`zoneExclusionGeo` (`zoneExclusionFk`, `geoFk`) @@ -2751,10 +2751,14 @@ INSERT INTO `vn`.`mdbApp` (`app`, `baselineBranchFk`, `userFk`, `locked`) VALUES ('foo', 'master', NULL, NULL), ('bar', 'test', 9, util.VN_NOW()); -INSERT INTO `vn`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`) - VALUES - (1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1); +INSERT INTO `vn`.`profileType` (`id`, `name`) + VALUES + (1, 'working'); + +INSERT INTO `vn`.`newWorkerConfig` (`id`, `street`, `provinceFk`, `companyFk`, `profileTypeFk`, `roleFk`) + VALUES + (1, 'S/ ', 1, 442, 1, 1); INSERT INTO `salix`.`url` (`appName`, `environment`, `url`) VALUES @@ -2765,3 +2769,7 @@ INSERT INTO `vn`.`payDemDetail` (`id`, `detail`) VALUES (1, 1); +INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`) + VALUES + (1, NULL, 1); + diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index e9d9d0580..a1412f431 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -58,6 +58,7 @@ export default { deleteAccount: '.vn-menu [name="deleteUser"]', setPassword: '.vn-menu [name="setPassword"]', activateAccount: '.vn-menu [name="enableAccount"]', + disableAccount: '.vn-menu [name="disableAccount"]', activateUser: '.vn-menu [name="activateUser"]', deactivateUser: '.vn-menu [name="deactivateUser"]', newPassword: 'vn-textfield[ng-model="$ctrl.newPassword"]', @@ -969,6 +970,7 @@ export default { confirmButton: '.vn-confirm.shown button[response="accept"]' }, workerSummary: { + summaryIcon: 'vn-worker-descriptor a[title="Go to module summary"]', header: 'vn-worker-summary h5', id: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(3) > section > span', email: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(4) > section > span', @@ -1021,6 +1023,25 @@ export default { furlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(4)', halfFurlough: 'vn-worker-calendar > vn-side-menu [name="absenceTypes"] > vn-chip:nth-child(5)', }, + workerCreate: { + newWorkerButton: 'vn-worker-index a[ui-sref="worker.create"]', + firstname: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.firstName"]', + lastname: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.lastNames"]', + birth: 'vn-worker-create vn-date-picker[ng-model="$ctrl.worker.birth"]', + fi: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.fi"]', + code: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.code"]', + phone: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.phone"]', + city: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.city"]', + postcode: 'vn-worker-create vn-datalist[ng-model="$ctrl.worker.postcode"]', + street: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.street"]', + user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]', + email: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.email"]', + boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]', + role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]', + iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]', + switft: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bankEntityFk"]', + createButton: 'vn-worker-create vn-submit[label="Create"]', + }, invoiceOutIndex: { topbarSearch: 'vn-searchbar', searchResult: 'vn-invoice-out-index vn-card > vn-table > div > vn-tbody > a.vn-tr', diff --git a/e2e/paths/03-worker/06_create.spec.js b/e2e/paths/03-worker/06_create.spec.js new file mode 100644 index 000000000..5f6f5cf9f --- /dev/null +++ b/e2e/paths/03-worker/06_create.spec.js @@ -0,0 +1,74 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Worker create path', () => { + let browser; + let page; + let newWorker; + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('hr', 'worker'); + await page.waitToClick(selectors.workerCreate.newWorkerButton); + await page.waitForState('worker.create'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should insert default data', async() => { + await page.write(selectors.workerCreate.firstname, 'Victor'); + await page.write(selectors.workerCreate.lastname, 'Von Doom'); + await page.write(selectors.workerCreate.fi, '78457139E'); + await page.write(selectors.workerCreate.phone, '12356789'); + await page.write(selectors.workerCreate.postcode, '46680'); + await page.write(selectors.workerCreate.street, 'S/ Doomstadt'); + await page.write(selectors.workerCreate.email, 'doctorDoom@marvel.com'); + await page.write(selectors.workerCreate.iban, 'ES9121000418450200051332'); + await page.autocompleteSearch(selectors.workerCreate.switft, 'BBKKESMMMMM'); + + // should check for autocompleted worker code and worker user name + const workerCode = await page + .waitToGetProperty(selectors.workerCreate.code, 'value'); + + newWorker = await page + .waitToGetProperty(selectors.workerCreate.user, 'value'); + + expect(workerCode).toEqual('VVD'); + expect(newWorker).toContain('victorvd'); + + // should fail if necessary data is void + await page.waitToClick(selectors.workerCreate.createButton); + let message = await page.waitForSnackbar(); + + expect(message.text).toContain('is a required argument'); + + // should create a new worker and go to worker basic data' + await page.pickDate(selectors.workerCreate.birth, new Date(1962, 8, 5)); + await page.autocompleteSearch(selectors.workerCreate.boss, 'deliveryBoss'); + await page.waitToClick(selectors.workerCreate.createButton); + message = await page.waitForSnackbar(); + await page.waitForState('worker.card.basicData'); + + expect(message.text).toContain('Data saved!'); + + // 'rollback' + await page.loginAndModule('sysadmin', 'account'); + await page.accessToSearchResult(newWorker); + + await page.waitToClick(selectors.accountDescriptor.menuButton); + await page.waitToClick(selectors.accountDescriptor.deactivateUser); + await page.waitToClick(selectors.accountDescriptor.acceptButton); + message = await page.waitForSnackbar(); + + expect(message.text).toContain('User deactivated!'); + + await page.waitToClick(selectors.accountDescriptor.menuButton); + await page.waitToClick(selectors.accountDescriptor.disableAccount); + await page.waitToClick(selectors.accountDescriptor.acceptButton); + message = await page.waitForSnackbar(); + + expect(message.text).toContain('Account disabled!'); + }); +}); diff --git a/front/salix/components/reset-password/locale/es.yml b/front/salix/components/reset-password/locale/es.yml index 0771d5dc3..30893a152 100644 --- a/front/salix/components/reset-password/locale/es.yml +++ b/front/salix/components/reset-password/locale/es.yml @@ -1,6 +1,6 @@ Reset password: Restrablecer contraseña New password: Nueva contraseña -Repeat password: Repetir contraseñaç +Repeat password: Repetir contraseña Password changed!: ¡Contraseña cambiada! Password requirements: > La contraseña debe tener al menos {{ length }} caracteres de longitud, diff --git a/loopback/locale/es.json b/loopback/locale/es.json index f6f305dc3..d65054f37 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -252,9 +252,12 @@ "Receipt's bank was not found": "No se encontró el banco del recibo", "This receipt was not compensated": "Este recibo no ha sido compensado", "Client's email was not found": "No se encontró el email del cliente", + "This worker code already exists": "Este codigo de trabajador ya existe", + "This personal mail already exists": "Este correo personal ya existe", + "This worker already exists": "Este trabajador ya existe", "App name does not exist": "El nombre de aplicación no es válido", "Try again": "Vuelve a intentarlo", "Aplicación bloqueada por el usuario 9": "Aplicación bloqueada por el usuario 9", "Failed to upload file": "Error al subir archivo", "The DOCUWARE PDF document does not exists": "The DOCUWARE PDF document does not exists" -} \ No newline at end of file +} diff --git a/modules/worker/back/methods/worker/new.js b/modules/worker/back/methods/worker/new.js new file mode 100644 index 000000000..a7bb883cd --- /dev/null +++ b/modules/worker/back/methods/worker/new.js @@ -0,0 +1,256 @@ +/* eslint max-len: ["error", { "code": 130 }]*/ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethodCtx('new', { + description: 'Creates a new worker and returns the id', + accessType: 'WRITE', + accepts: [ + { + arg: 'fi', + type: 'string', + description: `The worker fi`, + required: true, + }, + { + arg: 'name', + type: 'string', + description: `The user name`, + required: true, + }, + { + arg: 'firstName', + type: 'string', + description: `The worker firstname`, + required: true, + }, + { + arg: 'lastNames', + type: 'string', + description: `The worker lastnames`, + required: true, + }, + { + arg: 'email', + type: 'string', + description: `The worker email`, + required: true, + }, + { + arg: 'street', + type: 'string', + description: `The worker address`, + required: true, + }, + { + arg: 'city', + type: 'string', + description: `The worker city`, + required: true, + }, + { + arg: 'provinceFk', + type: 'number', + description: `The worker province`, + required: true, + }, + { + arg: 'iban', + type: 'string', + description: `The worker iban`, + required: true, + }, + { + arg: 'bankEntityFk', + type: 'number', + description: `The worker bank entity`, + required: true, + }, + { + arg: 'companyFk', + type: 'number', + description: `The worker company`, + required: true, + }, + { + arg: 'postcode', + type: 'string', + description: `The worker postcode`, + required: true, + }, + { + arg: 'phone', + type: 'string', + description: `The worker phone`, + required: true, + }, + { + arg: 'code', + type: 'string', + description: `The worker code`, + required: true, + }, + { + arg: 'bossFk', + type: 'number', + description: `The worker boss`, + required: true, + }, + { + arg: 'birth', + type: 'date', + description: `The worker birth`, + required: true, + } + ], + returns: { + type: 'number', + root: true, + }, + http: { + path: `/new`, + verb: 'POST', + }, + }); + + Self.new = async(ctx, options) => { + const models = Self.app.models; + const myOptions = {}; + const args = ctx.args; + + let tx; + + if (typeof options == 'object') Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + let client; + + try { + client = await models.Client.findOne( + { + where: {fi: args.fi}, + }, + myOptions + ); + + if (!client) { + const nickname = args.firstName.concat(' ', args.lastNames); + const workerConfig = await models.WorkerConfig.findOne({fields: ['roleFk']}); + const [randomPassword] = await models.Worker.rawSql( + 'SELECT account.passwordGenerate() as password;' + ); + + const user = await models.Account.create( + { + name: args.name, + nickname, + password: randomPassword.password, + email: args.email, + roleFk: workerConfig.roleFk, + }, + myOptions + ); + + await models.UserAccount.create( + { + id: user.id, + }, + myOptions + ); + + await models.Worker.rawSql( + 'CALL vn.clientCreate(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', + [ + args.firstName, + args.lastNames, + args.fi, + args.street, + args.postalCode, + args.city, + args.provinceFk, + args.companyFk, + args.phone, + args.email, + user.id, + ], + myOptions + ); + + const address = await models.Address.create( + { + clientFk: user.id, + street: args.street, + city: args.city, + provinceFk: args.provinceFk, + postalCode: args.postalCode, + mobile: args.phone, + nickname: nickname, + isDefaultAddress: true, + }, + myOptions + ); + + client = await models.Client.findById( + user.id, + {fields: ['id', 'name', 'socialName', 'street', 'city', 'iban', 'bankEntityFk', 'defaultAddressFk']}, + myOptions + ); + + await client.updateAttributes( + { + iban: args.iban, + bankEntityFk: args.bankEntityFk, + defaultAddressFk: address.id, + }, + myOptions + ); + } + + const user = await models.Account.findById(client.id, null, myOptions); + await user.updateAttribute('email', args.email, myOptions); + + await models.Worker.rawSql( + 'CALL vn.workerCreate(?, ?, ?, ?, ?, ?, ?)', + [ + args.firstName, + args.lastNames, + args.code, + args.bossFk, + client.id, + args.fi, + args.birth, + ], + myOptions + ); + + if (tx) await tx.commit(); + } catch (error) { + if (tx) await tx.rollback(); + const code = error.code; + const message = error.sqlMessage; + + if (code === 'ER_DUP_ENTRY' && message.includes(`for key 'mail'`)) + throw new UserError(`This personal mail already exists`); + + if (code === 'ER_DUP_ENTRY' && message.includes(`CodigoTrabajador_UNIQUE`)) + throw new UserError(`This worker code already exists`); + + if (code === 'ER_DUP_ENTRY' && message.includes(`PRIMARY`)) + throw new UserError(`This worker already exists`); + + throw error; + } + + await models.user.resetPassword({ + email: args.email, + emailTemplate: 'worker-welcome', + id: client.id + }); + + return {id: client.id}; + }; +}; diff --git a/modules/worker/back/methods/worker/specs/new.spec.js b/modules/worker/back/methods/worker/specs/new.spec.js new file mode 100644 index 000000000..f695ab80e --- /dev/null +++ b/modules/worker/back/methods/worker/specs/new.spec.js @@ -0,0 +1,139 @@ +const models = require('vn-loopback/server/server').models; +const LoopBackContext = require('loopback-context'); + +describe('Worker new', () => { + beforeAll(async() => { + const activeCtx = { + accessToken: {userId: 9}, + http: { + req: { + headers: {origin: 'http://localhost'} + } + } + }; + + spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({ + active: activeCtx + }); + }); + + const employeeId = 1; + const defaultWorker = { + fi: '78457139E', + name: 'defaultWorker', + firstName: 'default', + lastNames: 'worker', + email: 'defaultWorker@mydomain.com', + street: 'S/ defaultWorkerStreet', + city: 'defaultWorkerCity', + provinceFk: 1, + iban: 'ES8304879798578129532677', + bankEntityFk: 128, + companyFk: 442, + postcode: '46680', + phone: '123456789', + code: 'DWW', + bossFk: 9, + birth: '2022-12-11T23:00:00.000Z' + }; + + it('should return error if personal mail already exists', async() => { + const user = await models.Account.findById(employeeId, {fields: ['email']}); + + const tx = await models.Worker.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + const ctx = { + args: Object.assign({}, defaultWorker, {email: user.email}) + }; + + await models.Worker.new(ctx, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual('This personal mail already exists'); + }); + + it('should return error if worker code already exists', async() => { + const worker = await models.Worker.findById(employeeId, {fields: ['code']}); + + const tx = await models.Worker.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + const ctx = { + args: Object.assign({}, defaultWorker, {code: worker.code}) + }; + + await models.Worker.new(ctx, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual('This worker code already exists'); + }); + + it('should return error if worker already exists', async() => { + const worker = await models.Client.findById(employeeId, {fields: ['fi']}); + + const tx = await models.Worker.beginTransaction({}); + + let error; + try { + const options = {transaction: tx}; + const ctx = { + args: Object.assign({}, defaultWorker, {fi: worker.fi}) + }; + await models.Worker.new(ctx, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toEqual('This worker already exists'); + }); + + it('should create a new worker', async() => { + const newWorker = await models.Worker.new({args: defaultWorker}); + + await models.Worker.destroyById(newWorker.id); + await models.Address.destroyAll({clientFk: newWorker.id}); + await models.Mandate.destroyAll({clientFk: newWorker.id}); + await models.Client.destroyById(newWorker.id); + await models.Account.destroyById(newWorker.id); + + expect(newWorker.id).toBeDefined(); + }); + + it('should create a new worker in client', async() => { + const bruceWayneId = 1101; + const client = await models.Client.findById(bruceWayneId, {fields: ['fi', 'email']}); + + const newWorkerData = { + args: Object.assign( + {}, + defaultWorker, + { + fi: client.fi, + email: client.email + }) + }; + const newWorker = await models.Worker.new(newWorkerData); + + await models.Worker.destroyById(newWorker.id); + + expect(newWorker.id).toEqual(bruceWayneId); + }); +}); diff --git a/modules/worker/back/model-config.json b/modules/worker/back/model-config.json index 3f3416504..7e03c8a23 100644 --- a/modules/worker/back/model-config.json +++ b/modules/worker/back/model-config.json @@ -1,7 +1,7 @@ { "AbsenceType": { "dataSource": "vn" - }, + }, "Calendar": { "dataSource": "vn" }, @@ -16,13 +16,19 @@ }, "Department": { "dataSource": "vn" - }, + }, + "Device": { + "dataSource": "vn" + }, "EducationLevel": { "dataSource": "vn" }, "Journey": { "dataSource": "vn" }, + "ProfileType":{ + "dataSource": "vn" + }, "Time": { "dataSource": "vn" }, @@ -32,39 +38,42 @@ "WorkCenterHoliday": { "dataSource": "vn" }, - "WorkerDms": { + "Worker": { "dataSource": "vn" }, - "Worker": { + "WorkerConfig": { + "dataSource": "vn" + }, + "WorkerDepartment": { + "dataSource": "vn" + }, + "WorkerDisableExcluded": { + "dataSource": "vn" + }, + "WorkerDms": { "dataSource": "vn" }, "WorkerLabour": { "dataSource": "vn" }, + "WorkerLog": { + "dataSource": "vn" + }, "WorkerMana": { "dataSource": "vn" }, + "WorkerMedia": { + "dataSource": "vn" + }, "WorkerTeam": { "dataSource": "vn" }, "WorkerTeamCollegues": { "dataSource": "vn" }, - "WorkerMedia": { - "dataSource": "vn" - }, - "WorkerDepartment": { - "dataSource": "vn" - }, "WorkerTimeControl": { "dataSource": "vn" }, - "Device": { - "dataSource": "vn" - }, - "WorkerLog": { - "dataSource": "vn" - }, "WorkerTimeControlConfig": { "dataSource": "vn" }, @@ -73,9 +82,6 @@ }, "WorkerTimeControlMail": { "dataSource": "vn" - }, - "WorkerDisableExcluded": { - "dataSource": "vn" } } diff --git a/modules/worker/back/models/profile-type.json b/modules/worker/back/models/profile-type.json new file mode 100644 index 000000000..d1d750de8 --- /dev/null +++ b/modules/worker/back/models/profile-type.json @@ -0,0 +1,19 @@ +{ + "name": "ProfileType", + "base": "VnModel", + "options": { + "mysql": { + "table": "profileType" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "name": { + "type": "string" + } + } +} diff --git a/modules/worker/back/models/worker-config.json b/modules/worker/back/models/worker-config.json new file mode 100644 index 000000000..05cdfef42 --- /dev/null +++ b/modules/worker/back/models/worker-config.json @@ -0,0 +1,27 @@ +{ + "name": "WorkerConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "workerConfig" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "roleFk": { + "type": "number" + } + }, + "acls": [ + { + "accessType": "READ", + "principalType": "ROLE", + "principalId": "$everyone", + "permission": "ALLOW" + } + ] +} diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index ec6c4af28..e66259cd0 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -13,4 +13,5 @@ module.exports = Self => { require('../methods/worker/contracts')(Self); require('../methods/worker/holidays')(Self); require('../methods/worker/activeContract')(Self); + require('../methods/worker/new')(Self); }; diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json index 3d41707ce..e3a941dd3 100644 --- a/modules/worker/back/models/worker.json +++ b/modules/worker/back/models/worker.json @@ -52,6 +52,9 @@ }, "mobileExtension": { "type" : "number" + }, + "code": { + "type" : "string" } }, "relations": { @@ -91,4 +94,4 @@ "foreignKey": "sectorFk" } } -} \ No newline at end of file +} diff --git a/modules/worker/front/create/index.html b/modules/worker/front/create/index.html new file mode 100644 index 000000000..5f5ab9d07 --- /dev/null +++ b/modules/worker/front/create/index.html @@ -0,0 +1,194 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + {{code}} - {{town.name}} ({{town.province.name}}, + {{town.province.country.country}}) + + + + + + + + {{name}} ({{country.country}}) + + + + + + {{name}}, {{province.name}} + ({{province.country.country}}) + + + + + + + + + + + + + + + + + + + + + + {{bic}} {{name}} + + + + + + + + + + + + + +
+ + + diff --git a/modules/worker/front/create/index.js b/modules/worker/front/create/index.js new file mode 100644 index 000000000..7e837fe02 --- /dev/null +++ b/modules/worker/front/create/index.js @@ -0,0 +1,127 @@ +import ngModule from '../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + this.worker = {companyFk: this.vnConfig.user.companyFk}; + } + + onSubmit() { + return this.$.watcher.submit().then(json => { + this.$state.go('worker.card.basicData', {id: json.data.id}); + }); + } + + autofillBic() { + if (!this.worker || !this.worker.iban) return; + + let bankEntityId = parseInt(this.worker.iban.substr(4, 4)); + let filter = {where: {id: bankEntityId}}; + + if (this.ibanCountry != 'ES') return; + + this.$http.get(`BankEntities`, {filter}).then(response => { + const hasData = response.data && response.data[0]; + + if (hasData) + this.worker.bankEntityFk = response.data[0].id; + else if (!hasData) + this.worker.bankEntityFk = null; + }); + } + + generateCodeUser() { + if (!this.worker.firstName || !this.worker.lastNames) return; + + const totalName = this.worker.firstName.concat(' ' + this.worker.lastNames).toLowerCase(); + const totalNameArray = totalName.split(' '); + let newCode = ''; + + for (let part of totalNameArray) + newCode += part.charAt(0); + + this.worker.code = newCode.toUpperCase().slice(0, 3); + this.worker.name = totalNameArray[0] + newCode.slice(1); + + if (!this.worker.companyFk) + this.worker.companyFk = this.vnConfig.user.companyFk; + } + + get province() { + return this._province; + } + + // Province auto complete + set province(selection) { + this._province = selection; + + if (!selection) return; + + const country = selection.country; + + if (!this.worker.countryFk) + this.worker.countryFk = country.id; + } + + get town() { + return this._town; + } + + // Town auto complete + set town(selection) { + this._town = selection; + + if (!selection) return; + + const province = selection.province; + const country = province.country; + const postcodes = selection.postcodes; + + if (!this.worker.provinceFk) + this.worker.provinceFk = province.id; + + if (!this.worker.countryFk) + this.worker.countryFk = country.id; + + if (postcodes.length === 1) + this.worker.postcode = postcodes[0].code; + } + + get postcode() { + return this._postcode; + } + + // Postcode auto complete + set postcode(selection) { + this._postcode = selection; + + if (!selection) return; + + const town = selection.town; + const province = town.province; + const country = province.country; + + if (!this.worker.city) + this.worker.city = town.name; + + if (!this.worker.provinceFk) + this.worker.provinceFk = province.id; + + if (!this.worker.countryFk) + this.worker.countryFk = country.id; + } + + onResponse(response) { + this.worker.postcode = response.code; + this.worker.city = response.city; + this.worker.provinceFk = response.provinceFk; + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnWorkerCreate', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/worker/front/create/index.spec.js b/modules/worker/front/create/index.spec.js new file mode 100644 index 000000000..c2e9acce0 --- /dev/null +++ b/modules/worker/front/create/index.spec.js @@ -0,0 +1,133 @@ +import './index'; + +describe('Worker', () => { + describe('Component vnWorkerCreate', () => { + let $scope; + let $state; + let controller; + + beforeEach(ngModule('worker')); + + beforeEach(inject(($componentController, $rootScope, _$state_) => { + $scope = $rootScope.$new(); + $state = _$state_; + $scope.watcher = { + submit: () => { + return { + then: callback => { + callback({data: {id: '1234'}}); + } + }; + } + }; + const $element = angular.element(''); + controller = $componentController('vnWorkerCreate', {$element, $scope}); + controller.worker = {}; + controller.vnConfig = {user: {companyFk: 1}}; + })); + + describe('onSubmit()', () => { + it(`should call submit() on the watcher then expect a callback`, () => { + jest.spyOn($state, 'go'); + controller.onSubmit(); + + expect(controller.$state.go).toHaveBeenCalledWith('worker.card.basicData', {id: '1234'}); + }); + }); + + describe('province() setter', () => { + it(`should set countryFk property`, () => { + controller.worker.countryFk = null; + controller.province = { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + }; + + expect(controller.worker.countryFk).toEqual(2); + }); + }); + + describe('town() setter', () => { + it(`should set provinceFk property`, () => { + controller.town = { + provinceFk: 1, + code: 46001, + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + }, + postcodes: [] + }; + + expect(controller.worker.provinceFk).toEqual(1); + }); + + it(`should set provinceFk property and fill the postalCode if there's just one`, () => { + controller.town = { + provinceFk: 1, + code: 46001, + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + }, + postcodes: [{code: '46001'}] + }; + + expect(controller.worker.provinceFk).toEqual(1); + expect(controller.worker.postcode).toEqual('46001'); + }); + }); + + describe('postcode() setter', () => { + it(`should set the town, provinceFk and contryFk properties`, () => { + controller.postcode = { + townFk: 1, + code: 46001, + town: { + id: 1, + name: 'New York', + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + } + } + }; + + expect(controller.worker.city).toEqual('New York'); + expect(controller.worker.provinceFk).toEqual(1); + expect(controller.worker.countryFk).toEqual(2); + }); + }); + + describe('generateCodeUser()', () => { + it(`should generate worker code, name and company `, () => { + controller.worker = { + firstName: 'default', + lastNames: 'generate worker' + }; + + controller.generateCodeUser(); + + expect(controller.worker.code).toEqual('DGW'); + expect(controller.worker.name).toEqual('defaultgw'); + expect(controller.worker.companyFk).toEqual(controller.vnConfig.user.companyFk); + }); + }); + }); +}); diff --git a/modules/worker/front/create/locale/es.yml b/modules/worker/front/create/locale/es.yml new file mode 100644 index 000000000..8c79d770c --- /dev/null +++ b/modules/worker/front/create/locale/es.yml @@ -0,0 +1,12 @@ +Firstname: Nombre +Lastname: Apellidos +Fi: DNI/NIF/NIE +Birth: Fecha de nacimiento +Code: Código de trabajador +Province: Provincia +City: Población +ProfileType: Tipo de perfil +Street: Dirección +Postcode: Código postal +Web user: Usuario Web +Access permission: Permiso de acceso diff --git a/modules/worker/front/index.js b/modules/worker/front/index.js index f703e7c21..97126407c 100644 --- a/modules/worker/front/index.js +++ b/modules/worker/front/index.js @@ -4,6 +4,7 @@ import './main'; import './index/'; import './summary'; import './card'; +import './create'; import './descriptor'; import './descriptor-popover'; import './search-panel'; diff --git a/modules/worker/front/index/index.html b/modules/worker/front/index/index.html index 98df416b4..7044ca551 100644 --- a/modules/worker/front/index/index.html +++ b/modules/worker/front/index/index.html @@ -6,23 +6,23 @@ class="vn-w-sm">
-
{{::worker.nickname}}
- - - -
@@ -42,8 +42,16 @@
+ + + - \ No newline at end of file + diff --git a/modules/worker/front/index/locale/es.yml b/modules/worker/front/index/locale/es.yml new file mode 100644 index 000000000..df6383273 --- /dev/null +++ b/modules/worker/front/index/locale/es.yml @@ -0,0 +1 @@ +New worker: Nuevo trabajador diff --git a/modules/worker/front/routes.json b/modules/worker/front/routes.json index ca33eaa76..dad55512b 100644 --- a/modules/worker/front/routes.json +++ b/modules/worker/front/routes.json @@ -16,7 +16,7 @@ {"state": "worker.card.timeControl", "icon": "access_time"}, {"state": "worker.card.dms.index", "icon": "cloud_upload"}, { - "icon": "icon-wiki", + "icon": "icon-wiki", "external":true, "url": "http://wiki.verdnatura.es", "description": "Wikipedia" @@ -134,6 +134,13 @@ "worker": "$ctrl.worker" }, "acl": ["hr"] + }, + { + "url": "/create", + "state": "worker.create", + "component": "vn-worker-create", + "description": "New worker", + "acl": ["hr"] } ] -} \ No newline at end of file +} diff --git a/print/templates/email/email-verify/email-verify.js b/print/templates/email/email-verify/email-verify.js index 7f0b80a13..4f2b29266 100755 --- a/print/templates/email/email-verify/email-verify.js +++ b/print/templates/email/email-verify/email-verify.js @@ -10,7 +10,7 @@ module.exports = { }, props: { url: { - type: [String], + type: String, required: true } } diff --git a/print/templates/email/recover-password/recover-password.js b/print/templates/email/recover-password/recover-password.js index b589411a9..d8448f370 100755 --- a/print/templates/email/recover-password/recover-password.js +++ b/print/templates/email/recover-password/recover-password.js @@ -10,7 +10,7 @@ module.exports = { }, props: { url: { - type: [String], + type: String, required: true } } diff --git a/print/templates/email/worker-welcome/assets/css/import.js b/print/templates/email/worker-welcome/assets/css/import.js new file mode 100644 index 000000000..4b4bb7086 --- /dev/null +++ b/print/templates/email/worker-welcome/assets/css/import.js @@ -0,0 +1,11 @@ +const Stylesheet = require(`vn-print/core/stylesheet`); + +const path = require('path'); +const vnPrintPath = path.resolve('print'); + +module.exports = new Stylesheet([ + `${vnPrintPath}/common/css/spacing.css`, + `${vnPrintPath}/common/css/misc.css`, + `${vnPrintPath}/common/css/layout.css`, + `${vnPrintPath}/common/css/email.css`]) + .mergeStyles(); diff --git a/print/templates/email/worker-welcome/locale/es.yml b/print/templates/email/worker-welcome/locale/es.yml new file mode 100644 index 000000000..d53a4e1f0 --- /dev/null +++ b/print/templates/email/worker-welcome/locale/es.yml @@ -0,0 +1,8 @@ +subject: Bienvenido a Verdnatura +title: "¡Te damos la bienvenida!" +dearWorker: Estimado trabajador +workerData: 'Estos son los datos de tu usuario de Verdnatura. + Usuario: {0}. Haz click aquí para + establecer tu contraseña + .' diff --git a/print/templates/email/worker-welcome/sql/worker.sql b/print/templates/email/worker-welcome/sql/worker.sql new file mode 100644 index 000000000..f75d135d9 --- /dev/null +++ b/print/templates/email/worker-welcome/sql/worker.sql @@ -0,0 +1,7 @@ +SELECT + u.id, + u.name, + e.email +FROM account.user u + LEFT JOIN account.emailUser e ON e.userFk = u.id +WHERE u.id = ?; diff --git a/print/templates/email/worker-welcome/worker-welcome.html b/print/templates/email/worker-welcome/worker-welcome.html new file mode 100644 index 000000000..fbb05d149 --- /dev/null +++ b/print/templates/email/worker-welcome/worker-welcome.html @@ -0,0 +1,9 @@ + +
+
+

{{ $t('title', [id]) }}

+

{{ $t('dearWorker') }},

+

+
+
+
diff --git a/print/templates/email/worker-welcome/worker-welcome.js b/print/templates/email/worker-welcome/worker-welcome.js new file mode 100755 index 000000000..043a172a1 --- /dev/null +++ b/print/templates/email/worker-welcome/worker-welcome.js @@ -0,0 +1,27 @@ +const Component = require(`vn-print/core/component`); +const emailBody = new Component('email-body'); + +module.exports = { + name: 'worker-welcome', + async serverPrefetch() { + this.worker = await this.fetchWorker(this.id); + }, + methods: { + fetchWorker(id) { + return this.findOneFromDef('worker', [id]); + }, + }, + components: { + 'email-body': emailBody.build(), + }, + props: { + id: { + type: Number, + required: true + }, + url: { + type: String, + required: true + } + } +};