Merge pull request '3052-feat(client_defaulter): section and tests' (#858) from 3052-client_defaulter into dev
gitea/salix/pipeline/head There was a failure building this commit
Details
gitea/salix/pipeline/head There was a failure building this commit
Details
Reviewed-on: #858 Reviewed-by: Carlos Jimenez Ruiz <carlosjr@verdnatura.es>
This commit is contained in:
commit
9fac23038f
|
@ -2435,3 +2435,11 @@ CALL `cache`.`last_buy_refresh`(FALSE);
|
||||||
|
|
||||||
UPDATE `vn`.`item` SET `genericFk` = 9
|
UPDATE `vn`.`item` SET `genericFk` = 9
|
||||||
WHERE `id` = 2;
|
WHERE `id` = 2;
|
||||||
|
|
||||||
|
INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced`)
|
||||||
|
VALUES
|
||||||
|
(1101, 500, CURDATE(), CURDATE()),
|
||||||
|
(1102, 500, CURDATE(), CURDATE()),
|
||||||
|
(1103, 500, CURDATE(), CURDATE()),
|
||||||
|
(1107, 500, CURDATE(), CURDATE()),
|
||||||
|
(1109, 500, CURDATE(), CURDATE());
|
||||||
|
|
|
@ -304,6 +304,16 @@ export default {
|
||||||
saveNewInsuranceCredit: 'vn-client-credit-insurance-insurance-create button[type="submit"]',
|
saveNewInsuranceCredit: 'vn-client-credit-insurance-insurance-create button[type="submit"]',
|
||||||
anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr',
|
anyCreditInsuranceLine: 'vn-client-credit-insurance-insurance-index vn-tbody > vn-tr',
|
||||||
},
|
},
|
||||||
|
clientDefaulter: {
|
||||||
|
anyClient: 'vn-client-defaulter-index vn-tbody > vn-tr',
|
||||||
|
firstClientName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(2) > span',
|
||||||
|
firstSalesPersonName: 'vn-client-defaulter-index vn-tbody > vn-tr:nth-child(1) > vn-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"]',
|
||||||
|
allDefaulterCheckbox: 'vn-client-defaulter-index vn-thead vn-multi-check',
|
||||||
|
addObservationButton: 'vn-client-defaulter-index vn-button[icon="icon-notes"]',
|
||||||
|
observation: '.vn-dialog.shown vn-textarea[ng-model="$ctrl.defaulter.observation"]',
|
||||||
|
saveButton: 'button[response="accept"]'
|
||||||
|
},
|
||||||
clientContacts: {
|
clientContacts: {
|
||||||
addContactButton: 'vn-client-contact vn-icon[icon="add_circle"]',
|
addContactButton: 'vn-client-contact vn-icon[icon="add_circle"]',
|
||||||
name: 'vn-client-contact vn-textfield[ng-model="contact.name"]',
|
name: 'vn-client-contact vn-textfield[ng-model="contact.name"]',
|
||||||
|
|
|
@ -0,0 +1,73 @@
|
||||||
|
import selectors from '../../helpers/selectors.js';
|
||||||
|
import getBrowser from '../../helpers/puppeteer';
|
||||||
|
|
||||||
|
describe('Client defaulter path', () => {
|
||||||
|
let browser;
|
||||||
|
let page;
|
||||||
|
|
||||||
|
beforeAll(async() => {
|
||||||
|
browser = await getBrowser();
|
||||||
|
page = browser.page;
|
||||||
|
await page.loginAndModule('insurance', 'client');
|
||||||
|
await page.accessToSection('client.defaulter.index');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(async() => {
|
||||||
|
await browser.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should count the amount of clients in the turns section', async() => {
|
||||||
|
const result = await page.countElement(selectors.clientDefaulter.anyClient);
|
||||||
|
|
||||||
|
expect(result).toEqual(5);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should check contain expected client', async() => {
|
||||||
|
const clientName =
|
||||||
|
await page.waitToGetProperty(selectors.clientDefaulter.firstClientName, 'innerText');
|
||||||
|
const salesPersonName =
|
||||||
|
await page.waitToGetProperty(selectors.clientDefaulter.firstSalesPersonName, 'innerText');
|
||||||
|
|
||||||
|
expect(clientName).toEqual('Ororo Munroe');
|
||||||
|
expect(salesPersonName).toEqual('salesPerson');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should first observation not changed', async() => {
|
||||||
|
const expectedObservation = 'Madness, as you know, is like gravity, all it takes is a little push';
|
||||||
|
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
|
||||||
|
|
||||||
|
expect(result).toContain(expectedObservation);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not add empty observation', async() => {
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox);
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.addObservationButton);
|
||||||
|
await page.write(selectors.clientDefaulter.observation, '');
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.saveButton);
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
|
||||||
|
expect(message.text).toContain(`The message can't be empty`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shoul checked all defaulters', async() => {
|
||||||
|
await page.loginAndModule('insurance', 'client');
|
||||||
|
await page.accessToSection('client.defaulter.index');
|
||||||
|
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.allDefaulterCheckbox);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add observation for all clients', async() => {
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.addObservationButton);
|
||||||
|
await page.write(selectors.clientDefaulter.observation, 'My new observation');
|
||||||
|
await page.waitToClick(selectors.clientDefaulter.saveButton);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should first observation changed', async() => {
|
||||||
|
const message = await page.waitForSnackbar();
|
||||||
|
const result = await page.waitToGetProperty(selectors.clientDefaulter.firstObservation, 'value');
|
||||||
|
|
||||||
|
expect(message.text).toContain('Observation saved!');
|
||||||
|
expect(result).toContain('My new observation');
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,90 @@
|
||||||
|
|
||||||
|
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
|
||||||
|
const buildFilter = require('vn-loopback/util/filter').buildFilter;
|
||||||
|
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
|
||||||
|
|
||||||
|
module.exports = Self => {
|
||||||
|
Self.remoteMethodCtx('filter', {
|
||||||
|
description: 'Find all instances of the model matched by filter from the data source.',
|
||||||
|
accessType: 'READ',
|
||||||
|
accepts: [
|
||||||
|
{
|
||||||
|
arg: 'filter',
|
||||||
|
type: 'object',
|
||||||
|
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
|
||||||
|
http: {source: 'query'}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
arg: 'search',
|
||||||
|
type: 'string',
|
||||||
|
description: `If it's and integer searchs by id, otherwise it searchs by name`
|
||||||
|
}
|
||||||
|
],
|
||||||
|
returns: {
|
||||||
|
type: ['object'],
|
||||||
|
root: true
|
||||||
|
},
|
||||||
|
http: {
|
||||||
|
path: `/filter`,
|
||||||
|
verb: 'GET'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Self.filter = async(ctx, filter, options) => {
|
||||||
|
const conn = Self.dataSource.connector;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
const where = buildFilter(ctx.args, (param, value) => {
|
||||||
|
switch (param) {
|
||||||
|
case 'search':
|
||||||
|
return {or: [
|
||||||
|
{'d.clientFk': value},
|
||||||
|
{'d.clientName': {like: `%${value}%`}}
|
||||||
|
]};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
filter = mergeFilters(ctx.args.filter, {where});
|
||||||
|
|
||||||
|
const stmts = [];
|
||||||
|
|
||||||
|
const stmt = new ParameterizedSQL(
|
||||||
|
`SELECT *
|
||||||
|
FROM (
|
||||||
|
SELECT
|
||||||
|
DISTINCT c.id clientFk,
|
||||||
|
c.name clientName,
|
||||||
|
c.salesPersonFk,
|
||||||
|
u.name salesPersonName,
|
||||||
|
d.amount,
|
||||||
|
co.created,
|
||||||
|
CONCAT(DATE(co.created), ' ', co.text) observation,
|
||||||
|
uw.id workerFk,
|
||||||
|
uw.name workerName,
|
||||||
|
c.creditInsurance,
|
||||||
|
d.defaulterSinced
|
||||||
|
FROM vn.defaulter d
|
||||||
|
JOIN vn.client c ON c.id = d.clientFk
|
||||||
|
LEFT JOIN vn.clientObservation co ON co.clientFk = c.id
|
||||||
|
LEFT JOIN account.user u ON u.id = c.salesPersonFk
|
||||||
|
LEFT JOIN account.user uw ON uw.id = co.workerFk
|
||||||
|
WHERE
|
||||||
|
d.created = CURDATE()
|
||||||
|
AND d.amount > 0
|
||||||
|
ORDER BY co.created DESC) d`
|
||||||
|
);
|
||||||
|
|
||||||
|
stmt.merge(conn.makeWhere(filter.where));
|
||||||
|
stmt.merge(`GROUP BY d.clientFk`);
|
||||||
|
stmt.merge(conn.makeOrderBy(filter.order));
|
||||||
|
|
||||||
|
const itemsIndex = stmts.push(stmt) - 1;
|
||||||
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
const result = await conn.executeStmt(sql, myOptions);
|
||||||
|
|
||||||
|
return itemsIndex === 0 ? result : result[itemsIndex];
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1,63 @@
|
||||||
|
const models = require('vn-loopback/server/server').models;
|
||||||
|
|
||||||
|
describe('defaulter filter()', () => {
|
||||||
|
const authUserId = 9;
|
||||||
|
it('should all return the tickets matching the filter', async() => {
|
||||||
|
const tx = await models.Defaulter.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const filter = {};
|
||||||
|
const ctx = {req: {accessToken: {userId: authUserId}}, args: {filter: filter}};
|
||||||
|
|
||||||
|
const result = await models.Defaulter.filter(ctx, null, options);
|
||||||
|
const firstRow = result[0];
|
||||||
|
|
||||||
|
expect(firstRow.clientFk).toEqual(1101);
|
||||||
|
expect(result.length).toEqual(5);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the defaulter with id', async() => {
|
||||||
|
const tx = await models.Defaulter.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 1101}};
|
||||||
|
|
||||||
|
const result = await models.Defaulter.filter(ctx, null, options);
|
||||||
|
const firstRow = result[0];
|
||||||
|
|
||||||
|
expect(firstRow.clientFk).toEqual(1101);
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return the defaulter matching the client name', async() => {
|
||||||
|
const tx = await models.Defaulter.beginTransaction({});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const options = {transaction: tx};
|
||||||
|
const ctx = {req: {accessToken: {userId: authUserId}}, args: {search: 'bruce'}};
|
||||||
|
|
||||||
|
const result = await models.Defaulter.filter(ctx, null, options);
|
||||||
|
const firstRow = result[0];
|
||||||
|
|
||||||
|
expect(firstRow.clientName).toEqual('Bruce Wayne');
|
||||||
|
|
||||||
|
await tx.rollback();
|
||||||
|
} catch (e) {
|
||||||
|
await tx.rollback();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = Self => {
|
||||||
|
require('../methods/defaulter/filter')(Self);
|
||||||
|
};
|
|
@ -8,6 +8,9 @@
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"properties": {
|
"properties": {
|
||||||
|
"id": {
|
||||||
|
"type": "Number"
|
||||||
|
},
|
||||||
"created": {
|
"created": {
|
||||||
"type": "Date"
|
"type": "Date"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,186 @@
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="model"
|
||||||
|
url="Defaulters/filter"
|
||||||
|
filter="::$ctrl.filter"
|
||||||
|
limit="20"
|
||||||
|
data="defaulters"
|
||||||
|
auto-load="true">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-portal slot="topbar">
|
||||||
|
<vn-searchbar
|
||||||
|
vn-focus
|
||||||
|
placeholder="Search client"
|
||||||
|
info="Search client by id or name"
|
||||||
|
auto-state="false"
|
||||||
|
model="model">
|
||||||
|
</vn-searchbar>
|
||||||
|
</vn-portal>
|
||||||
|
<vn-data-viewer
|
||||||
|
model="model"
|
||||||
|
class="vn-w-xl">
|
||||||
|
<vn-card>
|
||||||
|
<vn-tool-bar>
|
||||||
|
<div class="vn-pa-md">
|
||||||
|
<div class="totalBox" style="text-align: center;">
|
||||||
|
<h6 translate>Total</h6>
|
||||||
|
<vn-label-value
|
||||||
|
label="Balance due"
|
||||||
|
value="{{$ctrl.balanceDueTotal}} €">
|
||||||
|
</vn-label-value>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="vn-pa-md">
|
||||||
|
<vn-button
|
||||||
|
ng-show="$ctrl.checked.length > 0"
|
||||||
|
ng-click="notesDialog.show()"
|
||||||
|
name="notesDialog"
|
||||||
|
vn-tooltip="Add observation"
|
||||||
|
icon="icon-notes">
|
||||||
|
</vn-button>
|
||||||
|
</div>
|
||||||
|
</vn-tool-bar>
|
||||||
|
<vn-table model="model">
|
||||||
|
<vn-thead>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-th shrink>
|
||||||
|
<vn-multi-check
|
||||||
|
model="model">
|
||||||
|
</vn-multi-check>
|
||||||
|
</vn-th>
|
||||||
|
<vn-th field="clientName">Client</vn-th>
|
||||||
|
<vn-th field="salesPersonFk">Comercial</vn-th>
|
||||||
|
<vn-th
|
||||||
|
field="amount"
|
||||||
|
vn-tooltip="Balance due"
|
||||||
|
number>
|
||||||
|
Balance D.
|
||||||
|
</vn-th>
|
||||||
|
<vn-th
|
||||||
|
vn-tooltip="Worker who made the last observation"
|
||||||
|
shrink>
|
||||||
|
Author
|
||||||
|
</vn-th>
|
||||||
|
<vn-th expand>Last observation</vn-th>
|
||||||
|
<vn-th
|
||||||
|
vn-tooltip="Credit insurance"
|
||||||
|
number>
|
||||||
|
Credit I.
|
||||||
|
</vn-th>
|
||||||
|
<vn-th shrink-datetime>From</vn-th>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-thead>
|
||||||
|
<vn-tbody>
|
||||||
|
<vn-tr ng-repeat="defaulter in defaulters">
|
||||||
|
<vn-td shrink>
|
||||||
|
<vn-check
|
||||||
|
ng-model="defaulter.checked"
|
||||||
|
vn-click-stop>
|
||||||
|
</vn-check>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td>
|
||||||
|
<span
|
||||||
|
vn-click-stop="clientDescriptor.show($event, defaulter.clientFk)"
|
||||||
|
title ="{{::defaulter.clientName}}"
|
||||||
|
class="link">
|
||||||
|
{{::defaulter.clientName}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td>
|
||||||
|
<span
|
||||||
|
title="{{::defaulter.salesPersonName}}"
|
||||||
|
vn-click-stop="workerDescriptor.show($event, defaulter.salesPersonFk)"
|
||||||
|
class="link" >
|
||||||
|
{{::defaulter.salesPersonName | dashIfEmpty}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>{{::defaulter.amount}}</vn-td>
|
||||||
|
<vn-td shrink>
|
||||||
|
<span
|
||||||
|
title="{{::defaulter.workerName}}"
|
||||||
|
vn-click-stop="workerDescriptor.show($event, defaulter.workerFk)"
|
||||||
|
class="link" >
|
||||||
|
{{::defaulter.workerName | dashIfEmpty}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>
|
||||||
|
<vn-textarea
|
||||||
|
vn-three
|
||||||
|
disabled="true"
|
||||||
|
label="Observation"
|
||||||
|
ng-model="defaulter.observation">
|
||||||
|
</vn-textarea>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td number>{{::defaulter.creditInsurance}}</vn-td>
|
||||||
|
<vn-td shrink-datetime>{{::defaulter.defaulterSinced | date: 'dd/MM/yyyy'}}</vn-td>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-tbody>
|
||||||
|
</vn-table>
|
||||||
|
</vn-card>
|
||||||
|
</vn-data-viewer>
|
||||||
|
<vn-client-descriptor-popover
|
||||||
|
vn-id="clientDescriptor">
|
||||||
|
</vn-client-descriptor-popover>
|
||||||
|
<vn-worker-descriptor-popover
|
||||||
|
vn-id="workerDescriptor">
|
||||||
|
</vn-worker-descriptor-popover>
|
||||||
|
<vn-popup vn-id="dialog-summary-client">
|
||||||
|
<vn-client-summary
|
||||||
|
client="$ctrl.clientSelected">
|
||||||
|
</vn-client-summary>
|
||||||
|
</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 -->
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="notesDialog"
|
||||||
|
on-accept="$ctrl.onResponse()">
|
||||||
|
<tpl-body>
|
||||||
|
<section class="SMSDialog">
|
||||||
|
<h5 class="vn-py-sm">{{$ctrl.$t('Add observation to all selected clients', {total: $ctrl.checked.length})}}</h5>
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textarea vn-one
|
||||||
|
vn-id="message"
|
||||||
|
label="Message"
|
||||||
|
ng-model="$ctrl.defaulter.observation"
|
||||||
|
rows="3"
|
||||||
|
required="true"
|
||||||
|
rule>
|
||||||
|
</vn-textarea>
|
||||||
|
</vn-horizontal>
|
||||||
|
</section>
|
||||||
|
</tpl-body>
|
||||||
|
<tpl-buttons>
|
||||||
|
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||||
|
<button response="accept" translate>Save</button>
|
||||||
|
</tpl-buttons>
|
||||||
|
</vn-dialog>
|
|
@ -0,0 +1,65 @@
|
||||||
|
import ngModule from '../module';
|
||||||
|
import Section from 'salix/components/section';
|
||||||
|
import UserError from 'core/lib/user-error';
|
||||||
|
|
||||||
|
export default class Controller extends Section {
|
||||||
|
constructor($element, $) {
|
||||||
|
super($element, $);
|
||||||
|
this.defaulter = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
get balanceDueTotal() {
|
||||||
|
let balanceDueTotal = 0;
|
||||||
|
|
||||||
|
if (this.checked.length > 0) {
|
||||||
|
for (let defaulter of this.checked)
|
||||||
|
balanceDueTotal += defaulter.amount;
|
||||||
|
|
||||||
|
return balanceDueTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
return balanceDueTotal;
|
||||||
|
}
|
||||||
|
|
||||||
|
get checked() {
|
||||||
|
const clients = this.$.model.data || [];
|
||||||
|
const checkedLines = [];
|
||||||
|
for (let defaulter of clients) {
|
||||||
|
if (defaulter.checked)
|
||||||
|
checkedLines.push(defaulter);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkedLines;
|
||||||
|
}
|
||||||
|
|
||||||
|
onResponse() {
|
||||||
|
if (!this.defaulter.observation)
|
||||||
|
throw new UserError(`The message can't be empty`);
|
||||||
|
|
||||||
|
const params = [];
|
||||||
|
for (let defaulter of this.checked) {
|
||||||
|
params.push({
|
||||||
|
text: this.defaulter.observation,
|
||||||
|
clientFk: defaulter.clientFk
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$http.post(`ClientObservations`, params) .then(() => {
|
||||||
|
this.vnApp.showMessage(this.$t('Observation saved!'));
|
||||||
|
this.$state.reload();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
exprBuilder(param, value) {
|
||||||
|
switch (param) {
|
||||||
|
case 'clientName':
|
||||||
|
case 'salesPersonFk':
|
||||||
|
return {[`d.${param}`]: value};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ngModule.vnComponent('vnClientDefaulterIndex', {
|
||||||
|
template: require('./index.html'),
|
||||||
|
controller: Controller
|
||||||
|
});
|
|
@ -0,0 +1,98 @@
|
||||||
|
import './index';
|
||||||
|
import crudModel from 'core/mocks/crud-model';
|
||||||
|
|
||||||
|
describe('client defaulter', () => {
|
||||||
|
describe('Component vnClientDefaulterIndex', () => {
|
||||||
|
let controller;
|
||||||
|
let $httpBackend;
|
||||||
|
|
||||||
|
beforeEach(ngModule('client'));
|
||||||
|
|
||||||
|
beforeEach(inject(($componentController, _$httpBackend_) => {
|
||||||
|
$httpBackend = _$httpBackend_;
|
||||||
|
const $element = angular.element('<vn-client-defaulter></vn-client-defaulter>');
|
||||||
|
controller = $componentController('vnClientDefaulterIndex', {$element});
|
||||||
|
controller.$.model = crudModel;
|
||||||
|
controller.$.model.data = [
|
||||||
|
{clientFk: 1101, amount: 125},
|
||||||
|
{clientFk: 1102, amount: 500},
|
||||||
|
{clientFk: 1103, amount: 250}
|
||||||
|
];
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('checked() getter', () => {
|
||||||
|
it('should return the checked lines', () => {
|
||||||
|
const data = controller.$.model.data;
|
||||||
|
data[1].checked = true;
|
||||||
|
data[2].checked = true;
|
||||||
|
|
||||||
|
const checkedRows = controller.checked;
|
||||||
|
|
||||||
|
const firstCheckedRow = checkedRows[0];
|
||||||
|
const secondCheckedRow = checkedRows[1];
|
||||||
|
|
||||||
|
expect(firstCheckedRow.clientFk).toEqual(1102);
|
||||||
|
expect(secondCheckedRow.clientFk).toEqual(1103);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('balanceDueTotal() getter', () => {
|
||||||
|
it('should return balance due total', () => {
|
||||||
|
const data = controller.$.model.data;
|
||||||
|
data[1].checked = true;
|
||||||
|
data[2].checked = true;
|
||||||
|
|
||||||
|
const checkedRows = controller.checked;
|
||||||
|
const expectedAmount = checkedRows[0].amount + checkedRows[1].amount;
|
||||||
|
|
||||||
|
const result = controller.balanceDueTotal;
|
||||||
|
|
||||||
|
expect(result).toEqual(expectedAmount);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('onResponse()', () => {
|
||||||
|
it('should return error for empty message', () => {
|
||||||
|
let error;
|
||||||
|
try {
|
||||||
|
controller.onResponse();
|
||||||
|
} catch (e) {
|
||||||
|
error = e;
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(error).toBeDefined();
|
||||||
|
expect(error.message).toBe(`The message can't be empty`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return saved message', () => {
|
||||||
|
const data = controller.$.model.data;
|
||||||
|
data[1].checked = true;
|
||||||
|
controller.defaulter = {observation: 'My new observation'};
|
||||||
|
|
||||||
|
const params = [{text: controller.defaulter.observation, clientFk: data[1].clientFk}];
|
||||||
|
|
||||||
|
jest.spyOn(controller.vnApp, 'showMessage');
|
||||||
|
$httpBackend.expect('POST', `ClientObservations`, params).respond(200, params);
|
||||||
|
|
||||||
|
controller.onResponse();
|
||||||
|
$httpBackend.flush();
|
||||||
|
|
||||||
|
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Observation saved!');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('exprBuilder()', () => {
|
||||||
|
it('should search by sales person', () => {
|
||||||
|
let expr = controller.exprBuilder('salesPersonFk', '5');
|
||||||
|
|
||||||
|
expect(expr).toEqual({'d.salesPersonFk': '5'});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should search by client name', () => {
|
||||||
|
let expr = controller.exprBuilder('clientName', '1foo');
|
||||||
|
|
||||||
|
expect(expr).toEqual({'d.clientName': '1foo'});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
Last observation: Última 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)
|
||||||
|
Credit I.: Crédito A.
|
||||||
|
Balance D.: Saldo V.
|
||||||
|
Worker who made the last observation: Trabajador que ha realizado la última observación
|
|
@ -44,3 +44,4 @@ import './dms/create';
|
||||||
import './dms/edit';
|
import './dms/edit';
|
||||||
import './consumption';
|
import './consumption';
|
||||||
import './consumption-search-panel';
|
import './consumption-search-panel';
|
||||||
|
import './defaulter';
|
||||||
|
|
|
@ -33,6 +33,7 @@ Search client by id or name: Buscar clientes por identificador o nombre
|
||||||
# Sections
|
# Sections
|
||||||
|
|
||||||
Clients: Clientes
|
Clients: Clientes
|
||||||
|
Defaulter: Morosos
|
||||||
New client: Nuevo cliente
|
New client: Nuevo cliente
|
||||||
Fiscal data: Datos fiscales
|
Fiscal data: Datos fiscales
|
||||||
Billing data: Forma de pago
|
Billing data: Forma de pago
|
||||||
|
|
|
@ -6,7 +6,8 @@
|
||||||
"dependencies": ["worker", "invoiceOut"],
|
"dependencies": ["worker", "invoiceOut"],
|
||||||
"menus": {
|
"menus": {
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "client.index", "icon": "person"}
|
{"state": "client.index", "icon": "person"},
|
||||||
|
{"state": "client.defaulter.index", "icon": "person"}
|
||||||
],
|
],
|
||||||
"card": [
|
"card": [
|
||||||
{"state": "client.card.basicData", "icon": "settings"},
|
{"state": "client.card.basicData", "icon": "settings"},
|
||||||
|
@ -360,6 +361,18 @@
|
||||||
"params": {
|
"params": {
|
||||||
"client": "$ctrl.client"
|
"client": "$ctrl.client"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/defaulter",
|
||||||
|
"state": "client.defaulter",
|
||||||
|
"component": "ui-view",
|
||||||
|
"description": "Defaulter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "/index?q",
|
||||||
|
"state": "client.defaulter.index",
|
||||||
|
"component": "vn-client-defaulter-index",
|
||||||
|
"description": "Defaulter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue