Merge pull request '3622-feat(client_defaulter): implemented smart-table' (#881) from 3622-client_defaulter into dev
gitea/salix/pipeline/head This commit looks good Details

Reviewed-on: #881
Reviewed-by: Joan Sanchez <joan@verdnatura.es>
This commit is contained in:
Joan Sanchez 2022-03-02 09:17:07 +00:00
commit 6a29f31bb5
8 changed files with 223 additions and 147 deletions

View File

@ -305,11 +305,11 @@ export default {
anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr', anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr',
}, },
clientDefaulter: { clientDefaulter: {
anyClient: 'vn-client-defaulter-index vn-tbody > vn-tr', anyClient: 'vn-client-defaulter-index tbody > tr',
firstClientName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span', firstClientName: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(2) > span',
firstSalesPersonName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(3) > span', firstSalesPersonName: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(3) > span',
firstObservation: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]', firstObservation: 'vn-client-defaulter-index tbody > tr:nth-child(1) > td:nth-child(6) > vn-textarea[ng-model="defaulter.observation"]',
allDefaulterCheckbox: 'vn-client-defaulter-index vn-thead vn-multi-check', allDefaulterCheckbox: 'vn-client-defaulter-index thead vn-multi-check',
addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]', addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]',
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]', observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
saveButton: 'button[response="accept"]' saveButton: 'button[response="accept"]'

View File

@ -28,8 +28,8 @@ describe('Client defaulter path', () => {
const salesPersonName = const salesPersonName =
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText'); await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
expect(clientName).toEqual('Ororo Munroe'); expect(clientName).toEqual('Batman');
expect(salesPersonName).toEqual('salesPerson'); expect(salesPersonName).toEqual('salesPersonNick');
}); });
it('should first observation not changed', async() => { it('should first observation not changed', async() => {
@ -65,6 +65,7 @@ describe('Client defaulter path', () => {
it('should first observation changed', async() => { it('should first observation changed', async() => {
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
await page.waitForSelector(selectors.clientDefaulter.firstObservation);
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value'); const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
expect(message.text).toContain('Observation saved!'); expect(message.text).toContain('Observation saved!');

View File

@ -56,18 +56,18 @@ module.exports = Self => {
FROM ( FROM (
SELECT SELECT
DISTINCT c.id clientFk, DISTINCT c.id clientFk,
c.name clientName, c.socialName clientName,
c.salesPersonFk, c.salesPersonFk,
u.name salesPersonName, u.nickname salesPersonName,
d.amount, d.amount,
co.created, co.created,
CONCAT(DATE(co.created), ' ', co.text) observation, co.text observation,
uw.id workerFk, uw.id workerFk,
uw.name workerName, uw.nickname workerName,
c.creditInsurance, c.creditInsurance,
d.defaulterSinced d.defaulterSinced
FROM vn.defaulter d FROM vn.defaulter d
JOIN vn.client c ON c.id = d.clientFk JOIN vn.client c ON c.id = d.clientFk
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
LEFT JOIN account.user u ON u.id = c.salesPersonFk LEFT JOIN account.user u ON u.id = c.salesPersonFk
LEFT JOIN account.user uw ON uw.id = co.workerFk LEFT JOIN account.user uw ON uw.id = co.workerFk

View File

@ -47,12 +47,12 @@ describe('defaulter filter()', () => {
try { try {
const options = {transaction: tx}; const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}}; const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'spider'}};
const result = await models.Defaulter.filter(ctx, null, options); const result = await models.Defaulter.filter(ctx, null, options);
const firstRow = result[0]; const firstRow = result[0];
expect(firstRow.clientName).toEqual('Bruce Wayne'); expect(firstRow.clientName).toEqual('Spider man');
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -15,17 +15,18 @@
model="model"> model="model">
</vn-searchbar> </vn-searchbar>
</vn-portal> </vn-portal>
<vn-data-viewer <vn-card>
model="model" <smart-table
class="vn-w-xl"> model="model"
<vn-card> options="$ctrl.smartTableOptions"
<vn-tool-bar> expr-builder="$ctrl.exprBuilder(param, value)">
<div class="vn-pa-md"> <slot-actions>
<div>
<div class="totalBox" style="text-align: center;"> <div class="totalBox" style="text-align: center;">
<h6 translate>Total</h6> <h6 translate>Total</h6>
<vn-label-value <vn-label-value
label="Balance due" label="Balance due"
value="{{$ctrl.balanceDueTotal}}"> value="{{$ctrl.balanceDueTotal | currency: 'EUR': 2}}">
</vn-label-value> </vn-label-value>
</div> </div>
</div> </div>
@ -38,90 +39,109 @@
icon="icon-notes"> icon="icon-notes">
</vn-button> </vn-button>
</div> </div>
</vn-tool-bar> </slot-actions>
<vn-table model="model"> <slot-table>
<vn-thead> <table>
<vn-tr> <thead>
<vn-th shrink> <tr>
<vn-multi-check <th shrink>
model="model"> <vn-multi-check
</vn-multi-check> model="model">
</vn-th> </vn-multi-check>
<vn-th field="clientName">Client</vn-th> </th>
<vn-th field="salesPersonFk">Comercial</vn-th> <th field="clientName">
<vn-th <span translate>Client</span>
field="amount" </th>
vn-tooltip="Balance due" <th field="salesPersonFk">
number> <span translate>Comercial</span>
Balance D. </th>
</vn-th> <th
<vn-th field="amount"
vn-tooltip="Worker who made the last observation" vn-tooltip="Balance due">
shrink> <span translate>Balance D.</span>
Author </th>
</vn-th> <th
<vn-th expand>Last observation</vn-th> field="workerFk"
<vn-th vn-tooltip="Worker who made the last observation">
vn-tooltip="Credit insurance" <span translate>Author</span>
number> </th>
Credit I. <th field="observation" expand>
</vn-th> <span translate>Last observation</span>
<vn-th shrink-datetime>From</vn-th> </th>
</vn-tr> <th
</vn-thead> vn-tooltip="Last observation date"
<vn-tbody> field="created"
<vn-tr ng-repeat="defaulter in defaulters"> shrink-datetime>
<vn-td shrink> <span translate>Last observation D.</span>
<vn-check </th>
ng-model="defaulter.checked" <th
vn-click-stop> vn-tooltip="Credit insurance"
</vn-check> field="creditInsurance" >
</vn-td> <span translate>Credit I.</span>
<vn-td> </th>
<span <th field="defaulterSinced">
vn-click-stop="clientDescriptor.show($event, defaulter.clientFk)" <span translate>From</span>
title ="{{::defaulter.clientName}}" </th>
class="link"> </tr>
{{::defaulter.clientName}} </thead>
</span> <tbody>
</vn-td> <tr ng-repeat="defaulter in defaulters">
<vn-td> <td shrink>
<span <vn-check
title="{{::defaulter.salesPersonName}}" ng-model="defaulter.checked"
vn-click-stop="workerDescriptor.show($event, defaulter.salesPersonFk)" vn-click-stop>
class="link" > </vn-check>
{{::defaulter.salesPersonName | dashIfEmpty}} </td>
</span> <td title="{{::defaulter.clientName}}">
</vn-td> <span
<vn-td number>{{::defaulter.amount}}</vn-td> vn-click-stop="clientDescriptor.show($event, defaulter.clientFk)"
<vn-td shrink> title ="{{::defaulter.clientName}}"
<span class="link">
title="{{::defaulter.workerName}}" {{::defaulter.clientName}}
vn-click-stop="workerDescriptor.show($event, defaulter.workerFk)" </span>
class="link" > </td>
{{::defaulter.workerName | dashIfEmpty}} <td>
</span> <span
</vn-td> title="{{::defaulter.salesPersonName}}"
<vn-td expand> vn-click-stop="workerDescriptor.show($event, defaulter.salesPersonFk)"
<vn-textarea class="link">
vn-three {{::defaulter.salesPersonName | dashIfEmpty}}
disabled="true" </span>
label="Observation" </td>
ng-model="defaulter.observation"> <td>{{::defaulter.amount | currency: 'EUR': 2}}</td>
</vn-textarea> <td>
</vn-td> <span
<vn-td number>{{::defaulter.creditInsurance}}</vn-td> title="{{::defaulter.workerName}}"
<vn-td shrink-datetime>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</vn-td> vn-click-stop="workerDescriptor.show($event, defaulter.workerFk)"
</vn-tr> class="link">
</vn-tbody> {{::defaulter.workerName | dashIfEmpty}}
</vn-table> </span>
</vn-card> </td>
</vn-data-viewer> <td expand>
<vn-textarea
vn-three
disabled="true"
ng-model="defaulter.observation">
</vn-textarea>
</td>
<td shrink-datetime>
<span class="chip {{::$ctrl.chipColor(defaulter.created)}}">
{{::defaulter.created | date: 'dd/MM/yyyy'}}
</span>
</td>
<td>{{::defaulter.creditInsurance | currency: 'EUR': 2}}</td>
<td>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</td>
</tr>
</tbody>
</table>
</slot-table>
</smart-table>
</vn-card>
<vn-client-descriptor-popover <vn-client-descriptor-popover
vn-id="clientDescriptor"> vn-id="client-descriptor">
</vn-client-descriptor-popover> </vn-client-descriptor-popover>
<vn-worker-descriptor-popover <vn-worker-descriptor-popover
vn-id="workerDescriptor"> vn-id="worker-descriptor">
</vn-worker-descriptor-popover> </vn-worker-descriptor-popover>
<vn-popup vn-id="dialog-summary-client"> <vn-popup vn-id="dialog-summary-client">
<vn-client-summary <vn-client-summary
@ -129,37 +149,6 @@
</vn-client-summary> </vn-client-summary>
</vn-popup> </vn-popup>
<!--Context menu-->
<vn-contextmenu vn-id="contextmenu" targets="['vn-data-viewer']" model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
<vn-item translate
ng-if="contextmenu.isActionAllowed()"
ng-click="contextmenu.copyValue()">
Copy value
</vn-item>
</slot-menu>
</vn-contextmenu>
<!-- Dialog of add notes button --> <!-- Dialog of add notes button -->
<vn-dialog <vn-dialog
vn-id="notesDialog" vn-id="notesDialog"

View File

@ -6,17 +6,61 @@ export default class Controller extends Section {
constructor($element, $) { constructor($element, $) {
super($element, $); super($element, $);
this.defaulter = {}; this.defaulter = {};
this.smartTableOptions = {
activeButtons: {
search: true
},
columns: [
{
field: 'clientName',
autocomplete: {
url: 'Clients',
showField: 'socialName',
valueField: 'socialName'
}
},
{
field: 'salesPersonFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
where: `{role: 'salesPerson'}`,
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'workerFk',
autocomplete: {
url: 'Workers/activeWithInheritedRole',
searchFunction: '{firstName: $search}',
showField: 'nickname',
valueField: 'id',
}
},
{
field: 'observation',
searchable: false
},
{
field: 'created',
searchable: false
},
{
field: 'defaulterSinced',
searchable: false
}
]
};
} }
get balanceDueTotal() { get balanceDueTotal() {
let balanceDueTotal = 0; let balanceDueTotal = 0;
const defaulters = this.$.model.data || [];
if (this.checked.length > 0) { for (let defaulter of defaulters)
for (let defaulter of this.checked) balanceDueTotal += defaulter.amount;
balanceDueTotal += defaulter.amount;
return balanceDueTotal;
}
return balanceDueTotal; return balanceDueTotal;
} }
@ -32,6 +76,22 @@ export default class Controller extends Section {
return checkedLines; return checkedLines;
} }
chipColor(date) {
const day = 24 * 60 * 60 * 1000;
const today = new Date();
today.setHours(0, 0, 0, 0);
const observationShipped = new Date(date);
observationShipped.setHours(0, 0, 0, 0);
const difference = today - observationShipped;
if (difference > (day * 20))
return 'alert';
if (difference > (day * 10))
return 'warning';
}
onResponse() { onResponse() {
if (!this.defaulter.observation) if (!this.defaulter.observation)
throw new UserError(`The message can't be empty`); throw new UserError(`The message can't be empty`);
@ -52,7 +112,10 @@ export default class Controller extends Section {
exprBuilder(param, value) { exprBuilder(param, value) {
switch (param) { switch (param) {
case 'creditInsurance':
case 'amount':
case 'clientName': case 'clientName':
case 'workerFk':
case 'salesPersonFk': case 'salesPersonFk':
return {[`d.${param}`]: value}; return {[`d.${param}`]: value};
} }

View File

@ -39,11 +39,7 @@ describe('client defaulter', () => {
describe('balanceDueTotal() getter', () => { describe('balanceDueTotal() getter', () => {
it('should return balance due total', () => { it('should return balance due total', () => {
const data = controller.$.model.data; const data = controller.$.model.data;
data[1].checked = true; const expectedAmount = data[0].amount + data[1].amount + data[2].amount;
data[2].checked = true;
const checkedRows = controller.checked;
const expectedAmount = checkedRows[0].amount + checkedRows[1].amount;
const result = controller.balanceDueTotal; const result = controller.balanceDueTotal;
@ -51,6 +47,31 @@ describe('client defaulter', () => {
}); });
}); });
describe('chipColor()', () => {
it('should return undefined when the date is the present', () => {
let today = new Date();
let result = controller.chipColor(today);
expect(result).toEqual(undefined);
});
it('should return warning when the date is 10 days in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 11);
let result = controller.chipColor(pastDate);
expect(result).toEqual('warning');
});
it('should return alert when the date is 20 days in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 21);
let result = controller.chipColor(pastDate);
expect(result).toEqual('alert');
});
});
describe('onResponse()', () => { describe('onResponse()', () => {
it('should return error for empty message', () => { it('should return error for empty message', () => {
let error; let error;

View File

@ -1,7 +1,9 @@
Last observation: Última observación
Add observation: Añadir observación Add observation: Añadir observación
Search client: Buscar clientes
Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s) Add observation to all selected clients: Añadir observación a {{total}} cliente(s) seleccionado(s)
Credit I.: Crédito A.
Balance D.: Saldo V. Balance D.: Saldo V.
Credit I.: Crédito A.
Last observation: Última observación
Last observation D.: Fecha última 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