Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 4846-claim.search-panel
This commit is contained in:
commit
7f3265be55
|
@ -32,7 +32,7 @@ module.exports = Self => {
|
|||
let message = $t(`There's a new urgent ticket:`);
|
||||
const ostUri = 'https://cau.verdnatura.es/scp/tickets.php?id=';
|
||||
tickets.forEach(ticket => {
|
||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} (@${ticket.username})](${ostUri + ticket.id})`;
|
||||
message += `\r\n[ID: *${ticket.number}* - ${ticket.subject} @${ticket.username}](${ostUri + ticket.id})`;
|
||||
});
|
||||
|
||||
const department = await models.Department.findOne({
|
||||
|
@ -42,7 +42,5 @@ module.exports = Self => {
|
|||
|
||||
if (channelName)
|
||||
return Self.send(ctx, `#${channelName}`, `@all ➔ ${message}`);
|
||||
|
||||
return;
|
||||
};
|
||||
};
|
||||
|
|
|
@ -4,4 +4,21 @@ module.exports = Self => {
|
|||
require('../methods/chat/sendCheckingPresence')(Self);
|
||||
require('../methods/chat/notifyIssues')(Self);
|
||||
require('../methods/chat/sendQueued')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (!ctx.isNewInstance) return;
|
||||
|
||||
let {message} = ctx.instance;
|
||||
if (!message) return;
|
||||
|
||||
const parts = message.match(/(?<=\[).*(?=])/g);
|
||||
const replacedParts = parts.map(part => {
|
||||
return part.replace(/[*()]/g, '');
|
||||
});
|
||||
|
||||
for (const [index, part] of parts.entries())
|
||||
message = message.replace(part, replacedParts[index]);
|
||||
|
||||
ctx.instance.message = message;
|
||||
});
|
||||
};
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
INSERT INTO `salix`.`ACL` (model,property,accessType,permission,principalType,principalId)
|
||||
VALUES ('NotificationQueue','*','*','ALLOW','ROLE','employee');
|
|
@ -0,0 +1,8 @@
|
|||
ALTER TABLE
|
||||
`vn`.`client`
|
||||
ADD
|
||||
COLUMN `hasElectronicInvoice` TINYINT(1) NOT NULL DEFAULT 0 COMMENT 'Registro de facturas mediante FACe'
|
||||
AFTER
|
||||
`hasInvoiceSimplified`;
|
||||
|
||||
-- sería más correcto hasElectronicInvoice pero ya existe un campo hasInvoiceSimplified
|
|
@ -0,0 +1 @@
|
|||
insert into `util`.`notification` (`id`, `name`,`description`) values (2, 'invoiceElectronic', 'A electronic invoice has been generated');
|
|
@ -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');
|
|
@ -0,0 +1,6 @@
|
|||
UPDATE
|
||||
`vn`.`client`
|
||||
SET
|
||||
hasElectronicInvoice = TRUE
|
||||
WHERE
|
||||
businessTypeFk = 'officialOrganism';
|
|
@ -0,0 +1 @@
|
|||
Delete this 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),
|
||||
(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),
|
||||
(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`)
|
||||
VALUES
|
||||
|
@ -2731,3 +2732,7 @@ 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`.`ticketLog` (`id`, `originFk`, `userFk`, `action`, `changedModel`, `oldInstance`, `newInstance`, `changedModelId`)
|
||||
VALUES
|
||||
(1, 1, 9, 'insert', 'Ticket', '{}', '{"clientFk":1, "nickname": "Bat cave"}', 1);
|
|
@ -19,6 +19,7 @@
|
|||
-- Current Database: `account`
|
||||
--
|
||||
|
||||
|
||||
CREATE DATABASE /*!32312 IF NOT EXISTS*/ `account` /*!40100 DEFAULT CHARACTER SET utf8mb3 */;
|
||||
|
||||
USE `account`;
|
||||
|
|
|
@ -324,7 +324,8 @@ export default {
|
|||
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)',
|
||||
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: {
|
||||
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"]',
|
||||
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"]',
|
||||
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]'
|
||||
},
|
||||
invoiceInTax: {
|
||||
|
|
|
@ -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!');
|
||||
});
|
||||
});
|
|
@ -4,6 +4,7 @@ import getBrowser from '../../helpers/puppeteer';
|
|||
describe('InvoiceIn basic data path', () => {
|
||||
let browser;
|
||||
let page;
|
||||
let newDms;
|
||||
|
||||
beforeAll(async() => {
|
||||
browser = await getBrowser();
|
||||
|
@ -24,6 +25,8 @@ describe('InvoiceIn basic data path', () => {
|
|||
await page.autocompleteSearch(selectors.invoiceInBasicData.supplier, 'Verdnatura');
|
||||
await page.clearInput(selectors.invoiceInBasicData.supplierRef);
|
||||
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.booked, now);
|
||||
await page.autocompleteSearch(selectors.invoiceInBasicData.currency, 'USD');
|
||||
|
@ -61,4 +64,141 @@ describe('InvoiceIn basic data path', () => {
|
|||
|
||||
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!');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -26,6 +26,7 @@ export default class Searchbar extends Component {
|
|||
this.autoState = true;
|
||||
this.separateIndex = true;
|
||||
this.entityState = 'card.summary';
|
||||
this.isIndex = false;
|
||||
|
||||
this.deregisterCallback = this.$transitions.onSuccess(
|
||||
{}, transition => this.onStateChange(transition));
|
||||
|
@ -102,6 +103,9 @@ export default class Searchbar extends Component {
|
|||
filter = {};
|
||||
}
|
||||
|
||||
let stateParts = this.$state.current.name.split('.');
|
||||
this.isIndex = stateParts[1] == 'index';
|
||||
|
||||
this.doSearch(filter, 'state');
|
||||
}
|
||||
|
||||
|
@ -198,7 +202,7 @@ export default class Searchbar extends Component {
|
|||
}
|
||||
|
||||
doSearch(filter, source) {
|
||||
if (filter === this.filter && source != 'state') return;
|
||||
if (filter === this.filter && !this.isIndex) return;
|
||||
let promise = this.onSearch({$params: filter});
|
||||
promise = promise || this.$q.resolve();
|
||||
promise.then(data => this.onFilter(filter, source, data));
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"name": "salix-front",
|
||||
"version": "1.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"lockfileVersion": 1,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
|
@ -189,10 +189,14 @@
|
|||
"integrity": "sha512-5qjkWIQQVsHj4Sb5TcEs4WZWpFeVFHXwxEBHUhrny41D8UrBAd6T/6nPPAsLngJCReIOqi95W3mxdveveutpZw=="
|
||||
},
|
||||
"angular-animate": {
|
||||
"version": "1.8.2"
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/angular-animate/-/angular-animate-1.8.2.tgz",
|
||||
"integrity": "sha512-Jbr9+grNMs9Kj57xuBU3Ju3NOPAjS1+g2UAwwDv7su1lt0/PLDy+9zEwDiu8C8xJceoTbmBNKiWGPJGBdCQLlA=="
|
||||
},
|
||||
"angular-moment": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/angular-moment/-/angular-moment-1.3.0.tgz",
|
||||
"integrity": "sha512-KG8rvO9MoaBLwtGnxTeUveSyNtrL+RNgGl1zqWN36+HDCCVGk2DGWOzqKWB6o+eTTbO3Opn4hupWKIElc8XETA==",
|
||||
"requires": {
|
||||
"moment": ">=2.8.0 <3.0.0"
|
||||
}
|
||||
|
@ -215,18 +219,26 @@
|
|||
},
|
||||
"argparse": {
|
||||
"version": "1.0.10",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
|
||||
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
|
||||
"requires": {
|
||||
"sprintf-js": "~1.0.2"
|
||||
}
|
||||
},
|
||||
"croppie": {
|
||||
"version": "2.6.5"
|
||||
"version": "2.6.5",
|
||||
"resolved": "https://registry.npmjs.org/croppie/-/croppie-2.6.5.tgz",
|
||||
"integrity": "sha512-IlChnVUGG5T3w2gRZIaQgBtlvyuYnlUWs2YZIXXR3H9KrlO1PtBT3j+ykxvy9eZIWhk+V5SpBmhCQz5UXKrEKQ=="
|
||||
},
|
||||
"esprima": {
|
||||
"version": "4.0.1"
|
||||
"version": "4.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
|
||||
"integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "3.14.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
|
||||
"integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
|
||||
"requires": {
|
||||
"argparse": "^1.0.7",
|
||||
"esprima": "^4.0.0"
|
||||
|
@ -234,6 +246,8 @@
|
|||
},
|
||||
"mg-crud": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/mg-crud/-/mg-crud-1.1.2.tgz",
|
||||
"integrity": "sha512-mAR6t0aQHKnT0QHKHpLOi0kNPZfO36iMpIoiLjFHxuio6mIJyuveBJ4VNlNXJRxLh32/FLADEb41/sYo7QUKFw==",
|
||||
"requires": {
|
||||
"angular": "^1.6.1"
|
||||
}
|
||||
|
@ -244,19 +258,27 @@
|
|||
"integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
|
||||
},
|
||||
"oclazyload": {
|
||||
"version": "0.6.3"
|
||||
"version": "0.6.3",
|
||||
"resolved": "https://registry.npmjs.org/oclazyload/-/oclazyload-0.6.3.tgz",
|
||||
"integrity": "sha512-HpOSYUgjtt6sTB/C6+FWsExR+9HCnXKsUA96RWkDXfv11C8Cc9X2DlR0WIZwFIiG6FQU0pwB5dhoYyut8bFAOQ=="
|
||||
},
|
||||
"require-yaml": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/require-yaml/-/require-yaml-0.0.1.tgz",
|
||||
"integrity": "sha512-M6eVEgLPRbeOhgSCnOTtdrOOEQzbXRchg24Xa13c39dMuraFKdI9emUo97Rih0YEFzSICmSKg8w4RQp+rd9pOQ==",
|
||||
"requires": {
|
||||
"js-yaml": ""
|
||||
"js-yaml": "^4.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"argparse": {
|
||||
"version": "2.0.1"
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
|
||||
},
|
||||
"js-yaml": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
|
||||
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
|
||||
"requires": {
|
||||
"argparse": "^2.0.1"
|
||||
}
|
||||
|
@ -264,10 +286,14 @@
|
|||
}
|
||||
},
|
||||
"sprintf-js": {
|
||||
"version": "1.0.3"
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
|
||||
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g=="
|
||||
},
|
||||
"validator": {
|
||||
"version": "6.3.0"
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/validator/-/validator-6.3.0.tgz",
|
||||
"integrity": "sha512-BylxTwhqwjQI5MDJF7amCy/L0ejJO+74DvCsLV52Lq3+3bhVcVMKqNqOiNcQJm2G48u9EAcw4xFERAmFbwXM9Q=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,16 +33,18 @@ export default class Controller extends Section {
|
|||
|
||||
set logs(value) {
|
||||
this._logs = value;
|
||||
if (!value) return;
|
||||
|
||||
if (this.logs) {
|
||||
this.logs.forEach(log => {
|
||||
log.oldProperties = this.getInstance(log.oldInstance);
|
||||
log.newProperties = this.getInstance(log.newInstance);
|
||||
const validations = window.validations;
|
||||
value.forEach(log => {
|
||||
const locale = validations[log.changedModel].locale ? validations[log.changedModel].locale : {};
|
||||
|
||||
log.oldProperties = this.getInstance(log.oldInstance, locale);
|
||||
log.newProperties = this.getInstance(log.newInstance, locale);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getInstance(instance) {
|
||||
getInstance(instance, locale) {
|
||||
const properties = [];
|
||||
let validDate = /^(-?(?:[1-9][0-9]*)?[0-9]{4})-(1[0-2]|0[1-9])-(3[01]|0[1-9]|[12][0-9])T(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]+)?(Z)?$/;
|
||||
|
||||
|
@ -51,7 +53,8 @@ export default class Controller extends Section {
|
|||
if (validDate.test(instance[property]))
|
||||
instance[property] = new Date(instance[property]).toLocaleString('es-ES');
|
||||
|
||||
properties.push({key: property, value: instance[property]});
|
||||
const key = locale[property] || property;
|
||||
properties.push({key, value: instance[property]});
|
||||
});
|
||||
return properties;
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('modelInfo', {
|
||||
|
@ -19,6 +21,42 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
const modelsLocale = new Map();
|
||||
const modulesDir = path.resolve(`${__dirname}/../../../../modules`);
|
||||
const modules = fs.readdirSync(modulesDir);
|
||||
|
||||
for (const mod of modules) {
|
||||
const modelsDir = path.join(modulesDir, mod, `back/locale`);
|
||||
if (!fs.existsSync(modelsDir)) continue;
|
||||
const models = fs.readdirSync(modelsDir);
|
||||
|
||||
for (const model of models) {
|
||||
const localeDir = path.join(modelsDir, model);
|
||||
const localeFiles = fs.readdirSync(localeDir);
|
||||
|
||||
let modelName = model.charAt(0).toUpperCase() + model.substring(1);
|
||||
modelName = modelName.replace(/-\w/g, match => {
|
||||
return match.charAt(1).toUpperCase();
|
||||
});
|
||||
|
||||
const modelLocale = new Map();
|
||||
modelsLocale.set(modelName, modelLocale);
|
||||
|
||||
for (const localeFile of localeFiles) {
|
||||
const localePath = path.join(localeDir, localeFile);
|
||||
|
||||
const match = localeFile.match(/^([a-z]+)\.yml$/);
|
||||
if (!match) {
|
||||
console.warn(`Skipping wrong model locale file: ${localeFile}`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const translations = require(localePath);
|
||||
modelLocale.set(match[1], translations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self.modelInfo = async function(ctx) {
|
||||
let json = {};
|
||||
let models = Self.app.models;
|
||||
|
@ -49,9 +87,14 @@ module.exports = Self => {
|
|||
jsonValidations[fieldName] = jsonField;
|
||||
}
|
||||
|
||||
const modelLocale = modelsLocale.get(modelName);
|
||||
const lang = ctx.req.getLocale();
|
||||
const locale = modelLocale && modelLocale.get(lang);
|
||||
|
||||
json[modelName] = {
|
||||
properties: model.definition.rawProperties,
|
||||
validations: jsonValidations
|
||||
validations: jsonValidations,
|
||||
locale
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -138,5 +138,8 @@
|
|||
"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",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -244,5 +244,8 @@
|
|||
"Ticket merged": "Ticket [{{id}}]({{{fullPath}}}) ({{{originDated}}}) fusionado con [{{tfId}}]({{{fullPathFuture}}}) ({{{futureDated}}})",
|
||||
"Already has this status": "Ya tiene este estado",
|
||||
"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"
|
||||
}
|
||||
|
|
|
@ -99,6 +99,10 @@ module.exports = Self => {
|
|||
{
|
||||
arg: 'hasIncoterms',
|
||||
type: 'boolean'
|
||||
},
|
||||
{
|
||||
arg: 'hasElectronicInvoice',
|
||||
type: 'boolean'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
|
@ -122,19 +126,15 @@ module.exports = Self => {
|
|||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const isSalesAssistant = await models.Account.hasRole(userId, 'salesAssistant', myOptions);
|
||||
const client = await models.Client.findById(clientId, null, myOptions);
|
||||
|
||||
if (!isSalesAssistant && client.isTaxDataChecked)
|
||||
throw new UserError(`Not enough privileges to edit a client with verified data`);
|
||||
|
||||
// Sage data validation
|
||||
const taxDataChecked = args.isTaxDataChecked;
|
||||
const sageTaxChecked = client.sageTaxTypeFk || args.sageTaxTypeFk;
|
||||
|
@ -143,7 +143,6 @@ module.exports = Self => {
|
|||
|
||||
if (taxDataChecked && !hasSageData)
|
||||
throw new UserError(`You need to fill sage information before you check verified data`);
|
||||
|
||||
if (args.despiteOfClient) {
|
||||
const logRecord = {
|
||||
originFk: clientId,
|
||||
|
@ -158,7 +157,6 @@ module.exports = Self => {
|
|||
|
||||
await models.ClientLog.create(logRecord, myOptions);
|
||||
}
|
||||
|
||||
// Remove unwanted properties
|
||||
delete args.ctx;
|
||||
delete args.id;
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const {Email} = require('vn-print');
|
||||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('balanceCompensationEmail', {
|
||||
|
@ -10,7 +11,7 @@ module.exports = Self => {
|
|||
type: 'Number',
|
||||
required: true,
|
||||
description: 'The receipt id',
|
||||
http: { source: 'path' }
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
|
@ -23,18 +24,29 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.balanceCompensationEmail = async (ctx, id) => {
|
||||
|
||||
Self.balanceCompensationEmail = async(ctx, id) => {
|
||||
const models = Self.app.models;
|
||||
const receipt = await models.Receipt.findById(id, {fields: ['clientFk']});
|
||||
const client = await models.Client.findById(receipt.clientFk, {fields:['email']});
|
||||
const receipt = await models.Receipt.findById(id, {fields: ['clientFk', 'bankFk']});
|
||||
|
||||
const bank = await models.Bank.findById(receipt.bankFk);
|
||||
if (!bank)
|
||||
throw new UserError(`Receipt's bank was not found`);
|
||||
|
||||
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',
|
||||
recipient: client.email + ',administracion@verdnatura.es',
|
||||
id
|
||||
});
|
||||
|
||||
return email.send();
|
||||
}
|
||||
};
|
||||
};
|
||||
|
|
|
@ -56,11 +56,14 @@ module.exports = Self => {
|
|||
u.name userName,
|
||||
r.clientFk,
|
||||
FALSE hasPdf,
|
||||
FALSE isInvoice
|
||||
FALSE isInvoice,
|
||||
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
|
||||
FROM vn.receipt r
|
||||
LEFT JOIN vn.worker w ON w.id = r.workerFk
|
||||
LEFT JOIN account.user u ON u.id = w.userFk
|
||||
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 = ?
|
||||
UNION ALL
|
||||
SELECT
|
||||
|
@ -77,9 +80,12 @@ module.exports = Self => {
|
|||
NULL,
|
||||
i.clientFk,
|
||||
i.hasPdf,
|
||||
TRUE isInvoice
|
||||
TRUE isInvoice,
|
||||
CASE WHEN at2.code LIKE 'compensation' THEN True ELSE False END as isCompensation
|
||||
FROM vn.invoiceOut i
|
||||
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 = ?
|
||||
ORDER BY payed DESC, created DESC
|
||||
) t ORDER BY payed DESC, created DESC`,
|
||||
|
|
|
@ -142,7 +142,11 @@
|
|||
},
|
||||
"salesPersonFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"hasElectronicInvoice": {
|
||||
"type": "boolean"
|
||||
}
|
||||
|
||||
},
|
||||
"relations": {
|
||||
"account": {
|
||||
|
|
|
@ -121,7 +121,7 @@
|
|||
</vn-icon-button>
|
||||
</a>
|
||||
</vn-td>
|
||||
<vn-td center shrink ng-if="!balance.isInvoice">
|
||||
<vn-td center shrink ng-if="balance.isCompensation">
|
||||
<vn-icon-button
|
||||
vn-dialog="send_compensation"
|
||||
icon="outgoing_mail"
|
||||
|
|
|
@ -151,5 +151,19 @@ describe('Client', () => {
|
|||
$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);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
BILL: N/INV {{ref}}
|
||||
Notify compensation: Do you want to report compensation to the client by mail?
|
||||
Send compensation: Send compensation
|
||||
|
|
|
@ -1,112 +1,52 @@
|
|||
<mg-ajax path="Clients/{{patch.params.id}}/updateFiscalData" options="vnPatch"></mg-ajax>
|
||||
<vn-watcher
|
||||
vn-id="watcher"
|
||||
data="$ctrl.client"
|
||||
id-field="id"
|
||||
form="form"
|
||||
save="patch">
|
||||
<vn-watcher vn-id="watcher" data="$ctrl.client" id-field="id" form="form" save="patch">
|
||||
</vn-watcher>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Provinces/location"
|
||||
data="provincesLocation"
|
||||
order="name">
|
||||
<vn-crud-model auto-load="true" url="Provinces/location" data="provincesLocation" order="name">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Countries"
|
||||
data="countries"
|
||||
order="country">
|
||||
<vn-crud-model auto-load="true" url="Countries" data="countries" order="country">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="SageTaxTypes"
|
||||
data="sageTaxTypes"
|
||||
order="vat">
|
||||
<vn-crud-model auto-load="true" url="SageTaxTypes" data="sageTaxTypes" order="vat">
|
||||
</vn-crud-model>
|
||||
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-two
|
||||
vn-focus
|
||||
label="Social name"
|
||||
ng-model="$ctrl.client.socialName"
|
||||
rule
|
||||
info="Only letters, numbers and spaces can be used"
|
||||
required="true">
|
||||
<vn-textfield vn-two vn-focus label="Social name" ng-model="$ctrl.client.socialName" rule
|
||||
info="Only letters, numbers and spaces can be used" required="true">
|
||||
</vn-textfield>
|
||||
<vn-textfield
|
||||
vn-one
|
||||
label="Tax number"
|
||||
ng-model="$ctrl.client.fi"
|
||||
rule>
|
||||
<vn-textfield vn-one label="Tax number" ng-model="$ctrl.client.fi" rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
vn-two
|
||||
label="Street"
|
||||
ng-model="$ctrl.client.street"
|
||||
rule>
|
||||
<vn-textfield vn-two label="Street" ng-model="$ctrl.client.street" rule>
|
||||
</vn-textfield>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.client.sageTaxTypeFk"
|
||||
data="sageTaxTypes"
|
||||
show-field="vat"
|
||||
value-field="id"
|
||||
label="Sage tax type"
|
||||
vn-acl="salesAssistant"
|
||||
rule>
|
||||
<vn-autocomplete vn-one ng-model="$ctrl.client.sageTaxTypeFk" data="sageTaxTypes" show-field="vat"
|
||||
value-field="id" label="Sage tax type" vn-acl="salesAssistant" rule>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-one
|
||||
ng-model="$ctrl.client.sageTransactionTypeFk"
|
||||
url="SageTransactionTypes"
|
||||
show-field="transaction"
|
||||
value-field="id"
|
||||
label="Sage transaction type"
|
||||
<vn-autocomplete vn-one ng-model="$ctrl.client.sageTransactionTypeFk" url="SageTransactionTypes"
|
||||
show-field="transaction" value-field="id" label="Sage transaction type"
|
||||
search-function="{or: [{id: $search}, {transaction: {like: '%'+ $search +'%'}}]}"
|
||||
vn-acl="salesAssistant"
|
||||
order="transaction"
|
||||
rule>
|
||||
vn-acl="salesAssistant" order="transaction" rule>
|
||||
<tpl-item>{{id}}: {{transaction}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-datalist vn-one
|
||||
label="Postcode"
|
||||
ng-model="$ctrl.client.postcode"
|
||||
selection="$ctrl.postcode"
|
||||
url="Postcodes/location"
|
||||
fields="['code','townFk']"
|
||||
order="code, townFk"
|
||||
value-field="code"
|
||||
show-field="code"
|
||||
rule>
|
||||
<vn-datalist vn-one label="Postcode" ng-model="$ctrl.client.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 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-datalist vn-id="town" vn-one
|
||||
label="City"
|
||||
ng-model="$ctrl.client.city"
|
||||
selection="$ctrl.town"
|
||||
url="Towns/location"
|
||||
fields="['id', 'name', 'provinceFk']"
|
||||
show-field="name"
|
||||
value-field="name">
|
||||
<vn-datalist vn-id="town" vn-one label="City" ng-model="$ctrl.client.city" selection="$ctrl.town"
|
||||
url="Towns/location" fields="['id', 'name', 'provinceFk']" show-field="name" value-field="name">
|
||||
<tpl-item>
|
||||
{{name}}, {{province.name}}
|
||||
({{province.country.country}})
|
||||
|
@ -114,112 +54,64 @@
|
|||
</vn-datalist>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete vn-id="province" vn-one
|
||||
label="Province"
|
||||
ng-model="$ctrl.client.provinceFk"
|
||||
selection="$ctrl.province"
|
||||
data="provincesLocation"
|
||||
fields="['id', 'name', 'countryFk']"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
rule>
|
||||
<vn-autocomplete vn-id="province" vn-one label="Province" ng-model="$ctrl.client.provinceFk"
|
||||
selection="$ctrl.province" data="provincesLocation" fields="['id', 'name', 'countryFk']"
|
||||
show-field="name" value-field="id" rule>
|
||||
<tpl-item>{{name}} ({{country.country}})</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete vn-id="country" vn-one
|
||||
ng-model="$ctrl.client.countryFk"
|
||||
data="countries"
|
||||
show-field="country"
|
||||
value-field="id"
|
||||
label="Country"
|
||||
rule>
|
||||
<vn-autocomplete vn-id="country" vn-one ng-model="$ctrl.client.countryFk" data="countries"
|
||||
show-field="country" value-field="id" label="Country" rule>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Active"
|
||||
ng-model="$ctrl.client.isActive">
|
||||
<vn-check vn-one label="Active" ng-model="$ctrl.client.isActive">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Frozen"
|
||||
ng-model="$ctrl.client.isFreezed">
|
||||
<vn-check vn-one label="Frozen" ng-model="$ctrl.client.isFreezed">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Has to invoice"
|
||||
ng-model="$ctrl.client.hasToInvoice">
|
||||
<vn-check vn-one label="Has to invoice" ng-model="$ctrl.client.hasToInvoice">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Vies"
|
||||
ng-model="$ctrl.client.isVies">
|
||||
<vn-check vn-one label="Vies" ng-model="$ctrl.client.isVies">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Notify by email"
|
||||
ng-model="$ctrl.client.isToBeMailed">
|
||||
<vn-check vn-one label="Notify by email" ng-model="$ctrl.client.isToBeMailed">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Invoice by address"
|
||||
ng-model="$ctrl.client.hasToInvoiceByAddress">
|
||||
<vn-check vn-one label="Invoice by address" ng-model="$ctrl.client.hasToInvoiceByAddress">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Is equalizated"
|
||||
ng-model="$ctrl.client.isEqualizated"
|
||||
<vn-check vn-one label="Is equalizated" ng-model="$ctrl.client.isEqualizated"
|
||||
info="In order to invoice, this field is not consulted, but the consignee's ET. When modifying this field if the invoice by address option is not checked, the change will be automatically propagated to all addresses, otherwise the user will be asked if he wants to propagate it or not."
|
||||
on-change="$ctrl.onChangeEqualizated(value)">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Verified data"
|
||||
ng-model="$ctrl.client.isTaxDataChecked"
|
||||
vn-acl="salesAssistant">
|
||||
<vn-check vn-one label="Verified data" ng-model="$ctrl.client.isTaxDataChecked" vn-acl="salesAssistant">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Incoterms authorization"
|
||||
ng-model="$ctrl.client.hasIncoterms"
|
||||
<vn-check vn-one label="Incoterms authorization" ng-model="$ctrl.client.hasIncoterms"
|
||||
vn-acl="administrative">
|
||||
</vn-check>
|
||||
<vn-check vn-one label="Electronic invoice" ng-model="$ctrl.client.hasElectronicInvoice"
|
||||
vn-acl="administrative">
|
||||
</vn-check>
|
||||
</vn-horizontal>
|
||||
</vn-card>
|
||||
<vn-button-bar>
|
||||
<vn-submit
|
||||
disabled="!watcher.dataChanged()"
|
||||
label="Save">
|
||||
<vn-submit disabled="!watcher.dataChanged()" label="Save">
|
||||
</vn-submit>
|
||||
<vn-button
|
||||
class="cancel"
|
||||
label="Undo changes"
|
||||
disabled="!watcher.dataChanged()"
|
||||
<vn-button class="cancel" label="Undo changes" disabled="!watcher.dataChanged()"
|
||||
ng-click="watcher.loadOriginalData()">
|
||||
</vn-button>
|
||||
</vn-button-bar>
|
||||
</form>
|
||||
<vn-confirm
|
||||
vn-id="propagate-isEqualizated"
|
||||
question="You changed the equalization tax"
|
||||
message="Do you want to spread the change?"
|
||||
on-accept="$ctrl.onAcceptEt()">
|
||||
<vn-confirm vn-id="propagate-isEqualizated" question="You changed the equalization tax"
|
||||
message="Do you want to spread the change?" on-accept="$ctrl.onAcceptEt()">
|
||||
</vn-confirm>
|
||||
<vn-confirm
|
||||
vn-id="confirm-duplicatedClient"
|
||||
message="Found a client with this data"
|
||||
<vn-confirm vn-id="confirm-duplicatedClient" message="Found a client with this data"
|
||||
on-accept="$ctrl.onAcceptDuplication()">
|
||||
</vn-confirm>
|
||||
<!-- New postcode dialog -->
|
||||
<vn-geo-postcode
|
||||
vn-id="postcode"
|
||||
on-response="$ctrl.onResponse($response)">
|
||||
<vn-geo-postcode vn-id="postcode" on-response="$ctrl.onResponse($response)">
|
||||
</vn-geo-postcode>
|
|
@ -11,3 +11,4 @@ Sage transaction type: Tipo de transacción Sage
|
|||
Previous client: Cliente anterior
|
||||
In case of a company succession, specify the grantor company: En el caso de que haya habido una sucesión de empresa, indicar la empresa cedente
|
||||
Incoterms authorization: Autorización incoterms
|
||||
Electronic invoice: Factura electrónica
|
||||
|
|
|
@ -5,6 +5,24 @@
|
|||
form="form"
|
||||
save="patch">
|
||||
</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">
|
||||
<vn-card class="vn-pa-lg">
|
||||
<vn-horizontal>
|
||||
|
@ -57,6 +75,36 @@
|
|||
{{id}} - {{name}}
|
||||
</tpl-item>
|
||||
</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-date-picker
|
||||
|
@ -105,3 +153,163 @@
|
|||
</vn-button>
|
||||
</vn-button-bar>
|
||||
</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>
|
||||
|
|
|
@ -1,9 +1,181 @@
|
|||
import ngModule from '../module';
|
||||
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', {
|
||||
template: require('./index.html'),
|
||||
controller: Section,
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
invoiceIn: '<'
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1 @@
|
|||
ContentTypesInfo: Allowed file types {{allowedContentTypes}}
|
|
@ -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
|
|
@ -21,3 +21,4 @@ Total net: Total neto
|
|||
Total stems: Total tallos
|
||||
Show agricultural invoice as PDF: Ver factura agrícola como PDF
|
||||
Send agricultural invoice as PDF: Enviar factura agrícola como PDF
|
||||
New InvoiceIn: Nueva Factura
|
|
@ -64,7 +64,7 @@ describe('InvoiceOut filter()', () => {
|
|||
const invoiceOut = await models.InvoiceOut.findById(1, null, options);
|
||||
await invoiceOut.updateAttribute('hasPdf', true, options);
|
||||
|
||||
const result = await models.InvoiceOut.filter(ctx, {}, options);
|
||||
const result = await models.InvoiceOut.filter(ctx, {id: invoiceOut.id}, options);
|
||||
|
||||
expect(result.length).toEqual(1);
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
concept: concept
|
||||
quantity: quantity
|
||||
price: price
|
||||
discount: discount
|
||||
reserved: reserved
|
||||
isPicked: is picked
|
||||
created: created
|
||||
originalQuantity: original quantity
|
||||
itemFk: item
|
||||
ticketFk: ticket
|
||||
saleFk: sale
|
|
@ -0,0 +1,11 @@
|
|||
concept: concepto
|
||||
quantity: cantidad
|
||||
price: precio
|
||||
discount: descuento
|
||||
reserved: reservado
|
||||
isPicked: esta seleccionado
|
||||
created: creado
|
||||
originalQuantity: cantidad original
|
||||
itemFk: artículo
|
||||
ticketFk: ticket
|
||||
saleFk: línea
|
|
@ -0,0 +1,22 @@
|
|||
shipped: shipped
|
||||
landed: landed
|
||||
nickname: nickname
|
||||
location: location
|
||||
solution: solution
|
||||
packages: packages
|
||||
updated: updated
|
||||
isDeleted: is deleted
|
||||
priority: priority
|
||||
zoneFk: zone
|
||||
zonePrice: zone price
|
||||
zoneBonus: zone bonus
|
||||
totalWithVat: total with vat
|
||||
totalWithoutVat: total without vat
|
||||
clientFk: client
|
||||
warehouseFk: warehouse
|
||||
refFk: reference
|
||||
addressFk: address
|
||||
routeFk: route
|
||||
companyFk: company
|
||||
agencyModeFk: agency
|
||||
ticketFk: ticket
|
|
@ -0,0 +1,22 @@
|
|||
shipped: fecha salida
|
||||
landed: fecha entrega
|
||||
nickname: alias
|
||||
location: ubicación
|
||||
solution: solución
|
||||
packages: embalajes
|
||||
updated: fecha última actualización
|
||||
isDeleted: esta eliminado
|
||||
priority: prioridad
|
||||
zoneFk: zona
|
||||
zonePrice: precio zona
|
||||
zoneBonus: bonus zona
|
||||
totalWithVat: total con IVA
|
||||
totalWithoutVat: total sin IVA
|
||||
clientFk: cliente
|
||||
warehouseFk: almacén
|
||||
refFk: referencia
|
||||
addressFk: dirección
|
||||
routeFk: ruta
|
||||
companyFk: empresa
|
||||
agencyModeFk: agencia
|
||||
ticketFk: ticket
|
|
@ -88,6 +88,11 @@ module.exports = Self => {
|
|||
type: 'boolean',
|
||||
description: `Whether to show only tickets with route`
|
||||
},
|
||||
{
|
||||
arg: 'hasInvoice',
|
||||
type: 'boolean',
|
||||
description: `Whether to show only tickets with invoice`
|
||||
},
|
||||
{
|
||||
arg: 'pending',
|
||||
type: 'boolean',
|
||||
|
@ -199,6 +204,10 @@ module.exports = Self => {
|
|||
if (value == true)
|
||||
return {'t.routeFk': {neq: null}};
|
||||
return {'t.routeFk': null};
|
||||
case 'hasInvoice':
|
||||
if (value == true)
|
||||
return {'t.refFk': {neq: null}};
|
||||
return {'t.refFk': null};
|
||||
case 'pending':
|
||||
return {'st.isNotValidated': value};
|
||||
case 'id':
|
||||
|
|
|
@ -39,7 +39,7 @@ describe('ticket filter()', () => {
|
|||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(6);
|
||||
expect(result.length).toBeGreaterThan(3);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
|
@ -278,4 +278,42 @@ describe('ticket filter()', () => {
|
|||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the tickets with the invoice on false', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {hasInvoice: true}};
|
||||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toEqual(6);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should return the tickets with the invoice on null', async() => {
|
||||
const tx = await models.Ticket.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
|
||||
const ctx = {req: {accessToken: {userId: 9}}, args: {hasInvoice: null}};
|
||||
const filter = {};
|
||||
const result = await models.Ticket.filter(ctx, filter, options);
|
||||
|
||||
expect(result.length).toBeGreaterThanOrEqual(27);
|
||||
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
|
|
@ -35,7 +35,8 @@ class Controller extends ModuleCard {
|
|||
'credit',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile'
|
||||
'mobile',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
|
|
@ -65,7 +65,8 @@ class Controller extends Section {
|
|||
'credit',
|
||||
'email',
|
||||
'phone',
|
||||
'mobile'
|
||||
'mobile',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
@ -243,6 +244,23 @@ class Controller extends Section {
|
|||
|
||||
makeInvoice() {
|
||||
const params = {ticketsIds: [this.id]};
|
||||
/*
|
||||
This should call the notification sistem to insert a new notification
|
||||
in te queue, yet to check how to handle user permissions,
|
||||
as of 08-11-2022 every employee can insert a new notification in the queue
|
||||
|
||||
*/
|
||||
const client = this.ticket.client;
|
||||
|
||||
if (client.hasElectronicInvoice) {
|
||||
this.$http.post(`NotificationQueues`, {
|
||||
notificationFk: 'invoiceElectronic',
|
||||
authorFk: client.id,
|
||||
}).then(a => {
|
||||
this.vnApp.showSuccess(this.$t('Invoice sent'));
|
||||
});
|
||||
}
|
||||
|
||||
return this.$http.post(`Tickets/makeInvoice`, params)
|
||||
.then(() => this.reload())
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Ticket invoiced')));
|
||||
|
|
|
@ -9,5 +9,6 @@ Send CSV Delivery Note: Enviar albarán en CSV
|
|||
Send PDF Delivery Note: Enviar albarán en PDF
|
||||
Show Proforma: Ver proforma
|
||||
Refund all: Abonar todo
|
||||
Invoice sent: Factura enviada
|
||||
The following refund ticket have been created: "Se ha creado siguiente ticket de abono: {{ticketId}}"
|
||||
Transfer client: Transferir cliente
|
|
@ -39,7 +39,8 @@ class Controller extends Descriptor {
|
|||
'name',
|
||||
'isActive',
|
||||
'isFreezed',
|
||||
'isTaxDataChecked'
|
||||
'isTaxDataChecked',
|
||||
'hasElectronicInvoice',
|
||||
],
|
||||
include: {
|
||||
relation: 'salesPersonUser',
|
||||
|
|
|
@ -141,6 +141,11 @@
|
|||
ng-model="filter.hasRoute"
|
||||
triple-state="true">
|
||||
</vn-check>
|
||||
<vn-check
|
||||
vn-one
|
||||
label="Has invoice"
|
||||
ng-model="filter.hasInvoice"
|
||||
triple-state="true">
|
||||
</vn-horizontal>
|
||||
<vn-horizontal class="vn-px-lg vn-pb-lg vn-mt-lg">
|
||||
<vn-submit label="Search"></vn-submit>
|
||||
|
|
|
@ -13,6 +13,7 @@ Grouped States: Estado agrupado
|
|||
Days onward: Días adelante
|
||||
With problems: Con problemas
|
||||
Has route: Con ruta
|
||||
Has invoice: Con factura
|
||||
Pending: Pendiente
|
||||
FREE: Libre
|
||||
DELIVERED: Servido
|
||||
|
|
|
@ -201,7 +201,7 @@
|
|||
<vn-dialog
|
||||
vn-id="excludeDialog"
|
||||
on-response="$ctrl.onExcludeResponse($response)"
|
||||
message="{{$ctrl.isNew ? 'Exclusion' : 'Edit exclusion'}}"
|
||||
message="{{$ctrl.isNew ? 'Add exclusion' : 'Edit exclusion'}}"
|
||||
on-open="$ctrl.onSearch($params)"
|
||||
on-close="$ctrl.resetExclusions()">
|
||||
<tpl-body>
|
||||
|
|
|
@ -8,3 +8,5 @@ All: Todo
|
|||
Specific locations: Localizaciones concretas
|
||||
Locations where it is not distributed: Localizaciones en las que no se reparte
|
||||
You must select a location: Debes seleccionar una localización
|
||||
Add exclusion: Añadir exclusión
|
||||
Edit exclusion: Editar exclusión
|
||||
|
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue