Merge pull request '5410-client.defaulter_getChecked' (!1403) from 5410-client.defaulter_getChecked into dev
gitea/salix/pipeline/head There was a failure building this commit Details

Reviewed-on: #1403
Reviewed-by: Javi Gallego <jgallego@verdnatura.es>
Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
Joan Sanchez 2023-04-06 07:04:32 +00:00
commit 033927d5b4
10 changed files with 188 additions and 26 deletions

View File

@ -8,13 +8,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [2314.01] - 2023-04-20
### Added
- (Clientes -> Morosos) Ahora se puede filtrar por las columnas "Desde" y "Fecha Ú. O.". También se envia un email al comercial cuando se añade una nota.
- (Monitor tickets) Muestra un icono al lado de la zona, si el ticket es frágil y se envía por agencia
- (Facturas recibidas -> Bases negativas) Nueva sección
### Changed
-
### Fixed
-
- (Clientes -> Morosos) Ahora se mantienen los elementos seleccionados al hacer sroll.
## [2312.01] - 2023-04-06

View File

@ -0,0 +1,2 @@
INSERT INTO `salix`.`ACL` ( model, property, accessType, permission, principalType, principalId)
VALUES('Mail', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -268,9 +268,11 @@
"Exists an invoice with a previous date": "Existe una factura con fecha anterior",
"Invoice date can't be less than max date": "La fecha de factura no puede ser inferior a la fecha límite",
"Warehouse inventory not set": "El almacén inventario no está establecido",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"This locker has already been assigned": "Esta taquilla ya ha sido asignada",
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Insert a date range": "Inserte un rango de fechas"
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Insert a date range": "Inserte un rango de fechas",
"Added observation": "{{user}} añadió esta observacion: {{text}}",
"Comment added to client": "Observación añadida al cliente {{clientFk}}"
}

View File

@ -0,0 +1,52 @@
module.exports = Self => {
Self.remoteMethodCtx('observationEmail', {
description: 'Send an email with the observation',
accessType: 'WRITE',
accepts: [
{
arg: 'defaulters',
type: ['object'],
required: true,
description: 'The defaulters to send the email'
},
{
arg: 'observation',
type: 'string',
required: true,
description: 'The observation'
}],
returns: {
arg: 'observationEmail'
},
http: {
path: `/observationEmail`,
verb: 'POST'
}
});
Self.observationEmail = async(ctx, defaulters, observation, options) => {
const models = Self.app.models;
const $t = ctx.req.__; // $translate
const myOptions = {};
const userId = ctx.req.accessToken.userId;
if (typeof options == 'object')
Object.assign(myOptions, options);
for (const defaulter of defaulters) {
const user = await models.Account.findById(userId, {fields: ['name']}, myOptions);
const body = $t('Added observation', {
user: user.name,
text: observation
});
await models.Mail.create({
subject: $t('Comment added to client', {clientFk: defaulter.clientFk}),
body: body,
receiver: `${defaulter.salesPersonName}@verdnatura.es`,
replyTo: `${user.name}@verdnatura.es`
}, myOptions);
}
};
};

View File

@ -1,3 +1,4 @@
module.exports = Self => {
require('../methods/defaulter/filter')(Self);
require('../methods/defaulter/observationEmail')(Self);
};

View File

@ -5,6 +5,7 @@
limit="20"
order="amount DESC"
data="defaulters"
on-data-change="$ctrl.reCheck()"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
@ -17,22 +18,22 @@
</vn-searchbar>
</vn-portal>
<vn-card>
<smart-table
<smart-table
model="model"
options="$ctrl.smartTableOptions"
options="$ctrl.smartTableOptions"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-actions>
<div>
<div class="totalBox" style="text-align: center;">
<h6 translate>Total</h6>
<vn-label-value
label="Balance due"
<vn-label-value
label="Balance due"
value="{{$ctrl.balanceDueTotal | currency: 'EUR': 2}}">
</vn-label-value>
</div>
</div>
<div class="vn-pa-md">
<vn-button
<vn-button
ng-show="$ctrl.checked.length > 0"
ng-click="notesDialog.show()"
name="notesDialog"
@ -46,7 +47,7 @@
<thead>
<tr>
<th shrink>
<vn-multi-check
<vn-multi-check
model="model">
</vn-multi-check>
</th>
@ -56,25 +57,25 @@
<th field="salesPersonFk">
<span translate>Comercial</span>
</th>
<th
field="amount"
<th
field="amount"
vn-tooltip="Balance due">
<span translate>Balance D.</span>
</th>
<th
field="workerFk"
<th
field="workerFk"
vn-tooltip="Worker who made the last observation">
<span translate>Author</span>
</th>
<th field="observation" expand>
<span translate>Last observation</span>
</th>
<th
<th
vn-tooltip="Last observation date"
field="created">
<span translate>L. O. Date</span>
</th>
<th
<th
vn-tooltip="Credit insurance"
field="creditInsurance"
shrink>
@ -88,8 +89,9 @@
<tbody>
<tr ng-repeat="defaulter in defaulters">
<td shrink>
<vn-check
<vn-check
ng-model="defaulter.checked"
on-change="$ctrl.saveChecked(defaulter.clientFk)"
vn-click-stop>
</vn-check>
</td>
@ -150,7 +152,7 @@
</vn-client-summary>
</vn-popup>
<!-- Dialog of add notes button -->
<!-- Dialog of add notes button -->
<vn-dialog
vn-id="notesDialog"
on-accept="$ctrl.onResponse()">
@ -160,7 +162,7 @@
<vn-horizontal>
<vn-textarea vn-one
vn-id="message"
label="Message"
label="Message"
ng-model="$ctrl.defaulter.observation"
rows="3"
required="true"

View File

@ -6,6 +6,7 @@ export default class Controller extends Section {
constructor($element, $) {
super($element, $);
this.defaulter = {};
this.checkedDefaulers = [];
this.smartTableOptions = {
activeButtons: {
@ -45,11 +46,11 @@ export default class Controller extends Section {
},
{
field: 'created',
searchable: false
datepicker: true
},
{
field: 'defaulterSinced',
searchable: false
datepicker: true
}
]
};
@ -68,6 +69,19 @@ export default class Controller extends Section {
return checkedLines;
}
saveChecked(clientId) {
this.checkedDefaulers = this.checkedDefaulers.includes(clientId) ?
this.checkedDefaulers.filter(id => id !== clientId) : [...this.checkedDefaulers, clientId];
}
reCheck() {
if (!this.$.model.data || !this.checkedDefaulers.length) return;
this.$.model.data.forEach(defaulter => {
defaulter.checked = this.checkedDefaulers.includes(defaulter.clientFk);
});
}
getBalanceDueTotal() {
this.$http.get('Defaulters/filter')
.then(res => {
@ -109,11 +123,20 @@ export default class Controller extends Section {
}
this.$http.post(`ClientObservations`, params) .then(() => {
this.vnApp.showMessage(this.$t('Observation saved!'));
this.vnApp.showSuccess(this.$t('Observation saved!'));
this.sendMail();
this.$state.reload();
});
}
sendMail() {
const params = {
defaulters: this.checked,
observation: this.defaulter.observation
};
this.$http.post(`Defaulters/observationEmail`, params);
}
exprBuilder(param, value) {
switch (param) {
case 'creditInsurance':
@ -122,8 +145,25 @@ export default class Controller extends Section {
case 'workerFk':
case 'salesPersonFk':
return {[`d.${param}`]: value};
case 'created':
return {'d.created': {
between: this.dateRange(value)}
};
case 'defaulterSinced':
return {'d.defaulterSinced': {
between: this.dateRange(value)}
};
}
}
dateRange(value) {
const minHour = new Date(value);
minHour.setHours(0, 0, 0, 0);
const maxHour = new Date(value);
maxHour.setHours(23, 59, 59, 59);
return [minHour, maxHour];
}
}
ngModule.vnComponent('vnClientDefaulter', {

View File

@ -81,14 +81,15 @@ describe('client defaulter', () => {
const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}];
jest.spyOn(controller.vnApp, 'showMessage');
jest.spyOn(controller.vnApp, 'showSuccess');
$httpBackend.expect('GET', `Defaulters/filter`).respond(200);
$httpBackend.expect('POST', `ClientObservations`, params).respond(200, params);
$httpBackend.expect('POST', `Defaulters/observationEmail`).respond(200);
controller.onResponse();
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Observation saved!');
expect(controller.vnApp.showSuccess).toHaveBeenCalledWith('Observation saved!');
});
});
@ -117,5 +118,62 @@ describe('client defaulter', () => {
expect(controller.balanceDueTotal).toEqual(875);
});
});
describe('dateRange()', () => {
it('should return two dates with the hours at the start and end of the given date', () => {
const now = Date.vnNew();
const today = now.getDate();
const dateRange = controller.dateRange(now);
const start = dateRange[0].toString();
const end = dateRange[1].toString();
expect(start).toContain(today);
expect(start).toContain('00:00:00');
expect(end).toContain(today);
expect(end).toContain('23:59:59');
});
});
describe('reCheck()', () => {
it(`should recheck buys`, () => {
controller.$.model.data = [
{checked: false, clientFk: 1},
{checked: false, clientFk: 2},
{checked: false, clientFk: 3},
{checked: false, clientFk: 4},
];
controller.checkedDefaulers = [1, 2];
controller.reCheck();
expect(controller.$.model.data[0].checked).toEqual(true);
expect(controller.$.model.data[1].checked).toEqual(true);
expect(controller.$.model.data[2].checked).toEqual(false);
expect(controller.$.model.data[3].checked).toEqual(false);
});
});
describe('saveChecked()', () => {
it(`should check buy`, () => {
const buyCheck = 3;
controller.checkedDefaulers = [1, 2];
controller.saveChecked(buyCheck);
expect(controller.checkedDefaulers[2]).toEqual(buyCheck);
});
it(`should uncheck buy`, () => {
const buyUncheck = 3;
controller.checkedDefaulers = [1, 2, 3];
controller.saveChecked(buyUncheck);
expect(controller.checkedDefaulers[2]).toEqual(undefined);
});
});
});
});

View File

@ -6,4 +6,6 @@ Last observation: Última observación
L. O. Date: Fecha Ú. O.
Last observation date: Fecha última observación
Search client: Buscar clientes
Worker who made the last observation: Trabajador que ha realizado la última observación
Worker who made the last observation: Trabajador que ha realizado la última observación
Email sended!: Email enviado!
Observation saved!: Observación añadida!

View File

@ -5,4 +5,5 @@ M3 Price: Precio M3
Route Price: Precio ruta
Minimum Km: Km minimos
Remove row: Eliminar fila
Add row: Añadir fila
Add row: Añadir fila
New autonomous: Nuevo autónomo