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, {
|
const client = await models.Client.findById(args.clientId, {
|
||||||
fields: ['id', 'hasToInvoiceByAddress']
|
fields: ['id', 'hasToInvoiceByAddress']
|
||||||
}, myOptions);
|
}, 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
|
if (client.hasToInvoiceByAddress) {
|
||||||
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
await Self.rawSql('CALL ticketToInvoiceByAddress(?, ?, ?, ?)', [
|
||||||
|
args.minShipped,
|
||||||
// Validates ticket nagative base
|
args.maxShipped,
|
||||||
const hasAnyNegativeBase = await getNegativeBase(myOptions);
|
args.addressId,
|
||||||
if (hasAnyNegativeBase && isSpanishCompany)
|
args.companyFk
|
||||||
return tx.rollback();
|
], myOptions);
|
||||||
|
} else {
|
||||||
query = `SELECT invoiceSerial(?, ?, ?) AS serial`;
|
await Self.rawSql('CALL invoiceFromClient(?, ?, ?)', [
|
||||||
const [invoiceSerial] = await Self.rawSql(query, [
|
args.maxShipped,
|
||||||
client.id,
|
client.id,
|
||||||
args.companyFk,
|
args.companyFk
|
||||||
'G'
|
|
||||||
], myOptions);
|
], 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, {
|
// Make invoice
|
||||||
include: {
|
const isSpanishCompany = await getIsSpanishCompany(args.companyFk, myOptions);
|
||||||
relation: 'client'
|
|
||||||
}
|
// Validates ticket nagative base
|
||||||
}, myOptions);
|
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();
|
if (tx) await tx.commit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -132,15 +126,14 @@ module.exports = Self => {
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.args = {
|
if (invoiceId) {
|
||||||
reference: invoiceOut.ref,
|
ctx.args = {
|
||||||
recipientId: invoiceOut.clientFk,
|
reference: invoiceOut.ref,
|
||||||
recipient: invoiceOut.client().email
|
recipientId: invoiceOut.clientFk,
|
||||||
};
|
recipient: invoiceOut.client().email
|
||||||
try {
|
};
|
||||||
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
await models.InvoiceOut.invoiceEmail(ctx, invoiceOut.ref);
|
||||||
} catch (err) {}
|
}
|
||||||
|
|
||||||
return invoiceId;
|
return invoiceId;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -165,26 +158,4 @@ module.exports = Self => {
|
||||||
|
|
||||||
return supplierCompany && supplierCompany.total;
|
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 ngModule from '../module';
|
||||||
import Dialog from 'core/components/dialog';
|
import Section from 'salix/components/section';
|
||||||
|
import UserError from 'core/lib/user-error';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
|
|
||||||
class Controller extends Dialog {
|
class Controller extends Section {
|
||||||
constructor($element, $, $transclude) {
|
constructor($element, $, $transclude) {
|
||||||
super($element, $, $transclude);
|
super($element, $, $transclude);
|
||||||
this.invoice = {
|
this.invoice = {
|
||||||
maxShipped: new Date()
|
maxShipped: new Date(),
|
||||||
|
companyFk: this.vnConfig.companyFk
|
||||||
};
|
};
|
||||||
this.clientsNumber = 'allClients';
|
this.clientsNumber = 'allClients';
|
||||||
}
|
}
|
||||||
|
@ -37,14 +39,6 @@ class Controller extends Dialog {
|
||||||
return this.$http.get('Clients/findOne', {params});
|
return this.$http.get('Clients/findOne', {params});
|
||||||
}
|
}
|
||||||
|
|
||||||
get companyFk() {
|
|
||||||
return this.invoice.companyFk;
|
|
||||||
}
|
|
||||||
|
|
||||||
set companyFk(value) {
|
|
||||||
this.invoice.companyFk = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
restartValues() {
|
restartValues() {
|
||||||
this.lastClientId = null;
|
this.lastClientId = null;
|
||||||
this.$.invoiceButton.disabled = false;
|
this.$.invoiceButton.disabled = false;
|
||||||
|
@ -69,45 +63,51 @@ class Controller extends Dialog {
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = this.cancelRequest();
|
const index = this.$.data.findIndex(element => element.id == clientAndAddress.clientId);
|
||||||
|
return this.$http.post(`InvoiceOuts/invoiceClient`, params)
|
||||||
return this.$http.post(`InvoiceOuts/invoiceClient`, params, options)
|
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
this.$.data[index].status = 'ok';
|
||||||
|
}).catch(() => {
|
||||||
|
this.$.data[index].status = 'error';
|
||||||
|
}).finally(() => {
|
||||||
clientsAndAddresses.shift();
|
clientsAndAddresses.shift();
|
||||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
return this.invoiceOut(invoice, clientsAndAddresses);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
responseHandler(response) {
|
makeInvoice() {
|
||||||
try {
|
try {
|
||||||
if (response !== 'accept')
|
|
||||||
return super.responseHandler(response);
|
|
||||||
|
|
||||||
if (!this.invoice.invoiceDate || !this.invoice.maxShipped)
|
if (!this.invoice.invoiceDate || !this.invoice.maxShipped)
|
||||||
throw new Error('Invoice date and the max date should be filled');
|
throw new Error('Invoice date and the max date should be filled');
|
||||||
|
|
||||||
if (!this.invoice.fromClientId || !this.invoice.toClientId)
|
if (!this.invoice.fromClientId || !this.invoice.toClientId)
|
||||||
throw new Error('Choose a valid clients range');
|
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.$.invoiceButton.disabled = true;
|
||||||
this.packageInvoicing = 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 => {
|
.then(res => {
|
||||||
this.packageInvoicing = false;
|
this.packageInvoicing = false;
|
||||||
const invoice = res.data.invoice;
|
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;
|
const clientsAndAddresses = res.data.clientsAndAddresses;
|
||||||
if (!clientsAndAddresses.length) return super.responseHandler(response);
|
if (!clientsAndAddresses.length) throw new UserError(`There aren't clients to invoice`);
|
||||||
this.lastClientId = clientsAndAddresses[clientsAndAddresses.length - 1].clientId;
|
|
||||||
return this.invoiceOut(invoice, clientsAndAddresses);
|
return this.invoiceOut(invoice, clientsAndAddresses);
|
||||||
})
|
})
|
||||||
.then(() => super.responseHandler(response))
|
|
||||||
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
.then(() => this.vnApp.showSuccess(this.$t('Data saved!')))
|
||||||
.finally(() => this.restartValues());
|
.finally(() => this.restartValues());
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
@ -116,14 +116,15 @@ class Controller extends Dialog {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
clean() {
|
||||||
|
this.$.data = this.$.data.filter(client => client.status == 'error');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope', '$transclude'];
|
Controller.$inject = ['$element', '$scope', '$transclude'];
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', {
|
ngModule.vnComponent('vnInvoiceOutGlobalInvoicing', {
|
||||||
slotTemplate: require('./index.html'),
|
template: require('./index.html'),
|
||||||
controller: Controller,
|
controller: Controller
|
||||||
bindings: {
|
|
||||||
companyFk: '<?'
|
|
||||||
}
|
|
||||||
});
|
});
|
|
@ -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-popover';
|
||||||
import './descriptor-menu';
|
import './descriptor-menu';
|
||||||
import './index/manual';
|
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>
|
|
|
@ -18,7 +18,7 @@
|
||||||
<vn-thead>
|
<vn-thead>
|
||||||
<vn-tr>
|
<vn-tr>
|
||||||
<vn-th shrink>
|
<vn-th shrink>
|
||||||
<vn-multi-check
|
<vn-multi-check
|
||||||
model="model">
|
model="model">
|
||||||
</vn-multi-check>
|
</vn-multi-check>
|
||||||
</vn-th>
|
</vn-th>
|
||||||
|
@ -37,7 +37,7 @@
|
||||||
class="clickable vn-tr search-result"
|
class="clickable vn-tr search-result"
|
||||||
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})">
|
||||||
<vn-td>
|
<vn-td>
|
||||||
<vn-check
|
<vn-check
|
||||||
ng-model="invoiceOut.checked"
|
ng-model="invoiceOut.checked"
|
||||||
vn-click-stop>
|
vn-click-stop>
|
||||||
</vn-check>
|
</vn-check>
|
||||||
|
@ -103,7 +103,3 @@
|
||||||
<vn-invoice-out-manual
|
<vn-invoice-out-manual
|
||||||
vn-id="manual-invoicing">
|
vn-id="manual-invoicing">
|
||||||
</vn-invoice-out-manual>
|
</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"],
|
"dependencies": ["worker", "client", "ticket"],
|
||||||
"menus": {
|
"menus": {
|
||||||
"main": [
|
"main": [
|
||||||
{"state": "invoiceOut.index", "icon": "icon-invoice-out"}
|
{"state": "invoiceOut.index", "icon": "icon-invoice-out"},
|
||||||
|
{"state": "invoiceOut.global-invoicing", "icon": "contact_support"}
|
||||||
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"routes": [
|
"routes": [
|
||||||
|
@ -24,6 +26,12 @@
|
||||||
"component": "vn-invoice-out-index",
|
"component": "vn-invoice-out-index",
|
||||||
"description": "InvoiceOut"
|
"description": "InvoiceOut"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"url": "/global-invoicing?q",
|
||||||
|
"state": "invoiceOut.global-invoicing",
|
||||||
|
"component": "vn-invoice-out-global-invoicing",
|
||||||
|
"description": "Global invoicing"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"url": "/summary",
|
"url": "/summary",
|
||||||
"state": "invoiceOut.card.summary",
|
"state": "invoiceOut.card.summary",
|
||||||
|
@ -40,4 +48,4 @@
|
||||||
"component": "vn-invoice-out-card"
|
"component": "vn-invoice-out-card"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue