Merge branch 'dev' into 4793-models_translation
gitea/salix/pipeline/head There was a failure building this commit Details

This commit is contained in:
Vicent Llopis 2022-11-24 09:14:01 +00:00
commit 927b5a63ec
18 changed files with 764 additions and 42 deletions

View File

@ -0,0 +1,4 @@
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
VALUES
('Receipt', 'balanceCompensationEmail', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
('Receipt', 'balanceCompensationPdf', 'READ', 'ALLOW', 'ROLE', 'employee');

View File

@ -1859,7 +1859,8 @@ INSERT INTO `vn`.`receipt`(`id`, `invoiceFk`, `amountPaid`, `payed`, `workerFk`,
(1, 'Cobro web', 100.50, util.VN_CURDATE(), 9, 1, 1101, util.VN_CURDATE(), 442, 1), (1, 'Cobro web', 100.50, util.VN_CURDATE(), 9, 1, 1101, util.VN_CURDATE(), 442, 1),
(2, 'Cobro web', 200.50, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 DAY), 9, 1, 1101, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 DAY), 442, 1), (2, 'Cobro web', 200.50, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 DAY), 9, 1, 1101, DATE_ADD(util.VN_CURDATE(), INTERVAL -5 DAY), 442, 1),
(3, 'Cobro en efectivo', 300.00, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), 9, 1, 1102, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), 442, 0), (3, 'Cobro en efectivo', 300.00, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), 9, 1, 1102, DATE_ADD(util.VN_CURDATE(), INTERVAL -10 DAY), 442, 0),
(4, 'Cobro en efectivo', 400.00, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 9, 1, 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 442, 0); (4, 'Cobro en efectivo', 400.00, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 9, 1, 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 442, 0),
(5, 'Compensación', 400.00, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 9, 3, 1103, DATE_ADD(util.VN_CURDATE(), INTERVAL -15 DAY), 442, 0);
INSERT INTO `vn`.`workerTeam`(`id`, `team`, `workerFk`) INSERT INTO `vn`.`workerTeam`(`id`, `team`, `workerFk`)
VALUES VALUES

View File

@ -324,7 +324,8 @@ export default {
anyBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr', anyBalanceLine: 'vn-client-balance-index vn-tbody > vn-tr',
firstLineBalance: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)', firstLineBalance: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td:nth-child(8)',
firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable', firstLineReference: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable',
firstLineReferenceInput: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable > div > field > vn-textfield' firstLineReferenceInput: 'vn-client-balance-index vn-tbody > vn-tr:nth-child(1) > vn-td-editable > div > field > vn-textfield',
compensationButton: 'vn-client-balance-index vn-icon-button[vn-dialog="send_compensation"]'
}, },
webPayment: { webPayment: {
confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]', confirmFirstPaymentButton: 'vn-client-web-payment vn-tr:nth-child(1) vn-icon-button[icon="done_all"]',
@ -1038,6 +1039,17 @@ export default {
booked: 'vn-invoice-in-basic-data vn-date-picker[ng-model="$ctrl.invoiceIn.booked"]', booked: 'vn-invoice-in-basic-data vn-date-picker[ng-model="$ctrl.invoiceIn.booked"]',
currency: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.currencyFk"]', currency: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.currencyFk"]',
company: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.companyFk"]', company: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.companyFk"]',
dms: 'vn-invoice-in-basic-data vn-textfield[ng-model="$ctrl.invoiceIn.dmsFk"]',
download: 'vn-invoice-in-basic-data vn-textfield[ng-model="$ctrl.invoiceIn.dmsFk"] > div.container > div.prepend > prepend > vn-icon-button',
edit: 'vn-invoice-in-basic-data vn-textfield[ng-model="$ctrl.invoiceIn.dmsFk"] > div.container > div.append > append > vn-icon-button[icon="edit"]',
create: 'vn-invoice-in-basic-data vn-textfield[ng-model="$ctrl.invoiceIn.dmsFk"] > div.container > div.append > append > vn-icon-button[icon="add_circle"]',
reference: 'vn-textfield[ng-model="$ctrl.dms.reference"]',
companyId: 'vn-autocomplete[ng-model="$ctrl.dms.companyId"]',
warehouseId: 'vn-autocomplete[ng-model="$ctrl.dms.warehouseId"]',
dmsTypeId: 'vn-autocomplete[ng-model="$ctrl.dms.dmsTypeId"]',
description: 'vn-textarea[ng-model="$ctrl.dms.description"]',
inputFile: 'vn-input-file[ng-model="$ctrl.dms.files"]',
confirm: 'button[response="accept"]',
save: 'vn-invoice-in-basic-data button[type=submit]' save: 'vn-invoice-in-basic-data button[type=submit]'
}, },
invoiceInTax: { invoiceInTax: {

View File

@ -0,0 +1,27 @@
import selectors from '../../helpers/selectors';
import getBrowser from '../../helpers/puppeteer';
describe('Client Send balance compensation', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'client');
await page.accessToSearchResult('Clark Kent');
await page.accessToSection('client.card.balance.index');
});
afterAll(async() => {
await browser.close();
});
it(`should click on send compensation button`, async() => {
await page.autocompleteSearch(selectors.clientBalance.company, 'VNL');
await page.waitToClick(selectors.clientBalance.compensationButton);
await page.waitToClick(selectors.clientBalance.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Notification sent!');
});
});

View File

@ -4,6 +4,7 @@ import getBrowser from '../../helpers/puppeteer';
describe('InvoiceIn basic data path', () => { describe('InvoiceIn basic data path', () => {
let browser; let browser;
let page; let page;
let newDms;
beforeAll(async() => { beforeAll(async() => {
browser = await getBrowser(); browser = await getBrowser();
@ -24,6 +25,8 @@ describe('InvoiceIn basic data path', () => {
await page.autocompleteSearch(selectors.invoiceInBasicData.supplier, 'Verdnatura'); await page.autocompleteSearch(selectors.invoiceInBasicData.supplier, 'Verdnatura');
await page.clearInput(selectors.invoiceInBasicData.supplierRef); await page.clearInput(selectors.invoiceInBasicData.supplierRef);
await page.write(selectors.invoiceInBasicData.supplierRef, '9999'); await page.write(selectors.invoiceInBasicData.supplierRef, '9999');
await page.clearInput(selectors.invoiceInBasicData.dms);
await page.write(selectors.invoiceInBasicData.dms, '2');
await page.pickDate(selectors.invoiceInBasicData.bookEntried, now); await page.pickDate(selectors.invoiceInBasicData.bookEntried, now);
await page.pickDate(selectors.invoiceInBasicData.booked, now); await page.pickDate(selectors.invoiceInBasicData.booked, now);
await page.autocompleteSearch(selectors.invoiceInBasicData.currency, 'USD'); await page.autocompleteSearch(selectors.invoiceInBasicData.currency, 'USD');
@ -61,4 +64,141 @@ describe('InvoiceIn basic data path', () => {
expect(result).toEqual('ORN'); expect(result).toEqual('ORN');
}); });
it(`should confirm the invoiceIn dms was edited`, async() => {
const result = await page
.waitToGetProperty(selectors.invoiceInBasicData.dms, 'value');
expect(result).toEqual('2');
});
it(`should create a new invoiceIn dms and save the changes`, async() => {
await page.clearInput(selectors.invoiceInBasicData.dms);
await page.waitToClick(selectors.invoiceInBasicData.create);
await page.clearInput(selectors.invoiceInBasicData.reference);
await page.write(selectors.invoiceInBasicData.reference, 'New Dms');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
let message = await page.waitForSnackbar();
expect(message.text).toContain('The company can\'t be empty');
await page.clearInput(selectors.invoiceInBasicData.companyId);
await page.autocompleteSearch(selectors.invoiceInBasicData.companyId, 'VNL');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
message = await page.waitForSnackbar();
expect(message.text).toContain('The warehouse can\'t be empty');
await page.clearInput(selectors.invoiceInBasicData.warehouseId);
await page.autocompleteSearch(selectors.invoiceInBasicData.warehouseId, 'Warehouse One');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
message = await page.waitForSnackbar();
expect(message.text).toContain('The DMS Type can\'t be empty');
await page.clearInput(selectors.invoiceInBasicData.dmsTypeId);
await page.autocompleteSearch(selectors.invoiceInBasicData.dmsTypeId, 'Ticket');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
message = await page.waitForSnackbar();
expect(message.text).toContain('The description can\'t be empty');
await page.waitToClick(selectors.invoiceInBasicData.description);
await page.write(selectors.invoiceInBasicData.description, 'Dms without edition.');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
message = await page.waitForSnackbar();
expect(message.text).toContain('The files can\'t be empty');
let currentDir = process.cwd();
let filePath = `${currentDir}/e2e/assets/thermograph.jpeg`;
const [fileChooser] = await Promise.all([
page.waitForFileChooser(),
page.waitToClick(selectors.invoiceInBasicData.inputFile)
]);
await fileChooser.accept([filePath]);
await page.waitToClick(selectors.invoiceInBasicData.confirm);
message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
newDms = await page
.waitToGetProperty(selectors.invoiceInBasicData.dms, 'value');
});
it(`should confirm the invoiceIn was edited with the new dms`, async() => {
await page.reloadSection('invoiceIn.card.basicData');
const result = await page
.waitToGetProperty(selectors.invoiceInBasicData.dms, 'value');
expect(result).toEqual(newDms);
});
it(`should edit the invoiceIn`, async() => {
await page.waitToClick(selectors.invoiceInBasicData.edit);
await page.clearInput(selectors.invoiceInBasicData.reference);
await page.write(selectors.invoiceInBasicData.reference, 'Dms Edited');
await page.clearInput(selectors.invoiceInBasicData.companyId);
await page.autocompleteSearch(selectors.invoiceInBasicData.companyId, 'CCs');
await page.clearInput(selectors.invoiceInBasicData.warehouseId);
await page.autocompleteSearch(selectors.invoiceInBasicData.warehouseId, 'Algemesi');
await page.clearInput(selectors.invoiceInBasicData.dmsTypeId);
await page.autocompleteSearch(selectors.invoiceInBasicData.dmsTypeId, 'Basura');
await page.waitToClick(selectors.invoiceInBasicData.description);
await page.write(selectors.invoiceInBasicData.description, ' Nevermind, now is edited.');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
let message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it(`should confirm the new dms has been edited`, async() => {
await page.reloadSection('invoiceIn.card.basicData');
await page.waitToClick(selectors.invoiceInBasicData.edit);
const reference = await page
.waitToGetProperty(selectors.invoiceInBasicData.reference, 'value');
const companyId = await page
.waitToGetProperty(selectors.invoiceInBasicData.companyId, 'value');
const warehouseId = await page
.waitToGetProperty(selectors.invoiceInBasicData.warehouseId, 'value');
const dmsTypeId = await page
.waitToGetProperty(selectors.invoiceInBasicData.dmsTypeId, 'value');
const description = await page
.waitToGetProperty(selectors.invoiceInBasicData.description, 'value');
expect(reference).toEqual('Dms Edited');
expect(companyId).toEqual('CCs');
expect(warehouseId).toEqual('Algemesi');
expect(dmsTypeId).toEqual('Basura');
expect(description).toEqual('Dms without edition. Nevermind, now is edited.');
await page.waitToClick(selectors.invoiceInBasicData.confirm);
});
it(`should disable edit and download if dms doesn't exists, and set back the original dms`, async() => {
await page.clearInput(selectors.invoiceInBasicData.dms);
await page.write(selectors.invoiceInBasicData.dms, '9999');
await page.waitForSelector(`${selectors.invoiceInBasicData.download}.disabled`);
await page.waitForSelector(`${selectors.invoiceInBasicData.edit}.disabled`);
await page.clearInput(selectors.invoiceInBasicData.dms);
await page.write(selectors.invoiceInBasicData.dms, '1');
await page.waitToClick(selectors.invoiceInBasicData.save);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
}); });

View File

@ -138,5 +138,8 @@
"You don't have grant privilege": "You don't have grant privilege", "You don't have grant privilege": "You don't have grant privilege",
"You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user", "You don't own the role and you can't assign it to another user": "You don't own the role and you can't assign it to another user",
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})", "Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) merged with [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
"Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production" "Sale(s) blocked, please contact production": "Sale(s) blocked, please contact production",
"Receipt's bank was not found": "Receipt's bank was not found",
"This receipt was not compensated": "This receipt was not compensated",
"Client's email was not found": "Client's email was not found"
} }

View File

@ -244,5 +244,8 @@
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) fusionado con [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})", "Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) fusionado con [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
"Already has this status": "Ya tiene este estado", "Already has this status": "Ya tiene este estado",
"There aren't records for this week": "No existen registros para esta semana", "There aren't records for this week": "No existen registros para esta semana",
"Empty data source": "Origen de datos vacio" "Empty data source": "Origen de datos vacio",
"Receipt's bank was not found": "No se encontró el banco del recibo",
"This receipt was not compensated": "Este recibo no ha sido compensado",
"Client's email was not found": "No se encontró el email del cliente"
} }

View File

@ -1,4 +1,5 @@
const {Email} = require('vn-print'); const {Email} = require('vn-print');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('balanceCompensationEmail', { Self.remoteMethodCtx('balanceCompensationEmail', {
@ -10,7 +11,7 @@ module.exports = Self => {
type: 'Number', type: 'Number',
required: true, required: true,
description: 'The receipt id', description: 'The receipt id',
http: { source: 'path' } http: {source: 'path'}
} }
], ],
returns: { returns: {
@ -23,18 +24,29 @@ module.exports = Self => {
} }
}); });
Self.balanceCompensationEmail = async (ctx, id) => { Self.balanceCompensationEmail = async(ctx, id) => {
const models = Self.app.models; const models = Self.app.models;
const receipt = await models.Receipt.findById(id, {fields: ['clientFk']}); const receipt = await models.Receipt.findById(id, {fields: ['clientFk', 'bankFk']});
const client = await models.Client.findById(receipt.clientFk, {fields:['email']});
const email = new Email('balance-compensation', { const bank = await models.Bank.findById(receipt.bankFk);
lang: ctx.req.getLocale(), if (!bank)
recipient: client.email+',administracion@verdnatura.es', throw new UserError(`Receipt's bank was not found`);
id
});
return email.send(); const accountingType = await models.AccountingType.findById(bank.accountingTypeFk);
if (!(accountingType && accountingType.code == 'compensation'))
throw new UserError(`This receipt was not compensated`);
const client = await models.Client.findById(receipt.clientFk, {fields: ['email']});
if (!client.email)
throw new UserError(`Client's email was not found`);
else {
const email = new Email('balance-compensation', {
lang: ctx.req.getLocale(),
recipient: client.email + ',administracion@verdnatura.es',
id
});
return email.send();
}
}; };
}; };

View File

@ -56,11 +56,14 @@ module.exports = Self => {
u.name userName, u.name userName,
r.clientFk, r.clientFk,
FALSE hasPdf, FALSE hasPdf,
FALSE isInvoice FALSE isInvoice,
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
FROM vn.receipt r FROM vn.receipt r
LEFT JOIN vn.worker w ON w.id = r.workerFk LEFT JOIN vn.worker w ON w.id = r.workerFk
LEFT JOIN account.user u ON u.id = w.userFk LEFT JOIN account.user u ON u.id = w.userFk
JOIN vn.company c ON c.id = r.companyFk JOIN vn.company c ON c.id = r.companyFk
JOIN vn.accounting a ON a.id = r.bankFk
JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk
WHERE r.clientFk = ? AND r.companyFk = ? WHERE r.clientFk = ? AND r.companyFk = ?
UNION ALL UNION ALL
SELECT SELECT
@ -77,9 +80,12 @@ module.exports = Self => {
NULL, NULL,
i.clientFk, i.clientFk,
i.hasPdf, i.hasPdf,
TRUE isInvoice TRUE isInvoice,
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
FROM vn.invoiceOut i FROM vn.invoiceOut i
JOIN vn.company c ON c.id = i.companyFk JOIN vn.company c ON c.id = i.companyFk
JOIN vn.accounting a ON a.id = i.bankFk
JOIN vn.accountingType at2 ON at2.id = a.accountingTypeFk
WHERE i.clientFk = ? AND i.companyFk = ? WHERE i.clientFk = ? AND i.companyFk = ?
ORDER BY payed DESC, created DESC ORDER BY payed DESC, created DESC
) t ORDER BY payed DESC, created DESC`, ) t ORDER BY payed DESC, created DESC`,

View File

@ -121,7 +121,7 @@
</vn-icon-button> </vn-icon-button>
</a> </a>
</vn-td> </vn-td>
<vn-td center shrink ng-if="!balance.isInvoice"> <vn-td center shrink ng-if="balance.isCompensation">
<vn-icon-button <vn-icon-button
vn-dialog="send_compensation" vn-dialog="send_compensation"
icon="outgoing_mail" icon="outgoing_mail"

View File

@ -70,7 +70,7 @@ class Controller extends Section {
const balances = this.$.model.data; const balances = this.$.model.data;
balances.forEach((balance, index) => { balances.forEach((balance, index) => {
if (index === 0) if (index === 0)
balance.balance = this.getCurrentBalance(); balance.balance = this.getCurrentBalance();
if (index > 0) { if (index > 0) {
let previousBalance = balances[index - 1]; let previousBalance = balances[index - 1];
balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit); balance.balance = previousBalance.balance - (previousBalance.debit - previousBalance.credit);
@ -89,7 +89,7 @@ class Controller extends Section {
const params = {description: balance.description}; const params = {description: balance.description};
const endpoint = `Receipts/${balance.id}`; const endpoint = `Receipts/${balance.id}`;
this.$http.patch(endpoint, params) this.$http.patch(endpoint, params)
.then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); .then(() => this.vnApp.showSuccess(this.$t('Data saved!')));
} }
sendEmail(balance) { sendEmail(balance) {

View File

@ -151,5 +151,19 @@ describe('Client', () => {
$httpBackend.flush(); $httpBackend.flush();
}); });
}); });
describe('sendEmail()', () => {
it('should send an email', () => {
jest.spyOn(controller.vnEmail, 'send');
const $data = {id: 1103};
controller.sendEmail($data);
const expectedPath = `Receipts/${$data.id}/balance-compensation-email`;
expect(controller.vnEmail.send).toHaveBeenCalledWith(expectedPath);
});
});
}); });
}); });

View File

@ -1 +1,3 @@
BILL: N/INV {{ref}} BILL: N/INV {{ref}}
Notify compensation: Do you want to report compensation to the client by mail?
Send compensation: Send compensation

View File

@ -5,6 +5,24 @@
form="form" form="form"
save="patch"> save="patch">
</vn-watcher> </vn-watcher>
<vn-crud-model
auto-load="true"
url="Companies"
data="companies"
order="code">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="Warehouses"
data="warehouses"
order="name">
</vn-crud-model>
<vn-crud-model
auto-load="true"
url="DmsTypes"
data="dmsTypes"
order="name">
</vn-crud-model>
<form name="form" ng-submit="watcher.submit()" class="vn-w-md"> <form name="form" ng-submit="watcher.submit()" class="vn-w-md">
<vn-card class="vn-pa-lg"> <vn-card class="vn-pa-lg">
<vn-horizontal> <vn-horizontal>
@ -57,6 +75,36 @@
{{id}} - {{name}} {{id}} - {{name}}
</tpl-item> </tpl-item>
</vn-datalist> </vn-datalist>
<vn-textfield
label="Document"
ng-model="$ctrl.invoiceIn.dmsFk"
ng-change="$ctrl.checkFileExists($ctrl.invoiceIn.dmsFk)"
rule>
<prepend>
<vn-icon-button
disabled="$ctrl.editDownloadDisabled"
ng-if="$ctrl.invoiceIn.dmsFk"
title="{{'Download file' | translate}}"
icon="cloud_download"
ng-click="$ctrl.downloadFile($ctrl.invoiceIn.dmsFk)">
</vn-icon-button>
</prepend>
<append>
<vn-icon-button
disabled="$ctrl.editDownloadDisabled"
ng-if="$ctrl.invoiceIn.dmsFk"
ng-click="$ctrl.openEditDialog($ctrl.invoiceIn.dmsFk)"
icon="edit"
title="{{'Edit document' | translate}}">
</vn-icon-button>
<vn-icon-button
ng-if="!$ctrl.invoiceIn.dmsFk"
ng-click="$ctrl.openCreateDialog()"
icon="add_circle"
title="{{'Create document' | translate}}">
</vn-icon-button>
</append>
</vn-textfield>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-date-picker <vn-date-picker
@ -105,3 +153,163 @@
</vn-button> </vn-button>
</vn-button-bar> </vn-button-bar>
</form> </form>
<!-- Create edit dms dialog -->
<vn-dialog
vn-id="dmsEditDialog"
message="Edit document"
on-accept="$ctrl.onEdit()">
<tpl-body>
<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"
url="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}}"
required="false"
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="true"
label="Generate identifier for original file"
ng-model="$ctrl.dms.hasFile">
</vn-check>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Save</button>
</tpl-buttons>
</vn-dialog>
<!-- Create new dms dialog -->
<vn-dialog
vn-id="dmsCreateDialog"
message="Create document"
on-accept="$ctrl.onCreate()">
<tpl-body>
<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"
data="companies"
show-field="code"
value-field="id"
required="true">
</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"
required="true">
</vn-autocomplete>
<vn-autocomplete
vn-one
label="Type"
ng-model="$ctrl.dms.dmsTypeId"
data="dmsTypes"
show-field="name"
value-field="id"
required="true">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textarea
vn-one
label="Description"
ng-model="$ctrl.dms.description"
required="true"
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>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Create</button>
</tpl-buttons>
</vn-dialog>

