diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js index 95c51a96d..72bdb3ec7 100644 --- a/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js +++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceClient.js @@ -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`) + ':

'; - - body += `ID: ${failedClient.id} -
${failedClient.stacktrace}

`; - - await Self.rawSql(` - INSERT INTO vn.mail (sender, replyTo, sent, subject, body) - VALUES (?, ?, FALSE, ?, ?)`, [ - worker.email, - worker.email, - subject, - body - ], options); - } }; diff --git a/modules/invoiceOut/front/global-invoicing/index.html b/modules/invoiceOut/front/global-invoicing/index.html new file mode 100644 index 000000000..1b14a72f6 --- /dev/null +++ b/modules/invoiceOut/front/global-invoicing/index.html @@ -0,0 +1,122 @@ +
+ + + + + + Id + Status + + + + + + + {{::client.id}} + + + + + + + + + + + + + + + +
+ + + + + + +
+ + + + + + + + + + + + + + + {{::id}} - {{::name}} + + + {{::id}} - {{::name}} + + + + + + + + +
+
+ + + diff --git a/modules/invoiceOut/front/index/global-invoicing/index.js b/modules/invoiceOut/front/global-invoicing/index.js similarity index 67% rename from modules/invoiceOut/front/index/global-invoicing/index.js rename to modules/invoiceOut/front/global-invoicing/index.js index f772a4936..0750d8d6c 100644 --- a/modules/invoiceOut/front/index/global-invoicing/index.js +++ b/modules/invoiceOut/front/global-invoicing/index.js @@ -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: ' { + let $httpBackend; + let controller; + let $element; + + beforeEach(ngModule('zone')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $element = angular.element(' { + 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); + }); + }); +}); diff --git a/modules/invoiceOut/front/global-invoicing/locale/es.yml b/modules/invoiceOut/front/global-invoicing/locale/es.yml new file mode 100644 index 000000000..5c556fa39 --- /dev/null +++ b/modules/invoiceOut/front/global-invoicing/locale/es.yml @@ -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... diff --git a/modules/invoiceOut/front/global-invoicing/style.scss b/modules/invoiceOut/front/global-invoicing/style.scss new file mode 100644 index 000000000..41dd3fac2 --- /dev/null +++ b/modules/invoiceOut/front/global-invoicing/style.scss @@ -0,0 +1,5 @@ +@import "variables"; + +.error { + color: $color-alert; + } diff --git a/modules/invoiceOut/front/index.js b/modules/invoiceOut/front/index.js index 0307b2b4b..f7cebc0d0 100644 --- a/modules/invoiceOut/front/index.js +++ b/modules/invoiceOut/front/index.js @@ -9,4 +9,4 @@ import './descriptor'; import './descriptor-popover'; import './descriptor-menu'; import './index/manual'; -import './index/global-invoicing'; +import './global-invoicing'; diff --git a/modules/invoiceOut/front/index/global-invoicing/index.html b/modules/invoiceOut/front/index/global-invoicing/index.html deleted file mode 100644 index c2d1c4304..000000000 --- a/modules/invoiceOut/front/index/global-invoicing/index.html +++ /dev/null @@ -1,96 +0,0 @@ - - Create global invoice - - - - - - -
- -
- {{'Calculating packages to invoice...' | translate}} -
-
-
-
- -
- {{'Id Client' | translate}}: {{$ctrl.currentClientId}} - {{'of' | translate}} {{::$ctrl.lastClientId}} -
-
-
- - - - - - - - - - - - - - - {{::id}} - {{::name}} - - - {{::id}} - {{::name}} - - - - - - -
- - - {{$ctrl.isInvoicing}} - \ No newline at end of file diff --git a/modules/invoiceOut/front/index/index.html b/modules/invoiceOut/front/index/index.html index e2cf2120a..e2b0c221f 100644 --- a/modules/invoiceOut/front/index/index.html +++ b/modules/invoiceOut/front/index/index.html @@ -18,7 +18,7 @@ - @@ -37,7 +37,7 @@ class="clickable vn-tr search-result" ui-sref="invoiceOut.card.summary({id: {{::invoiceOut.id}}})"> - @@ -103,7 +103,3 @@ - - \ No newline at end of file diff --git a/modules/invoiceOut/front/routes.json b/modules/invoiceOut/front/routes.json index 09d9f3d33..c396a5334 100644 --- a/modules/invoiceOut/front/routes.json +++ b/modules/invoiceOut/front/routes.json @@ -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", @@ -40,4 +48,4 @@ "component": "vn-invoice-out-card" } ] -} \ No newline at end of file +}