4856-worker.time-control #1375
|
@ -8,13 +8,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||||
## [2310.01] - 2023-03-23
|
## [2310.01] - 2023-03-23
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
-
|
- (Trabajadores -> Control de horario) Ahora se puede confirmar/no confirmar el registro horario de cada semana desde esta sección
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
-
|
-
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
|
- (Clientes -> Listado extendido) Resuelto error al filtrar por clientes inactivos desde la columna "Activo"
|
||||||
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
|
- (General) Al pasar el ratón por encima del icono de "Borrar" en un campo, se hacía más grande afectando a la interfaz
|
||||||
|
|
||||||
## [2308.01] - 2023-03-09
|
## [2308.01] - 2023-03-09
|
||||||
|
|
|
@ -2825,4 +2825,11 @@ INSERT INTO `vn`.`deviceProductionUser` (`deviceProductionFk`, `userFk`, `create
|
||||||
(1, 1, util.VN_NOW()),
|
(1, 1, util.VN_NOW()),
|
||||||
(3, 3, util.VN_NOW());
|
(3, 3, util.VN_NOW());
|
||||||
|
|
||||||
|
INSERT INTO `vn`.`workerTimeControlMail` (`id`, `workerFk`, `year`, `week`, `state`, `updated`, `sendedCounter`, `reason`)
|
||||||
|
VALUES
|
||||||
|
(1, 9, 2000, 49, 'REVISE', util.VN_NOW(), 1, 'test2'),
|
||||||
|
(2, 9, 2000, 50, 'SENDED', util.VN_NOW(), 1, NULL),
|
||||||
|
(3, 9, 2000, 51, 'CONFIRMED', util.VN_NOW(), 1, NULL),
|
||||||
|
(4, 9, 2001, 1, 'SENDED', util.VN_NOW(), 1, NULL);
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<div class="weekdays">
|
<div class="weekdays">
|
||||||
<section
|
<section
|
||||||
ng-repeat="day in ::$ctrl.weekDays"
|
ng-repeat="day in ::$ctrl.weekDays"
|
||||||
translate-attr="::{title: day.name}"
|
translate-attr="::{title: day.name}"
|
||||||
ng-click="$ctrl.selectWeekDay($event, day.index)">
|
ng-click="$ctrl.selectWeekDay($event, day.index)">
|
||||||
<span>{{::day.localeChar}}</span>
|
<span>{{::day.localeChar}}</span>
|
||||||
</section>
|
</section>
|
||||||
|
@ -57,4 +57,4 @@
|
||||||
</section>
|
</section>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -15,9 +15,9 @@ export default class Calendar extends FormInput {
|
||||||
constructor($element, $scope, vnWeekDays, moment) {
|
constructor($element, $scope, vnWeekDays, moment) {
|
||||||
super($element, $scope);
|
super($element, $scope);
|
||||||
this.weekDays = vnWeekDays.locales;
|
this.weekDays = vnWeekDays.locales;
|
||||||
this.defaultDate = Date.vnNew();
|
|
||||||
this.displayControls = true;
|
this.displayControls = true;
|
||||||
this.moment = moment;
|
this.moment = moment;
|
||||||
|
this.defaultDate = Date.vnNew();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -207,14 +207,23 @@ export default class Calendar extends FormInput {
|
||||||
}
|
}
|
||||||
|
|
||||||
repeatLast() {
|
repeatLast() {
|
||||||
if (!this.formatDay) return;
|
if (this.formatDay) {
|
||||||
|
const days = this.element.querySelectorAll('.days > .day');
|
||||||
|
for (let i = 0; i < days.length; i++) {
|
||||||
|
this.formatDay({
|
||||||
|
$day: this.days[i],
|
||||||
|
$element: days[i]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let days = this.element.querySelectorAll('.days > .day');
|
if (this.formatWeek) {
|
||||||
for (let i = 0; i < days.length; i++) {
|
const weeks = this.element.querySelectorAll('.weeks > .day');
|
||||||
this.formatDay({
|
for (const week of weeks) {
|
||||||
$day: this.days[i],
|
this.formatWeek({
|
||||||
$element: days[i]
|
$element: week
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -228,6 +237,7 @@ ngModule.vnComponent('vnCalendar', {
|
||||||
hasEvents: '&?',
|
hasEvents: '&?',
|
||||||
getClass: '&?',
|
getClass: '&?',
|
||||||
formatDay: '&?',
|
formatDay: '&?',
|
||||||
|
formatWeek: '&?',
|
||||||
displayControls: '<?',
|
displayControls: '<?',
|
||||||
hideYear: '<?',
|
hideYear: '<?',
|
||||||
hideContiguous: '<?',
|
hideContiguous: '<?',
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('getMailStates', {
|
||||||
|
description: 'Get the states of a month about time control mail',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [{
|
||||||
|
arg: 'id',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The worker id',
|
||||||
|
http: {source: 'path'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'month',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The number of the month'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'year',
|
||||||
|
type: 'number',
|
||||||
|
description: 'The number of the year'
|
||||||
|
}],
|
||||||
|
returns: [{
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
}],
|
||||||
|
http: {
|
||||||
|
path: `/:id/getMailStates`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.getMailStates = async(ctx, workerId, options) => {
|
||||||
|
const models = Self.app.models;
|
||||||
|
const args = ctx.args;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const times = await models.Time.find({
|
||||||
|
fields: ['week'],
|
||||||
|
where: {
|
||||||
|
month: args.month,
|
||||||
|
year: args.year
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
const weeks = times.map(time => time.week);
|
||||||
|
const weekNumbersSet = new Set(weeks);
|
||||||
|
const weekNumbers = Array.from(weekNumbersSet);
|
||||||
|
|
||||||
|
const workerTimeControlMails = await models.WorkerTimeControlMail.find({
|
||||||
|
where: {
|
||||||
|
workerFk: workerId,
|
||||||
|
year: args.year,
|
||||||
|
week: {inq: weekNumbers}
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
return workerTimeControlMails;
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,29 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('workerTimeControl getMailStates()', () => {
|
||||||
|
const workerId = 9;
|
||||||
|
const ctx = {args: {
|
||||||
|
month: 12,
|
||||||
|
year: 2000
|
||||||
|
}};
|
||||||
|
|
||||||
|
it('should get the states of a month about time control mail', async() => {
|
||||||
|
const tx = await models.WorkerTimeControl.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
|
||||||
|
const response = await models.WorkerTimeControl.getMailStates(ctx, workerId, options);
|
||||||
|
|
||||||
|
expect(response[0].state).toEqual('REVISE');
|
||||||
|
expect(response[1].state).toEqual('SENDED');
|
||||||
|
expect(response[2].state).toEqual('CONFIRMED');
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
|
@ -47,6 +47,10 @@ module.exports = Self => {
|
||||||
if (typeof options == 'object')
|
if (typeof options == 'object')
|
||||||
Object.assign(myOptions, options);
|
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({
|
const workerTimeControlMail = await models.WorkerTimeControlMail.findOne({
|
||||||
where: {
|
where: {
|
||||||
workerFk: args.workerId,
|
workerFk: args.workerId,
|
||||||
|
@ -60,8 +64,6 @@ module.exports = Self => {
|
||||||
const oldState = workerTimeControlMail.state;
|
const oldState = workerTimeControlMail.state;
|
||||||
const oldReason = workerTimeControlMail.reason;
|
const oldReason = workerTimeControlMail.reason;
|
||||||
|
|
||||||
if (oldState == args.state) throw new UserError('Already has this status');
|
|
||||||
|
|
||||||
await workerTimeControlMail.updateAttributes({
|
await workerTimeControlMail.updateAttributes({
|
||||||
state: args.state,
|
state: args.state,
|
||||||
reason: args.reason || null
|
reason: args.reason || null
|
||||||
|
|
|
@ -14,6 +14,9 @@
|
||||||
"year": {
|
"year": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
},
|
},
|
||||||
|
"month": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
"week": {
|
"week": {
|
||||||
"type": "number"
|
"type": "number"
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ module.exports = Self => {
|
||||||
require('../methods/worker-time-control/sendMail')(Self);
|
require('../methods/worker-time-control/sendMail')(Self);
|
||||||
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
|
require('../methods/worker-time-control/updateWorkerTimeControlMail')(Self);
|
||||||
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
require('../methods/worker-time-control/weeklyHourRecordEmail')(Self);
|
||||||
|
require('../methods/worker-time-control/getMailStates')(Self);
|
||||||
|
|
||||||
Self.rewriteDbError(function(err) {
|
Self.rewriteDbError(function(err) {
|
||||||
if (err.code === 'ER_DUP_ENTRY')
|
if (err.code === 'ER_DUP_ENTRY')
|
||||||
|
|
|
@ -25,11 +25,11 @@
|
||||||
<div class="totalBox vn-mb-sm" style="text-align: center;">
|
<div class="totalBox vn-mb-sm" style="text-align: center;">
|
||||||
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
|
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
|
||||||
<div>
|
<div>
|
||||||
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
|
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
|
||||||
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
|
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
|
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
|
||||||
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
|
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
|
@ -40,11 +40,11 @@
|
||||||
<div class="totalBox" style="text-align: center;">
|
<div class="totalBox" style="text-align: center;">
|
||||||
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
|
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
|
||||||
<div>
|
<div>
|
||||||
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
|
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
|
||||||
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
|
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
|
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
|
||||||
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
|
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
order="businessFk DESC"
|
order="businessFk DESC"
|
||||||
limit="5">
|
limit="5">
|
||||||
<tpl-item>
|
<tpl-item>
|
||||||
<div>#{{businessFk}}</div>
|
<div>#{{businessFk}}</div>
|
||||||
<div class="text-caption text-secondary">
|
<div class="text-caption text-secondary">
|
||||||
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
|
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -87,17 +87,17 @@
|
||||||
<vn-chip>
|
<vn-chip>
|
||||||
<vn-avatar class="festive">
|
<vn-avatar class="festive">
|
||||||
</vn-avatar>
|
</vn-avatar>
|
||||||
<span translate>Festive</span>
|
<span translate>Festive</span>
|
||||||
</vn-chip>
|
</vn-chip>
|
||||||
<vn-chip>
|
<vn-chip>
|
||||||
<vn-avatar class="today">
|
<vn-avatar class="today">
|
||||||
</vn-avatar>
|
</vn-avatar>
|
||||||
<span translate>Current day</span>
|
<span translate>Current day</span>
|
||||||
</vn-chip>
|
</vn-chip>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</vn-side-menu>
|
</vn-side-menu>
|
||||||
<vn-confirm
|
<vn-confirm
|
||||||
vn-id="confirm"
|
vn-id="confirm"
|
||||||
message="This item will be deleted"
|
message="This item will be deleted"
|
||||||
question="Are you sure you want to continue?">
|
question="Are you sure you want to continue?">
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="model"
|
vn-id="model"
|
||||||
url="WorkerTimeControls/filter"
|
url="WorkerTimeControls/filter"
|
||||||
filter="::$ctrl.filter"
|
filter="::$ctrl.filter"
|
||||||
data="$ctrl.hours">
|
data="$ctrl.hours">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-card class="vn-pa-lg vn-w-lg">
|
<vn-card class="vn-pa-lg vn-w-lg">
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
{{::weekday.dated | date: 'MMMM'}}
|
{{::weekday.dated | date: 'MMMM'}}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<vn-chip
|
<vn-chip
|
||||||
title="{{::weekday.event.name}}"
|
title="{{::weekday.event.name}}"
|
||||||
ng-class="{invisible: !weekday.event}">
|
ng-class="{invisible: !weekday.event}">
|
||||||
<vn-avatar
|
<vn-avatar
|
||||||
|
@ -66,7 +66,7 @@
|
||||||
</vn-td>
|
</vn-td>
|
||||||
</vn-tr>
|
</vn-tr>
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-td center ng-repeat="weekday in $ctrl.weekDays">
|
<vn-td center ng-repeat="weekday in $ctrl.weekDays">
|
||||||
<vn-icon-button
|
<vn-icon-button
|
||||||
icon="add_circle"
|
icon="add_circle"
|
||||||
vn-tooltip="Add time"
|
vn-tooltip="Add time"
|
||||||
|
@ -78,27 +78,43 @@
|
||||||
</vn-table>
|
</vn-table>
|
||||||
</vn-card>
|
</vn-card>
|
||||||
|
|
||||||
<vn-button-bar class="vn-pa-xs vn-w-lg">
|
<vn-button-bar ng-show="$ctrl.state" class="vn-w-lg">
|
||||||
<vn-button
|
<vn-button
|
||||||
label="Satisfied"
|
label="Satisfied"
|
||||||
|
disabled="$ctrl.state == 'CONFIRMED'"
|
||||||
|
ng-if="$ctrl.isHimSelf"
|
||||||
ng-click="$ctrl.isSatisfied()">
|
ng-click="$ctrl.isSatisfied()">
|
||||||
</vn-button>
|
</vn-button>
|
||||||
<vn-button
|
<vn-button
|
||||||
label="Not satisfied"
|
label="Not satisfied"
|
||||||
|
disabled="$ctrl.state == 'REVISE'"
|
||||||
|
ng-if="$ctrl.isHimSelf"
|
||||||
ng-click="reason.show()">
|
ng-click="reason.show()">
|
||||||
</vn-button>
|
</vn-button>
|
||||||
|
<vn-button
|
||||||
|
label="Reason"
|
||||||
|
ng-if="$ctrl.reason && ($ctrl.isHimSelf || $ctrl.isHr)"
|
||||||
|
ng-click="reason.show()">
|
||||||
|
</vn-button>
|
||||||
|
<vn-button
|
||||||
|
label="Resend"
|
||||||
|
ng-click="sendEmailConfirmation.show()"
|
||||||
|
class="right"
|
||||||
|
vn-tooltip="Resend email of this week to the user"
|
||||||
|
ng-show="::$ctrl.isHr">
|
||||||
|
</vn-button>
|
||||||
</vn-button-bar>
|
</vn-button-bar>
|
||||||
|
|
||||||
<vn-side-menu side="right">
|
<vn-side-menu side="right">
|
||||||
<div class="vn-pa-md">
|
<div class="vn-pa-md">
|
||||||
<div class="totalBox" style="text-align: center;">
|
<div class="totalBox" style="text-align: center;">
|
||||||
<h6 translate>Hours</h6>
|
<h6 translate>Hours</h6>
|
||||||
<vn-label-value
|
<vn-label-value
|
||||||
label="Week total"
|
label="Week total"
|
||||||
value="{{$ctrl.weekTotalHours}} h.">
|
value="{{$ctrl.weekTotalHours}} h.">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
<vn-label-value
|
<vn-label-value
|
||||||
label="Finish at"
|
label="Finish at"
|
||||||
value="{{$ctrl.getFinishTime()}}">
|
value="{{$ctrl.getFinishTime()}}">
|
||||||
</vn-label-value>
|
</vn-label-value>
|
||||||
</div>
|
</div>
|
||||||
|
@ -106,6 +122,8 @@
|
||||||
vn-id="calendar"
|
vn-id="calendar"
|
||||||
class="vn-pt-md"
|
class="vn-pt-md"
|
||||||
ng-model="$ctrl.date"
|
ng-model="$ctrl.date"
|
||||||
|
format-week="$ctrl.formatWeek($element)"
|
||||||
|
on-move="$ctrl.getMailStates($date)"
|
||||||
has-events="$ctrl.hasEvents($day)">
|
has-events="$ctrl.hasEvents($day)">
|
||||||
</vn-calendar>
|
</vn-calendar>
|
||||||
</div>
|
</div>
|
||||||
|
@ -166,15 +184,31 @@
|
||||||
vn-id="reason"
|
vn-id="reason"
|
||||||
on-accept="$ctrl.isUnsatisfied()">
|
on-accept="$ctrl.isUnsatisfied()">
|
||||||
<tpl-body>
|
<tpl-body>
|
||||||
<vn-textarea
|
<div class="reasonDialog">
|
||||||
label="Reason"
|
<vn-textarea
|
||||||
ng-model="$ctrl.reason"
|
label="Reason"
|
||||||
required="true"
|
ng-model="$ctrl.reason"
|
||||||
rows="3">
|
disabled="!$ctrl.isHimSelf"
|
||||||
</vn-textarea>
|
rows="5"
|
||||||
|
required="true">
|
||||||
|
</vn-textarea>
|
||||||
|
</div>
|
||||||
</tpl-body>
|
</tpl-body>
|
||||||
<tpl-buttons>
|
<tpl-buttons ng-if="$ctrl.isHimSelf">
|
||||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
<button response="accept" translate>Save</button>
|
<button response="accept" translate>Save</button>
|
||||||
</tpl-buttons>
|
</tpl-buttons>
|
||||||
</vn-dialog>
|
</vn-dialog>
|
||||||
|
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="sendEmailConfirmation"
|
||||||
|
on-accept="$ctrl.resendEmail()"
|
||||||
|
message="Send time control email">
|
||||||
|
<tpl-body style="min-width: 500px;">
|
||||||
|
<span translate>Are you sure you want to send it?</span>
|
||||||
|
</tpl-body>
|
||||||
|
<tpl-buttons>
|
||||||
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
|
<button response="accept" translate>Confirm</button>
|
||||||
|
</tpl-buttons>
|
||||||
|
</vn-dialog>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import ngModule from '../module';
|
import ngModule from '../module';
|
||||||
import Section from 'salix/components/section';
|
import Section from 'salix/components/section';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
import UserError from 'core/lib/user-error';
|
||||||
|
|
||||||
class Controller extends Section {
|
class Controller extends Section {
|
||||||
constructor($element, $, vnWeekDays) {
|
constructor($element, $, vnWeekDays) {
|
||||||
|
@ -24,12 +25,31 @@ class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.date = initialDate;
|
this.date = initialDate;
|
||||||
|
|
||||||
|
this.getMailStates(this.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isHr() {
|
||||||
|
return this.aclService.hasAny(['hr']);
|
||||||
|
}
|
||||||
|
|
||||||
|
get isHimSelf() {
|
||||||
|
const userId = window.localStorage.currentUserWorkerId;
|
||||||
|
return userId == this.$params.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get worker() {
|
get worker() {
|
||||||
return this._worker;
|
return this._worker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get weekNumber() {
|
||||||
|
return this.getWeekNumber(this.date);
|
||||||
|
}
|
||||||
|
|
||||||
|
set weekNumber(value) {
|
||||||
|
this._weekNumber = value;
|
||||||
|
}
|
||||||
|
|
||||||
set worker(value) {
|
set worker(value) {
|
||||||
this._worker = value;
|
this._worker = value;
|
||||||
}
|
}
|
||||||
|
@ -68,6 +88,27 @@ class Controller extends Section {
|
||||||
}
|
}
|
||||||
|
|
||||||
this.fetchHours();
|
this.fetchHours();
|
||||||
|
this.getWeekData();
|
||||||
|
}
|
||||||
|
|
||||||
|
getWeekData() {
|
||||||
|
const filter = {
|
||||||
|
where: {
|
||||||
|
workerFk: this.$params.id,
|
||||||
|
year: this._date.getFullYear(),
|
||||||
|
week: this.getWeekNumber(this._date)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
this.$http.get('WorkerTimeControlMails', {filter})
|
||||||
|
.then(res => {
|
||||||
|
const workerTimeControlMail = res.data;
|
||||||
|
if (!workerTimeControlMail.length) {
|
||||||
|
this.state = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.state = workerTimeControlMail[0].state;
|
||||||
|
this.reason = workerTimeControlMail[0].reason;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -294,42 +335,56 @@ class Controller extends Section {
|
||||||
this.$.editEntry.show($event);
|
this.$.editEntry.show($event);
|
||||||
}
|
}
|
||||||
|
|
||||||
getWeekNumber(currentDate) {
|
getWeekNumber(date) {
|
||||||
const startDate = new Date(currentDate.getFullYear(), 0, 1);
|
const tempDate = new Date(date);
|
||||||
let days = Math.floor((currentDate - startDate) /
|
let dayOfWeek = tempDate.getDay();
|
||||||
(24 * 60 * 60 * 1000));
|
dayOfWeek = (dayOfWeek === 0) ? 7 : dayOfWeek;
|
||||||
return Math.ceil(days / 7);
|
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
isSatisfied() {
|
isSatisfied() {
|
||||||
const weekNumber = this.getWeekNumber(this.date);
|
|
||||||
const params = {
|
const params = {
|
||||||
workerId: this.worker.id,
|
workerId: this.worker.id,
|
||||||
year: this.date.getFullYear(),
|
year: this.date.getFullYear(),
|
||||||
week: weekNumber,
|
week: this.weekNumber,
|
||||||
state: 'CONFIRMED'
|
state: 'CONFIRMED'
|
||||||
};
|
};
|
||||||
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
||||||
this.$http.post(query, params).then(() => {
|
this.$http.post(query, params).then(() => {
|
||||||
|
this.getMailStates(this.date);
|
||||||
|
this.getWeekData();
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
isUnsatisfied() {
|
isUnsatisfied() {
|
||||||
const weekNumber = this.getWeekNumber(this.date);
|
if (!this.reason) throw new UserError(`You must indicate a reason`);
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
workerId: this.worker.id,
|
workerId: this.worker.id,
|
||||||
year: this.date.getFullYear(),
|
year: this.date.getFullYear(),
|
||||||
week: weekNumber,
|
week: this.weekNumber,
|
||||||
state: 'REVISE',
|
state: 'REVISE',
|
||||||
reason: this.reason
|
reason: this.reason
|
||||||
};
|
};
|
||||||
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
const query = `WorkerTimeControls/updateWorkerTimeControlMail`;
|
||||||
this.$http.post(query, params).then(() => {
|
this.$http.post(query, params).then(() => {
|
||||||
|
this.getMailStates(this.date);
|
||||||
|
this.getWeekData();
|
||||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
changeState(state, reason) {
|
||||||
|
this.state = state;
|
||||||
|
this.reason = reason;
|
||||||
|
this.repaint();
|
||||||
|
}
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
try {
|
try {
|
||||||
const entry = this.selectedRow;
|
const entry = this.selectedRow;
|
||||||
|
@ -345,6 +400,77 @@ class Controller extends Section {
|
||||||
this.vnApp.showError(this.$t(e.message));
|
this.vnApp.showError(this.$t(e.message));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
resendEmail() {
|
||||||
|
const timestamp = this.date.getTime() / 1000;
|
||||||
|
const url = `${window.location.origin}/#!/worker/${this.worker.id}/time-control?timestamp=${timestamp}`;
|
||||||
|
const params = {
|
||||||
|
recipient: this.worker.user.emailUser.email,
|
||||||
|
week: this.weekNumber,
|
||||||
|
year: this.date.getFullYear(),
|
||||||
|
url: url,
|
||||||
|
};
|
||||||
|
this.$http.post(`WorkerTimeControls/weekly-hour-hecord-email`, params)
|
||||||
|
.then(() => {
|
||||||
|
this.vnApp.showSuccess(this.$t('Email sended'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getTime(timeString) {
|
||||||
|
const [hours, minutes, seconds] = timeString.split(':');
|
||||||
|
return [parseInt(hours), parseInt(minutes), parseInt(seconds)];
|
||||||
|
}
|
||||||
|
|
||||||
|
getMailStates(date) {
|
||||||
|
const params = {
|
||||||
|
month: date.getMonth() + 1,
|
||||||
|
year: date.getFullYear()
|
||||||
|
};
|
||||||
|
const query = `WorkerTimeControls/${this.$params.id}/getMailStates`;
|
||||||
|
this.$http.get(query, {params})
|
||||||
|
.then(res => {
|
||||||
|
this.workerTimeControlMails = res.data;
|
||||||
|
this.repaint();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
formatWeek($element) {
|
||||||
|
const weekNumberHTML = $element.firstElementChild;
|
||||||
|
const weekNumberValue = weekNumberHTML.innerHTML;
|
||||||
|
|
||||||
|
if (!this.workerTimeControlMails) return;
|
||||||
|
const workerTimeControlMail = this.workerTimeControlMails.find(
|
||||||
|
workerTimeControlMail => workerTimeControlMail.week == weekNumberValue
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!workerTimeControlMail) return;
|
||||||
|
const state = workerTimeControlMail.state;
|
||||||
|
|
||||||
|
if (state == 'CONFIRMED') {
|
||||||
|
weekNumberHTML.classList.remove('revise');
|
||||||
|
weekNumberHTML.classList.remove('sended');
|
||||||
|
|
||||||
|
weekNumberHTML.classList.add('confirmed');
|
||||||
|
weekNumberHTML.setAttribute('title', 'Conforme');
|
||||||
|
}
|
||||||
|
if (state == 'REVISE') {
|
||||||
|
weekNumberHTML.classList.remove('confirmed');
|
||||||
|
weekNumberHTML.classList.remove('sended');
|
||||||
|
|
||||||
|
weekNumberHTML.classList.add('revise');
|
||||||
|
weekNumberHTML.setAttribute('title', 'No conforme');
|
||||||
|
}
|
||||||
|
if (state == 'SENDED') {
|
||||||
|
weekNumberHTML.classList.add('sended');
|
||||||
|
weekNumberHTML.setAttribute('title', 'Pendiente');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
repaint() {
|
||||||
|
let calendars = this.element.querySelectorAll('vn-calendar');
|
||||||
|
for (let calendar of calendars)
|
||||||
|
calendar.$ctrl.repaint();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
|
Controller.$inject = ['$element', '$scope', 'vnWeekDays'];
|
||||||
|
|
|
@ -5,12 +5,14 @@ describe('Component vnWorkerTimeControl', () => {
|
||||||
let $scope;
|
let $scope;
|
||||||
let $element;
|
let $element;
|
||||||
let controller;
|
let controller;
|
||||||
|
let $httpParamSerializer;
|
||||||
|
|
||||||
beforeEach(ngModule('worker'));
|
beforeEach(ngModule('worker'));
|
||||||
|
|
||||||
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_) => {
|
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_, _$httpParamSerializer_) => {
|
||||||
$stateParams.id = 1;
|
$stateParams.id = 1;
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
|
$httpParamSerializer = _$httpParamSerializer_;
|
||||||
$scope = $rootScope.$new();
|
$scope = $rootScope.$new();
|
||||||
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
|
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
|
||||||
controller = $componentController('vnWorkerTimeControl', {$element, $scope});
|
controller = $componentController('vnWorkerTimeControl', {$element, $scope});
|
||||||
|
@ -82,6 +84,9 @@ describe('Component vnWorkerTimeControl', () => {
|
||||||
$httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours')
|
$httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours')
|
||||||
.respond(response);
|
.respond(response);
|
||||||
|
|
||||||
|
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
|
||||||
|
.respond([]);
|
||||||
|
|
||||||
today.setHours(0, 0, 0, 0);
|
today.setHours(0, 0, 0, 0);
|
||||||
|
|
||||||
let weekOffset = today.getDay() - 1;
|
let weekOffset = today.getDay() - 1;
|
||||||
|
@ -97,7 +102,6 @@ describe('Component vnWorkerTimeControl', () => {
|
||||||
controller.ended = ended;
|
controller.ended = ended;
|
||||||
|
|
||||||
controller.getWorkedHours(controller.started, controller.ended);
|
controller.getWorkedHours(controller.started, controller.ended);
|
||||||
|
|
||||||
$httpBackend.flush();
|
$httpBackend.flush();
|
||||||
|
|
||||||
expect(controller.weekDays.length).toEqual(7);
|
expect(controller.weekDays.length).toEqual(7);
|
||||||
|
@ -152,5 +156,120 @@ describe('Component vnWorkerTimeControl', () => {
|
||||||
expect(controller.date.toDateString()).toEqual(date.toDateString());
|
expect(controller.date.toDateString()).toEqual(date.toDateString());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('getWeekData() ', () => {
|
||||||
|
it(`should make a query an then update the state and reason`, () => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
const response = [
|
||||||
|
{
|
||||||
|
state: 'SENDED',
|
||||||
|
reason: null
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
controller._date = today;
|
||||||
|
|
||||||
|
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
|
||||||
|
.respond(response);
|
||||||
|
|
||||||
|
controller.getWeekData();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.state).toBe('SENDED');
|
||||||
|
expect(controller.reason).toBe(null);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isSatisfied() ', () => {
|
||||||
|
it(`should make a query an then call three methods`, () => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
jest.spyOn(controller, 'getWeekData').mockReturnThis();
|
||||||
|
jest.spyOn(controller, 'getMailStates').mockReturnThis();
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
|
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
|
||||||
|
controller.worker = {id: 1};
|
||||||
|
controller.date = today;
|
||||||
|
controller.weekNumber = 1;
|
||||||
|
|
||||||
|
$httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond();
|
||||||
|
controller.isSatisfied();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.getMailStates).toHaveBeenCalledWith(controller.date);
|
||||||
|
expect(controller.getWeekData).toHaveBeenCalled();
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('isUnsatisfied() ', () => {
|
||||||
|
it(`should throw an error is reason is empty`, () => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
controller.isUnsatisfied();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.message).toBe(`You must indicate a reason`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it(`should make a query an then call three methods`, () => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
jest.spyOn(controller, 'getWeekData').mockReturnThis();
|
||||||
|
jest.spyOn(controller, 'getMailStates').mockReturnThis();
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
|
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
|
||||||
|
controller.worker = {id: 1};
|
||||||
|
controller.date = today;
|
||||||
|
controller.weekNumber = 1;
|
||||||
|
controller.reason = 'reason';
|
||||||
|
|
||||||
|
$httpBackend.expect('POST', 'WorkerTimeControls/updateWorkerTimeControlMail').respond();
|
||||||
|
controller.isSatisfied();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.getMailStates).toHaveBeenCalledWith(controller.date);
|
||||||
|
expect(controller.getWeekData).toHaveBeenCalled();
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('resendEmail() ', () => {
|
||||||
|
it(`should make a query an then call showSuccess method`, () => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
jest.spyOn(controller, 'getWeekData').mockReturnThis();
|
||||||
|
jest.spyOn(controller.vnApp, 'showSuccess');
|
||||||
|
|
||||||
|
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
|
||||||
|
controller.worker = {id: 1};
|
||||||
|
controller.worker = {user: {emailUser: {email: 'employee@verdnatura.es'}}};
|
||||||
|
controller.date = today;
|
||||||
|
controller.weekNumber = 1;
|
||||||
|
|
||||||
|
$httpBackend.expect('POST', 'WorkerTimeControls/weekly-hour-hecord-email').respond();
|
||||||
|
controller.resendEmail();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getMailStates() ', () => {
|
||||||
|
it(`should make a query an then call showSuccess method`, () => {
|
||||||
|
const today = Date.vnNew();
|
||||||
|
jest.spyOn(controller, 'repaint').mockReturnThis();
|
||||||
|
|
||||||
|
controller.$params = {id: 1};
|
||||||
|
|
||||||
|
$httpBackend.expect('GET', `WorkerTimeControls/1/getMailStates?month=1&year=2001`).respond();
|
||||||
|
controller.getMailStates(today);
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.repaint).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -13,4 +13,10 @@ Entry removed: Fichada borrada
|
||||||
The entry type can't be empty: El tipo de fichada no puede quedar vacía
|
The entry type can't be empty: El tipo de fichada no puede quedar vacía
|
||||||
Satisfied: Conforme
|
Satisfied: Conforme
|
||||||
Not satisfied: No conforme
|
Not satisfied: No conforme
|
||||||
Reason: Motivo
|
Reason: Motivo
|
||||||
|
Resend: Reenviar
|
||||||
|
Email sended: Email enviado
|
||||||
|
You must indicate a reason: Debes indicar un motivo
|
||||||
|
Send time control email: Enviar email control horario
|
||||||
|
Are you sure you want to send it?: ¿Seguro que quieres enviarlo?
|
||||||
|
Resend email of this week to the user: Reenviar email de esta semana al usuario
|
||||||
|
|
|
@ -14,7 +14,7 @@ vn-worker-time-control {
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
padding: 4px 0;
|
padding: 4px 0;
|
||||||
|
|
||||||
& > vn-icon {
|
& > vn-icon {
|
||||||
color: $color-font-secondary;
|
color: $color-font-secondary;
|
||||||
padding-right: 1px;
|
padding-right: 1px;
|
||||||
|
@ -24,8 +24,29 @@ vn-worker-time-control {
|
||||||
.totalBox {
|
.totalBox {
|
||||||
max-width: none
|
max-width: none
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.reasonDialog{
|
||||||
|
min-width: 500px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.edit-time-entry {
|
.edit-time-entry {
|
||||||
width: 200px
|
width: 200px
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.confirmed {
|
||||||
|
color: #97B92F;
|
||||||
|
}
|
||||||
|
|
||||||
|
.revise {
|
||||||
|
color: #f61e1e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sended {
|
||||||
|
color: #d19b25;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue