Merge pull request '#7323 redirect woker module to Lilium' (!2825) from Worker_Migration into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #2825
Reviewed-by: Alex Moreno <alexm@verdnatura.es>
This commit is contained in:
Jorge Penadés 2024-08-08 06:54:55 +00:00
commit 00ba0bbe6d
64 changed files with 17 additions and 4419 deletions

View File

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

View File

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

View File

@ -1,34 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker summary path', () => {
const workerId = 3;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('agencyNick');
await httpDataResponse;
});
afterAll(async() => {
await browser.close();
});
it('should reach the employee summary section and check all properties', async() => {
expect(await page.getProperty(selectors.workerSummary.header, 'innerText')).toEqual('agency agency');
expect(await page.getProperty(selectors.workerSummary.id, 'innerText')).toEqual('3');
expect(await page.getProperty(selectors.workerSummary.email, 'innerText')).toEqual('agency@verdnatura.es');
expect(await page.getProperty(selectors.workerSummary.department, 'innerText')).toEqual('CAMARA');
expect(await page.getProperty(selectors.workerSummary.userId, 'innerText')).toEqual('3');
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('-');
});
});

View File

@ -1,40 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker basic data path', () => {
const workerId = 1106;
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
const httpDataResponse = page.waitForResponse(response => {
return response.status() === 200 && response.url().includes(`Workers/${workerId}`);
});
await page.accessToSearchResult('David Charles Haller');
await httpDataResponse;
await page.accessToSection('worker.card.basicData');
});
afterAll(async() => {
await browser.close();
});
it('should edit the form and then reload the section and check the data was edited', async() => {
await page.overwrite(selectors.workerBasicData.name, 'David C.');
await page.overwrite(selectors.workerBasicData.surname, 'H.');
await page.overwrite(selectors.workerBasicData.phone, '444332211');
await page.click(selectors.workerBasicData.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
await page.reloadSection('worker.card.basicData');
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');
});
});

View File

@ -1,32 +0,0 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker pbx path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('hr', 'worker');
await page.accessToSearchResult('employee');
await page.accessToSection('worker.card.pbx');
});
afterAll(async() => {
await browser.close();
});
it('should receive an error when the extension exceeds 4 characters and then sucessfully save the changes', async() => {
await page.write(selectors.workerPbx.extension, '55555');
await page.click(selectors.workerPbx.saveButton);
let message = await page.waitForSnackbar();
expect(message.text).toContain('Extension format is invalid');
await page.overwrite(selectors.workerPbx.extension, '4444');
await page.click(selectors.workerPbx.saveButton);
message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved! User must access web');
});
});

View File

@ -1,65 +0,0 @@
/* eslint max-len: ["error", { "code": 150 }]*/
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker time control path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('salesBoss', 'worker');
await page.accessToSearchResult('HankPym');
await page.accessToSection('worker.card.timeControl');
});
afterAll(async() => {
await browser.close();
});
const eightAm = '08:00';
const fourPm = '16:00';
const hankPymId = 1107;
it('should go to the next month, go to current month and go 1 month in the past', async() => {
let date = Date.vnNew();
date.setDate(1);
date.setMonth(date.getMonth() + 1);
let month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.nextMonthButton);
let result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
date = Date.vnNew();
date.setDate(1);
month = date.toLocaleString('default', {month: 'long'});
await page.waitToClick(selectors.workerTimeControl.previousMonthButton);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
date = Date.vnNew();
date.setDate(1);
date.setMonth(date.getMonth() - 1);
const timestamp = Math.round(date.getTime() / 1000);
month = date.toLocaleString('default', {month: 'long'});
await page.loginAndModule('salesBoss', 'worker');
await page.goto(`http://localhost:5000/#!/worker/${hankPymId}/time-control?timestamp=${timestamp}`);
await page.waitToClick(selectors.workerTimeControl.secondWeekDay);
result = await page.getProperty(selectors.workerTimeControl.monthName, 'innerText');
expect(result).toContain(month);
});
it('should change week of month', async() => {
await page.click(selectors.workerTimeControl.thrirdWeekDay);
const result = await page.getProperty(selectors.workerTimeControl.mondayWorkedHours, 'innerText');
expect(result).toEqual('00:00 h.');
});
});

View File

@ -1,114 +0,0 @@
/* eslint-disable max-len */
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('Worker calendar path', () => {
const reasonableTimeBetweenClicks = 300;
const date = Date.vnNew();
const lastYear = (date.getFullYear() - 1).toString();
let browser;
let page;
async function accessAs(user) {
await page.loginAndModule(user, 'worker');
await page.accessToSearchResult('Charles Xavier');
await page.accessToSection('worker.card.calendar');
}
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
accessAs('hr');
});
afterAll(async() => {
await browser.close();
});
describe('as hr', () => {
it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => {
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.secondFridayOfJun);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 1.5 ');
});
});
describe(`as salesBoss`, () => {
it(`should log in, get to Charles Xavier's calendar, undo what was done here, and check the total holidays used are back to what it was`, async() => {
accessAs('salesBoss');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.penultimateMondayOfJanuary);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.absence);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.lastMondayOfMarch);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfHoliday);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.fistMondayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.furlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondTuesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondWednesdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondThursdayOfMay);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.halfFurlough);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.waitToClick(selectors.workerCalendar.secondFridayOfJun);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
describe(`as Charles Xavier`, () => {
it('should log in and get to his calendar, make a futile attempt to add holidays, check the total holidays used are now the initial ones and use the year selector to go to the previous year', async() => {
accessAs('CharlesXavier');
await page.waitToClick(selectors.workerCalendar.holidays);
await page.waitForTimeout(reasonableTimeBetweenClicks);
await page.click(selectors.workerCalendar.penultimateMondayOfJanuary);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
await page.autocompleteSearch(selectors.workerCalendar.year, lastYear);
expect(await page.getProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText')).toContain(' 0 ');
});
});
});

View File

@ -1,73 +0,0 @@
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');
// 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, 'deliveryAssistant');
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('itManagement', '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!');
});
});

View File

@ -1,42 +0,0 @@
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('hr', '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');
});
});

View File

@ -1,93 +0,0 @@
<mg-ajax path="Workers/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.worker"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="$ctrl.worker.firstName"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Last name"
ng-model="$ctrl.worker.lastName"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Business phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Mobile extension"
ng-model="$ctrl.worker.mobileExtension"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-worker-autocomplete
ng-model="$ctrl.worker.bossFk"
show-field="nickname"
label="Boss">
</vn-worker-autocomplete>
<vn-autocomplete
label="Marital status"
data="$ctrl.maritalStatus"
show-field="name"
value-field="code"
ng-model="$ctrl.worker.maritalStatus">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
ng-model="$ctrl.worker.originCountryFk"
url="Countries"
fields="['id', 'name', 'code']"
show-field="name"
value-field="id"
label="Origin country">
</vn-autocomplete>
<vn-autocomplete
ng-model="$ctrl.worker.educationLevelFk"
url="EducationLevels"
fields="['id', 'name']"
show-field="name"
value-field="id"
label="Education level">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="SSN"
ng-model="$ctrl.worker.SSN"
rule>
</vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,27 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.maritalStatus = [
{code: 'M', name: this.$t('Married')},
{code: 'S', name: this.$t('Single')}
];
}
onSubmit() {
return this.$.watcher.submit()
.then(() => this.card.reload());
}
}
ngModule.vnComponent('vnWorkerBasicData', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
},
require: {
card: '^vnWorkerCard'
}
});

View File

@ -1,9 +0,0 @@
Marital status: Estado civil
Origin country: País origen
Education level: Nivel educación
SSN: NSS
Married: Casado/a
Single: Soltero/a
Business phone: Teléfono de empresa
Mobile extension: Extensión móvil
Locker: Taquilla

View File

@ -1,114 +0,0 @@
<vn-crud-model
url="AbsenceTypes"
data="absenceTypes"
auto-load="true">
</vn-crud-model>
<div ng-if="$ctrl.card.hasWorkCenter">
<div class="vn-w-lg">
<vn-card class="vn-pa-sm calendars">
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
vn-tooltip="To start adding absences, click an absence type from the right menu and then on the day you want to add an absence">
</vn-icon>
<vn-calendar
ng-repeat="month in $ctrl.months"
data="$ctrl.events"
default-date="month"
format-day="$ctrl.formatDay($day, $element)"
display-controls="false"
hide-contiguous="true"
hide-year="true"
on-selection="$ctrl.onSelection($event, $days)">
</vn-calendar>
</vn-card>
</div>
</div>
<div
ng-if="!$ctrl.card.hasWorkCenter"
class="bg-title"
translate>
Autonomous worker
</div>
<vn-side-menu side="right">
<div class="vn-pa-md">
<div class="totalBox vn-mb-sm" style="text-align: center;">
<h6>{{'Contract' | translate}} #{{$ctrl.businessId}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.contractHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Spent' | translate}} {{$ctrl.contractHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.contractHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
<div>
{{'Paid holidays' | translate}} {{$ctrl.contractHolidays.payedHolidays || 0}} {{'days' | translate}}
</div>
</div>
<div class="totalBox" style="text-align: center;">
<h6>{{'Year' | translate}} {{$ctrl.year}}</h6>
<div>
{{'Used' | translate}} {{$ctrl.yearHolidays.holidaysEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHolidays || 0}} {{'days' | translate}}
</div>
<div>
{{'Spent' | translate}} {{$ctrl.yearHolidays.hoursEnjoyed || 0}}
{{'of' | translate}} {{$ctrl.yearHolidays.totalHours || 0}} {{'hours' | translate}}
</div>
</div>
<div class="vn-pt-md">
<vn-autocomplete label="Year"
data="$ctrl.yearFilter"
ng-model="$ctrl.year"
show-field="year"
value-field="year"
order="DESC">
</vn-autocomplete>
<vn-autocomplete label="Contract"
url="Workers/{{$ctrl.$params.id}}/contracts"
fields="['started', 'ended']"
ng-model="$ctrl.businessId"
search-function="{businessFk: $search}"
show-field="businessFk"
value-field="businessFk"
order="businessFk DESC"
limit="5">
<tpl-item>
<div>#{{businessFk}}</div>
<div class="text-caption text-secondary">
{{started | date: 'dd/MM/yyyy'}} - {{ended ? (ended | date: 'dd/MM/yyyy') : 'Indef.'}}
</div>
</tpl-item>
</vn-autocomplete>
</div>
<div name="absenceTypes" class="input vn-py-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}" ng-click="$ctrl.pick(absenceType)">
<vn-avatar ng-style="{backgroundColor: absenceType.rgb}">
<vn-icon class="check" icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
</vn-avatar>
{{absenceType.name}}
</vn-chip>
</div>
<div class="vn-py-md">
<vn-chip>
<vn-avatar class="festive">
</vn-avatar>
<span translate>Festive</span>
</vn-chip>
<vn-chip>
<vn-avatar class="today">
</vn-avatar>
<span translate>Current day</span>
</vn-chip>
</div>
</div>
</vn-side-menu>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?">
</vn-confirm>

View File

@ -1,302 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.date = Date.vnNew();
this.events = {};
this.buildYearFilter();
}
get year() {
return this.date.getFullYear();
}
set year(value) {
const newYear = Date.vnNew();
newYear.setFullYear(value);
this.date = newYear;
this.refresh()
.then(() => this.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays());
}
get businessId() {
return this._businessId;
}
set businessId(value) {
if (!this.card.hasWorkCenter) return;
this._businessId = value;
if (value) {
this.refresh()
.then(() => this.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays());
}
}
get date() {
return this._date;
}
set date(value) {
this._date = value;
value.setHours(0, 0, 0, 0);
this.months = new Array(12);
for (let i = 0; i < this.months.length; i++) {
const now = new Date(value.getTime());
now.setDate(1);
now.setMonth(i);
this.months[i] = now;
}
}
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.getIsSubordinate();
this.getActiveContract();
}
}
buildYearFilter() {
const now = Date.vnNew();
now.setFullYear(now.getFullYear() + 1);
const maxYear = now.getFullYear();
const minRange = maxYear - 5;
const years = [];
for (let i = maxYear; i > minRange; i--)
years.push({year: i});
this.yearFilter = years;
}
getIsSubordinate() {
this.$http.get(`Workers/${this.worker.id}/isSubordinate`)
.then(res => this.isSubordinate = res.data);
}
getActiveContract() {
this.$http.get(`Workers/${this.worker.id}/activeContract`)
.then(res => {
if (res.data) this.businessId = res.data.businessFk;
});
}
getContractHolidays() {
this.getHolidays({
businessFk: this.businessId,
year: this.year
}, data => this.contractHolidays = data);
}
getYearHolidays() {
this.getHolidays({
year: this.year
}, data => this.yearHolidays = data);
}
getHolidays(params, cb) {
this.$http.get(`Workers/${this.worker.id}/holidays`, {params})
.then(res => cb(res.data));
}
onData(data) {
this.events = {};
this.calendar = data.calendar;
let addEvent = (day, newEvent) => {
const timestamp = new Date(day).getTime();
const event = this.events[timestamp];
if (event) {
const oldName = event.name;
Object.assign(event, newEvent);
event.name = `${oldName}, ${event.name}`;
} else
this.events[timestamp] = newEvent;
};
if (data.holidays) {
data.holidays.forEach(holiday => {
const holidayDetail = holiday.detail && holiday.detail.name;
const holidayType = holiday.type && holiday.type.name;
const holidayName = holidayDetail || holidayType;
addEvent(holiday.dated, {
name: holidayName,
className: 'festive'
});
});
}
if (data.absences) {
data.absences.forEach(absence => {
let type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
color: type.rgb,
type: type.code,
absenceId: absence.id
});
});
}
}
repaint() {
let calendars = this.element.querySelectorAll('vn-calendar');
for (let calendar of calendars)
calendar.$ctrl.repaint();
}
formatDay(day, element) {
let event = this.events[day.getTime()];
if (!event) return;
let dayNumber = element.firstElementChild;
dayNumber.title = event.name;
dayNumber.style.backgroundColor = event.color;
if (event.border)
dayNumber.style.border = event.border;
if (event.className)
dayNumber.classList.add(event.className);
}
pick(absenceType) {
if (!this.isSubordinate) return;
if (absenceType == this.absenceType)
absenceType = null;
this.absenceType = absenceType;
}
onSelection($event, $days) {
if (!this.absenceType)
return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu'));
const day = $days[0];
const stamp = day.getTime();
const event = this.events[stamp];
const calendar = $event.target.closest('vn-calendar').$ctrl;
if (event && event.absenceId) {
if (event.type == this.absenceType.code)
this.delete(calendar, day, event);
else
this.edit(calendar, event);
} else
this.create(calendar, day);
}
create(calendar, dated) {
const absenceType = this.absenceType;
const params = {
dated: dated,
absenceTypeId: absenceType.id,
businessFk: this.businessId
};
const path = `Workers/${this.$params.id}/createAbsence`;
this.$http.post(path, params).then(res => {
const newEvent = res.data;
this.events[dated.getTime()] = {
name: absenceType.name,
color: absenceType.rgb,
type: absenceType.code,
absenceId: newEvent.id
};
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
.then(() => this.repaint())
);
});
}
edit(calendar, event) {
const absenceType = this.absenceType;
const params = {
absenceId: event.absenceId,
absenceTypeId: absenceType.id
};
const path = `Workers/${this.$params.id}/updateAbsence`;
this.$http.patch(path, params).then(() => {
event.color = absenceType.rgb;
event.name = absenceType.name;
event.type = absenceType.code;
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
);
});
}
delete(calendar, day, event) {
const params = {absenceId: event.absenceId};
const path = `Workers/${this.$params.id}/deleteAbsence`;
this.$http.delete(path, {params}).then(() => {
delete this.events[day.getTime()];
this.repaintCanceller(() =>
this.refresh()
.then(calendar.repaint())
.then(() => this.getContractHolidays())
.then(() => this.getYearHolidays())
.then(() => this.repaint())
);
});
}
repaintCanceller(cb) {
if (this.canceller) {
clearTimeout(this.canceller);
this.canceller = null;
}
this.canceller = setTimeout(
() => cb(), 500);
}
refresh() {
const params = {
workerFk: this.$params.id,
businessFk: this.businessId,
year: this.year
};
return this.$http.get(`Calendars/absences`, {params})
.then(res => this.onData(res.data));
}
}
ngModule.vnComponent('vnWorkerCalendar', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
},
require: {
card: '^vnWorkerCard'
}
});

View File

@ -1,346 +0,0 @@
import './index';
describe('Worker', () => {
describe('Component vnWorkerCalendar', () => {
let $httpBackend;
let $httpParamSerializer;
let $scope;
let controller;
let year = Date.vnNew().getFullYear();
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-worker-calendar></vn-worker-calendar>');
controller = $componentController('vnWorkerCalendar', {$element, $scope});
controller.isSubordinate = true;
controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
controller.$params.id = 1106;
controller._worker = {id: 1106};
controller.card = {
hasWorkCenter: true
};
}));
describe('year() getter', () => {
it(`should return the year number of the calendar date`, () => {
expect(controller.year).toEqual(year);
});
});
describe('year() setter', () => {
it(`should set the year of the calendar date`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
const previousYear = year - 1;
controller.year = previousYear;
expect(controller.year).toEqual(previousYear);
expect(controller.date.getFullYear()).toEqual(previousYear);
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('businessId() setter', () => {
it(`should set the contract id and then call to the refresh method`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
controller.businessId = 1106;
expect(controller.refresh).toHaveBeenCalledWith();
});
});
describe('months property', () => {
it(`should return an array of twelve months length`, () => {
const started = new Date(year, 0, 1);
const ended = new Date(year, 11, 1);
expect(controller.months.length).toEqual(12);
expect(controller.months[0]).toEqual(started);
expect(controller.months[11]).toEqual(ended);
});
});
describe('worker() setter', () => {
it(`should perform a get query and set the reponse data on the model`, () => {
controller.getIsSubordinate = jest.fn();
controller.getActiveContract = jest.fn();
let today = Date.vnNew();
let tomorrow = new Date(today.getTime());
tomorrow.setDate(tomorrow.getDate() + 1);
let yesterday = new Date(today.getTime());
yesterday.setDate(yesterday.getDate() - 1);
controller.worker = {id: 1107};
expect(controller.getIsSubordinate).toHaveBeenCalledWith();
expect(controller.getActiveContract).toHaveBeenCalledWith();
});
});
describe('getIsSubordinate()', () => {
it(`should return whether the worker is a subordinate`, () => {
$httpBackend.expect('GET', `Workers/1106/isSubordinate`).respond(true);
controller.getIsSubordinate();
$httpBackend.flush();
expect(controller.isSubordinate).toBe(true);
});
});
describe('getActiveContract()', () => {
it(`should return the current contract and then set the businessId property`, () => {
jest.spyOn(controller, 'refresh').mockReturnValue(Promise.resolve());
$httpBackend.expect('GET', `Workers/1106/activeContract`).respond({businessFk: 1106});
controller.getActiveContract();
$httpBackend.flush();
expect(controller.businessId).toEqual(1106);
});
});
describe('getContractHolidays()', () => {
it(`should return the worker holidays amount and then set the contractHolidays property`, () => {
const today = Date.vnNew();
const year = today.getFullYear();
const serializedParams = $httpParamSerializer({year});
$httpBackend.expect('GET', `Workers/1106/holidays?${serializedParams}`).respond({totalHolidays: 28});
controller.getContractHolidays();
$httpBackend.flush();
expect(controller.contractHolidays).toEqual({totalHolidays: 28});
});
});
describe('formatDay()', () => {
it(`should set the day element style`, () => {
const today = Date.vnNew();
controller.events[today.getTime()] = {
name: 'Holiday',
color: '#000'
};
const dayElement = angular.element('<div><section></section></div>')[0];
const dayNumber = dayElement.firstElementChild;
controller.formatDay(today, dayElement);
expect(dayNumber.title).toEqual('Holiday');
expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)');
});
});
describe('pick()', () => {
it(`should set the absenceType property to null if they match with the current one`, () => {
const absenceType = {id: 1, name: 'Holiday'};
controller.absenceType = absenceType;
controller.pick(absenceType);
expect(controller.absenceType).toBeNull();
});
it(`should set the absenceType property`, () => {
const absenceType = {id: 1, name: 'Holiday'};
const expectedAbsence = {id: 2, name: 'Leave of absence'};
controller.absenceType = absenceType;
controller.pick(expectedAbsence);
expect(controller.absenceType).toEqual(expectedAbsence);
});
});
describe('onSelection()', () => {
it(`should show an snackbar message if no absence type is selected`, () => {
jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis();
const $event = {};
const $days = [];
controller.absenceType = null;
controller.onSelection($event, $days);
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu');
});
it(`should call to the create() method`, () => {
jest.spyOn(controller, 'create').mockReturnThis();
const selectedDay = Date.vnNew();
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.absenceType = {id: 1};
controller.onSelection($event, $days);
expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay);
});
it(`should call to the delete() method`, () => {
jest.spyOn(controller, 'delete').mockReturnThis();
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'holiday',
absenceId: 1
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent);
});
it(`should call to the edit() method`, () => {
jest.spyOn(controller, 'edit').mockReturnThis();
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence',
absenceId: 1
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent);
});
});
describe('create()', () => {
it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const dated = Date.vnNew();
const calendarElement = {};
const expectedResponse = {id: 10};
$httpBackend.expect('POST', `Workers/1106/createAbsence`).respond(200, expectedResponse);
controller.create(calendarElement, dated);
$httpBackend.flush();
const createdEvent = controller.events[dated.getTime()];
const absenceType = controller.absenceType;
expect(createdEvent.absenceId).toEqual(expectedResponse.id);
expect(createdEvent.color).toEqual(absenceType.rgb);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('edit()', () => {
it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const event = {absenceId: 10};
const calendarElement = {};
const newAbsenceType = {
id: 2,
name: 'Leave of absence',
code: 'leaveOfAbsence',
rgb: 'purple'
};
controller.absenceType = newAbsenceType;
const expectedParams = {absenceId: 10, absenceTypeId: 2};
$httpBackend.expect('PATCH', `Workers/1106/updateAbsence`, expectedParams).respond(200);
controller.edit(calendarElement, event);
$httpBackend.flush();
expect(event.name).toEqual(newAbsenceType.name);
expect(event.color).toEqual(newAbsenceType.rgb);
expect(event.type).toEqual(newAbsenceType.code);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('delete()', () => {
it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const expectedParams = {absenceId: 10};
const calendarElement = {};
const selectedDay = Date.vnNew();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence',
absenceId: 10
};
controller.events[selectedDay.getTime()] = expectedEvent;
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('DELETE', `Workers/1106/deleteAbsence?${serializedParams}`).respond(200);
controller.delete(calendarElement, selectedDay, expectedEvent);
$httpBackend.flush();
const event = controller.events[selectedDay.getTime()];
expect(event).toBeUndefined();
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('repaintCanceller()', () => {
it(`should cancell the callback execution timer`, () => {
jest.spyOn(window, 'clearTimeout');
jest.spyOn(window, 'setTimeout');
const timeoutId = 90;
controller.canceller = timeoutId;
controller.repaintCanceller(() => {
return 'My callback';
});
expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId);
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500);
});
});
describe('refresh()', () => {
it(`should make a HTTP GET query and then call to the onData() method`, () => {
jest.spyOn(controller, 'onData').mockReturnThis();
const expecteResponse = [{id: 1}];
const expectedParams = {workerFk: controller.worker.id, year: year};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `Calendars/absences?${serializedParams}`).respond(200, expecteResponse);
controller.refresh();
$httpBackend.flush();
expect(controller.onData).toHaveBeenCalledWith(expecteResponse);
});
});
});
});

View File

@ -1,15 +0,0 @@
Calendar: Calendario
Contract: Contrato
Festive: Festivo
Used: Utilizados
Spent: Utilizadas
Year: Año
of: de
days: días
hours: horas
Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia
You can just add absences within the current year: Solo puedes añadir ausencias dentro del año actual
Current day: Día actual
Paid holidays: Vacaciones pagadas
Autonomous worker: Trabajador autónomo

View File

@ -1,65 +0,0 @@
@import "variables";
vn-worker-calendar {
.calendars {
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
box-sizing: border-box;
padding: $spacing-md;
& > vn-calendar {
border: $border-thin;
margin: $spacing-md;
padding: $spacing-xs;
max-width: 288px;
}
}
vn-chip.selectable {
cursor: pointer
}
vn-chip.selectable:hover {
opacity: 0.8
}
vn-chip vn-avatar {
text-align: center;
color: white
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
vn-side-menu div > .input {
border-bottom: $border-thin;
}
.festive,
vn-avatar.today {
color: $color-font;
width: 24px;
min-width: 24px;
height: 24px
}
.festive {
border: 2px solid $color-alert
}
vn-avatar.today {
border: 2px solid $color-font-link
}
.check {
margin-top: 0.5px;
margin-left: -3px;
font-size: 125%;
}
}

View File

@ -1,198 +0,0 @@
<vn-watcher
vn-id="watcher"
url="Workers/new"
data="$ctrl.worker"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" vn-http-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
label="Firstname"
ng-model="$ctrl.worker.firstName"
rule
on-change="$ctrl.generateCodeUser()"
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Lastname"
on-change="$ctrl.generateCodeUser()"
ng-model="$ctrl.worker.lastNames"
rule>
</vn-textfield>
<vn-date-picker
vn-one
label="Birth"
ng-model="$ctrl.worker.birth">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Fi"
ng-model="$ctrl.worker.fi"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Worker code"
ng-model="$ctrl.worker.code"
maxLength="3"
on-change="$ctrl.worker.code = $ctrl.worker.code.toUpperCase()"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-datalist
label="Postcode"
vn-one
ng-model="$ctrl.worker.postcode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.name}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryAssistant"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-autocomplete
vn-id="province"
label="Province"
ng-model="$ctrl.worker.provinceFk"
selection="$ctrl.province"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
rule>
<tpl-item>{{name}} ({{country.name}})</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-datalist
vn-id="town"
label="City"
ng-model="$ctrl.worker.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.name}})
</tpl-item>
</vn-datalist>
<vn-textfield
vn-two
label="Street"
ng-model="$ctrl.worker.street"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
label="Web user"
ng-model="$ctrl.worker.name"
rule>
</vn-textfield>
<vn-textfield
label="Personal email"
ng-model="$ctrl.worker.email"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
vn-id="company"
ng-model="$ctrl.worker.companyFk"
url="Companies"
show-field="code"
value-field="id"
label="Company">
</vn-autocomplete>
<vn-worker-autocomplete
vn-one
ng-model="$ctrl.worker.bossFk"
show-field="nickname"
label="Boss">
</vn-worker-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
label="Pay method"
url="Paymethods"
ng-model="$ctrl.worker.payMethodFk"
initial-data="$ctrl.workerConfig.payMethodFk">
</vn-autocomplete>
<vn-textfield
vn-one
label="IBAN"
ng-model="$ctrl.worker.iban"
on-change="$ctrl.autofillBic()"
rule>
</vn-textfield>
<vn-autocomplete
vn-one
label="Swift / BIC"
url="BankEntities"
ng-model="$ctrl.worker.bankEntityFk"
fields="['name']"
initial-data="$ctrl.worker.bankEntityFk"
on-change="$ctrl.autofillBic()"
search-function="{or: [{bic: {like: $search +'%'}}, {name: {like: '%'+ $search +'%'}}]}"
value-field="id"
show-field="bic"
vn-acl="salesAssistant, hr"
disabled="$ctrl.ibanCountry == 'ES'">
<tpl-item>{{bic}} {{name}}</tpl-item>
<append>
<vn-icon-button
vn-auto
icon="add_circle"
vn-click-stop="bankEntity.show({countryFk: $ctrl.worker.countryFk})"
vn-tooltip="New bank entity"
vn-acl="salesAssistant, hr">
</vn-icon-button>
</append>
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Create">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="worker.index">
</vn-button>
</vn-button-bar>
</form>
<!-- New postcode dialog -->
<vn-geo-postcode
vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-geo-postcode>

View File

@ -1,141 +0,0 @@
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};
this.$http.get(`WorkerConfigs/findOne`, {field: ['payMethodFk']}).then(res => {
if (res.data) this.worker.payMethodFk = res.data.payMethodFk;
});
}
onSubmit() {
if (!this.worker.iban && !this.worker.bankEntityFk) {
delete this.worker.iban;
delete this.worker.bankEntityFk;
}
return this.$.watcher.submit().then(json => {
this.$state.go('worker.card.basicData', {id: json.data.id});
});
}
get ibanCountry() {
if (!this.worker || !this.worker.iban) return false;
let countryCode = this.worker.iban.substr(0, 2);
return countryCode;
}
autofillBic() {
if (!this.worker || !this.worker.iban) return;
let bankEntityId = parseInt(this.worker.iban.substr(4, 4));
let filter = {where: {id: bankEntityId}};
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
});

View File

@ -1,133 +0,0 @@
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('<vn-worker-create></vn-worker-create>');
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);
});
});
});
});

View File

@ -1,13 +0,0 @@
Firstname: Nombre
Lastname: Apellidos
Fi: DNI/NIF/NIE
Birth: Fecha de nacimiento
Worker 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
Pay method: Método de pago

View File

@ -1,94 +0,0 @@
<mg-ajax path="dms/upload" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md"
enctype="multipart/form-data">
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one
label="Company"
ng-model="$ctrl.dms.companyId"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
url="DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
accept="{{$ctrl.allowedContentTypes}}"
required="true"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
label="Upload">
</vn-submit>
<vn-button
class="cancel"
label="Cancel"
ui-sref="worker.card.dms.index"></vn-button>
</vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -1,113 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $) {
super($element, $);
this.dms = {
files: [],
hasFile: false,
hasFileAttached: false
};
}
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$t('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const params = {filter: {
where: {code: 'hhrrData'}
}};
this.$http.get('DmsTypes/findOne', {params}).then(res => {
const dmsType = res.data && res.data;
const companyId = this.vnConfig.companyFk;
const warehouseId = this.vnConfig.warehouseFk;
const defaultParams = {
reference: this.worker.id,
warehouseId: warehouseId,
companyId: companyId,
dmsTypeId: dmsType.id,
description: this.$t('WorkerFileDescription', {
dmsTypeName: dmsType.name,
workerId: this.worker.id,
workerName: this.worker.name
}).toUpperCase()
};
this.dms = Object.assign(this.dms, defaultParams);
});
}
onSubmit() {
const query = `Workers/${this.worker.id}/uploadFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('worker.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnWorkerDmsCreate', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -1,77 +0,0 @@
import './index';
describe('Client', () => {
describe('Component vnWorkerDmsCreate', () => {
let $element;
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('worker'));
beforeEach(inject(($compile, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$element = $compile(`<vn-worker-dms-create></vn-worker-dms-create>`)($rootScope);
controller = $element.controller('vnWorkerDmsCreate');
controller._worker = {id: 1101, name: 'Bruce wayne'};
$httpBackend.whenRoute('GET', `Warehouses?filter=%7B%7D`).respond([{$oldData: {}}]);
}));
describe('worker() setter', () => {
it('should set the worker data and then call setDefaultParams() and getAllowedContentTypes()', () => {
jest.spyOn(controller, 'setDefaultParams');
jest.spyOn(controller, 'getAllowedContentTypes');
controller.worker = {
id: 15,
name: 'Bruce wayne'
};
expect(controller.worker).toBeDefined();
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
$httpBackend.whenRoute('GET', `DmsTypes`).respond({id: 12, code: 'hhrrData'});
const params = {filter: {
where: {code: 'hhrrData'}
}};
let serializedParams = $httpParamSerializer(params);
$httpBackend.when('GET', `DmsTypes/findOne?${serializedParams}`).respond({id: 12, code: 'hhrrData'});
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(1101);
expect(controller.dms.dmsTypeId).toEqual(12);
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

@ -1,7 +0,0 @@
vn-ticket-request {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -1,87 +0,0 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.dms">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form
name="form"
ng-submit="$ctrl.onSubmit()"
class="vn-ma-md"
enctype="multipart/form-data">
<div class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
vn-focus
label="Reference"
ng-model="$ctrl.dms.reference"
rule>
</vn-textfield>
<vn-autocomplete vn-one required="true"
label="Company"
ng-model="$ctrl.dms.companyId"
url="Companies"
show-field="code"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one required="true"
label="Warehouse"
ng-model="$ctrl.dms.warehouseId"
data="warehouses"
show-field="name"
value-field="id">
</vn-autocomplete>
<vn-autocomplete vn-one required="true"
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
url="DmsTypes"
show-field="name"
value-field="id">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
required="true"
label="Description"
ng-model="$ctrl.dms.description"
rule>
</vn-textarea>
</vn-horizontal>
<vn-horizontal>
<vn-input-file
vn-one
label="File"
ng-model="$ctrl.dms.files"
on-change="$ctrl.onFileChange($files)"
accept="{{$ctrl.allowedContentTypes}}"
multiple="true">
<append>
<vn-icon vn-none
color-marginal
title="{{$ctrl.contentTypesInfo}}"
icon="info">
</vn-icon>
</append>
</vn-input-file>
</vn-horizontal>
<vn-vertical>
<vn-check disabled="watcher.orgData.hasFile"
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ui-sref="worker.card.dms.index" label="Cancel"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -1,94 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
get worker() {
return this._worker;
}
set worker(value) {
this._worker = value;
if (value) {
this.setDefaultParams();
this.getAllowedContentTypes();
}
}
getAllowedContentTypes() {
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
});
}
get contentTypesInfo() {
return this.$t('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
setDefaultParams() {
const path = `Dms/${this.$params.dmsId}`;
this.$http.get(path).then(res => {
const dms = res.data && res.data;
this.dms = {
reference: dms.reference,
warehouseId: dms.warehouseFk,
companyId: dms.companyFk,
dmsTypeId: dms.dmsTypeFk,
description: dms.description,
hasFile: dms.hasFile,
hasFileAttached: false,
files: []
};
});
}
onSubmit() {
const query = `dms/${this.$params.dmsId}/updateFile`;
const options = {
method: 'POST',
url: query,
params: this.dms,
headers: {
'Content-Type': undefined
},
transformRequest: files => {
const formData = new FormData();
for (let i = 0; i < files.length; i++)
formData.append(files[i].name, files[i]);
return formData;
},
data: this.dms.files
};
this.$http(options).then(res => {
if (res) {
this.vnApp.showSuccess(this.$t('Data saved!'));
this.$.watcher.updateOriginalData();
this.$state.go('worker.card.dms.index');
}
});
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
}
ngModule.vnComponent('vnWorkerDmsEdit', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -1,82 +0,0 @@
import './index';
describe('Worker', () => {
describe('Component vnClientDmsEdit', () => {
let controller;
let $scope;
let $element;
let $httpBackend;
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$element = angular.element(`<vn-worker-dms-edit></vn-worker-dms-edit`);
controller = $componentController('vnWorkerDmsEdit', {$element, $scope});
controller._worker = {id: 1106};
controller.$params = {dmsId: 4};
}));
describe('worker() setter', () => {
it('should set the worker data and then call setDefaultParams() and getAllowedContentTypes()', () => {
jest.spyOn(controller, 'setDefaultParams');
jest.spyOn(controller, 'getAllowedContentTypes');
controller._worker = undefined;
controller.worker = {
id: 1106
};
expect(controller.setDefaultParams).toHaveBeenCalledWith();
expect(controller.worker).toBeDefined();
expect(controller.getAllowedContentTypes).toHaveBeenCalledWith();
});
});
describe('setDefaultParams()', () => {
it('should perform a GET query and define the dms property on controller', () => {
const dmsId = 4;
const expectedResponse = {
reference: 1101,
warehouseFk: 1,
companyFk: 442,
dmsTypeFk: 3,
description: 'Test',
hasFile: false,
hasFileAttached: false
};
$httpBackend.expect('GET', `Dms/${dmsId}`).respond(expectedResponse);
controller.setDefaultParams();
$httpBackend.flush();
expect(controller.dms).toBeDefined();
expect(controller.dms.reference).toEqual(1101);
expect(controller.dms.dmsTypeId).toEqual(3);
});
});
describe('onFileChange()', () => {
it('should set dms hasFileAttached property to true if has any files', () => {
const files = [{id: 1, name: 'MyFile'}];
controller.dms = {hasFileAttached: false};
controller.onFileChange(files);
$scope.$apply();
expect(controller.dms.hasFileAttached).toBeTruthy();
});
});
describe('getAllowedContentTypes()', () => {
it('should make an HTTP GET request to get the allowed content types', () => {
const expectedResponse = ['image/png', 'image/jpg'];
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse);
controller.getAllowedContentTypes();
$httpBackend.flush();
expect(controller.allowedContentTypes).toBeDefined();
expect(controller.allowedContentTypes).toEqual('image/png, image/jpg');
});
});
});
});

View File

@ -1,7 +0,0 @@
vn-ticket-request {
.vn-textfield {
margin: 0!important;
max-width: 100px;
}
}

View File

@ -1,106 +0,0 @@
<vn-crud-model
vn-id="model"
url="WorkerDms/{{$ctrl.$params.id}}/filter"
link="{worker: $ctrl.$params.id}"
filter="$ctrl.filter"
limit="20"
data="$ctrl.workerDms"
order="dmsFk DESC"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-lg">
<vn-card>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th field="dmsFk" shrink>Id</vn-th>
<vn-th field="hardCopyNumber" shrink number>Order</vn-th>
<vn-th field="reference" shrink>Reference</vn-th>
<vn-th expand>Description</vn-th>
<vn-th field="hasFile" shrink>Original</vn-th>
<vn-th shrink>File</vn-th>
<vn-th field="created">Created</vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
<vn-th shrink></vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="document in $ctrl.workerDms">
<vn-td number shrink>{{::document.id}}</vn-td>
<vn-td shrink number>
<span class="chip" title="{{::document.dms.hardCopyNumber}}"
ng-class="{'message': document.hardCopyNumber}">
{{::document.dms.hardCopyNumber}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.reference}}">
{{::document.dms.reference}}
</span>
</vn-td>
<vn-td expand>
<span title="{{::document.dms.description}}">
{{::document.dms.description}}
</span>
</vn-td>
<vn-td shrink>
<vn-check
ng-model="document.dms.hasFile"
disabled="true">
</vn-check>
</vn-td>
<vn-td shrink>
<span title="{{'Download file' | translate}}" class="link"
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
{{::document.dms.file}}
</span>
</vn-td>
<vn-td shrink-datetime>
{{::document.dms.created | date:'dd/MM/yyyy HH:mm'}}
</vn-td>
<vn-td shrink>
<vn-icon-button title="{{'Download file' | translate}}"
icon="cloud_download"
ng-click="$ctrl.downloadFile(document.dmsFk, document.isDocuware)">
</vn-icon-button>
</vn-td>
<vn-td expand ng-if="::!document.dms.isDocuware">
<vn-icon-button ui-sref="worker.card.dms.edit({dmsId: {{::document.dmsFk}}})"
icon="edit"
title="{{'Edit file' | translate}}">
</vn-icon-button>
<vn-icon-button
icon="delete"
ng-click="confirm.show($index)"
title="{{'Remove file' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
<vn-td expand ng-if="::document.dms.isDocuware">
<vn-icon-button
icon="open_in_new"
ng-click="$ctrl.openDocuware()"
title="{{'Open in docuware' | translate}}"
tabindex="-1">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-card>
</vn-data-viewer>
<a ui-sref="worker.card.dms.create"
vn-tooltip="Upload file"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>
<vn-confirm
vn-id="confirm"
message="This file will be deleted"
question="Are you sure you want to continue?"
on-accept="$ctrl.deleteDms($data)">
</vn-confirm>

View File

@ -1,75 +0,0 @@
import ngModule from '../../module';
import Component from 'core/lib/component';
import './style.scss';
class Controller extends Component {
constructor($element, $, vnFile) {
super($element, $);
this.vnFile = vnFile;
this.filter = {
include: {
relation: 'dms',
scope: {
fields: [
'dmsTypeFk',
'reference',
'hardCopyNumber',
'workerFk',
'description',
'hasFile',
'file',
'created',
'companyFk',
'warehouseFk',
],
include: [
{
relation: 'dmsType',
scope: {
fields: ['name'],
},
},
{
relation: 'worker',
scope: {
fields: ['id'],
include: {
relation: 'user',
scope: {
fields: ['name'],
},
},
},
},
],
},
},
};
}
deleteDms(index) {
const workerDmsId = this.workerDms[index].dmsFk;
return this.$http.post(`WorkerDms/${workerDmsId}/removeFile`)
.then(() => {
this.$.model.remove(index);
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
downloadFile(dmsId, isDocuware) {
if (isDocuware) return this.vnFile.download(`api/workerDms/${dmsId}/docuwareDownload`);
this.vnFile.download(`api/workerDms/${dmsId}/downloadFile`);
}
async openDocuware() {
const url = await this.vnApp.getUrl(`WebClient`, 'docuware');
if (url) window.open(url).focus();
}
}
Controller.$inject = ['$element', '$scope', 'vnFile'];
ngModule.vnComponent('vnWorkerDmsIndex', {
template: require('./index.html'),
controller: Controller,
});

View File

@ -1,37 +0,0 @@
import './index';
import crudModel from 'core/mocks/crud-model';
describe('Worker', () => {
describe('Component vnWorkerDmsIndex', () => {
let $scope;
let $httpBackend;
let controller;
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnWorkerDmsIndex', {$element: null, $scope});
controller.$.model = crudModel;
}));
describe('deleteDms()', () => {
it('should make an HTTP Post query', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
jest.spyOn(controller.$.model, 'remove');
const workerDmsId = 4;
const dmsIndex = 0;
controller.workerDms = [{id: 1, dmsFk: 4}];
$httpBackend.expectPOST(`WorkerDms/${workerDmsId}/removeFile`).respond();
controller.deleteDms(dmsIndex);
$httpBackend.flush();
expect(controller.$.model.remove).toHaveBeenCalledWith(dmsIndex);
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});
});

View File

@ -1,9 +0,0 @@
Are you sure?: Estas seguro?
Download file: Descargar fichero
File: Fichero
File deleted: Fichero eliminado
Hard copy: Copia
My documentation: Mi documentacion
Remove file: Eliminar fichero
This file will be deleted: Este fichero va a ser borrado
Type: Tipo

View File

@ -1,6 +0,0 @@
vn-client-risk-index {
.totalBox {
display: table;
float: right;
}
}

View File

@ -1,2 +0,0 @@
ClientFileDescription: "{{dmsTypeName}} from client {{clientName}} id {{clientId}}"
ContentTypesInfo: Allowed file types {{allowedContentTypes}}

View File

@ -1,20 +0,0 @@
Reference: Referencia
Description: Descripción
Company: Empresa
Upload file: Subir fichero
Edit file: Editar fichero
Upload: Subir
File: Fichero
WorkerFileDescription: "{{dmsTypeName}} del empleado {{workerName}} id {{workerId}}"
ContentTypesInfo: "Tipos de archivo permitidos: {{allowedContentTypes}}"
Generate identifier for original file: Generar identificador para archivo original
Are you sure you want to continue?: ¿Seguro que quieres continuar?
File management: Gestión documental
Hard copy: Copia
This file will be deleted: Este fichero va a ser borrado
Are you sure?: ¿Seguro?
File deleted: Fichero eliminado
Remove file: Eliminar fichero
Download file: Descargar fichero
Created: Creado
Employee: Empleado

View File

@ -1,24 +1,9 @@
export * from './module'; export * from './module';
import './main'; import './main';
import './index/';
import './summary'; import './summary';
import './card'; import './card';
import './create';
import './descriptor'; import './descriptor';
import './descriptor-popover'; import './descriptor-popover';
import './search-panel';
import './basic-data';
import './pbx';
import './pda';
import './department'; import './department';
import './calendar';
import './time-control';
import './log';
import './dms/index';
import './dms/create';
import './dms/edit';
import './note/index';
import './note/create';
import './notifications';

View File

@ -1,57 +0,0 @@
<vn-auto-search
model="model">
</vn-auto-search>
<vn-data-viewer
model="model"
class="vn-w-sm">
<vn-card>
<div class="vn-list separated">
<a
ng-repeat="worker in model.data track by worker.id"
ui-sref="worker.card.summary({id: worker.id})"
translate-attr="{title: 'View worker'}"
class="vn-item search-result">
<vn-item-section>
<h6>{{::worker.nickname}}</h6>
<vn-label-value label="Id"
value="{{::worker.id}}">
</vn-label-value>
<vn-label-value label="User"
value="{{::worker.userName}}">
</vn-label-value>
<vn-label-value label="Email"
value="{{::worker.email}}">
</vn-label-value>
<vn-label-value label="Department"
value="{{::worker.department}}">
</vn-label-value>
</vn-item-section>
<vn-item-section side>
<vn-icon-button
ng-click="$ctrl.goToTimeControl($event, worker.id)"
vn-tooltip="Time control"
icon="access_time">
</vn-icon-button>
<vn-icon-button
ng-click="$ctrl.preview($event, worker)"
vn-tooltip="Preview"
icon="preview">
</vn-icon-button>
</vn-item-section>
</a>
</div>
</vn-card>
</vn-data-viewer>
<a ui-sref="worker.create"
vn-tooltip="New worker"
vn-bind="+"
vn-acl="hr"
vn-acl-action="remove"
fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>
</a>
<vn-popup vn-id="preview">
<vn-worker-summary
worker="$ctrl.selectedWorker">
</vn-worker-summary>
</vn-popup>

View File

@ -1,31 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
preview(event, worker) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.selectedWorker = worker;
this.$.preview.show();
}
goToTimeControl(event, workerId) {
if (event.defaultPrevented) return;
event.preventDefault();
event.stopPropagation();
this.$state.go('worker.card.timeControl', {id: workerId}, {absolute: true});
}
onMoreChange(callback) {
callback.call(this);
}
}
ngModule.vnComponent('vnWorkerIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1 +0,0 @@
New worker: Nuevo trabajador

View File

@ -1 +0,0 @@
<vn-log url="WorkerLogs" origin-id="$ctrl.$params.id"></vn-log>

View File

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

View File

@ -1,18 +0,0 @@
<vn-crud-model
vn-id="model"
url="Workers/filter"
limit="20"
order="id">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
vn-focus
panel="vn-worker-search-panel"
info="Search workers by id, firstName, lastName or user name"
model="model">
</vn-searchbar>
</vn-portal>
<vn-portal slot="menu">
<vn-left-menu></vn-left-menu>
</vn-portal>
<ui-view></ui-view>

View File

@ -1,7 +1,15 @@
import ngModule from '../module'; import ngModule from '../module';
import ModuleMain from 'salix/components/module-main'; import ModuleMain from 'salix/components/module-main';
export default class Worker extends ModuleMain {} export default class Worker extends ModuleMain {
constructor($element, $) {
super($element, $);
}
async $onInit() {
this.$state.go('home');
window.location.href = await this.vnApp.getUrl(`worker/`);
}
}
ngModule.vnComponent('vnWorker', { ngModule.vnComponent('vnWorker', {
controller: Worker, controller: Worker,

View File

@ -1,30 +0,0 @@
<vn-watcher
vn-id="watcher"
url="WorkerObservations"
id-field="id"
data="$ctrl.note"
insert-mode="true"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('worker.card.note.index')" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textarea
vn-one
label="Note"
ng-model="$ctrl.note.text"
vn-focus>
</vn-textarea>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
ng-if="watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
ng-click="$ctrl.cancel()"
label="Cancel">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,21 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.note = {
workerFk: parseInt(this.$params.id),
text: null
};
}
cancel() {
this.$state.go('worker.card.note.index', {id: this.$params.id});
}
}
ngModule.vnComponent('vnNoteWorkerCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -1,22 +0,0 @@
import './index';
describe('Worker', () => {
describe('Component vnNoteWorkerCreate', () => {
let $state;
let controller;
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, _$state_) => {
$state = _$state_;
$state.params.id = '1234';
const $element = angular.element('<vn-note-create></vn-note-create>');
controller = $componentController('vnNoteWorkerCreate', {$element, $state});
}));
it('should define workerFk using $state.params.id', () => {
expect(controller.note.workerFk).toBe(1234);
expect(controller.note.worker).toBe(undefined);
});
});
});

View File

@ -1,2 +0,0 @@
New note: Nueva nota
Note: Nota

View File

@ -1,32 +0,0 @@
<vn-crud-model
vn-id="model"
url="WorkerObservations"
filter="$ctrl.filter"
link="{workerFk: $ctrl.$params.id}"
include="{relation: 'user'}"
data="notes"
auto-load="true">
</vn-crud-model>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-card class="vn-pa-md">
<div
ng-repeat="note in notes"
class="note vn-pa-sm border-solid border-radius vn-mb-md">
<vn-horizontal class="vn-mb-sm" style="color: #666">
<vn-one>{{::note.user.nickname}}</vn-one>
<vn-auto>{{::note.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal class="text">
{{::note.text}}
</vn-horizontal>
</div>
</vn-card>
</vn-data-viewer>
<a vn-tooltip="New note"
ui-sref="worker.card.note.create({id: $ctrl.$params.id})"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -1,22 +0,0 @@
import ngModule from '../../module';
import Section from 'salix/components/section';
import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.filter = {
order: 'created DESC',
};
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnWorkerNote', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -1,5 +0,0 @@
vn-worker-note {
.note:last-child {
margin-bottom: 0;
}
}

View File

@ -1,2 +0,0 @@
<vn-card>
</vn-card>

View File

@ -1,21 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
async $onInit() {
const url = await this.vnApp.getUrl(`worker/${this.$params.id}/notifications`);
window.open(url).focus();
}
}
ngModule.vnComponent('vnWorkerNotifications', {
template: require('./index.html'),
controller: Controller,
bindings: {
ticket: '<'
}
});

View File

@ -1,28 +0,0 @@
<vn-watcher
vn-id="watcher"
data="$ctrl.worker.sip">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-vertical>
<vn-horizontal>
<vn-textfield
label="Extension"
ng-model="$ctrl.worker.sip.extension">
</vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
<vn-button
class="cancel"
label="Undo changes"
disabled="!watcher.dataChanged()"
ng-click="watcher.loadOriginalData()">
</vn-button>
</vn-button-bar>
</form>

View File

@ -1,25 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
onSubmit() {
const sip = this.worker.sip;
const params = {
userFk: this.worker.id,
extension: sip.extension
};
this.$.watcher.check();
this.$http.patch('Sips', params).then(() => {
this.$.watcher.updateOriginalData();
this.vnApp.showSuccess(this.$t('Data saved! User must access web'));
});
}
}
ngModule.vnComponent('vnWorkerPbx', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
}
});

View File

@ -1,18 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
constructor($element, $) {
super($element, $);
}
async $onInit() {
const url = await this.vnApp.getUrl(`worker/${this.$params.id}/pda`);
this.$state.go('worker.card.summary', {id: this.$params.id});
window.location.href = url;
}
}
ngModule.vnComponent('vnWorkerPda', {
controller: Controller
});

View File

@ -9,23 +9,6 @@
{"state": "worker.index", "icon": "icon-worker"}, {"state": "worker.index", "icon": "icon-worker"},
{"state": "worker.department", "icon": "work"} {"state": "worker.department", "icon": "work"}
], ],
"card": [
{"state": "worker.card.basicData", "icon": "settings"},
{"state": "worker.card.note.index", "icon": "insert_drive_file"},
{"state": "worker.card.timeControl", "icon": "access_time"},
{"state": "worker.card.calendar", "icon": "icon-calendar"},
{"state": "worker.card.pda", "icon": "phone_android"},
{"state": "worker.card.notifications", "icon": "notifications"},
{"state": "worker.card.pbx", "icon": "icon-pbx"},
{"state": "worker.card.dms.index", "icon": "cloud_upload"},
{
"icon": "icon-wiki",
"external":true,
"url": "http://wiki.verdnatura.es",
"description": "Wikipedia"
},
{"state": "worker.card.workerLog", "icon": "history"}
],
"department": [ "department": [
{"state": "worker.department.card.basicData", "icon": "settings"} {"state": "worker.department.card.basicData", "icon": "settings"}
] ]
@ -43,12 +26,14 @@
"abstract": true, "abstract": true,
"component": "vn-worker", "component": "vn-worker",
"description": "Workers" "description": "Workers"
}, { },
{
"url": "/index?q", "url": "/index?q",
"state": "worker.index", "state": "worker.index",
"component": "vn-worker-index", "component": "vn-worker-index",
"description": "Workers" "description": "Workers"
}, { },
{
"url" : "/summary", "url" : "/summary",
"state": "worker.card.summary", "state": "worker.card.summary",
"component": "vn-worker-summary", "component": "vn-worker-summary",
@ -56,91 +41,14 @@
"params": { "params": {
"worker": "$ctrl.worker" "worker": "$ctrl.worker"
} }
}, { },
"url": "/:id", {
"state": "worker.card",
"component": "vn-worker-card",
"abstract": true,
"description": "Detail"
}, {
"url": "/basic-data",
"state": "worker.card.basicData",
"component": "vn-worker-basic-data",
"description": "Basic data",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
}, {
"url" : "/log",
"state": "worker.card.workerLog",
"component": "vn-worker-log",
"description": "Log",
"acl": ["hr"]
}, {
"url": "/note",
"state": "worker.card.note",
"component": "ui-view",
"abstract": true
}, {
"url": "/index",
"state": "worker.card.note.index",
"component": "vn-worker-note",
"description": "Notes",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
}, {
"url": "/create",
"state": "worker.card.note.create",
"component": "vn-note-worker-create",
"description": "New note"
}, {
"url": "/pbx",
"state": "worker.card.pbx",
"component": "vn-worker-pbx",
"description": "Private Branch Exchange",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
}, {
"url": "/calendar",
"state": "worker.card.calendar",
"component": "vn-worker-calendar",
"description": "Calendar",
"params": {
"worker": "$ctrl.worker"
}
}, {
"url": "/notifications",
"state": "worker.card.notifications",
"component": "vn-worker-notifications",
"description": "Notifications",
"params": {
"worker": "$ctrl.worker"
}
}, {
"url": "/time-control?timestamp",
"state": "worker.card.timeControl",
"component": "vn-worker-time-control",
"description": "Time control",
"params": {
"worker": "$ctrl.worker"
}
}, {
"url": "/department?q", "url": "/department?q",
"state": "worker.department", "state": "worker.department",
"component": "vn-worker-department", "component": "vn-worker-department",
"description":"Departments" "description":"Departments"
}, { },
"url": "/:id", {
"state": "worker.department.card",
"component": "vn-worker-department-card",
"abstract": true,
"description": "Detail"
}, {
"url" : "/summary", "url" : "/summary",
"state": "worker.department.card.summary", "state": "worker.department.card.summary",
"component": "vn-worker-department-summary", "component": "vn-worker-department-summary",
@ -148,62 +56,6 @@
"params": { "params": {
"department": "$ctrl.department" "department": "$ctrl.department"
} }
},
{
"url": "/basic-data",
"state": "worker.department.card.basicData",
"component": "vn-worker-department-basic-data",
"description": "Basic data",
"params": {
"department": "$ctrl.department"
}
},
{
"url": "/dms",
"state": "worker.card.dms",
"abstract": true,
"component": "ui-view"
},
{
"url": "/index",
"state": "worker.card.dms.index",
"component": "vn-worker-dms-index",
"description": "My documentation",
"acl": ["employee"]
},
{
"url": "/create",
"state": "worker.card.dms.create",
"component": "vn-worker-dms-create",
"description": "Upload file",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
},
{
"url": "/:dmsId/edit",
"state": "worker.card.dms.edit",
"component": "vn-worker-dms-edit",
"description": "Edit file",
"params": {
"worker": "$ctrl.worker"
},
"acl": ["hr"]
},
{
"url": "/create",
"state": "worker.create",
"component": "vn-worker-create",
"description": "New worker",
"acl": ["hr"]
},
{
"url": "/pda",
"state": "worker.card.pda",
"component": "vn-worker-pda",
"description": "PDA",
"acl": ["hr", "productionAssi"]
} }
] ]
} }

View File

@ -1,67 +0,0 @@
<div class="search-panel">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield
vn-one
label="General search"
ng-model="filter.search"
info="Search workers by id, firstName, lastName or user name"
vn-focus>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Worker id"
ng-model="filter.id">
</vn-textfield>
<vn-textfield
vn-one
label="User id"
ng-model="filter.id">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="filter.firstName">
</vn-textfield>
<vn-textfield
vn-one
label="Last name"
ng-model="filter.lastName">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="User name"
ng-model="filter.userName">
</vn-textfield>
<vn-textfield
vn-one
label="Fiscal identifier"
ng-model="filter.fi">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete
vn-one
ng-model="filter.departmentFk"
url="Departments"
show-field="name"
value-field="id"
label="Department">
</vn-autocomplete>
<vn-textfield
vn-one
label="Extension"
ng-model="filter.extension">
</vn-textfield>
</vn-horizontal>
<vn-horizontal class="vn-mt-lg">
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,7 +0,0 @@
import ngModule from '../module';
import SearchPanel from 'core/components/searchbar/search-panel';
ngModule.vnComponent('vnWorkerSearchPanel', {
template: require('./index.html'),
controller: SearchPanel
});

View File

@ -1,219 +0,0 @@
<vn-crud-model
vn-id="model"
url="WorkerTimeControls/filter"
filter="::$ctrl.filter"
data="$ctrl.hours">
</vn-crud-model>
<div>
<vn-card class="vn-pa-lg vn-w-lg">
<vn-table model="model" auto-load="false">
<vn-thead>
<vn-tr>
<vn-td ng-repeat="weekday in $ctrl.weekDays" center>
<div class="weekday" translate>{{::$ctrl.weekdayNames[$index].name}}</div>
<div>
<span>{{::weekday.dated | date: 'dd'}}</span>
<span title="{{::weekday.dated | date: 'MMMM' | translate}}" translate>
{{::weekday.dated | date: 'MMMM'}}
</span>
</div>
<vn-chip
title="{{::weekday.event.name}}"
ng-class="{invisible: !weekday.event}">
<vn-avatar
ng-style="::{backgroundColor: weekday.event.color}">
</vn-avatar>
<div>
{{::weekday.event.name}}
</div>
</vn-chip>
</vn-td>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr>
<vn-td ng-repeat="weekday in $ctrl.weekDays" class="hours vn-pa-none" expand>
<section ng-repeat="hour in weekday.hours">
<vn-icon
icon="{{
::hour.direction == 'in' ? 'arrow_forward' : 'arrow_back'
}}"
title="{{
::(hour.direction == 'in' ? 'In' : 'Out') | translate
}}"
ng-class="::{'invisible': hour.direction == 'middle'}">
</vn-icon>
<vn-chip
ng-class="::{'colored': hour.manual, 'clickable': true}"
removable="::hour.manual"
on-remove="$ctrl.showDeleteDialog($event, hour)"
ng-click="$ctrl.edit($event, hour)"
>
<prepend>
<vn-icon icon="edit"
vn-tooltip="Edit">
</vn-icon>
</prepend>
{{::hour.timed | date: 'HH:mm'}}
</vn-chip>
</section>
</vn-td>
</vn-tr>
</vn-tbody>
<vn-tfoot>
<vn-tr>
<vn-td ng-repeat="weekday in $ctrl.weekDays" center>
{{$ctrl.formatHours(weekday.workedHours)}} h.
</vn-td>
</vn-tr>
<vn-tr>
<vn-td center ng-repeat="weekday in $ctrl.weekDays">
<vn-icon-button
icon="add_circle"
vn-tooltip="Add time"
ng-click="$ctrl.showAddTimeDialog(weekday)">
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tfoot>
</vn-table>
</vn-card>
<vn-button-bar class="vn-w-lg">
<div ng-show="$ctrl.state">
<vn-button
label="Satisfied"
disabled="$ctrl.state == 'CONFIRMED'"
ng-show="$ctrl.isHimSelf"
ng-click="$ctrl.isSatisfied()">
</vn-button>
<vn-button
label="Not satisfied"
disabled="$ctrl.state == 'REVISE'"
ng-show="$ctrl.isHimSelf"
ng-click="reason.show()">
</vn-button>
<vn-button
label="Reason"
ng-show="$ctrl.reason && ($ctrl.isHimSelf || $ctrl.isHr)"
ng-click="reason.show()">
</vn-button>
</div>
<vn-button
label="{{$ctrl.state ? 'Resend' : 'Send'}}"
ng-click="sendEmailConfirmation.show()"
class="right"
vn-tooltip="{{$ctrl.state ? 'Resend' : 'Send'}} email of this week to the user"
ng-if="$ctrl.isHr && $ctrl.state != 'CONFIRMED' && $ctrl.canResend">
</vn-button>
</vn-button-bar>
</div>
<vn-side-menu side="right">
<div class="vn-pa-md">
<div class="totalBox" style="text-align: center;">
<h6 translate>Hours</h6>
<vn-label-value
label="Week total"
value="{{$ctrl.weekTotalHours}} h.">
</vn-label-value>
<vn-label-value
label="Finish at"
value="{{$ctrl.getFinishTime()}}">
</vn-label-value>
</div>
<vn-calendar
vn-id="calendar"
class="vn-pt-md"
ng-model="$ctrl.date"
format-week="$ctrl.formatWeek($element)"
on-move="$ctrl.getMailStates($date)"
has-events="$ctrl.hasEvents($day)">
</vn-calendar>
</div>
</vn-side-menu>
<vn-dialog
vn-id="addTimeDialog"
on-accept="$ctrl.addTime()"
message="Add time">
<tpl-body>
<vn-input-time
vn-one
vn-focus
ng-model="$ctrl.newTimeEntry.timed"
label="Hour"
required="true">
</vn-input-time>
<vn-autocomplete
label="Type"
ng-model="$ctrl.newTimeEntry.direction"
data="$ctrl.entryDirections"
select-fields="['code','description']"
show-field="description"
value-field="code"
required="true">
</vn-autocomplete>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<vn-confirm
vn-id="delete-entry-dialog"
on-accept="$ctrl.deleteTimeEntry()"
message="This time entry will be deleted"
question="Are you sure you want to delete this entry?">
</vn-confirm>
<!-- Edit entry Popover -->
<vn-popover vn-id="editEntry">
<vn-horizontal class="vn-pa-sm edit-time-entry">
<vn-autocomplete class="dense"
ng-model="$ctrl.selectedRow.direction"
data="$ctrl.entryDirections"
select-fields="['code','description']"
show-field="description"
value-field="code">
</vn-autocomplete>
<vn-icon-button vn-none
icon="check"
vn-tooltip="Save"
ng-click="$ctrl.save()">
</vn-icon-button>
</vn-horizontal>
</vn-popover>
<vn-dialog
vn-id="reason"
on-accept="$ctrl.isUnsatisfied()">
<tpl-body>
<div class="reasonDialog">
<vn-textarea
label="Reason"
ng-model="$ctrl.reason"
disabled="!$ctrl.isHimSelf"
rows="5"
required="true">
</vn-textarea>
</div>
</tpl-body>
<tpl-buttons ng-if="$ctrl.isHimSelf">
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<vn-dialog
vn-id="sendEmailConfirmation"
on-accept="$ctrl.resendEmail()"
message="Send time control email">
<tpl-body>
<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>

View File

@ -1,507 +0,0 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
import UserError from 'core/lib/user-error';
class Controller extends Section {
constructor($element, $, vnWeekDays, moment) {
super($element, $);
this.weekDays = [];
this.weekdayNames = vnWeekDays.locales;
this.moment = moment;
this.entryDirections = [
{code: 'in', description: this.$t('In')},
{code: 'middle', description: this.$t('Intermediate')},
{code: 'out', description: this.$t('Out')}
];
}
$postLink() {
const timestamp = this.$params.timestamp;
let initialDate = Date.vnNew();
if (timestamp) {
initialDate = new Date(timestamp * 1000);
this.$.calendar.defaultDate = 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() {
return this._worker;
}
get weekNumber() {
return this.getWeekNumber(this.date);
}
set weekNumber(value) {
this._weekNumber = value;
}
set worker(value) {
this._worker = value;
this.fetchHours();
if (this.date)
this.getWeekData();
}
/**
* Worker hours data
*/
get hours() {
return this._hours;
}
set hours(value) {
this._hours = value;
for (const weekDay of this.weekDays) {
if (value) {
let day = weekDay.dated.getDay();
weekDay.hours = value
.filter(hour => new Date(hour.timed).getDay() == day)
.sort((a, b) => new Date(a.timed) - new Date(b.timed));
} else
weekDay.hours = null;
}
}
/**
* The current selected date
*/
get date() {
return this._date;
}
set date(value) {
this._date = value;
value.setHours(0, 0, 0, 0);
let weekOffset = value.getDay() - 1;
if (weekOffset < 0) weekOffset = 6;
let started = new Date(value.getTime());
started.setDate(started.getDate() - weekOffset);
this.started = started;
let ended = new Date(started.getTime());
ended.setHours(23, 59, 59, 59);
ended.setDate(ended.getDate() + 6);
this.ended = ended;
this.weekDays = [];
let dayIndex = new Date(started.getTime());
while (dayIndex < ended) {
this.weekDays.push({
dated: new Date(dayIndex.getTime())
});
dayIndex.setDate(dayIndex.getDate() + 1);
}
if (this.worker) {
this.fetchHours();
this.getWeekData();
}
}
set weekTotalHours(totalHours) {
this._weekTotalHours = this.formatHours(totalHours);
}
get weekTotalHours() {
return this._weekTotalHours;
}
getWeekData() {
const filter = {
where: {
workerFk: this.$params.id,
year: this._date.getFullYear(),
week: this.getWeekNumber(this._date)
},
};
this.$http.get('WorkerTimeControlMails', {filter})
.then(res => {
if (!res.data.length) {
this.state = null;
return;
}
const [mail] = res.data;
this.state = mail.state;
this.reason = mail.reason;
});
this.canBeResend();
}
canBeResend() {
this.canResend = false;
const filter = {
where: {
year: this._date.getFullYear(),
week: this.getWeekNumber(this._date)
},
limit: 1
};
this.$http.get('WorkerTimeControlMails', {filter})
.then(res => {
if (res.data.length)
this.canResend = true;
});
}
fetchHours() {
if (!this.worker || !this.date) return;
const params = {workerFk: this.$params.id};
const filter = {
where: {and: [
{timed: {gte: this.started}},
{timed: {lte: this.ended}}
]}
};
this.$.model.applyFilter(filter, params).then(() => {
this.getWorkedHours(this.started, this.ended);
this.getAbsences();
});
}
getWorkedHours(from, to) {
this.weekTotalHours = null;
let weekTotalHours = 0;
let params = {
id: this.$params.id,
from: from,
to: to
};
const query = `Workers/${this.$params.id}/getWorkedHours`;
return this.$http.get(query, {params}).then(res => {
const workDays = res.data;
const map = new Map();
for (const workDay of workDays) {
workDay.dated = new Date(workDay.dated);
map.set(workDay.dated, workDay);
weekTotalHours += workDay.workedHours;
}
for (const weekDay of this.weekDays) {
const workDay = workDays.find(day => {
let from = new Date(day.dated);
from.setHours(0, 0, 0, 0);
let to = new Date(day.dated);
to.setHours(23, 59, 59, 59);
return weekDay.dated >= from && weekDay.dated <= to;
});
if (workDay) {
weekDay.expectedHours = workDay.expectedHours;
weekDay.workedHours = workDay.workedHours;
}
}
this.weekTotalHours = weekTotalHours;
});
}
getAbsences() {
const fullYear = this.started.getFullYear();
let params = {
workerFk: this.$params.id,
businessFk: null,
year: fullYear
};
return this.$http.get(`Calendars/absences`, {params})
.then(res => this.onData(res.data));
}
hasEvents(day) {
return day >= this.started && day < this.ended;
}
onData(data) {
const events = {};
const addEvent = (day, event) => {
events[new Date(day).getTime()] = event;
};
if (data.holidays) {
data.holidays.forEach(holiday => {
const holidayDetail = holiday.detail && holiday.detail.description;
const holidayType = holiday.type && holiday.type.name;
const holidayName = holidayDetail || holidayType;
addEvent(holiday.dated, {
name: holidayName,
color: '#ff0'
});
});
}
if (data.absences) {
data.absences.forEach(absence => {
const type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
color: type.rgb
});
});
}
this.weekDays.forEach(day => {
const timestamp = day.dated.getTime();
if (events[timestamp])
day.event = events[timestamp];
});
}
getFinishTime() {
if (!this.weekDays) return;
let today = Date.vnNew();
today.setHours(0, 0, 0, 0);
let todayInWeek = this.weekDays.find(day => day.dated.getTime() === today.getTime());
if (todayInWeek && todayInWeek.hours && todayInWeek.hours.length) {
const remainingTime = todayInWeek.workedHours ?
((todayInWeek.expectedHours - todayInWeek.workedHours) * 1000) : null;
const lastKnownEntry = todayInWeek.hours[todayInWeek.hours.length - 1];
const lastKnownTime = new Date(lastKnownEntry.timed).getTime();
const finishTimeStamp = lastKnownTime && remainingTime ? lastKnownTime + remainingTime : null;
if (finishTimeStamp) {
let finishDate = new Date(finishTimeStamp);
let hour = finishDate.getHours();
let minute = finishDate.getMinutes();
if (hour < 10) hour = `0${hour}`;
if (minute < 10) minute = `0${minute}`;
return `${hour}:${minute} h.`;
}
}
}
formatHours(timestamp = 0) {
let hour = Math.floor(timestamp / 3600);
let min = Math.floor(timestamp / 60 - 60 * hour);
if (hour < 10) hour = `0${hour}`;
if (min < 10) min = `0${min}`;
return `${hour}:${min}`;
}
showAddTimeDialog(weekday) {
const timed = new Date(weekday.dated.getTime());
timed.setHours(0, 0, 0, 0);
this.newTimeEntry = {
workerFk: this.$params.id,
timed: timed
};
this.selectedWeekday = weekday;
this.$.addTimeDialog.show();
}
addTime() {
try {
const entry = this.newTimeEntry;
if (!entry.direction)
throw new Error(`The entry type can't be empty`);
const query = `WorkerTimeControls/${this.worker.id}/addTimeEntry`;
this.$http.post(query, entry)
.then(() => {
this.fetchHours();
this.getMailStates(this.date);
});
} catch (e) {
this.vnApp.showError(this.$t(e.message));
return false;
}
return true;
}
showDeleteDialog($event, hour) {
$event.preventDefault();
this.timeEntryToDelete = hour;
this.$.deleteEntryDialog.show();
}
deleteTimeEntry() {
const entryId = this.timeEntryToDelete.id;
this.$http.post(`WorkerTimeControls/${entryId}/deleteTimeEntry`).then(() => {
this.fetchHours();
this.getMailStates(this.date);
this.vnApp.showSuccess(this.$t('Entry removed'));
});
}
edit($event, hour) {
if ($event.defaultPrevented) return;
this.selectedRow = hour;
this.$.editEntry.show($event);
}
getWeekNumber(date) {
const tempDate = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
return this.moment(tempDate).isoWeek();
}
isSatisfied() {
this.updateWorkerTimeControlMail('CONFIRMED');
}
isUnsatisfied() {
if (!this.reason) throw new UserError(`You must indicate a reason`);
this.updateWorkerTimeControlMail('REVISE', this.reason);
}
updateWorkerTimeControlMail(state, reason) {
const params = {
year: this.date.getFullYear(),
week: this.weekNumber,
state
};
if (reason)
params.reason = reason;
const query = `WorkerTimeControls/${this.worker.id}/updateMailState`;
this.$http.post(query, params).then(() => {
this.getMailStates(this.date);
this.getWeekData();
this.vnApp.showSuccess(this.$t('Data saved!'));
});
}
state(state, reason) {
this.state = state;
this.reason = reason;
this.repaint();
}
save() {
try {
const entry = this.selectedRow;
if (!entry.direction)
throw new Error(`The entry type can't be empty`);
const query = `WorkerTimeControls/${entry.id}/updateTimeEntry`;
if (entry.direction !== entry.$orgRow.direction) {
this.$http.post(query, {direction: entry.direction})
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
.then(() => this.$.editEntry.hide())
.then(() => this.fetchHours())
.then(() => this.getMailStates(this.date));
}
} catch (e) {
this.vnApp.showError(this.$t(e.message));
}
}
resendEmail() {
const params = {
recipient: this.worker.user.emailUser.email,
week: this.weekNumber,
year: this.date.getFullYear(),
workerId: this.worker.id,
state: 'SENDED'
};
this.$http.post(`WorkerTimeControls/weekly-hour-record-email`, params)
.then(() => {
this.getMailStates(this.date);
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', 'moment'];
ngModule.vnComponent('vnWorkerTimeControl', {
template: require('./index.html'),
controller: Controller,
bindings: {
worker: '<'
},
require: {
card: '^vnWorkerCard'
}
});

View File

@ -1,286 +0,0 @@
import './index.js';
describe('Component vnWorkerTimeControl', () => {
let $httpBackend;
let $scope;
let $element;
let controller;
let $httpParamSerializer;
beforeEach(ngModule('worker'));
beforeEach(inject(($componentController, $rootScope, $stateParams, _$httpBackend_, _$httpParamSerializer_) => {
$stateParams.id = 1;
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
$scope = $rootScope.$new();
$element = angular.element('<vn-worker-time-control></vn-worker-time-control>');
controller = $componentController('vnWorkerTimeControl', {$element, $scope});
controller.card = {
hasWorkCenter: true
};
}));
describe('date() setter', () => {
it(`should set the weekDays and the date in the controller`, () => {
let today = Date.vnNew();
jest.spyOn(controller, 'fetchHours').mockReturnThis();
controller.date = today;
expect(controller._date).toEqual(today);
expect(controller.started).toBeDefined();
expect(controller.ended).toBeDefined();
expect(controller.weekDays.length).toEqual(7);
});
});
describe('hours() setter', () => {
it(`should set hours data at it's corresponding week day`, () => {
let today = Date.vnNew();
jest.spyOn(controller, 'fetchHours').mockReturnThis();
controller.date = today;
let hours = [
{
id: 1,
timed: controller.started.toJSON(),
userFk: 1
}, {
id: 2,
timed: controller.ended.toJSON(),
userFk: 1
}, {
id: 3,
timed: controller.ended.toJSON(),
userFk: 1
}
];
controller.hours = hours;
expect(controller.weekDays.length).toEqual(7);
expect(controller.weekDays[0].hours.length).toEqual(1);
expect(controller.weekDays[6].hours.length).toEqual(2);
});
});
describe('getWorkedHours() ', () => {
it('should set the weekdays expected and worked hours plus the total worked hours', () => {
let today = Date.vnNew();
jest.spyOn(controller, 'fetchHours').mockReturnThis();
controller.date = today;
let sixHoursInSeconds = 6 * 60 * 60;
let tenHoursInSeconds = 10 * 60 * 60;
let response = [
{
dated: today,
expectedHours: sixHoursInSeconds,
workedHours: tenHoursInSeconds,
},
];
$httpBackend.whenRoute('GET', 'Workers/:id/getWorkedHours')
.respond(response);
$httpBackend.whenRoute('GET', 'WorkerTimeControlMails')
.respond([]);
today.setHours(0, 0, 0, 0);
let weekOffset = today.getDay() - 1;
if (weekOffset < 0) weekOffset = 6;
let started = new Date(today.getTime());
started.setDate(started.getDate() - weekOffset);
controller.started = started;
let ended = new Date(started.getTime());
ended.setHours(23, 59, 59, 59);
ended.setDate(ended.getDate() + 6);
controller.ended = ended;
controller.getWorkedHours(controller.started, controller.ended);
$httpBackend.flush();
expect(controller.weekDays.length).toEqual(7);
expect(controller.weekDays[weekOffset].expectedHours).toEqual(response[0].expectedHours);
expect(controller.weekDays[weekOffset].workedHours).toEqual(response[0].workedHours);
expect(controller.weekTotalHours).toEqual('10:00');
});
describe('formatHours() ', () => {
it(`should format a passed timestamp to hours and minutes`, () => {
const result = controller.formatHours(3600);
expect(result).toEqual('01:00');
});
});
describe('save() ', () => {
it(`should make a query an then call to the fetchHours() method`, () => {
const today = Date.vnNew();
jest.spyOn(controller, 'getWeekData').mockReturnThis();
jest.spyOn(controller, 'getMailStates').mockReturnThis();
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.date = today;
controller.fetchHours = jest.fn();
controller.selectedRow = {id: 1, timed: Date.vnNew(), direction: 'in', $orgRow: {direction: null}};
controller.$.editEntry = {
hide: () => {}
};
const expectedParams = {direction: 'in'};
$httpBackend.expect('POST', 'WorkerTimeControls/1/updateTimeEntry', expectedParams).respond(200);
controller.save();
$httpBackend.flush();
expect(controller.fetchHours).toHaveBeenCalledWith();
});
});
describe('$postLink() ', () => {
it(`should set the controller date as today if no timestamp is defined`, () => {
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.$params = {timestamp: undefined};
controller.$postLink();
expect(controller.date).toEqual(jasmine.any(Date));
});
it(`should set the controller date using the received timestamp`, () => {
const timestamp = 1;
const date = new Date(timestamp);
controller.$.model = {applyFilter: jest.fn().mockReturnValue(Promise.resolve())};
controller.$.calendar = {};
controller.$params = {timestamp: timestamp};
controller.$postLink();
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/1/updateMailState').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/1/updateMailState').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, 'getMailStates').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-record-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();
});
});
});
});

View File

@ -1,22 +0,0 @@
In: Entrada
Out: Salida
Intermediate: Intermedio
Hour: Hora
Hours: Horas
Add time: Añadir hora
Week total: Total semana
Current week: Semana actual
This time entry will be deleted: Se eliminará la hora fichada
Are you sure you want to delete this entry?: ¿Seguro que quieres eliminarla?
Finish at: Termina a las
Entry removed: Fichada borrada
The entry type can't be empty: El tipo de fichada no puede quedar vacía
Satisfied: Conforme
Not satisfied: No conforme
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

View File

@ -1,52 +0,0 @@
@import "variables";
vn-worker-time-control {
vn-thead > vn-tr > vn-td > div.weekday {
margin-bottom: 5px;
color: $color-main
}
vn-td.hours {
min-width: 100px;
vertical-align: top;
& > section {
display: flex;
align-items: center;
justify-content: center;
padding: 4px 0;
& > vn-icon {
color: $color-font-secondary;
padding-right: 1px;
}
}
}
.totalBox {
max-width: none
}
}
.reasonDialog{
min-width: 500px;
}
.edit-time-entry {
width: 200px
}
.right {
float: right;
}
.confirmed {
color: #97B92F;
}
.revise {
color: #f61e1e;
}
.sended {
color: #d19b25;
}