Merge branch '3328-item_search-panel' of https://gitea.verdnatura.es/verdnatura/salix into 3328-item_search-panel
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Alex Moreno 2021-11-15 12:14:37 +01:00
commit 73211c5b3b
35 changed files with 553 additions and 362 deletions

View File

@ -0,0 +1,4 @@
ALTER TABLE vn.payMethod CHANGE ibanRequired ibanRequiredForClients tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod ADD ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL;
ALTER TABLE vn.payMethod CHANGE ibanRequiredForSuppliers ibanRequiredForSuppliers tinyint(3) DEFAULT 0 NULL AFTER ibanRequiredForClients;
UPDATE vn.payMethod SET ibanRequiredForSuppliers = 1 WHERE code = 'wireTransfer';

View File

@ -217,14 +217,14 @@ UPDATE `vn`.`agencyMode` SET `web` = 1, `reportMail` = 'no-reply@gothamcity.com'
UPDATE `vn`.`agencyMode` SET `code` = 'refund' WHERE `id` = 23;
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequired`)
INSERT INTO `vn`.`payMethod`(`id`,`code`, `name`, `graceDays`, `outstandingDebt`, `ibanRequiredForClients`, `ibanRequiredForSuppliers`)
VALUES
(1, NULL, 'PayMethod one', 0, 001, 0),
(2, NULL, 'PayMethod two', 10, 001, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1),
(5, NULL, 'PayMethod five', 10, 001, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1);
(1, NULL, 'PayMethod one', 0, 001, 0, 0),
(2, NULL, 'PayMethod two', 10, 001, 0, 0),
(3, 'compensation', 'PayMethod three', 0, 001, 0, 0),
(4, NULL, 'PayMethod with IBAN', 0, 001, 1, 0),
(5, NULL, 'PayMethod five', 10, 001, 0, 0),
(8,'wireTransfer', 'WireTransfer', 5, 001, 1, 1);
INSERT INTO `vn`.`payDem`(`id`, `payDem`)
VALUES

View File

@ -33928,7 +33928,8 @@ CREATE TABLE `payMethod` (
`solution` varchar(1) COLLATE utf8_unicode_ci DEFAULT NULL,
`outstandingDebt` tinyint(3) unsigned zerofill NOT NULL DEFAULT '000',
`graceDays` int(11) unsigned NOT NULL DEFAULT '0',
`ibanRequired` tinyint(3) DEFAULT '0',
`ibanRequiredForClients` tinyint(3) DEFAULT '0',
`ibanRequiredForSuppliers` tinyint(3) DEFAULT '0',
`isNotified` tinyint(3) NOT NULL DEFAULT '1',
PRIMARY KEY (`id`)
) ENGINE=InnoDBDEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

View File

@ -119,6 +119,7 @@ export default {
name: 'vn-client-create vn-textfield[ng-model="$ctrl.client.name"]',
taxNumber: 'vn-client-create vn-textfield[ng-model="$ctrl.client.fi"]',
socialName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.socialName"]',
businessType: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]',
street: 'vn-client-create vn-textfield[ng-model="$ctrl.client.street"]',
addPostCode: 'vn-client-create vn-datalist[ng-model="$ctrl.client.postcode"] vn-icon-button[icon="add_circle"]',
addProvince: 'vn-autocomplete[ng-model="$ctrl.location.provinceFk"] vn-icon-button[icon="add_circle"]',

View File

@ -27,16 +27,19 @@ describe('Client create path', () => {
await page.waitForState('client.create');
});
it('should receive an error when clicking the create button having name and Business name fields empty', async() => {
await page.write(selectors.createClientView.taxNumber, '74451390E');
await page.write(selectors.createClientView.userName, 'CaptainMarvel');
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
it('should receive an error when clicking the create button having name and Business name fields empty',
async() => {
await page.write(selectors.createClientView.taxNumber, '74451390E');
await page.write(selectors.createClientView.userName, 'CaptainMarvel');
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.autocompleteSearch(selectors.createClientView.salesPerson, 'salesPerson');
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});
expect(message.text).toContain('Some fields are invalid');
}
);
it(`should create a new province`, async() => {
await page.waitToClick(selectors.createClientView.addPostCode);
@ -80,9 +83,18 @@ describe('Client create path', () => {
expect(message.text).toContain('Some fields are invalid');
});
it(`should attempt to create a new user with all it's data but wrong postal code`, async() => {
it(`should attempt to create a new user with all it's data but wrong business type`, async() => {
await page.clearInput(selectors.createClientView.email);
await page.write(selectors.createClientView.email, 'CarolDanvers@verdnatura.es');
await page.clearInput(selectors.createClientView.businessType);
await page.waitToClick(selectors.createClientView.createButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Some fields are invalid');
});
it(`should attempt to create a new user with all it's data but wrong postal code`, async() => {
await page.autocompleteSearch(selectors.createClientView.businessType, 'florist');
await page.clearInput(selectors.createClientView.postcode);
await page.write(selectors.createClientView.postcode, '479999');
await page.waitToClick(selectors.createClientView.createButton);

View File

@ -117,5 +117,6 @@
"INACTIVE_PROVIDER": "Inactive provider",
"reference duplicated": "reference duplicated",
"The PDF document does not exists": "The PDF document does not exists. Try regenerating it from 'Regenerate invoice PDF' option",
"This item is not available": "This item is not available"
"This item is not available": "This item is not available",
"Deny buy request": "Purchase request for ticket id [{{ticketId}}]({{{url}}}) has been rejected. Reason: {{observation}}"
}

View File

@ -133,6 +133,7 @@
"reserved": "reservado",
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
"Deny buy request":"Se ha rechazado la petición de compra para el ticket id [{{ticketId}}]({{{url}}}). Motivo: {{observation}}",
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",

View File

@ -50,7 +50,8 @@ module.exports = function(Self) {
city: data.city,
provinceFk: data.provinceFk,
countryFk: data.countryFk,
isEqualizated: data.isEqualizated
isEqualizated: data.isEqualizated,
businessTypeFk: data.businessTypeFk
}, myOptions);
const address = await models.Address.create({

View File

@ -8,7 +8,8 @@ describe('Client Create', () => {
name: 'Wade',
socialName: 'Deadpool Marvel',
street: 'Wall Street',
city: 'New York'
city: 'New York',
businessTypeFk: 'florist'
};
it(`should not find Deadpool as he's not created yet`, async() => {
@ -45,6 +46,7 @@ describe('Client Create', () => {
expect(client.email).toEqual(newAccount.email);
expect(client.fi).toEqual(newAccount.fi);
expect(client.socialName).toEqual(newAccount.socialName);
expect(client.businessTypeFk).toEqual(newAccount.businessTypeFk);
await tx.rollback();
} catch (e) {

View File

@ -129,7 +129,7 @@ module.exports = Self => {
function hasIban(err, done) {
Self.app.models.PayMethod.findById(this.payMethodFk, (_, instance) => {
if (instance && instance.ibanRequired && !this.iban)
if (instance && instance.ibanRequiredForClients && !this.iban)
err();
done();
});

View File

@ -130,6 +130,13 @@
"mysql": {
"columnName": "transactionTypeSageFk"
}
},
"businessTypeFk": {
"type": "string",
"mysql": {
"columnName": "businessTypeFk"
},
"required": true
}
},
"relations": {

View File

@ -25,7 +25,10 @@
"outstandingDebt": {
"type": "Number"
},
"ibanRequired": {
"ibanRequiredForClients": {
"type": "boolean"
},
"ibanRequiredForSuppliers": {
"type": "boolean"
}
}

View File

@ -19,7 +19,7 @@
vn-acl="salesAssistant"
ng-model="$ctrl.client.payMethodFk"
data="paymethods"
fields="['ibanRequired']"
fields="['ibanRequiredForClients']"
initial-data="$ctrl.client.payMethod">
</vn-autocomplete>
<vn-input-number

View File

@ -26,18 +26,28 @@
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-two
label="Business name"
ng-model="$ctrl.client.socialName"
<vn-autocomplete
vn-id="businessTypeFk"
ng-model="$ctrl.client.businessTypeFk"
url="BusinessTypes"
show-field="description"
value-field="code"
label="Business type"
rule>
</vn-textfield>
</vn-autocomplete>
<vn-textfield
label="Tax number"
ng-model="$ctrl.client.fi"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
label="Business name"
ng-model="$ctrl.client.socialName"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-two

View File

@ -56,7 +56,7 @@ module.exports = Self => {
{
relation: 'client',
scope: {
fields: ['id', 'socialName']
fields: ['id', 'socialName', 'email']
}
}
]

View File

@ -0,0 +1,139 @@
<vn-icon-button
icon="more_vert"
vn-popover="menu">
</vn-icon-button>
<vn-menu vn-id="menu">
<vn-list>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
translate>
Show invoice...
<vn-menu vn-id="showInvoiceMenu">
<vn-list>
<a class="vn-item"
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
target="_blank"
name="showInvoicePdf"
translate>
Show as PDF
</a>
<vn-item
ng-click="$ctrl.showCsvInvoice()"
translate>
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendInvoiceMenu.show($event, 'left')"
name="sendInvoice"
translate>
Send invoice...
<vn-menu vn-id="sendInvoiceMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="deleteInvoice"
translate>
Delete Invoice
</vn-item>
<vn-item
ng-click="bookConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="bookInvoice"
translate>
Book invoice
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.hasInvoicing || !$ctrl.invoiceOut.hasPdf"
name="regenerateInvoice"
translate>
{{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
<vn-item
ng-click="$ctrl.showExportationLetter()"
ng-show="$ctrl.invoiceOut.serial == 'E'"
translate>
Show CIES letter
</vn-item>
</vn-list>
</vn-menu>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteInvoiceOut()"
question="Are you sure you want to delete this invoice?">
</vn-confirm>
<vn-confirm
vn-id="bookConfirmation"
on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?">
</vn-confirm>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createPdfInvoice()"
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send PDF invoice confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfInvoice($data)"
message="Send PDF invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
label="Email"
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV invoice confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvInvoice($data)"
message="Send CSV invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
label="Email"
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,121 @@
import ngModule from '../module';
import Section from 'salix/components/section';
import './style.scss';
class Controller extends Section {
constructor($element, $, vnReport, vnEmail) {
super($element, $);
this.vnReport = vnReport;
this.vnEmail = vnEmail;
}
get invoiceOut() {
return this._invoiceOut;
}
set invoiceOut(value) {
this._invoiceOut = value;
if (value)
this.id = value.id;
}
loadData() {
const filter = {
include: [
{
relation: 'company',
scope: {
fields: ['id', 'code']
}
}, {
relation: 'client',
scope: {
fields: ['id', 'name', 'email']
}
}
]
};
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}`, {filter})
.then(res => this.invoiceOut = res.data);
}
reload() {
return this.loadData().then(() => {
if (this.parentReload)
this.parentReload();
});
}
cardReload() {
// Prevents error when not defined
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createPdfInvoice() {
return this.$http.post(`InvoiceOuts/${this.id}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id
});
}
sendPdfInvoice($data) {
if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
sendCsvInvoice($data) {
if (!$data.email)
return this.vnApp.showError(this.$t(`The email can't be empty`));
return this.vnEmail.sendCsv('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
showExportationLetter() {
this.vnReport.show('exportation', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id
});
}
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
ngModule.vnComponent('vnInvoiceOutDescriptorMenu', {
template: require('./index.html'),
controller: Controller,
bindings: {
invoiceOut: '<',
parentReload: '&'
}
});

View File

@ -0,0 +1,96 @@
import './index';
describe('vnInvoiceOutDescriptorMenu', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;
const invoiceOut = {
id: 1,
client: {id: 1101}
};
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnInvoiceOutDescriptorMenu', {$element: null});
}));
describe('createPdfInvoice()', () => {
it('should make a query to the createPdf() endpoint and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('showCsvInvoice()', () => {
it('should make a query to the csv invoice download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
controller.invoiceOut = invoiceOut;
const expectedParams = {
invoiceId: invoiceOut.id,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond();
controller.sendPdfInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('sendCsvInvoice()', () => {
it('should make a query to the csv invoice send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond();
controller.sendCsvInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
});

View File

@ -0,0 +1,17 @@
Show invoice...: Ver factura...
Send invoice...: Enviar factura...
Send PDF invoice: Enviar factura en PDF
Send CSV invoice: Enviar factura en CSV
Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES
InvoiceOut deleted: Factura eliminada
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
The email can't be empty: El correo no puede estar vacío

View File

@ -0,0 +1,24 @@
@import "./effects";
@import "variables";
vn-invoice-out-descriptor-menu {
& > vn-icon-button[icon="more_vert"] {
display: flex;
min-width: 45px;
height: 45px;
box-sizing: border-box;
align-items: center;
justify-content: center;
}
& > vn-icon-button[icon="more_vert"] {
@extend %clickable;
color: inherit;
& > vn-icon {
padding: 10px;
}
vn-icon {
font-size: 1.75rem;
}
}
}

View File

@ -1,81 +1,12 @@
<vn-descriptor-content
module="invoiceOut"
description="$ctrl.invoiceOut.ref">
<slot-menu>
<vn-item class="dropdown"
vn-click-stop="showInvoiceMenu.show($event, 'left')"
name="showInvoicePdf"
translate>
Show invoice...
<vn-menu vn-id="showInvoiceMenu">
<vn-list>
<a class="vn-item"
href="api/InvoiceOuts/{{$ctrl.id}}/download?access_token={{$ctrl.vnToken.token}}"
target="_blank"
name="showInvoicePdf"
translate>
Show as PDF
</a>
<vn-item
ng-click="$ctrl.showCsvInvoice()"
translate>
Show as CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item class="dropdown"
vn-click-stop="sendInvoiceMenu.show($event, 'left')"
name="sendInvoice"
translate>
Send invoice...
<vn-menu vn-id="sendInvoiceMenu">
<vn-list>
<vn-item
ng-click="sendPdfConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send PDF
</vn-item>
<vn-item
ng-click="sendCsvConfirmation.show({email: $ctrl.invoiceOut.client.email})"
translate>
Send CSV
</vn-item>
</vn-list>
</vn-menu>
</vn-item>
<vn-item
ng-click="deleteConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="deleteInvoice"
translate>
Delete Invoice
</vn-item>
<vn-item
ng-click="bookConfirmation.show()"
vn-acl="invoicing"
vn-acl-action="remove"
name="bookInvoice"
translate>
Book invoice
</vn-item>
<vn-item
ng-click="createInvoicePdfConfirmation.show()"
ng-show="$ctrl.hasInvoicing || !$ctrl.invoiceOut.hasPdf"
name="regenerateInvoice"
translate>
{{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}}
</vn-item>
<vn-item
ng-click="$ctrl.showExportationLetter()"
ng-show="$ctrl.invoiceOut.serial == 'E'"
translate>
Show CIES letter
</vn-item>
</slot-menu>
<slot-dot-menu>
<vn-invoice-out-descriptor-menu
invoice-out="$ctrl.invoiceOut"
parent-reload="$ctrl.reload()"
/>
</slot-dot-menu>
<slot-body>
<div class="attributes">
<vn-label-value
@ -118,59 +49,4 @@
</div>
</div>
</slot-body>
</vn-descriptor-content>
<vn-confirm
vn-id="deleteConfirmation"
on-accept="$ctrl.deleteInvoiceOut()"
question="Are you sure you want to delete this invoice?">
</vn-confirm>
<vn-confirm
vn-id="bookConfirmation"
on-accept="$ctrl.bookInvoiceOut()"
question="Are you sure you want to book this invoice?">
</vn-confirm>
<vn-client-descriptor-popover
vn-id="clientDescriptor">
</vn-client-descriptor-popover>
<!-- Create invoice PDF confirmation dialog -->
<vn-confirm
vn-id="createInvoicePdfConfirmation"
on-accept="$ctrl.createPdfInvoice()"
question="Are you sure you want to generate/regenerate the PDF invoice?"
message="Generate PDF invoice document">
</vn-confirm>
<!-- Send PDF invoice confirmation popup -->
<vn-dialog
vn-id="sendPdfConfirmation"
on-accept="$ctrl.sendPdfInvoice($data)"
message="Send PDF invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendPdfConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
<!-- Send CSV invoice confirmation popup -->
<vn-dialog
vn-id="sendCsvConfirmation"
on-accept="$ctrl.sendCsvInvoice($data)"
message="Send CSV invoice">
<tpl-body>
<span translate>Are you sure you want to send it?</span>
<vn-textfield vn-one
ng-model="sendCsvConfirmation.data.email">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
<button response="accept" translate>Confirm</button>
</tpl-buttons>
</vn-dialog>
</vn-descriptor-content>

View File

@ -41,70 +41,6 @@ class Controller extends Descriptor {
return this.getData(`InvoiceOuts/${this.id}`, {filter})
.then(res => this.entity = res.data);
}
reload() {
return this.loadData().then(() => {
if (this.cardReload)
this.cardReload();
});
}
cardReload() {
// Prevents error when not defined
}
deleteInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.id}/delete`)
.then(() => this.$state.go('invoiceOut.index'))
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut deleted')));
}
bookInvoiceOut() {
return this.$http.post(`InvoiceOuts/${this.invoiceOut.ref}/book`)
.then(() => this.$state.reload())
.then(() => this.vnApp.showSuccess(this.$t('InvoiceOut booked')));
}
createPdfInvoice() {
const invoiceId = this.invoiceOut.id;
return this.$http.post(`InvoiceOuts/${invoiceId}/createPdf`)
.then(() => this.reload())
.then(() => {
const snackbarMessage = this.$t(
`The invoice PDF document has been regenerated`);
this.vnApp.showSuccess(snackbarMessage);
});
}
showCsvInvoice() {
this.vnReport.showCsv('invoice', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id,
});
}
sendPdfInvoice($data) {
return this.vnEmail.send('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
sendCsvInvoice($data) {
return this.vnEmail.sendCsv('invoice', {
recipientId: this.invoiceOut.client.id,
recipient: $data.email,
invoiceId: this.id
});
}
showExportationLetter() {
this.vnReport.show('exportation', {
recipientId: this.invoiceOut.client.id,
invoiceId: this.id,
});
}
}
ngModule.vnComponent('vnInvoiceOutDescriptor', {
@ -112,6 +48,5 @@ ngModule.vnComponent('vnInvoiceOutDescriptor', {
controller: Controller,
bindings: {
invoiceOut: '<',
cardReload: '&'
}
});

View File

@ -3,17 +3,11 @@ import './index';
describe('vnInvoiceOutDescriptor', () => {
let controller;
let $httpBackend;
let $httpParamSerializer;
const invoiceOut = {
id: 1,
client: {id: 1101}
};
beforeEach(ngModule('invoiceOut'));
beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => {
beforeEach(inject(($componentController, _$httpBackend_) => {
$httpBackend = _$httpBackend_;
$httpParamSerializer = _$httpParamSerializer_;
controller = $componentController('vnInvoiceOutDescriptor', {$element: null});
}));
@ -29,81 +23,4 @@ describe('vnInvoiceOutDescriptor', () => {
expect(controller.invoiceOut).toEqual(response);
});
});
describe('createPdfInvoice()', () => {
it('should make a query to the createPdf() endpoint and show a success snackbar', () => {
jest.spyOn(controller.vnApp, 'showSuccess');
controller.invoiceOut = invoiceOut;
$httpBackend.whenGET(`InvoiceOuts/${invoiceOut.id}`).respond();
$httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/createPdf`).respond();
controller.createPdfInvoice();
$httpBackend.flush();
expect(controller.vnApp.showSuccess).toHaveBeenCalled();
});
});
describe('showCsvInvoice()', () => {
it('should make a query to the csv invoice download endpoint and show a message snackbar', () => {
jest.spyOn(window, 'open').mockReturnThis();
controller.invoiceOut = invoiceOut;
const expectedParams = {
invoiceId: invoiceOut.id,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
const expectedPath = `api/csv/invoice/download?${serializedParams}`;
controller.showCsvInvoice();
expect(window.open).toHaveBeenCalledWith(expectedPath);
});
});
describe('sendPdfInvoice()', () => {
it('should make a query to the email invoice endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`email/invoice?${serializedParams}`).respond();
controller.sendPdfInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
describe('sendCsvInvoice()', () => {
it('should make a query to the csv invoice send endpoint and show a message snackbar', () => {
jest.spyOn(controller.vnApp, 'showMessage');
controller.invoiceOut = invoiceOut;
const $data = {email: 'brucebanner@gothamcity.com'};
const expectedParams = {
invoiceId: invoiceOut.id,
recipient: $data.email,
recipientId: invoiceOut.client.id
};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expectGET(`csv/invoice/send?${serializedParams}`).respond();
controller.sendCsvInvoice($data);
$httpBackend.flush();
expect(controller.vnApp.showMessage).toHaveBeenCalled();
});
});
});

View File

@ -1,20 +1,2 @@
Volume exceded: Volumen excedido
Volume: Volumen
Client card: Ficha del cliente
Invoice ticket list: Listado de tickets de la factura
Show invoice...: Ver factura...
Send invoice...: Enviar factura...
Send PDF invoice: Enviar factura en PDF
Send CSV invoice: Enviar factura en CSV
Delete Invoice: Eliminar factura
Clone Invoice: Clonar factura
Book invoice: Asentar factura
Generate PDF invoice: Generar PDF factura
Show CIES letter: Ver carta CIES
InvoiceOut deleted: Factura eliminada
Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura?
Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura?
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
Invoice ticket list: Listado de tickets de la factura

View File

@ -7,5 +7,6 @@ import './summary';
import './card';
import './descriptor';
import './descriptor-popover';
import './descriptor-menu';
import './index/manual';
import './index/global-invoicing';

View File

@ -13,6 +13,10 @@
<vn-icon-button icon="launch"></vn-icon-button>
</a>
<span>{{$ctrl.summary.invoiceOut.ref}} - {{$ctrl.summary.invoiceOut.client.socialName}}</span>
<vn-invoice-out-descriptor-menu
invoice-out="$ctrl.summary.invoiceOut"
parent-reload="$ctrl.reload()"
/>
</h5>
<vn-horizontal>
<vn-one>

View File

@ -50,7 +50,7 @@
on-last="$ctrl.scrollToLine(sale.lastPreparedLineFk)"
ng-attr-id="vnItemDiary-{{::sale.lineFk}}">
<vn-td shrink>
<a ui-sref="claim.card.basicData({id: sale.claimFk})">
<a ui-sref="claim.card.summary({id: sale.claimFk})">
<vn-icon icon="icon-claims"
ng-show="sale.claimFk"
vn-tooltip="{{::$ctrl.$t('Claim')}}: {{::sale.claimFk}}">

View File

@ -1,12 +1,13 @@
const app = require('vn-loopback/server/server');
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('loopback model Supplier', () => {
let supplierOne;
let supplierTwo;
beforeAll(async() => {
supplierOne = await app.models.Supplier.findById(1);
supplierTwo = await app.models.Supplier.findById(442);
supplierOne = await models.Supplier.findById(1);
supplierTwo = await models.Supplier.findById(442);
});
afterAll(async() => {
@ -18,9 +19,9 @@ describe('loopback model Supplier', () => {
it('should throw an error when attempting to set an invalid payMethod id in the supplier', async() => {
let error;
const expectedError = 'You can not select this payment method without a registered bankery account';
const supplier = await app.models.Supplier.findById(1);
const supplier = await models.Supplier.findById(1);
await supplier.updateAttribute('payMethodFk', 4)
await supplier.updateAttribute('payMethodFk', 8)
.catch(e => {
error = e;
@ -31,14 +32,27 @@ describe('loopback model Supplier', () => {
});
it('should not throw if the payMethod id is valid', async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
let error;
const supplier = await app.models.Supplier.findById(442);
const supplier = await models.Supplier.findById(442);
await supplier.updateAttribute('payMethodFk', 4)
.catch(e => {
error = e;
});
expect(error).toBeDefined();
expect(error).not.toBeDefined();
});
});
});

View File

@ -9,40 +9,40 @@
"properties": {
"id": {
"id": true,
"type": "Number",
"type": "number",
"forceId": false
},
"originFk": {
"type": "Number",
"type": "number",
"required": true
},
"userFk": {
"type": "Number"
"type": "number"
},
"action": {
"type": "String",
"type": "string",
"required": true
},
"changedModel": {
"type": "String"
"type": "string"
},
"oldInstance": {
"type": "Object"
"type": "object"
},
"newInstance": {
"type": "Object"
"type": "object"
},
"creationDate": {
"type": "Date"
"type": "date"
},
"changedModelId": {
"type": "String"
"type": "string"
},
"changedModelValue": {
"type": "String"
"type": "string"
},
"description": {
"type": "String"
"type": "string"
}
},
"relations": {

View File

@ -80,7 +80,7 @@ module.exports = Self => {
const supplierAccount = await Self.app.models.SupplierAccount.findOne({where: {supplierFk: this.id}});
const hasIban = supplierAccount && supplierAccount.iban;
if (payMethod && payMethod.ibanRequired && !hasIban)
if (payMethod && payMethod.ibanRequiredForSuppliers && !hasIban)
err();
done();

View File

@ -24,7 +24,7 @@
vn-acl="salesAssistant"
ng-model="$ctrl.supplier.payMethodFk"
data="paymethods"
fields="['ibanRequired']"
fields="['ibanRequiredForSuppliers']"
initial-data="$ctrl.supplier.payMethod">
</vn-autocomplete>
<vn-autocomplete

View File

@ -24,6 +24,8 @@ module.exports = Self => {
});
Self.deny = async(ctx, options) => {
const models = Self.app.models;
const $t = ctx.req.__; // $translate
const myOptions = {};
let tx;
@ -48,6 +50,17 @@ module.exports = Self => {
const request = await Self.app.models.TicketRequest.findById(ctx.args.id, null, myOptions);
await request.updateAttributes(params, myOptions);
const origin = ctx.req.headers.origin;
const requesterId = request.requesterFk;
const message = $t('Deny buy request', {
ticketId: request.ticketFk,
url: `${origin}/#!/ticket/${request.ticketFk}/request/index`,
observation: params.response
});
await models.Chat.sendCheckingPresence(ctx, requesterId, message, myOptions);
if (tx) await tx.commit();
return request;

View File

@ -1,13 +1,23 @@
const models = require('vn-loopback/server/server').models;
describe('ticket-request deny()', () => {
it('should return the dinied ticket request', async() => {
it('should return the denied ticket request', async() => {
const tx = await models.TicketRequest.beginTransaction({});
try {
const options = {transaction: tx};
const ctx = {req: {accessToken: {userId: 9}}, args: {id: 4, observation: 'my observation'}};
const ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost'}
},
args: {id: 4, observation: 'my observation'},
};
ctx.req.__ = value => {
return value;
};
const result = await models.TicketRequest.deny(ctx, options);

View File

@ -25,7 +25,7 @@
<vn-th number>Quantity</vn-th>
<vn-th number>Price</vn-th>
<vn-th number>Item id</vn-th>
<vn-th number>Ok</vn-th>
<vn-th number>State</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
@ -78,13 +78,9 @@
{{::request.saleFk | zeroFill:6}}
</span>
</vn-td>
<vn-td number>
<vn-check vn-one
ng-model="::request.isOk"
triple-state="true"
title="{{$ctrl.getRequestState(request.isOk)}}"
disabled="true">
</vn-check>
<vn-td number
translate>
{{$ctrl.getRequestState(request.isOk)}}
</vn-td>
<vn-td number>
<vn-icon-button
@ -93,7 +89,7 @@
ng-click="$ctrl.removeLine($index)"
vn-tooltip="Remove request"
tabindex="-1">
</vn-icon-button>
</vn-icon-button>
</vn-td>
</vn-tr>
</vn-tbody>

View File

@ -4,4 +4,7 @@ Remove request: Eliminar petición
New request: Crear petición
Sale id: Id linea
Requester: Solicitante
New purchase request: Nueva petición de compra
New purchase request: Nueva petición de compra
Denied: Denegada
Acepted: Aceptada
New: Nueva