feat: add subsection Global invoicing
This commit is contained in:
parent
7731ca143e
commit
e324716f2f
|
@ -68,63 +68,57 @@ module.exports = Self => {
|
|||
const client = await models.Client.findById(args.clientId, {
|
||||
fields: ['id', 'hasToInvoiceByAddress']
|
||||
}, myOptions);
|
||||
try {
|
||||
if (client.hasToInvoiceByAddress) {
|
||||
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
||||
args.minShipped,
|
||||
args.maxShipped,
|
||||
args.addressId,
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
} else {
|
||||
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
||||
args.maxShipped,
|
||||
client.id,
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
}
|
||||
|
||||
// Make invoice
|
||||
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
||||
|
||||
// Validates ticket nagative base
|
||||
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
||||
if (hasAnyNegativeBase && isSpanishCompany)
|
||||
return tx.rollback();
|
||||
|
||||
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
||||
const [invoiceSerial] = await Self.rawSql(query, [
|
||||
if (client.hasToInvoiceByAddress) {
|
||||
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
||||
args.minShipped,
|
||||
args.maxShipped,
|
||||
args.addressId,
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
} else {
|
||||
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
||||
args.maxShipped,
|
||||
client.id,
|
||||
args.companyFk,
|
||||
'G'
|
||||
args.companyFk
|
||||
], myOptions);
|
||||
const serialLetter = invoiceSerial.serial;
|
||||
|
||||
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
||||
await Self.rawSql(query, [
|
||||
serialLetter,
|
||||
args.invoiceDate
|
||||
], myOptions);
|
||||
|
||||
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
||||
if (newInvoice.id) {
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
||||
|
||||
invoiceId = newInvoice.id;
|
||||
}
|
||||
} catch (e) {
|
||||
const failedClient = {
|
||||
id: client.id,
|
||||
stacktrace: e
|
||||
};
|
||||
await notifyFailures(ctx, failedClient, myOptions);
|
||||
}
|
||||
|
||||
invoiceOut = await models.InvoiceOut.findById(invoiceId, {
|
||||
include: {
|
||||
relation: 'client'
|
||||
}
|
||||
}, myOptions);
|
||||
// Make invoice
|
||||
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
||||
|
||||
// Validates ticket nagative base
|
||||
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
||||
if (hasAnyNegativeBase && isSpanishCompany)
|
||||
return tx.rollback();
|
||||
|
||||
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
||||
const [invoiceSerial] = await Self.rawSql(query, [
|
||||
client.id,
|
||||
args.companyFk,
|
||||
'G'
|
||||
], myOptions);
|
||||
const serialLetter = invoiceSerial.serial;
|
||||
|
||||
query = `CALL invoiceOut_new(?, ?, NULL, @invoiceId)`;
|
||||
await Self.rawSql(query, [
|
||||
serialLetter,
|
||||
args.invoiceDate
|
||||
], myOptions);
|
||||
if (client.id == 1102)
|
||||
throw new Error('Error1');
|
||||
const [newInvoice] = await Self.rawSql(`SELECT @invoiceId id`, null, myOptions);
|
||||
if (newInvoice.id) {
|
||||
await Self.rawSql('CALL invoiceOutBooking(?)', [newInvoice.id], myOptions);
|
||||
|
||||
invoiceOut = await models.InvoiceOut.findById(newInvoice.id, {
|
||||
include: {
|
||||
relation: 'client'
|
||||
}
|
||||
}, myOptions);
|
||||
|
||||
invoiceId = newInvoice.id;
|
||||
}
|
||||
|
||||
if (tx) await tx.commit();
|
||||
} catch (e) {
|
||||
|
@ -132,15 +126,14 @@ module.exports = Self => {
|
|||
throw e;
|
||||
}
|
||||
|
||||
ctx.args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: invoiceOut.clientFk,
|
||||
recipient: invoiceOut.client().email
|
||||
};
|
||||
try {
|
||||
if (invoiceId) {
|
||||
ctx.args = {
|
||||
reference: invoiceOut.ref,
|
||||
recipientId: invoiceOut.clientFk,
|
||||
recipient: invoiceOut.client().email
|
||||
};
|
||||
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
||||
} catch (err) {}
|
||||
|
||||
}
|
||||
return invoiceId;
|
||||
};
|
||||
|
||||
|
@ -165,26 +158,4 @@ module.exports = Self => {
|
|||
|
||||
return supplierCompany && supplierCompany.total;
|
||||
}
|
||||
|
||||
async function notifyFailures(ctx, failedClient, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
const $t = ctx.req.__; // $translate
|
||||
|
||||
const worker = await models.EmailUser.findById(userId, null, options);
|
||||
const subject = $t('Global invoicing failed');
|
||||
let body = $t(`Wasn't able to invoice the following clients`) + ':<br/><br/>';
|
||||
|
||||
body += `ID: <strong>${failedClient.id}</strong>
|
||||
<br/> <strong>${failedClient.stacktrace}</strong><br/><br/>`;
|
||||
|
||||
await Self.rawSql(`
|
||||
INSERT INTO vn.mail (sender, replyTo, sent, subject, body)
|
||||
VALUES (?, ?, FALSE, ?, ?)`, [
|
||||
worker.email,
|
||||
worker.email,
|
||||
subject,
|
||||
body
|
||||
], options);
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
<div class="vn-w-md">
|
||||
<vn-data-viewer
|
||||
data="data"
|
||||
class="vn-w-md vn-mb-xl">
|
||||
<vn-card>
|
||||
<vn-table>
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th field="id">Id</vn-th>
|
||||
<vn-th field="name">Status</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr
|
||||
ng-repeat="client in data track by client.id">
|
||||
<vn-td>
|
||||
<span
|
||||
vn-click-stop="clientDescriptor.show($event, client.id)"
|
||||
class="link">
|
||||
{{::client.id}}
|
||||
</span>
|
||||
</vn-td>
|
||||
<vn-td>
|
||||
<vn-spinner
|
||||
ng-if="client.status == 'waiting'"
|
||||
enable="true">
|
||||
</vn-spinner>
|
||||
<vn-icon
|
||||
ng-if="client.status == 'ok'"
|
||||
icon="check">
|
||||
</vn-icon>
|
||||
<vn-icon
|
||||
class="error"
|
||||
ng-if="client.status == 'error'"
|
||||
icon="error">
|
||||
</vn-icon>
|
||||
</vn-td>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-card>
|
||||
</vn-data-viewer>
|
||||
</div>
|
||||
|
||||
<vn-side-menu side="right">
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="InvoiceOutSerials"
|
||||
data="invoiceOutSerials"
|
||||
order="code">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Companies"
|
||||
data="companies"
|
||||
order="code">
|
||||
</vn-crud-model>
|
||||
<form class="vn-pa-md">
|
||||
<vn-vertical>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Invoice date"
|
||||
ng-model="$ctrl.invoice.invoiceDate">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Max date"
|
||||
ng-model="$ctrl.invoice.maxShipped">
|
||||
</vn-date-picker>
|
||||
</vn-vertical>
|
||||
<vn-horizontal>
|
||||
<vn-radio
|
||||
label="All clients"
|
||||
val="allClients"
|
||||
ng-model="$ctrl.clientsNumber"
|
||||
ng-click="$ctrl.$onInit()">
|
||||
</vn-radio>
|
||||
<vn-radio
|
||||
label="Clients range"
|
||||
val="clientsRange"
|
||||
ng-model="$ctrl.clientsNumber">
|
||||
</vn-radio>
|
||||
</vn-horizontal>
|
||||
<vn-vertical ng-show="$ctrl.clientsNumber == 'clientsRange'">
|
||||
<vn-autocomplete
|
||||
url="Clients"
|
||||
label="From client"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
||||
order="id"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.fromClientId">
|
||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
url="Clients"
|
||||
label="To client"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
||||
order="id"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.toClientId">
|
||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-vertical>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
url="Companies"
|
||||
label="Company"
|
||||
show-field="code"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.companyFk">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-submit vn-id="invoiceButton" ng-click="$ctrl.makeInvoice()" label="Invoice" class="vn-mt-sm" ></vn-submit>
|
||||
<vn-button ng-click="$ctrl.clean()" label="Clean" class="vn-mt-sm"></vn-button>
|
||||
</form>
|
||||
</vn-side-menu>
|
||||
|
||||
<vn-client-descriptor-popover
|
||||
vn-id="clientDescriptor">
|
||||
</vn-client-descriptor-popover>
|
|
@ -1,12 +1,14 @@
|
|||
import ngModule from '../../module';
|
||||
import Dialog from 'core/components/dialog';
|
||||
import ngModule from '../module';
|
||||
import Section from 'salix/components/section';
|
||||
import UserError from 'core/lib/user-error';
|
||||
import './style.scss';
|
||||
|
||||
class Controller extends Dialog {
|
||||
class Controller extends Section {
|
||||
constructor($element, $, $transclude) {
|
||||
super($element, $, $transclude);
|
||||
this.invoice = {
|
||||
maxShipped: new Date()
|
||||
maxShipped: new Date(),
|
||||
companyFk: this.vnConfig.companyFk
|
||||
};
|
||||
this.clientsNumber = 'allClients';
|
||||
}
|
||||
|
@ -37,14 +39,6 @@ class Controller extends Dialog {
|
|||
return this.$http.get('Clients/findOne', {params});
|
||||
}
|
||||
|
||||
get companyFk() {
|
||||
return this.invoice.companyFk;
|
||||
}
|
||||
|
||||
set companyFk(value) {
|
||||
this.invoice.companyFk = value;
|
||||
}
|
||||
|
||||
restartValues() {
|
||||
this.lastClientId = null;
|
||||
this.$.invoiceButton.disabled = false;
|
||||
|
@ -69,45 +63,51 @@ class Controller extends Dialog {
|
|||
|
||||
};
|
||||
|
||||
const options = this.cancelRequest();
|
||||
|
||||
return this.$http.post(`InvoiceOuts/invoiceClient`, params, options)
|
||||
const index = this.$.data.findIndex(element => element.id == clientAndAddress.clientId);
|
||||
return this.$http.post(`InvoiceOuts/invoiceClient`, params)
|
||||
.then(() => {
|
||||
this.$.data[index].status = 'ok';
|
||||
}).catch(() => {
|
||||
this.$.data[index].status = 'error';
|
||||
}).finally(() => {
|
||||
clientsAndAddresses.shift();
|
||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
||||
});
|
||||
}
|
||||
|
||||
responseHandler(response) {
|
||||
makeInvoice() {
|
||||
try {
|
||||
if (response !== 'accept')
|
||||
return super.responseHandler(response);
|
||||
|
||||
if (!this.invoice.invoiceDate || !this.invoice.maxShipped)
|
||||
throw new Error('Invoice date and the max date should be filled');
|
||||
|
||||
if (!this.invoice.fromClientId || !this.invoice.toClientId)
|
||||
throw new Error('Choose a valid clients range');
|
||||
|
||||
this.on('close', () => {
|
||||
if (this.canceler) this.canceler.resolve();
|
||||
this.vnApp.showSuccess(this.$t('Data saved!'));
|
||||
});
|
||||
|
||||
this.$.invoiceButton.disabled = true;
|
||||
this.packageInvoicing = true;
|
||||
const options = this.cancelRequest();
|
||||
|
||||
this.$http.post(`InvoiceOuts/clientsToInvoice`, this.invoice, options)
|
||||
this.$http.post(`InvoiceOuts/clientsToInvoice`, this.invoice)
|
||||
.then(res => {
|
||||
this.packageInvoicing = false;
|
||||
const invoice = res.data.invoice;
|
||||
|
||||
const clientsIds = [];
|
||||
for (const clientAndAddress of res.data.clientsAndAddresses)
|
||||
clientsIds.push(clientAndAddress.clientId);
|
||||
const dataArr = new Set(clientsIds);
|
||||
const clientsIdsNoRepeat = [...dataArr];
|
||||
const clients = clientsIdsNoRepeat.map(clientId => {
|
||||
return {
|
||||
id: clientId,
|
||||
status: 'waiting'
|
||||
};
|
||||
});
|
||||
this.$.data = clients;
|
||||
|
||||
const clientsAndAddresses = res.data.clientsAndAddresses;
|
||||
if (!clientsAndAddresses.length) return super.responseHandler(response);
|
||||
this.lastClientId = clientsAndAddresses[clientsAndAddresses.length - 1].clientId;
|
||||
if (!clientsAndAddresses.length) throw new UserError(`There aren't clients to invoice`);
|
||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
||||
})
|
||||
.then(() => super.responseHandler(response))
|
||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
||||
.finally(() => this.restartValues());
|
||||
} catch (e) {
|
||||
|
@ -116,14 +116,15 @@ class Controller extends Dialog {
|
|||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
clean() {
|
||||
this.$.data = this.$.data.filter(client => client.status == 'error');
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', '$transclude'];
|
||||
|
||||
ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', {
|
||||
slotTemplate: require('./index.html'),
|
||||
controller: Controller,
|
||||
bindings: {
|
||||
companyFk: '<?'
|
||||
}
|
||||
template: require('./index.html'),
|
||||
controller: Controller
|
||||
});
|
|
@ -0,0 +1,126 @@
|
|||
import './index.js';
|
||||
import popover from 'core/mocks/popover';
|
||||
import crudModel from 'core/mocks/crud-model';
|
||||
|
||||
describe('Zone Component vnZoneDeliveryDays', () => {
|
||||
let $httpBackend;
|
||||
let controller;
|
||||
let $element;
|
||||
|
||||
beforeEach(ngModule('zone'));
|
||||
|
||||
beforeEach(inject(($componentController, _$httpBackend_) => {
|
||||
$httpBackend = _$httpBackend_;
|
||||
$element = angular.element('<vn-zone-delivery-days></vn-zone-delivery-days');
|
||||
controller = $componentController('vnZoneDeliveryDays', {$element});
|
||||
controller.$.zoneEvents = popover;
|
||||
controller.$.params = {};
|
||||
controller.$.zoneModel = crudModel;
|
||||
}));
|
||||
|
||||
describe('deliveryMethodFk() setter', () => {
|
||||
it('should set the deliveryMethodFk property as pickup and then perform a query that sets the filter', () => {
|
||||
$httpBackend.expect('GET', 'DeliveryMethods').respond([{id: 999}]);
|
||||
controller.deliveryMethodFk = 'pickUp';
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.agencyFilter).toEqual({deliveryMethodFk: {inq: [999]}});
|
||||
});
|
||||
});
|
||||
|
||||
describe('setParams()', () => {
|
||||
it('should do nothing when no params are received', () => {
|
||||
controller.setParams();
|
||||
|
||||
expect(controller.deliveryMethodFk).toBeUndefined();
|
||||
expect(controller.geoFk).toBeUndefined();
|
||||
expect(controller.agencyModeFk).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should set the controller properties when the params are provided', () => {
|
||||
controller.$params = {
|
||||
deliveryMethodFk: 3,
|
||||
geoFk: 2,
|
||||
agencyModeFk: 1
|
||||
};
|
||||
controller.setParams();
|
||||
|
||||
expect(controller.deliveryMethodFk).toEqual(controller.$params.deliveryMethodFk);
|
||||
expect(controller.geoFk).toEqual(controller.$params.geoFk);
|
||||
expect(controller.agencyModeFk).toEqual(controller.$params.agencyModeFk);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchData()', () => {
|
||||
it('should make an HTTP GET query and then call the showMessage() method', () => {
|
||||
jest.spyOn(controller.vnApp, 'showMessage');
|
||||
jest.spyOn(controller.$state, 'go');
|
||||
|
||||
controller.agencyModeFk = 1;
|
||||
controller.deliveryMethodFk = 2;
|
||||
controller.geoFk = 3;
|
||||
controller.$state.current.name = 'myState';
|
||||
|
||||
const expectedData = {events: []};
|
||||
|
||||
const url = 'Zones/getEvents?agencyModeFk=1&deliveryMethodFk=2&geoFk=3';
|
||||
|
||||
$httpBackend.when('GET', 'DeliveryMethods').respond([]);
|
||||
$httpBackend.expect('GET', url).respond({events: []});
|
||||
controller.fetchData();
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$.data).toEqual(expectedData);
|
||||
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('No service for the specified zone');
|
||||
expect(controller.$state.go).toHaveBeenCalledWith(
|
||||
controller.$state.current.name,
|
||||
{
|
||||
agencyModeFk: 1,
|
||||
deliveryMethodFk: 2,
|
||||
geoFk: 3
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onSelection()', () => {
|
||||
it('should not call the show popover method if events array is empty', () => {
|
||||
jest.spyOn(controller.$.zoneEvents, 'show');
|
||||
|
||||
const event = new Event('click');
|
||||
const target = document.createElement('div');
|
||||
target.dispatchEvent(event);
|
||||
const events = [];
|
||||
controller.onSelection(event, events);
|
||||
|
||||
expect(controller.$.zoneEvents.show).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should call the show() method and call getZoneClosing() with the expected ids', () => {
|
||||
jest.spyOn(controller.$.zoneEvents, 'show');
|
||||
|
||||
const event = new Event('click');
|
||||
const target = document.createElement('div');
|
||||
target.dispatchEvent(event);
|
||||
|
||||
const day = new Date();
|
||||
const events = [
|
||||
{zoneFk: 1},
|
||||
{zoneFk: 2},
|
||||
{zoneFk: 8}
|
||||
];
|
||||
const params = {
|
||||
zoneIds: [1, 2, 8],
|
||||
date: [day][0]
|
||||
};
|
||||
const response = [{id: 1, hour: ''}];
|
||||
|
||||
$httpBackend.when('POST', 'Zones/getZoneClosing', params).respond({response});
|
||||
controller.onSelection(event, events, [day]);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.$.zoneEvents.show).toHaveBeenCalledWith(target);
|
||||
expect(controller.zoneClosing.id).toEqual(response.id);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
There aren't clients to invoice: No existen clientes para facturar
|
||||
Max date: Fecha límite
|
||||
Invoice date: Fecha de factura
|
||||
Invoice date and the max date should be filled: La fecha de factura y la fecha límite deben rellenarse
|
||||
Choose a valid clients range: Selecciona un rango válido de clientes
|
||||
Clients range: Rango de clientes
|
||||
Calculating packages to invoice...: Calculando paquetes a factura...
|
|
@ -0,0 +1,5 @@
|
|||
@import "variables";
|
||||
|
||||
.error {
|
||||
color: $color-alert;
|
||||
}
|
|
@ -9,4 +9,4 @@ import './descriptor';
|
|||
import './descriptor-popover';
|
||||
import './descriptor-menu';
|
||||
import './index/manual';
|
||||
import './index/global-invoicing';
|
||||
import './global-invoicing';
|
||||
|
|
|
@ -1,96 +0,0 @@
|
|||
<tpl-title translate>
|
||||
Create global invoice
|
||||
</tpl-title>
|
||||
<tpl-body id="manifold-form">
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="InvoiceOutSerials"
|
||||
data="invoiceOutSerials"
|
||||
order="code">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="Companies"
|
||||
data="companies"
|
||||
order="code">
|
||||
</vn-crud-model>
|
||||
<div
|
||||
class="progress vn-my-md"
|
||||
ng-if="$ctrl.packageInvoicing">
|
||||
<vn-horizontal>
|
||||
<div>
|
||||
{{'Calculating packages to invoice...' | translate}}
|
||||
</div>
|
||||
</vn-horizontal>
|
||||
</div>
|
||||
<div
|
||||
class="progress vn-my-md"
|
||||
ng-if="$ctrl.lastClientId">
|
||||
<vn-horizontal>
|
||||
<div>
|
||||
{{'Id Client' | translate}}: {{$ctrl.currentClientId}}
|
||||
{{'of' | translate}} {{::$ctrl.lastClientId}}
|
||||
</div>
|
||||
</vn-horizontal>
|
||||
</div>
|
||||
<vn-horizontal>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Invoice date"
|
||||
ng-model="$ctrl.invoice.invoiceDate">
|
||||
</vn-date-picker>
|
||||
<vn-date-picker
|
||||
vn-one
|
||||
label="Max date"
|
||||
ng-model="$ctrl.invoice.maxShipped">
|
||||
</vn-date-picker>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-radio
|
||||
label="All clients"
|
||||
val="allClients"
|
||||
ng-model="$ctrl.clientsNumber"
|
||||
ng-click="$ctrl.$onInit()">
|
||||
</vn-radio>
|
||||
<vn-radio
|
||||
label="Clients range"
|
||||
val="clientsRange"
|
||||
ng-model="$ctrl.clientsNumber">
|
||||
</vn-radio>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal ng-show="$ctrl.clientsNumber == 'clientsRange'">
|
||||
<vn-autocomplete
|
||||
url="Clients"
|
||||
label="From client"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
||||
order="id"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.fromClientId">
|
||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
url="Clients"
|
||||
label="To client"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+$search+'%'}}]}"
|
||||
order="id"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.toClientId">
|
||||
<tpl-item>{{::id}} - {{::name}}</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
url="Companies"
|
||||
label="Company"
|
||||
show-field="code"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.invoice.companyFk">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<input type="button" response="cancel" translate-attr="{value: 'Cancel'}"/>
|
||||
<button vn-id="invoiceButton" response="accept" translate>Invoice</button>{{$ctrl.isInvoicing}}
|
||||
</tpl-buttons>
|
|
@ -103,7 +103,3 @@
|
|||
<vn-invoice-out-manual
|
||||
vn-id="manual-invoicing">
|
||||
</vn-invoice-out-manual>
|
||||
<vn-invoice-out-global-invoicing
|
||||
vn-id="global-invoicing"
|
||||
company-fk="$ctrl.vnConfig.companyFk">
|
||||
</vn-invoice-out-global-invoicing>
|
|
@ -6,7 +6,9 @@
|
|||
"dependencies": ["worker", "client", "ticket"],
|
||||
"menus": {
|
||||
"main": [
|
||||
{"state": "invoiceOut.index", "icon": "icon-invoice-out"}
|
||||
{"state": "invoiceOut.index", "icon": "icon-invoice-out"},
|
||||
{"state": "invoiceOut.global-invoicing", "icon": "contact_support"}
|
||||
|
||||
]
|
||||
},
|
||||
"routes": [
|
||||
|
@ -24,6 +26,12 @@
|
|||
"component": "vn-invoice-out-index",
|
||||
"description": "InvoiceOut"
|
||||
},
|
||||
{
|
||||
"url": "/global-invoicing?q",
|
||||
"state": "invoiceOut.global-invoicing",
|
||||
"component": "vn-invoice-out-global-invoicing",
|
||||
"description": "Global invoicing"
|
||||
},
|
||||
{
|
||||
"url": "/summary",
|
||||
"state": "invoiceOut.card.summary",
|
||||
|
|
Loading…
Reference in New Issue