View File

@ -1,9 +1,181 @@
import ngModule from '../module'; import ngModule from '../module';
import Section from 'salix/components/section'; import Section from 'salix/components/section';
import UserError from 'core/lib/user-error';
class Controller extends Section {
constructor($element, $, vnFile) {
super($element, $, vnFile);
this.dms = {
files: [],
hasFile: false,
hasFileAttached: false
};
this.vnFile = vnFile;
this.getAllowedContentTypes();
this._editDownloadDisabled = false;
}
get contentTypesInfo() {
return this.$t('ContentTypesInfo', {
allowedContentTypes: this.allowedContentTypes
});
}
get editDownloadDisabled() {
return this._editDownloadDisabled;
}
async checkFileExists(dmsId) {
if (!dmsId) return;
let filter = {
fields: ['id']
};
await this.$http.get(`Dms/${dmsId}`, {filter})
.then(() => this._editDownloadDisabled = false)
.catch(() => this._editDownloadDisabled = true);
}
async getFile(dmsId) {
const path = `Dms/${dmsId}`;
await this.$http.get(path).then(res => {
const dms = res.data && res.data;
this.dms = {
dmsId: dms.id,
reference: dms.reference,
warehouseId: dms.warehouseFk,
companyId: dms.companyFk,
dmsTypeId: dms.dmsTypeFk,
description: dms.description,
hasFile: dms.hasFile,
hasFileAttached: false,
files: []
};
});
}
getAllowedContentTypes() {
this.$http.get('DmsContainers/allowedContentTypes').then(res => {
if (res.data.length > 0) {
const contentTypes = res.data.join(', ');
this.allowedContentTypes = contentTypes;
}
});
}
openEditDialog(dmsId) {
this.getFile(dmsId).then(() => this.$.dmsEditDialog.show());
}
openCreateDialog() {
this.dms = {
reference: null,
warehouseId: null,
companyId: null,
dmsTypeId: null,
description: null,
hasFile: true,
hasFileAttached: true,
files: null
};
this.$.dmsCreateDialog.show();
}
downloadFile(dmsId) {
this.vnFile.download(`api/dms/${dmsId}/downloadFile`);
}
onFileChange(files) {
let hasFileAttached = false;
if (files.length > 0)
hasFileAttached = true;
this.$.$applyAsync(() => {
this.dms.hasFileAttached = hasFileAttached;
});
}
onEdit() {
if (!this.dms.companyId)
throw new UserError(`The company can't be empty`);
if (!this.dms.warehouseId)
throw new UserError(`The warehouse can't be empty`);
if (!this.dms.dmsTypeId)
throw new UserError(`The DMS Type can't be empty`);
if (!this.dms.description)
throw new UserError(`The description can't be empty`);
const query = `dms/${this.dms.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!'));
if (res.data.length > 0) this.invoiceIn.dmsFk = res.data[0].id;
}
});
}
onCreate() {
if (!this.dms.companyId)
throw new UserError(`The company can't be empty`);
if (!this.dms.warehouseId)
throw new UserError(`The warehouse can't be empty`);
if (!this.dms.dmsTypeId)
throw new UserError(`The DMS Type can't be empty`);
if (!this.dms.description)
throw new UserError(`The description can't be empty`);
if (!this.dms.files)
throw new UserError(`The files can't be empty`);
const query = `Dms/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!'));
if (res.data.length > 0) this.invoiceIn.dmsFk = res.data[0].id;
}
});
}
}
Controller.$inject = ['$element', '$scope', 'vnFile'];
ngModule.vnComponent('vnInvoiceInBasicData', { ngModule.vnComponent('vnInvoiceInBasicData', {
template: require('./index.html'), template: require('./index.html'),
controller: Section, controller: Controller,
bindings: { bindings: {
invoiceIn: '<' invoiceIn: '<'
} }

View File

@ -0,0 +1,102 @@
import './index.js';
import watcher from 'core/mocks/watcher';
describe('InvoiceIn', () => {
describe('Component vnInvoiceInBasicData', () => {
let controller;
let $scope;
let $httpBackend;
let $httpParamSerializer;
beforeEach(ngModule('invoiceIn'));
beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-invoice-in-basic-data></vn-invoice-in-basic-data>');
controller = $componentController('vnInvoiceInBasicData', {$element, $scope});
controller.$.watcher = watcher;
$httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond({});
}));
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('checkFileExists()', () => {
it(`should return false if a file exists`, () => {
const fileIdExists = 1;
controller.checkFileExists(fileIdExists);
expect(controller.editDownloadDisabled).toBe(false);
});
});
describe('onEdit()', () => {
it(`should perform a POST query to edit the dms properties`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const dms = {
dmsId: 1,
reference: 'Ref1',
warehouseId: 1,
companyId: 442,
dmsTypeId: 20,
description: 'This is a description',
files: []
};
controller.dms = dms;
const serializedParams = $httpParamSerializer(controller.dms);
const query = `dms/${controller.dms.dmsId}/updateFile?${serializedParams}`;
$httpBackend.expectPOST(query).respond({});
controller.onEdit();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('onCreate()', () => {
it(`should perform a POST query to create a new dms`, () => {
jest.spyOn(controller.vnApp, 'showSuccess');
const dms = {
reference: 'Ref1',
warehouseId: 1,
companyId: 442,
dmsTypeId: 20,
description: 'This is a description',
files: [{
lastModified: 1668673957761,
lastModifiedDate: new Date(),
name: 'file-example.png',
size: 19653,
type: 'image/png',
webkitRelativePath: ''
}]
};
controller.dms = dms;
const serializedParams = $httpParamSerializer(controller.dms);
const query = `Dms/uploadFile?${serializedParams}`;
$httpBackend.expectPOST(query).respond({});
controller.onCreate();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
});
});

View File

@ -0,0 +1 @@
ContentTypesInfo: Allowed file types {{allowedContentTypes}}

View File

@ -0,0 +1,15 @@
Upload file: Subir fichero
Edit file: Editar fichero
Upload: Subir
Document: Documento
ContentTypesInfo: "Tipos de archivo permitidos: {{allowedContentTypes}}"
Generate identifier for original file: Generar identificador para archivo original
File management: Gestión documental
Hard copy: Copia
This file will be deleted: Este fichero va a ser borrado
Are you sure?: Estas seguro?
File deleted: Fichero eliminado
Remove file: Eliminar fichero
Download file: Descargar fichero
Edit document: Editar documento
Create document: Crear documento