diff --git a/db/changes/231401/00-workerNotes.sql b/db/changes/231401/00-workerNotes.sql
new file mode 100644
index 0000000000..0d9eaae7e2
--- /dev/null
+++ b/db/changes/231401/00-workerNotes.sql
@@ -0,0 +1,14 @@
+CREATE TABLE `vn`.`workerObservation` (
+ `id` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
+ `workerFk` int(10) unsigned DEFAULT NULL,
+ `userFk` int(10) unsigned DEFAULT NULL,
+ `text` text COLLATE utf8mb3_unicode_ci NOT NULL,
+ `created` timestamp NOT NULL DEFAULT current_timestamp(),
+ PRIMARY KEY (`id`),
+ CONSTRAINT `workerFk_workerObservation_FK` FOREIGN KEY (`workerFk`) REFERENCES `vn`.`worker` (`id`) ON UPDATE CASCADE,
+ CONSTRAINT `userFk_workerObservation_FK` FOREIGN KEY (`userFk`) REFERENCES `account`.`user`(`id`) ON UPDATE CASCADE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb3 COLLATE=utf8mb3_unicode_ci COMMENT='Todas las observaciones referentes a un trabajador';
+
+INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
+ VALUES
+ ('WorkerObservation', '*', '*', 'ALLOW', 'ROLE', 'hr');
diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js
index 06d6ed0821..b504244ea8 100644
--- a/e2e/helpers/selectors.js
+++ b/e2e/helpers/selectors.js
@@ -988,6 +988,12 @@ export default {
locker: 'vn-worker-basic-data vn-input-number[ng-model="$ctrl.worker.locker"]',
saveButton: 'vn-worker-basic-data button[type=submit]'
},
+ workerNotes: {
+ addNoteFloatButton: 'vn-float-button',
+ note: 'vn-textarea[ng-model="$ctrl.note.text"]',
+ saveButton: 'button[type=submit]',
+ firstNoteText: 'vn-worker-note .text'
+ },
workerPbx: {
extension: 'vn-worker-pbx vn-textfield[ng-model="$ctrl.worker.sip.extension"]',
saveButton: 'vn-worker-pbx button[type=submit]'
diff --git a/e2e/paths/03-worker/08_add_notes.spec.js b/e2e/paths/03-worker/08_add_notes.spec.js
new file mode 100644
index 0000000000..eb2e4c041e
--- /dev/null
+++ b/e2e/paths/03-worker/08_add_notes.spec.js
@@ -0,0 +1,42 @@
+import selectors from '../../helpers/selectors';
+import getBrowser from '../../helpers/puppeteer';
+
+describe('Worker Add notes path', () => {
+ let browser;
+ let page;
+ beforeAll(async() => {
+ browser = await getBrowser();
+ page = browser.page;
+ await page.loginAndModule('employee', 'worker');
+ await page.accessToSearchResult('Bruce Banner');
+ await page.accessToSection('worker.card.note.index');
+ });
+
+ afterAll(async() => {
+ await browser.close();
+ });
+
+ it(`should reach the notes index`, async() => {
+ await page.waitForState('worker.card.note.index');
+ });
+
+ it(`should click on the add note button`, async() => {
+ await page.waitToClick(selectors.workerNotes.addNoteFloatButton);
+ await page.waitForState('worker.card.note.create');
+ });
+
+ it(`should create a note`, async() => {
+ await page.waitForSelector(selectors.workerNotes.note);
+ await page.type(`${selectors.workerNotes.note} textarea`, 'Meeting with Black Widow 21st 9am');
+ await page.waitToClick(selectors.workerNotes.saveButton);
+ const message = await page.waitForSnackbar();
+
+ expect(message.text).toContain('Data saved!');
+ });
+
+ it('should confirm the note was created', async() => {
+ const result = await page.waitToGetProperty(selectors.workerNotes.firstNoteText, 'innerText');
+
+ expect(result).toEqual('Meeting with Black Widow 21st 9am');
+ });
+});
diff --git a/modules/worker/back/methods/worker-time-control/addTimeEntry.js b/modules/worker/back/methods/worker-time-control/addTimeEntry.js
index fef3cf2233..c8c08d9b1a 100644
--- a/modules/worker/back/methods/worker-time-control/addTimeEntry.js
+++ b/modules/worker/back/methods/worker-time-control/addTimeEntry.js
@@ -51,6 +51,8 @@ module.exports = Self => {
if (response[0] && response[0].error)
throw new UserError(response[0].error);
+ await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, workerId, args.timed, myOptions);
+
return response;
};
};
diff --git a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js
index c80dcab81b..e33d6b7906 100644
--- a/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js
+++ b/modules/worker/back/methods/worker-time-control/deleteTimeEntry.js
@@ -38,7 +38,11 @@ module.exports = Self => {
if (isSubordinate === false || (isSubordinate && isHimself && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
- return Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [
+ const response = await Self.rawSql('CALL vn.workerTimeControl_remove(?, ?)', [
targetTimeEntry.userFk, targetTimeEntry.timed], myOptions);
+
+ await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, targetTimeEntry.userFk, targetTimeEntry.timed, myOptions);
+
+ return response;
};
};
diff --git a/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js
new file mode 100644
index 0000000000..2452a29f9c
--- /dev/null
+++ b/modules/worker/back/methods/worker-time-control/resendWeeklyHourEmail.js
@@ -0,0 +1,68 @@
+module.exports = Self => {
+ Self.remoteMethodCtx('resendWeeklyHourEmail', {
+ description: 'Adds a new hour registry',
+ accessType: 'WRITE',
+ accepts: [{
+ arg: 'id',
+ type: 'number',
+ description: 'The worker id',
+ http: {source: 'path'}
+ },
+ {
+ arg: 'dated',
+ type: 'date',
+ required: true
+ }],
+ returns: [{
+ type: 'Object',
+ root: true
+ }],
+ http: {
+ path: `/:id/resendWeeklyHourEmail`,
+ verb: 'POST'
+ }
+ });
+
+ Self.resendWeeklyHourEmail = async(ctx, workerId, dated, options) => {
+ const models = Self.app.models;
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const yearNumber = dated.getFullYear();
+ const weekNumber = getWeekNumber(dated);
+ const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({
+ where: {
+ workerFk: workerId,
+ year: yearNumber,
+ week: weekNumber
+ }
+ }, myOptions);
+
+ if (workerTimeControlMail && workerTimeControlMail.state != 'SENDED') {
+ const worker = await models.EmailUser.findById(workerId, null, myOptions);
+ ctx.args = {
+ recipient: worker.email,
+ year: yearNumber,
+ week: weekNumber,
+ workerId: workerId,
+ state: 'SENDED'
+ };
+ return models.WorkerTimeControl.weeklyHourRecordEmail(ctx, myOptions);
+ }
+
+ return false;
+ };
+
+ function getWeekNumber(date) {
+ const tempDate = new Date(date);
+ let dayOfWeek = tempDate.getDay();
+ dayOfWeek = (dayOfWeek === 0) ? 7 : dayOfWeek;
+ const firstDayOfWeek = new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate() - (dayOfWeek - 1));
+ const firstDayOfYear = new Date(tempDate.getFullYear(), 0, 1);
+ const differenceInMilliseconds = firstDayOfWeek.getTime() - firstDayOfYear.getTime();
+ const weekNumber = Math.floor(differenceInMilliseconds / (1000 * 60 * 60 * 24 * 7)) + 1;
+ return weekNumber;
+ }
+};
diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js
index 579a83112a..2c827e3208 100644
--- a/modules/worker/back/methods/worker-time-control/sendMail.js
+++ b/modules/worker/back/methods/worker-time-control/sendMail.js
@@ -82,14 +82,9 @@ module.exports = Self => {
updated: Date.vnNew(), state: 'SENDED'
}, myOptions);
- stmt = new ParameterizedSQL(
- `CALL vn.timeControl_calculateByUser(?, ?, ?)
- `, [args.workerId, started, ended]);
+ stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`');
stmts.push(stmt);
-
- stmt = new ParameterizedSQL(
- `CALL vn.timeBusiness_calculateByUser(?, ?, ?)
- `, [args.workerId, started, ended]);
+ stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE tmp.`user` SELECT id userFk FROM account.user WHERE id = ?', [args.workerId]);
stmts.push(stmt);
} else {
await models.WorkerTimeControl.destroyAll({
@@ -105,13 +100,38 @@ module.exports = Self => {
updated: Date.vnNew(), state: 'SENDED'
}, myOptions);
- stmt = new ParameterizedSQL(`CALL vn.timeControl_calculateAll(?, ?)`, [started, ended]);
+ stmt = new ParameterizedSQL('DROP TEMPORARY TABLE IF EXISTS tmp.`user`');
stmts.push(stmt);
-
- stmt = new ParameterizedSQL(`CALL vn.timeBusiness_calculateAll(?, ?)`, [started, ended]);
+ stmt = new ParameterizedSQL('CREATE TEMPORARY TABLE IF NOT EXISTS tmp.`user` SELECT userFk FROM vn.worker w JOIN account.`user` u ON u.id = w.userFk WHERE userFk IS NOT NULL');
stmts.push(stmt);
}
+ stmt = new ParameterizedSQL(
+ `CALL vn.timeControl_calculate(?, ?)
+ `, [started, ended]);
+ stmts.push(stmt);
+
+ stmt = new ParameterizedSQL(
+ `CALL vn.timeBusiness_calculate(?, ?)
+ `, [started, ended]);
+ stmts.push(stmt);
+
+ stmt = new ParameterizedSQL(
+ `CALL vn.timeControl_getError(?, ?)
+ `, [started, ended]);
+ stmts.push(stmt);
+
+ stmt = new ParameterizedSQL(`INSERT INTO mail (receiver, subject, body)
+ SELECT CONCAT(u.name, '@verdnatura.es'),
+ CONCAT('Error registro de horas semana ', ?, ' año ', ?) ,
+ CONCAT('No se ha podido enviar el registro de horas al empleado/s: ', GROUP_CONCAT(DISTINCT CONCAT('
', w.id, ' ', w.firstName, ' ', w.lastName)))
+ FROM tmp.timeControlError tce
+ JOIN vn.workerTimeControl wtc ON wtc.id = tce.id
+ JOIN worker w ON w.id = wtc.userFK
+ JOIN account.user u ON u.id = w.bossFk
+ GROUP BY w.bossFk`, [args.week, args.year]);
+ stmts.push(stmt);
+
stmt = new ParameterizedSQL(`
SELECT CONCAT(u.name, '@verdnatura.es') receiver,
u.id workerFk,
@@ -131,20 +151,16 @@ module.exports = Self => {
JOIN business b ON b.id = tb.businessFk
LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk AND tc.dated = tb.dated
LEFT JOIN worker w ON w.id = u.id
- JOIN (SELECT tb.userFk,
- SUM(IF(tb.type IS NULL,
- IF(tc.timeWorkDecimal > 0, FALSE, IF(tb.timeWorkDecimal > 0, TRUE, FALSE)),
- TRUE))isTeleworkingWeek
- FROM tmp.timeBusinessCalculate tb
- LEFT JOIN tmp.timeControlCalculate tc ON tc.userFk = tb.userFk
- AND tc.dated = tb.dated
- GROUP BY tb.userFk
- HAVING isTeleworkingWeek > 0
- )sub ON sub.userFk = u.id
- WHERE d.hasToRefill
+ LEFT JOIN (
+ SELECT DISTINCT wtc.userFk
+ FROM tmp.timeControlError tce
+ JOIN vn.workerTimeControl wtc ON wtc.id = tce.id
+ )sub ON sub.userFk = tb.userFk
+ WHERE sub.userFK IS NULL
AND IFNULL(?, u.id) = u.id
AND b.companyCodeFk = 'VNL'
AND w.businessFk
+ AND d.isTeleworking
ORDER BY u.id, tb.dated
`, [args.workerId]);
const index = stmts.push(stmt) - 1;
@@ -332,17 +348,18 @@ module.exports = Self => {
const lastDay = days[index][days[index].length - 1];
if (day.workerFk != previousWorkerFk || day == lastDay) {
- const salix = await models.Url.findOne({
- where: {
- appName: 'salix',
- environment: process.env.NODE_ENV || 'dev'
- }
- }, myOptions);
+ const query = `INSERT IGNORE INTO workerTimeControlMail (workerFk, year, week)
+ VALUES(?, ?, ?);`;
+ await Self.rawSql(query, [previousWorkerFk, args.year, args.week]);
- const timestamp = started.getTime() / 1000;
- const url = `${salix.url}worker/${previousWorkerFk}/time-control?timestamp=${timestamp}`;
-
- await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, previousReceiver, args.week, args.year, url);
+ ctx.args = {
+ recipient: previousReceiver,
+ year: args.year,
+ week: args.week,
+ workerId: previousWorkerFk,
+ state: 'SENDED'
+ };
+ await models.WorkerTimeControl.weeklyHourRecordEmail(ctx, myOptions);
previousWorkerFk = day.workerFk;
previousReceiver = day.receiver;
diff --git a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js b/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js
deleted file mode 100644
index 24bfd69041..0000000000
--- a/modules/worker/back/methods/worker-time-control/specs/sendMail.spec.js
+++ /dev/null
@@ -1,120 +0,0 @@
-const models = require('vn-loopback/server/server').models;
-
-describe('workerTimeControl sendMail()', () => {
- const workerId = 18;
- const activeCtx = {
- getLocale: () => {
- return 'en';
- }
- };
- const ctx = {req: activeCtx, args: {}};
-
- it('should fill time control of a worker without records in Journey and with rest', async() => {
- const tx = await models.WorkerTimeControl.beginTransaction({});
-
- try {
- const options = {transaction: tx};
-
- await models.WorkerTimeControl.sendMail(ctx, options);
-
- const workerTimeControl = await models.WorkerTimeControl.find({
- where: {userFk: workerId}
- }, options);
-
- expect(workerTimeControl[0].timed.getHours()).toEqual(8);
- expect(workerTimeControl[1].timed.getHours()).toEqual(9);
- expect(`${workerTimeControl[2].timed.getHours()}:${workerTimeControl[2].timed.getMinutes()}`).toEqual('9:20');
- expect(workerTimeControl[3].timed.getHours()).toEqual(16);
-
- await tx.rollback();
- } catch (e) {
- await tx.rollback();
- throw e;
- }
- });
-
- it('should fill time control of a worker without records in Journey and without rest', async() => {
- const workdayOf20Hours = 3;
- const tx = await models.WorkerTimeControl.beginTransaction({});
-
- try {
- const options = {transaction: tx};
- query = `UPDATE business b
- SET b.calendarTypeFk = ?
- WHERE b.workerFk = ?; `;
- await models.WorkerTimeControl.rawSql(query, [workdayOf20Hours, workerId], options);
-
- await models.WorkerTimeControl.sendMail(ctx, options);
-
- const workerTimeControl = await models.WorkerTimeControl.find({
- where: {userFk: workerId}
- }, options);
-
- expect(workerTimeControl[0].timed.getHours()).toEqual(8);
- expect(workerTimeControl[1].timed.getHours()).toEqual(12);
-
- await tx.rollback();
- } catch (e) {
- await tx.rollback();
- throw e;
- }
- });
-
- it('should fill time control of a worker with records in Journey and with rest', async() => {
- const tx = await models.WorkerTimeControl.beginTransaction({});
-
- try {
- const options = {transaction: tx};
- query = `INSERT INTO postgresql.journey(journey_id, day_id, start, end, business_id)
- VALUES
- (1, 1, '09:00:00', '13:00:00', ?),
- (2, 1, '14:00:00', '19:00:00', ?);`;
- await models.WorkerTimeControl.rawSql(query, [workerId, workerId, workerId], options);
-
- await models.WorkerTimeControl.sendMail(ctx, options);
-
- const workerTimeControl = await models.WorkerTimeControl.find({
- where: {userFk: workerId}
- }, options);
-
- expect(workerTimeControl[0].timed.getHours()).toEqual(9);
- expect(workerTimeControl[2].timed.getHours()).toEqual(10);
- expect(`${workerTimeControl[3].timed.getHours()}:${workerTimeControl[3].timed.getMinutes()}`).toEqual('10:20');
- expect(workerTimeControl[1].timed.getHours()).toEqual(13);
- expect(workerTimeControl[4].timed.getHours()).toEqual(14);
- expect(workerTimeControl[5].timed.getHours()).toEqual(19);
-
- await tx.rollback();
- } catch (e) {
- await tx.rollback();
- throw e;
- }
- });
-
- it('should fill time control of a worker with records in Journey and without rest', async() => {
- const tx = await models.WorkerTimeControl.beginTransaction({});
-
- try {
- const options = {transaction: tx};
- query = `INSERT INTO postgresql.journey(journey_id, day_id, start, end, business_id)
- VALUES
- (1, 1, '12:30:00', '14:00:00', ?);`;
- await models.WorkerTimeControl.rawSql(query, [workerId, workerId, workerId], options);
-
- await models.WorkerTimeControl.sendMail(ctx, options);
-
- const workerTimeControl = await models.WorkerTimeControl.find({
- where: {userFk: workerId}
- }, options);
-
- expect(`${workerTimeControl[0].timed.getHours()}:${workerTimeControl[0].timed.getMinutes()}`).toEqual('12:30');
- expect(workerTimeControl[1].timed.getHours()).toEqual(14);
-
- await tx.rollback();
- } catch (e) {
- await tx.rollback();
- throw e;
- }
- });
-});
-
diff --git a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js
index a99a617704..83349ea63d 100644
--- a/modules/worker/back/methods/worker-time-control/updateTimeEntry.js
+++ b/modules/worker/back/methods/worker-time-control/updateTimeEntry.js
@@ -46,8 +46,12 @@ module.exports = Self => {
if (notAllowed)
throw new UserError(`You don't have enough privileges`);
- return targetTimeEntry.updateAttributes({
+ const timeEntryUpdated = await targetTimeEntry.updateAttributes({
direction: args.direction
}, myOptions);
+
+ await models.WorkerTimeControl.resendWeeklyHourEmail(ctx, targetTimeEntry.userFk, targetTimeEntry.timed, myOptions);
+
+ return timeEntryUpdated;
};
};
diff --git a/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js
index 642ff90d2e..6f794511f3 100644
--- a/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js
+++ b/modules/worker/back/methods/worker-time-control/updateWorkerTimeControlMail.js
@@ -47,10 +47,6 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
- const isHimself = userId == args.workerId;
- if (!isHimself)
- throw new UserError(`You don't have enough privileges`);
-
const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({
where: {
workerFk: args.workerId,
@@ -69,6 +65,12 @@ module.exports = Self => {
reason: args.reason || null
}, myOptions);
+ if (args.state == 'SENDED') {
+ await workerTimeControlMail.updateAttributes({
+ sendedCounter: workerTimeControlMail.sendedCounter + 1
+ }, myOptions);
+ }
+
const logRecord = {
originFk: args.workerId,
userFk: userId,
diff --git a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js
index 6feadb9364..f440805592 100644
--- a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js
+++ b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js
@@ -1,5 +1,3 @@
-const {Email} = require('vn-print');
-
module.exports = Self => {
Self.remoteMethodCtx('weeklyHourRecordEmail', {
description: 'Sends the weekly hour record',
@@ -22,7 +20,12 @@ module.exports = Self => {
required: true
},
{
- arg: 'url',
+ arg: 'workerId',
+ type: 'number',
+ required: true
+ },
+ {
+ arg: 'state',
type: 'string',
required: true
}
@@ -37,17 +40,48 @@ module.exports = Self => {
}
});
- Self.weeklyHourRecordEmail = async(ctx, recipient, week, year, url) => {
- const params = {
- recipient: recipient,
- lang: ctx.req.getLocale(),
- week: week,
- year: year,
- url: url
- };
+ Self.weeklyHourRecordEmail = async(ctx, options) => {
+ const models = Self.app.models;
+ const args = ctx.args;
+ const myOptions = {};
- const email = new Email('weekly-hour-record', params);
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
- return email.send();
+ const salix = await models.Url.findOne({
+ where: {
+ appName: 'salix',
+ environment: process.env.NODE_ENV || 'dev'
+ }
+ }, myOptions);
+
+ const dated = getMondayDateFromYearWeek(args.year, args.week);
+ const timestamp = dated.getTime() / 1000;
+
+ const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`;
+ ctx.args.url = url;
+
+ Self.sendTemplate(ctx, 'weekly-hour-record');
+
+ return models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, myOptions);
};
+
+ function getMondayDateFromYearWeek(yearNumber, weekNumber) {
+ const yearStart = new Date(yearNumber, 0, 1);
+ const firstMonday = new Date(yearStart.getTime() + ((7 - yearStart.getDay() + 1) % 7) * 86400000);
+ const firstMondayWeekNumber = getWeekNumber(firstMonday);
+
+ if (firstMondayWeekNumber > 1)
+ firstMonday.setDate(firstMonday.getDate() + 7);
+
+ firstMonday.setDate(firstMonday.getDate() + (weekNumber - 1) * 7);
+
+ return firstMonday;
+ }
+
+ function getWeekNumber(date) {
+ const firstDayOfYear = new Date(date.getFullYear(), 0, 1);
+ const daysPassed = (date - firstDayOfYear) / 86400000;
+ return Math.ceil((daysPassed + firstDayOfYear.getDay() + 1) / 7);
+ }
};
diff --git a/modules/worker/back/model-config.json b/modules/worker/back/model-config.json
index 63fc658279..1459347001 100644
--- a/modules/worker/back/model-config.json
+++ b/modules/worker/back/model-config.json
@@ -53,6 +53,9 @@
"Worker": {
"dataSource": "vn"
},
+ "WorkerObservation": {
+ "dataSource": "vn"
+ },
"WorkerConfig": {
"dataSource": "vn"
},
diff --git a/modules/worker/back/models/worker-observation.js b/modules/worker/back/models/worker-observation.js
new file mode 100644
index 0000000000..cccc2cfbdf
--- /dev/null
+++ b/modules/worker/back/models/worker-observation.js
@@ -0,0 +1,12 @@
+module.exports = function(Self) {
+ Self.validatesPresenceOf('text', {
+ message: 'Description cannot be blank'
+ });
+
+ Self.observe('before save', async function(ctx) {
+ ctx.instance.created = new Date();
+ let token = ctx.options.accessToken;
+ let userId = token && token.userId;
+ ctx.instance.userFk = userId;
+ });
+};
diff --git a/modules/worker/back/models/worker-observation.json b/modules/worker/back/models/worker-observation.json
new file mode 100644
index 0000000000..90eb358376
--- /dev/null
+++ b/modules/worker/back/models/worker-observation.json
@@ -0,0 +1,39 @@
+{
+ "name": "WorkerObservation",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "workerObservation"
+ }
+ },
+ "properties": {
+ "id": {
+ "id": true,
+ "type": "number"
+ },
+ "workerFk": {
+ "type": "number"
+ },
+ "userFk": {
+ "type": "number"
+ },
+ "text": {
+ "type": "string"
+ },
+ "created": {
+ "type": "date"
+ }
+ },
+ "relations": {
+ "worker": {
+ "type": "belongsTo",
+ "model": "Worker",
+ "foreignKey": "workerFk"
+ },
+ "user":{
+ "type": "belongsTo",
+ "model": "Account",
+ "foreignKey": "userFk"
+ }
+ }
+}
diff --git a/modules/worker/back/models/worker-time-control-mail.json b/modules/worker/back/models/worker-time-control-mail.json
index 78b99881dc..87eae9217d 100644
--- a/modules/worker/back/models/worker-time-control-mail.json
+++ b/modules/worker/back/models/worker-time-control-mail.json
@@ -28,6 +28,9 @@
},
"reason": {
"type": "string"
+ },
+ "sendedCounter": {
+ "type": "number"
}
},
"acls": [
diff --git a/modules/worker/back/models/worker-time-control.js b/modules/worker/back/models/worker-time-control.js
index 5b13e17f2f..d5da680cf8 100644
--- a/modules/worker/back/models/worker-time-control.js
+++ b/modules/worker/back/models/worker-time-control.js
@@ -9,6 +9,7 @@ module.exports = Self => {
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
require('../methods/worker-time-control/getMailStates')(Self);
+ require('../methods/worker-time-control/resendWeeklyHourEmail')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
diff --git a/modules/worker/front/index.js b/modules/worker/front/index.js
index 657f6a8c66..8fad2c0df7 100644
--- a/modules/worker/front/index.js
+++ b/modules/worker/front/index.js
@@ -18,3 +18,6 @@ import './log';
import './dms/index';
import './dms/create';
import './dms/edit';
+import './note/index';
+import './note/create';
+
diff --git a/modules/worker/front/locale/es.yml b/modules/worker/front/locale/es.yml
index b5bcfefa4a..a253771229 100644
--- a/modules/worker/front/locale/es.yml
+++ b/modules/worker/front/locale/es.yml
@@ -31,3 +31,5 @@ Deallocate PDA: Desasignar PDA
PDA deallocated: PDA desasignada
PDA allocated: PDA asignada
New PDA: Nueva PDA
+Notes: Notas
+New note: Nueva nota
diff --git a/modules/worker/front/note/create/index.html b/modules/worker/front/note/create/index.html
new file mode 100644
index 0000000000..d09fc2da57
--- /dev/null
+++ b/modules/worker/front/note/create/index.html
@@ -0,0 +1,30 @@
+