diff --git a/CHANGELOG.md b/CHANGELOG.md
index 17259f5459..3388ceb73a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- (Client -> Descriptor) Nuevo icono $ con barrotes para los clientes con impago
+- (Trabajador -> Datos Básicos) Añadido nuevo campo Taquilla
+- (Trabajador -> PDA) Nueva sección
### Changed
-
diff --git a/db/changes/230801/.gitkeep b/db/changes/230801/.gitkeep
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/db/changes/230801/00-workerLocker.sql b/db/changes/230801/00-workerLocker.sql
new file mode 100644
index 0000000000..0a72cca1e5
--- /dev/null
+++ b/db/changes/230801/00-workerLocker.sql
@@ -0,0 +1,15 @@
+ALTER TABLE `vn`.`worker` ADD locker INT UNSIGNED NULL UNIQUE;
+INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
+ VALUES
+ ('DeviceProduction', '*', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('DeviceProductionModels', '*', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('DeviceProductionState', '*', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('DeviceProductionUser', '*', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('DeviceProduction', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
+ ('DeviceProductionModels', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
+ ('DeviceProductionState', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
+ ('DeviceProductionUser', '*', '*', 'ALLOW', 'ROLE', 'productionAssi'),
+ ('Worker', 'deallocatePDA', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('Worker', 'allocatePDA', '*', 'ALLOW', 'ROLE', 'hr'),
+ ('Worker', 'deallocatePDA', '*', 'ALLOW', 'ROLE', 'productionAssi'),
+ ('Worker', 'allocatePDA', '*', 'ALLOW', 'ROLE', 'productionAssi');
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index 8fb12e822c..c0989f1ed0 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -2787,3 +2787,30 @@ INSERT INTO `vn`.`workerConfig` (`id`, `businessUpdated`, `roleFk`)
VALUES
(1, NULL, 1);
+INSERT INTO `vn`.`deviceProductionModels` (`code`)
+ VALUES
+ ('BLACKVIEW'),
+ ('DODGEE'),
+ ('ZEBRA');
+
+INSERT INTO `vn`.`deviceProductionState` (`code`, `description`)
+ VALUES
+ ('active', 'activo'),
+ ('idle', 'inactivo'),
+ ('lost', 'perdida'),
+ ('repair', 'reparación'),
+ ('retired', 'retirada');
+
+INSERT INTO `vn`.`deviceProduction` (`imei`, `modelFk`, `macWifi`, `serialNumber`, `android_id`, `purchased`, `stateFk`, `isInScalefusion`, `description`)
+ VALUES
+ ('ime1', 'BLACKVIEW', 'macWifi1', 'serialNumber1', 'android_id1', util.VN_NOW(), 'active', 0, NULL),
+ ('ime2', 'DODGEE', 'macWifi2', 'serialNumber2', 'android_id2', util.VN_NOW(), 'idle', 0, NULL),
+ ('ime3', 'ZEBRA', 'macWifi3', 'serialNumber3', 'android_id3', util.VN_NOW(), 'active', 0, NULL),
+ ('ime4', 'BLACKVIEW', 'macWifi4', 'serialNumber4', 'android_id4', util.VN_NOW(), 'idle', 0, NULL);
+
+INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `created`)
+ VALUES
+ (1, 1, util.VN_NOW()),
+ (3, 3, util.VN_NOW());
+
+
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index efaa13ee3c..e32bab57db 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -973,6 +973,7 @@ export default {
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',
department: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(5) > section > span',
+ locker: 'vn-worker-summary vn-one:nth-child(1) > vn-label-value:nth-child(10) > section > span',
userId: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(2) > section > span',
userName: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(3) > section > span',
role: 'vn-worker-summary vn-one:nth-child(2) > vn-label-value:nth-child(4) > section > span',
@@ -983,6 +984,7 @@ export default {
name: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.firstName"]',
surname: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.lastName"]',
phone: 'vn-worker-basic-data vn-textfield[ng-model="$ctrl.worker.phone"]',
+ locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
saveButton: 'vn-worker-basic-data button[type=submit]'
},
workerPbx: {
@@ -1040,6 +1042,12 @@ export default {
switft: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bankEntityFk"]',
createButton: 'vn-worker-create vn-submit[label="Create"]',
},
+ workerPda: {
+ currentPDA: 'vn-worker-pda vn-textfield[ng-model="$ctrl.currentPDA.description"]',
+ newPDA: 'vn-worker-pda vn-autocomplete[ng-model="$ctrl.newPDA"]',
+ delete: 'vn-worker-pda vn-icon-button[icon=delete]',
+ submit: 'vn-worker-pda vn-submit[label="Assign"]',
+ },
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/01_summary.spec.js b/e2e/paths/03-worker/01_summary.spec.js
index 4e5b0cfa90..51992b41df 100644
--- a/e2e/paths/03-worker/01_summary.spec.js
+++ b/e2e/paths/03-worker/01_summary.spec.js
@@ -29,5 +29,6 @@ describe('Worker summary path', () => {
expect(await page.getProperty(selectors.workerSummary.userName, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.role, 'innerText')).toEqual('agency');
expect(await page.getProperty(selectors.workerSummary.extension, 'innerText')).toEqual('1101');
+ expect(await page.getProperty(selectors.workerSummary.locker, 'innerText')).toEqual('-');
});
});
diff --git a/e2e/paths/03-worker/02_basicData.spec.js b/e2e/paths/03-worker/02_basicData.spec.js
index 66a597dd13..381375dc7e 100644
--- a/e2e/paths/03-worker/02_basicData.spec.js
+++ b/e2e/paths/03-worker/02_basicData.spec.js
@@ -25,6 +25,7 @@ describe('Worker basic data path', () => {
await page.overwrite(selectors.workerBasicData.name, 'David C.');
await page.overwrite(selectors.workerBasicData.surname, 'H.');
await page.overwrite(selectors.workerBasicData.phone, '444332211');
+ await page.overwrite(selectors.workerBasicData.locker, '1');
await page.click(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar();
@@ -36,5 +37,6 @@ describe('Worker basic data path', () => {
expect(await page.waitToGetProperty(selectors.workerBasicData.name, 'value')).toEqual('David C.');
expect(await page.waitToGetProperty(selectors.workerBasicData.surname, 'value')).toEqual('H.');
expect(await page.waitToGetProperty(selectors.workerBasicData.phone, 'value')).toEqual('444332211');
+ expect(await page.waitToGetProperty(selectors.workerBasicData.locker, 'value')).toEqual('1');
});
});
diff --git a/e2e/paths/03-worker/07_pda.spec.js b/e2e/paths/03-worker/07_pda.spec.js
new file mode 100644
index 0000000000..2b743823ec
--- /dev/null
+++ b/e2e/paths/03-worker/07_pda.spec.js
@@ -0,0 +1,41 @@
+import selectors from '../../helpers/selectors.js';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Worker pda path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('hr', 'worker');
+ await page.accessToSearchResult('employeeNick');
+ await page.accessToSection('worker.card.pda');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it('should check if worker has already a PDA allocated', async() => {
+ expect(await page.waitToGetProperty(selectors.workerPda.currentPDA, 'value')).toContain('serialNumber1');
+ });
+
+ it('should deallocate the PDA', async() => {
+ await page.waitToClick(selectors.workerPda.delete);
+ let message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('PDA deallocated');
+ });
+
+ it('should allocate a new PDA', async() => {
+ await page.autocompleteSearch(selectors.workerPda.newPDA, 'serialNumber2');
+ await page.waitToClick(selectors.workerPda.submit);
+ let message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('PDA allocated');
+ });
+
+ it('should check if a new PDA has been allocated', async() => {
+ expect(await page.waitToGetProperty(selectors.workerPda.currentPDA, 'value')).toContain('serialNumber2');
+ });
+});
diff --git a/front/salix/locale/es.yml b/front/salix/locale/es.yml
index d92c32b337..bed41c63b4 100644
--- a/front/salix/locale/es.yml
+++ b/front/salix/locale/es.yml
@@ -23,6 +23,7 @@ There is a new version, click here to reload: Hay una nueva versión, pulse aqu
Back: Volver
Save: Guardar
+Assign: Asignar
Create: Crear
Send: Enviar
Delete: Eliminar
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index ad6d53d640..10db9a54ff 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -263,6 +263,7 @@
"It is not possible to modify sales that their articles are from Floramondo": "No es posible modificar líneas de pedido cuyos artículos sean de Floramondo",
"It is not possible to modify cloned sales": "No es posible modificar líneas de pedido clonadas",
"A supplier with the same name already exists. Change the country.": "Un proveedor con el mismo nombre ya existe. Cambie el país.",
- "There is no assigned email for this client": "No hay correo asignado para este cliente"
+ "There is no assigned email for this client": "No hay correo asignado para este cliente",
+ "This locker has already been assigned": "Esta taquilla ya ha sido asignada"
}
diff --git a/modules/worker/back/methods/worker/allocatePDA.js b/modules/worker/back/methods/worker/allocatePDA.js
new file mode 100644
index 0000000000..8d0d25d2de
--- /dev/null
+++ b/modules/worker/back/methods/worker/allocatePDA.js
@@ -0,0 +1,66 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('allocatePDA', {
+ description: 'Deallocate the PDA of the worker',
+ accepts: [{
+ arg: 'id',
+ type: 'number',
+ required: true,
+ description: 'The worker id',
+ http: {source: 'path'}
+ }, {
+ arg: 'pda',
+ type: 'number',
+ required: true,
+ description: 'The pda id'
+ }],
+ returns: {
+ type: ['object'],
+ root: true
+ },
+ http: {
+ path: `/:id/allocatePDA`,
+ verb: 'POST'
+ }
+ });
+
+ Self.allocatePDA = async(ctx, options) => {
+ const models = Self.app.models;
+ const args = ctx.args;
+ let tx;
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ const pda = await models.DeviceProduction.findById(args.pda, myOptions);
+ if (pda.stateFk != 'idle') throw new UserError(`The PDA state is not idle`);
+ await pda.updateAttributes({stateFk: 'active'}, myOptions);
+ await models.DeviceProductionUser.create({
+ deviceProductionFk: args.pda,
+ userFk: args.id,
+ created: new Date()
+ }, myOptions);
+
+ if (tx) await tx.commit();
+
+ return {
+ deviceProductionFk: pda.id,
+ deviceProduction: {
+ modelFk: pda.modelFk,
+ serialNumber: pda.serialNumber
+ }
+ };
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/worker/back/methods/worker/deallocatePDA.js b/modules/worker/back/methods/worker/deallocatePDA.js
new file mode 100644
index 0000000000..7fa7855d16
--- /dev/null
+++ b/modules/worker/back/methods/worker/deallocatePDA.js
@@ -0,0 +1,53 @@
+module.exports = Self => {
+ Self.remoteMethodCtx('deallocatePDA', {
+ description: 'Deallocate the worker\'s PDA',
+ accepts: [{
+ arg: 'id',
+ type: 'number',
+ required: true,
+ description: 'The worker id',
+ http: {source: 'path'}
+ }, {
+ arg: 'pda',
+ type: 'number',
+ required: true,
+ description: 'The pda id'
+ }],
+ returns: {
+ type: ['object'],
+ root: true
+ },
+ http: {
+ path: `/:id/deallocatePDA`,
+ verb: 'POST'
+ }
+ });
+
+ Self.deallocatePDA = async(ctx, options) => {
+ const models = Self.app.models;
+ const args = ctx.args;
+ let tx;
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ const pda = await models.DeviceProduction.findById(args.pda, myOptions);
+ await pda.updateAttributes({stateFk: 'idle'}, myOptions);
+ await models.DeviceProductionUser.destroyAll({userFk: args.id, deviceProductionFk: args.pda}, myOptions);
+
+ if (tx) await tx.commit();
+
+ return pda;
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/worker/back/methods/worker/specs/allocatePDA.spec.js b/modules/worker/back/methods/worker/specs/allocatePDA.spec.js
new file mode 100644
index 0000000000..5c80b64080
--- /dev/null
+++ b/modules/worker/back/methods/worker/specs/allocatePDA.spec.js
@@ -0,0 +1,46 @@
+const models = require('vn-loopback/server/server').models;
+
+describe('Worker allocatePDA()', () => {
+ it('should allocate a new worker\'s PDA', async() => {
+ const tx = await models.Worker.beginTransaction({});
+ try {
+ const workerWithoutPDA = 1101;
+ const PDAWithIdleState = 4;
+ const options = {transaction: tx};
+ const ctx = {args: {id: workerWithoutPDA, pda: PDAWithIdleState}};
+
+ await models.Worker.allocatePDA(ctx, options);
+ const deviceProduction = await models.DeviceProduction.findById(PDAWithIdleState, null, options);
+ const deviceProductionUser = await models.DeviceProductionUser
+ .findById(PDAWithIdleState, null, options);
+
+ expect(deviceProduction.stateFk).toEqual('active');
+ expect(deviceProductionUser).not.toBeNull();
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+
+ it('should throw error trying to allocate a non idle PDA', async() => {
+ const tx = await models.Worker.beginTransaction({});
+ let error;
+ try {
+ const workerWithoutPDA = 1101;
+ const PDAWithActiveState = 1;
+ const options = {transaction: tx};
+ const ctx = {args: {id: workerWithoutPDA, pda: PDAWithActiveState}};
+
+ await models.Worker.allocatePDA(ctx, options);
+
+ await tx.rollback();
+ } catch (e) {
+ error = e;
+ await tx.rollback();
+ }
+
+ expect(error).toBeDefined();
+ });
+});
diff --git a/modules/worker/back/methods/worker/specs/deallocatePDA.spec.js b/modules/worker/back/methods/worker/specs/deallocatePDA.spec.js
new file mode 100644
index 0000000000..bddd017cd8
--- /dev/null
+++ b/modules/worker/back/methods/worker/specs/deallocatePDA.spec.js
@@ -0,0 +1,26 @@
+const models = require('vn-loopback/server/server').models;
+
+describe('Worker deallocatePDA()', () => {
+ it('should deallocate a worker\'s PDA', async() => {
+ const tx = await models.Worker.beginTransaction({});
+ try {
+ const workerWithPDA = 1;
+ const PDAWithActiveState = 1;
+ const options = {transaction: tx};
+ const ctx = {args: {id: workerWithPDA, pda: PDAWithActiveState}};
+
+ await models.Worker.deallocatePDA(ctx, options);
+ const deviceProduction = await models.DeviceProduction.findById(PDAWithActiveState, null, options);
+ const deviceProductionUser = await models.DeviceProductionUser
+ .findById(PDAWithActiveState, null, options);
+
+ expect(deviceProduction.stateFk).toEqual('idle');
+ expect(deviceProductionUser).toBe(null);
+
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+});
diff --git a/modules/worker/back/model-config.json b/modules/worker/back/model-config.json
index 7e03c8a232..8079bd1654 100644
--- a/modules/worker/back/model-config.json
+++ b/modules/worker/back/model-config.json
@@ -20,6 +20,18 @@
"Device": {
"dataSource": "vn"
},
+ "DeviceProduction": {
+ "dataSource": "vn"
+ },
+ "DeviceProductionModels": {
+ "dataSource": "vn"
+ },
+ "DeviceProductionState": {
+ "dataSource": "vn"
+ },
+ "DeviceProductionUser": {
+ "dataSource": "vn"
+ },
"EducationLevel": {
"dataSource": "vn"
},
diff --git a/modules/worker/back/models/device-production-models.json b/modules/worker/back/models/device-production-models.json
new file mode 100644
index 0000000000..dcf4475204
--- /dev/null
+++ b/modules/worker/back/models/device-production-models.json
@@ -0,0 +1,15 @@
+{
+ "name": "DeviceProductionModels",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "deviceProductionModels"
+ }
+ },
+ "properties": {
+ "code": {
+ "type": "string",
+ "id": true
+ }
+ }
+}
diff --git a/modules/worker/back/models/device-production-state.json b/modules/worker/back/models/device-production-state.json
new file mode 100644
index 0000000000..32695621a2
--- /dev/null
+++ b/modules/worker/back/models/device-production-state.json
@@ -0,0 +1,18 @@
+{
+ "name": "DeviceProductionState",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "deviceProductionState"
+ }
+ },
+ "properties": {
+ "code": {
+ "type": "string",
+ "id": true
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+}
diff --git a/modules/worker/back/models/device-production-user.json b/modules/worker/back/models/device-production-user.json
new file mode 100644
index 0000000000..568e794137
--- /dev/null
+++ b/modules/worker/back/models/device-production-user.json
@@ -0,0 +1,33 @@
+{
+ "name": "DeviceProductionUser",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "deviceProductionUser"
+ }
+ },
+ "properties": {
+ "deviceProductionFk": {
+ "type": "number",
+ "id": true
+ },
+ "userFk": {
+ "type": "number"
+ },
+ "created": {
+ "type": "date"
+ }
+ },
+ "relations": {
+ "deviceProduction": {
+ "type": "belongsTo",
+ "model": "DeviceProduction",
+ "foreignKey": "deviceProductionFk"
+ },
+ "user": {
+ "type": "belongsTo",
+ "model": "User",
+ "foreignKey": "userFk"
+ }
+ }
+}
diff --git a/modules/worker/back/models/device-production.json b/modules/worker/back/models/device-production.json
new file mode 100644
index 0000000000..63672500b4
--- /dev/null
+++ b/modules/worker/back/models/device-production.json
@@ -0,0 +1,54 @@
+{
+ "name": "DeviceProduction",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "deviceProduction"
+ }
+ },
+ "properties": {
+ "id": {
+ "type": "number",
+ "id": true
+ },
+ "imei": {
+ "type": "string"
+ },
+ "modelFk": {
+ "type": "number"
+ },
+ "macWifi": {
+ "type" : "string"
+ },
+ "serialNumber": {
+ "type" : "string"
+ },
+ "android_id": {
+ "type" : "string"
+ },
+ "purchased": {
+ "type" : "date"
+ },
+ "stateFk": {
+ "type" : "string"
+ },
+ "isInScaleFusion": {
+ "type" : "boolean"
+ },
+ "description": {
+ "type" : "string"
+ }
+ },
+ "relations": {
+ "model": {
+ "type": "belongsTo",
+ "model": "DeviceProductionModels",
+ "foreignKey": "modelFk"
+ },
+ "state": {
+ "type": "belongsTo",
+ "model": "DeviceProductionState",
+ "foreignKey": "stateFk"
+ }
+ }
+}
diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js
index e66259cd05..fa17640a86 100644
--- a/modules/worker/back/models/worker.js
+++ b/modules/worker/back/models/worker.js
@@ -14,4 +14,10 @@ module.exports = Self => {
require('../methods/worker/holidays')(Self);
require('../methods/worker/activeContract')(Self);
require('../methods/worker/new')(Self);
+ require('../methods/worker/deallocatePDA')(Self);
+ require('../methods/worker/allocatePDA')(Self);
+
+ Self.validatesUniquenessOf('locker', {
+ message: 'This locker has already been assigned'
+ });
};
diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json
index e3a941dd36..d21094f26c 100644
--- a/modules/worker/back/models/worker.json
+++ b/modules/worker/back/models/worker.json
@@ -55,6 +55,9 @@
},
"code": {
"type" : "string"
+ },
+ "locker": {
+ "type" : "number"
}
},
"relations": {
diff --git a/modules/worker/front/basic-data/index.html b/modules/worker/front/basic-data/index.html
index 5a3acdde59..d89d88f2e8 100644
--- a/modules/worker/front/basic-data/index.html
+++ b/modules/worker/front/basic-data/index.html
@@ -11,7 +11,7 @@