feat(worker): new worker

This commit is contained in:
Alex Moreno 2022-11-14 15:19:16 +01:00
parent 14d13acae5
commit db3a6df23d
19 changed files with 684 additions and 45 deletions

View File

@ -0,0 +1,18 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('ProfileType', '*', '*', 'ALLOW', 'ROLE', 'employee');
CREATE TABLE `vn`.`newWorkerConfig` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`street` VARCHAR(25) NULL,
`provinceFk` smallint(6) unsigned NULL,
`companyFk` smallint(5) unsigned NULL,
`profileTypeFk` INT(11) NULL,
`roleFk` int(10) unsigned NULL,
PRIMARY KEY (`id`),
CONSTRAINT `newWorkerConfig_province_fk` FOREIGN KEY (`provinceFk`) REFERENCES `vn`.`province` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `newWorkerConfig_company_fk` FOREIGN KEY (`companyFk`) REFERENCES `vn`.`company` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `newWorkerConfig_profileType_fk` FOREIGN KEY (`profileTypeFk`) REFERENCES `vn`.`profileType` (`id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `newWorkerConfig_role_fk` FOREIGN KEY (`roleFk`) REFERENCES `account`.`role` (`id`) ON DELETE CASCADE ON UPDATE CASCADE
);

View File

@ -2719,3 +2719,11 @@ UPDATE `account`.`user`
INSERT INTO `vn`.`osTicketConfig` (`id`, `host`, `user`, `password`, `oldStatus`, `newStatusId`, `day`, `comment`, `hostDb`, `userDb`, `passwordDb`, `portDb`, `responseType`, `fromEmailId`, `replyTo`)
VALUES
(0, 'http://localhost:56596/scp', 'ostadmin', 'Admin1', 'open', 3, 60, 'Este CAU se ha cerrado automáticamente. Si el problema persiste responda a este mensaje.', 'localhost', 'osticket', 'osticket', 40003, 'reply', 1, 'all');
INSERT INTO `vn`.`profileType` (`id`, `name`)
VALUES
(1, 'working');
INSERT INTO `vn`.`newWorkerConfig` (`id`, `street`, `provinceFk`, `companyFk`, `profileTypeFk`, `roleFk`)
VALUES
(1, 'S/ ', 1, 442, 1, 1);

View File

@ -8,7 +8,7 @@ module.exports = Self => {
accepts: [
{
arg: 'fi',
type: 'number',
type: 'string',
description: `The fi of worker`,
required: true
},
@ -30,12 +30,6 @@ module.exports = Self => {
description: `REPLACE!`,
required: true
},
{
arg: 'password',
type: 'string',
description: `REPLACE!`,
required: true
},
{
arg: 'email',
type: 'string',
@ -43,49 +37,76 @@ module.exports = Self => {
required: true
},
{
arg: 'role',
arg: 'roleFk',
type: 'number',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'street',
type: 'string',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'string',
type: 'number',
description: `REPLACE!`
arg: 'city',
type: 'string',
description: `REPLACE!`,
required: true
},
{
arg: 'provinceFk',
type: 'number',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'postalCode',
arg: 'iban',
type: 'string',
description: `REPLACE!`,
required: true
},
{
arg: 'bankEntityFk',
type: 'number',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'companyFk',
type: 'number',
description: `REPLACE!`,
required: true
},
{
arg: 'postcode',
type: 'string',
description: `REPLACE!`,
required: true
},
{
arg: 'phone',
type: 'number',
description: `REPLACE!`
type: 'string',
description: `REPLACE!`,
required: true
},
{
arg: 'workerCode',
arg: 'code',
type: 'string',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'bossFk',
type: 'number',
description: `REPLACE!`
description: `REPLACE!`,
required: true
},
{
arg: 'birth',
type: 'date',
description: `REPLACE!`
description: `REPLACE!`,
required: true
}
],
returns: {
@ -104,7 +125,7 @@ module.exports = Self => {
const args = ctx.args;
let tx;
console.log(args);
if (typeof options == 'object')
Object.assign(myOptions, options);
@ -116,15 +137,16 @@ module.exports = Self => {
let client;
try {
client = await models.Client.findOne({
where: {fi: fi},
where: {fi: args.fi},
}, myOptions);
if (!client) {
const nickname = args.firstName.concat(' ', args.lastNames);
const randomPassword = await models.Worker.rawSql('SELECT account.passwordGenerate();');
const user = await models.Account.create({
name: args.name,
nickname,
password: md5(args.password),
password: md5(randomPassword),
email: args.email,
role: args.role
}, myOptions);
@ -138,11 +160,11 @@ module.exports = Self => {
args.firstName,
args.lastNames,
args.fi,
args.address,
args.street,
args.postalCode,
args.town,
args.province,
args.company,
args.city,
args.provinceFk,
args.companyFk,
args.phone,
args.email,
user.id
@ -151,8 +173,8 @@ module.exports = Self => {
const address = await models.Address.create({
clientFk: user.id,
address: args.street,
town: args.town,
street: args.street,
city: args.city,
provinceFk: args.provinceFk,
postalCode: args.postalCode,
mobile: args.phone,
@ -160,17 +182,22 @@ module.exports = Self => {
isDefaultAddress: true
}, myOptions);
client = await models.Sale.findById(user.id, null, myOptions);
await client.updateAttribute('defaultAddressFk ', address.id);
}
client = await models.Client.findById(user.id, null, myOptions);
console.log(address.id);
await client.updateAttributes({
iban: args.iban,
bankEntityFk: args.bankEntityFk,
defaultAddressFk: address.id
}, myOptions);
}
await models.Worker.rawSql('CALL vn.workerCreate(?, ?, ?, ?, ?, ?, ?)',
[
args.firstName,
args.lastNames,
args.workerCode,
args.code,
args.bossFk,
user.id,
client.id,
args.fi,
args.birth
]
@ -184,6 +211,9 @@ module.exports = Self => {
// TODO: create this email, use client-welcome as template. And view CALL mail_insert in redmine for the body
// TODO: or use same funcionality back/methods/account/recover-password.js
// TODO: call worerWelcomeEmail, and this is who create the url for change password
const email = new Email('worker-welcome', {
recipient: args.email,
lang: ctx.req.getLocale()

View File

@ -1,7 +1,7 @@
{
"AbsenceType": {
"dataSource": "vn"
},
},
"Calendar": {
"dataSource": "vn"
},
@ -16,13 +16,22 @@
},
"Department": {
"dataSource": "vn"
},
},
"Device": {
"dataSource": "vn"
},
"EducationLevel": {
"dataSource": "vn"
},
"Journey": {
"dataSource": "vn"
},
"NewWorkerConfig":{
"dataSource": "vn"
},
"ProfileType":{
"dataSource": "vn"
},
"Time": {
"dataSource": "vn"
},
@ -55,11 +64,8 @@
},
"WorkerDepartment": {
"dataSource": "vn"
},
"WorkerTimeControl": {
"dataSource": "vn"
},
"Device": {
"WorkerTimeControl": {
"dataSource": "vn"
},
"WorkerLog": {

View File

@ -0,0 +1,39 @@
{
"name": "NewWorkerConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "newWorkerConfig"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"street": {
"type": "string"
},
"provinceFk": {
"type": "number"
},
"companyFk": {
"type": "number"
},
"profileTypeFk": {
"type": "number"
},
"roleFk": {
"type": "number"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,19 @@
{
"name": "ProfileType",
"base": "VnModel",
"options": {
"mysql": {
"table": "profileType"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "string"
}
}
}

View File

@ -52,6 +52,9 @@
},
"mobileExtension": {
"type" : "number"
},
"code": {
"type" : "string"
}
},
"relations": {
@ -91,4 +94,4 @@
"foreignKey": "sectorFk"
}
}
}
}

View File

@ -0,0 +1,208 @@
<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
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Lastname"
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="Code"
ng-model="$ctrl.worker.code"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.worker.phone"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<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.country}})</tpl-item>
</vn-autocomplete>
<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.country}})
</tpl-item>
</vn-datalist>
</vn-horizontal>
<vn-horizontal>
<vn-datalist
label="Postcode"
vn-two
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.country}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-textfield
vn-two
label="Street"
ng-model="$ctrl.worker.street"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
label="User"
ng-model="$ctrl.worker.name"
rule>
</vn-textfield>
<vn-textfield
label="Personal email"
ng-model="$ctrl.worker.email"
rule
info="You can save multiple emails">
</vn-textfield>
<vn-autocomplete
label="Access permission"
ng-model="$ctrl.worker.roleFk"
value-field="id"
show-field="description"
url="Roles"
rule>
</vn-autocomplete>
</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-autocomplete
vn-one
ng-model="$ctrl.worker.bossFk"
url="Workers/activeWithInheritedRole"
show-field="nickname"
search-function="{firstName: $search}"
where="{role: 'employee'}"
label="Boss">
</vn-autocomplete>
<vn-autocomplete
vn-one
vn-id="profileType"
ng-model="$ctrl.worker.profileTypeFk"
url="ProfileTypes"
show-field="name"
value-field="id"
label="ProfileType">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="IBAN"
ng-model="$ctrl.worker.iban"
rule
on-change="$ctrl.autofillBic()">
</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

@ -0,0 +1,98 @@
import ngModule from '../module';
import Section from 'salix/components/section';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.$http.get('NewWorkerConfigs/findOne').then(res => {
return this.worker = Object.assign({}, res.data);
});
}
onSubmit() {
return this.$.watcher.submit().then(json => {
this.$state.go('client.card.basicData', {id: json.data.id});
this.$http.get(`Clients/${this.client.id}/checkDuplicatedData`);
});
}
autofillBic() {
if (!this.worker || !this.worker.iban) return;
let bankEntityId = parseInt(this.worker.iban.substr(4, 4));
let filter = {where: {id: bankEntityId}};
if (this.ibanCountry != 'ES') return;
this.$http.get(`BankEntities`, {filter}).then(response => {
const hasData = response.data && response.data[0];
if (hasData)
this.worker.bankEntityFk = response.data[0].id;
else if (!hasData)
this.worker.bankEntityFk = null;
});
}
get province() {
return this._province;
}
// Province auto complete
set province(selection) {
this._province = selection;
}
get town() {
return this._town;
}
// Town auto complete
set town(selection) {
this._town = selection;
if (!selection) return;
const province = selection.province;
const postcodes = selection.postcodes;
if (!this.client.provinceFk)
this.client.provinceFk = province.id;
if (postcodes.length === 1)
this.client.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;
if (!this.client.city)
this.client.city = town.name;
if (!this.client.provinceFk)
this.client.provinceFk = province.id;
}
onResponse(response) {
this.client.postcode = response.code;
this.client.city = response.city;
this.client.provinceFk = response.provinceFk;
}
}
Controller.$inject = ['$element', '$scope'];
ngModule.vnComponent('vnWorkerCreate', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,122 @@
import './index';
describe('Client', () => {
describe('Component vnClientCreate', () => {
let $scope;
let $state;
let controller;
beforeEach(ngModule('client'));
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-client-create></vn-client-create>');
controller = $componentController('vnClientCreate', {$element, $scope});
}));
it('should define and set scope, state and client properties', () => {
expect(controller.$).toBe($scope);
expect(controller.$state).toBe($state);
expect(controller.client.active).toBe(true);
});
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
jest.spyOn($state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('client.card.basicData', {id: '1234'});
});
});
describe('province() setter', () => {
it(`should set countryFk property`, () => {
controller.client.countryFk = null;
controller.province = {
id: 1,
name: 'New york',
country: {
id: 2,
name: 'USA'
}
};
expect(controller.client.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.client.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.client.provinceFk).toEqual(1);
expect(controller.client.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.client.city).toEqual('New York');
expect(controller.client.provinceFk).toEqual(1);
expect(controller.client.countryFk).toEqual(2);
});
});
});
});

View File

@ -0,0 +1,12 @@
Name: Nombre
Tax number: NIF/CIF
Business name: Razón social
Web user: Usuario Web
Email: E-mail
Create and edit: Crear y editar
You can save multiple emails: >-
Puede guardar varios correos electrónicos encadenándolos mediante comas
sin espacios, ejemplo: user@dominio.com, user2@dominio.com siendo el primer
correo electrónico el principal
The type of business must be filled in basic data: El tipo de negocio debe estar rellenado en datos básicos
Access permission: Permiso de acceso

View File

@ -4,6 +4,7 @@ import './main';
import './index/';
import './summary';
import './card';
import './create';
import './descriptor';
import './descriptor-popover';
import './search-panel';

View File

@ -42,6 +42,12 @@
</div>
</vn-card>
</vn-data-viewer>
<a ui-sref="worker.create"
vn-tooltip="New worker"
vn-bind="+"
fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>
</a>
<vn-popup vn-id="preview">
<vn-worker-summary
worker="$ctrl.selectedWorker">

View File

@ -16,7 +16,7 @@
{"state": "worker.card.timeControl", "icon": "access_time"},
{"state": "worker.card.dms.index", "icon": "cloud_upload"},
{
"icon": "icon-wiki",
"icon": "icon-wiki",
"external":true,
"url": "http://wiki.verdnatura.es",
"description": "Wikipedia"
@ -134,6 +134,13 @@
"worker": "$ctrl.worker"
},
"acl": ["hr"]
},
{
"url": "/create",
"state": "worker.create",
"component": "vn-worker-create",
"description": "New worker",
"acl": ["hr"]
}
]
}
}

View File

@ -0,0 +1,11 @@
const Stylesheet = require(`vn-print/core/stylesheet`);
const path = require('path');
const vnPrintPath = path.resolve('print');
module.exports = new Stylesheet([
`${vnPrintPath}/common/css/spacing.css`,
`${vnPrintPath}/common/css/misc.css`,
`${vnPrintPath}/common/css/layout.css`,
`${vnPrintPath}/common/css/email.css`])
.mergeStyles();

View File

@ -0,0 +1,10 @@
<email-body v-bind="$props">
<div class="grid-row">
<div class="grid-block vn-pa-ml">
<h1>{{ $t('title', [id]) }}</h1>
<p>{{ $t('description.dearWorker') }},</p>
<p v-html="instructions"></p>
<p>{{ $t('workerData', this.user.name, this.url) }}</p>
</div>
</div>
</email-body>

View File

@ -0,0 +1,23 @@
const Component = require(`vn-print/core/component`);
const emailBody = new Component('email-body');
module.exports = {
name: 'client-welcome',
async serverPrefetch() {
this.client = await this.fetchClient(this.id);
},
methods: {
fetchClient(id) {
return this.findOneFromDef('client', [id]);
},
},
components: {
'email-body': emailBody.build(),
},
props: {
id: {
type: Number,
required: true
}
}
};

View File

@ -0,0 +1,7 @@
subject: Bienvenido a Verdnatura
title: "¡Te damos la bienvenida!"
dearWorker: Estimado trabajador
workerData: 'Bienvenido a Verdnatura SL, este es tu usuario: <strong>{0}</strong>. Pero primero debes
<a href="{{1}}"
title="Cambiar contraseña" target="_blank" style="color: #8dba25">cambiar tu contraseña.
</a>'

View File

@ -0,0 +1,11 @@
SELECT
c.id,
u.name AS userName,
CONCAT(w.lastName, ' ', w.firstName) salesPersonName,
w.phone AS salesPersonPhone,
CONCAT(wu.name, '@verdnatura.es') AS salesPersonEmail
FROM client c
JOIN account.user u ON u.id = c.id
LEFT JOIN worker w ON w.id = c.salesPersonFk
LEFT JOIN account.user wu ON wu.id = w.userFk
WHERE c.id = ?