diff --git a/e2e/paths/09-invoice-out/01_summary.spec.js b/e2e/paths/09-invoice-out/01_summary.spec.js deleted file mode 100644 index 09ac66ffce..0000000000 --- a/e2e/paths/09-invoice-out/01_summary.spec.js +++ /dev/null @@ -1,44 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer'; - -describe('InvoiceOut summary path', () => { - let browser; - let page; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - await page.loginAndModule('employee', 'invoiceOut'); - await page.accessToSearchResult('T1111111'); - }); - - afterAll(async() => { - await browser.close(); - }); - - it('should reach the summary section', async() => { - await page.waitForState('invoiceOut.card.summary'); - }); - - it('should contain the company from which the invoice is emited', async() => { - const result = await page.waitToGetProperty(selectors.invoiceOutSummary.company, 'innerText'); - - expect(result).toEqual('Company VNL'); - }); - - it('should contain the tax breakdown', async() => { - const firstTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxOne, 'innerText'); - const secondTax = await page.waitToGetProperty(selectors.invoiceOutSummary.taxTwo, 'innerText'); - - expect(firstTax).toContain('10%'); - expect(secondTax).toContain('21%'); - }); - - it('should contain the tickets info', async() => { - const firstTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketOne, 'innerText'); - const secondTicket = await page.waitToGetProperty(selectors.invoiceOutSummary.ticketTwo, 'innerText'); - - expect(firstTicket).toContain('Bat cave'); - expect(secondTicket).toContain('Bat cave'); - }); -}); diff --git a/e2e/paths/09-invoice-out/02_descriptor.spec.js b/e2e/paths/09-invoice-out/02_descriptor.spec.js deleted file mode 100644 index 5169345bcd..0000000000 --- a/e2e/paths/09-invoice-out/02_descriptor.spec.js +++ /dev/null @@ -1,137 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer'; - -describe('InvoiceOut descriptor path', () => { - let browser; - let page; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - await page.loginAndModule('administrative', 'ticket'); - }); - - afterAll(async() => { - await browser.close(); - }); - - describe('as Administrative', () => { - it('should search for tickets with an specific invoiceOut', async() => { - await page.waitToClick(selectors.ticketsIndex.openAdvancedSearchButton); - await page.clearInput(selectors.ticketsIndex.advancedSearchDaysOnward); - await page.write(selectors.ticketsIndex.advancedSearchInvoiceOut, 'T2222222'); - await page.waitToClick(selectors.ticketsIndex.advancedSearchButton); - await page.waitForState('ticket.card.summary'); - }); - - it('should navigate to the invoiceOut index', async() => { - await page.waitToClick(selectors.globalItems.applicationsMenuButton); - await page.waitForSelector(selectors.globalItems.applicationsMenuVisible); - await page.waitToClick(selectors.globalItems.invoiceOutButton); - await page.waitForSelector(selectors.invoiceOutIndex.topbarSearch); - await page.waitForState('invoiceOut.index'); - }); - - it(`should click on the search result to access to the invoiceOut summary`, async() => { - await page.accessToSearchResult('T2222222'); - await page.waitForState('invoiceOut.card.summary'); - }); - - it('should delete the invoiceOut using the descriptor more menu', async() => { - await page.waitToClick(selectors.invoiceOutDescriptor.moreMenu); - await page.waitToClick(selectors.invoiceOutDescriptor.moreMenuDeleteInvoiceOut); - await page.waitToClick(selectors.invoiceOutDescriptor.acceptDeleteButton); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('InvoiceOut deleted'); - }); - - it('should have been relocated to the invoiceOut index', async() => { - await page.waitForState('invoiceOut.index'); - }); - - it(`should search for the deleted invouceOut to find no results`, async() => { - await page.doSearch('T2222222'); - const nResults = await page.countElement(selectors.invoiceOutIndex.searchResult); - - expect(nResults).toEqual(0); - }); - - it('should navigate to the ticket index', async() => { - await page.waitToClick(selectors.globalItems.applicationsMenuButton); - await page.waitForSelector(selectors.globalItems.applicationsMenuVisible); - await page.waitToClick(selectors.globalItems.ticketsButton); - await page.waitForState('ticket.index'); - }); - - it('should search now for tickets with an specific invoiceOut to find no results', async() => { - await page.doSearch('T2222222'); - const nResults = await page.countElement(selectors.ticketsIndex.searchResult); - - expect(nResults).toEqual(0); - }); - - it('should now navigate to the invoiceOut index', async() => { - await page.waitToClick(selectors.globalItems.applicationsMenuButton); - await page.waitForSelector(selectors.globalItems.applicationsMenuVisible); - await page.waitToClick(selectors.globalItems.invoiceOutButton); - await page.waitForState('invoiceOut.index'); - }); - - it(`should search and access to the invoiceOut summary`, async() => { - await page.accessToSearchResult('T1111111'); - await page.waitForState('invoiceOut.card.summary'); - }); - - it(`should check the invoiceOut is booked in the summary data`, async() => { - await page.waitForTextInElement(selectors.invoiceOutSummary.bookedLabel, '/'); - const result = await page.waitToGetProperty(selectors.invoiceOutSummary.bookedLabel, 'innerText'); - - expect(result.length).toBeGreaterThan(1); - }); - - it('should re-book the invoiceOut using the descriptor more menu', async() => { - await page.waitToClick(selectors.invoiceOutDescriptor.moreMenu); - await page.waitToClick(selectors.invoiceOutDescriptor.moreMenuBookInvoiceOut); - await page.waitToClick(selectors.invoiceOutDescriptor.acceptBookingButton); - const message = await page.waitForSnackbar(); - - expect(message.text).toContain('InvoiceOut booked'); - }); - - it(`should check the invoiceOut booked in the summary data`, async() => { - let today = Date.vnNew(); - - let day = today.getDate(); - if (day < 10) day = `0${day}`; - - let month = (today.getMonth() + 1); - if (month < 10) month = `0${month}`; - - let expectedDate = `${day}/${month}/${today.getFullYear()}`; - - await page.waitForContentLoaded(); - const result = await page - .waitToGetProperty(selectors.invoiceOutSummary.bookedLabel, 'innerText'); - - expect(result).toEqual(expectedDate); - }); - }); - - describe('as salesPerson', () => { - it(`should log in as salesPerson then go to the target invoiceOut summary`, async() => { - await page.loginAndModule('salesPerson', 'invoiceOut'); - await page.accessToSearchResult('A1111111'); - }); - - it(`should check the salesPerson role doens't see the book option in the more menu`, async() => { - await page.waitToClick(selectors.invoiceOutDescriptor.moreMenu); - await page.waitForSelector(selectors.invoiceOutDescriptor.moreMenuShowInvoiceOutPdf); - await page.waitForSelector(selectors.invoiceOutDescriptor.moreMenuBookInvoiceOut, {hidden: true}); - }); - - it(`should check the salesPerson role doens't see the delete option in the more menu`, async() => { - await page.waitForSelector(selectors.invoiceOutDescriptor.moreMenuDeleteInvoiceOut, {hidden: true}); - }); - }); -}); diff --git a/e2e/paths/09-invoice-out/03_manualInvoice.spec.js b/e2e/paths/09-invoice-out/03_manualInvoice.spec.js deleted file mode 100644 index a1856f1b1b..0000000000 --- a/e2e/paths/09-invoice-out/03_manualInvoice.spec.js +++ /dev/null @@ -1,53 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer'; - -describe('InvoiceOut manual invoice path', () => { - let browser; - let page; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - await page.loginAndModule('administrative', 'invoiceOut'); - }); - - afterAll(async() => { - await browser.close(); - }); - - it('should create an invoice from a ticket', async() => { - await page.waitToClick(selectors.invoiceOutIndex.createInvoice); - await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); - - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceTicket, '15'); - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceSerial, 'Global nacional'); - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceTaxArea, 'national'); - await page.waitToClick(selectors.invoiceOutIndex.saveInvoice); - const message = await page.waitForSnackbar(); - - await page.waitForState('invoiceOut.card.summary'); - - expect(message.text).toContain('Data saved!'); - }); - - it(`should create another invoice from a client`, async() => { - await page.waitToClick(selectors.globalItems.applicationsMenuButton); - await page.waitForSelector(selectors.globalItems.applicationsMenuVisible); - await page.waitToClick(selectors.globalItems.invoiceOutButton); - await page.waitForSelector(selectors.invoiceOutIndex.topbarSearch); - await page.waitForState('invoiceOut.index'); - - await page.waitToClick(selectors.invoiceOutIndex.createInvoice); - await page.waitForSelector(selectors.invoiceOutIndex.manualInvoiceForm); - - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceClient, 'Petter Parker'); - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceSerial, 'Global nacional'); - await page.autocompleteSearch(selectors.invoiceOutIndex.manualInvoiceTaxArea, 'national'); - await page.waitToClick(selectors.invoiceOutIndex.saveInvoice); - const message = await page.waitForSnackbar(); - - await page.waitForState('invoiceOut.card.summary'); - - expect(message.text).toContain('Data saved!'); - }); -}); diff --git a/e2e/paths/09-invoice-out/04_globalInvoice.spec.js b/e2e/paths/09-invoice-out/04_globalInvoice.spec.js deleted file mode 100644 index 64cddfa250..0000000000 --- a/e2e/paths/09-invoice-out/04_globalInvoice.spec.js +++ /dev/null @@ -1,39 +0,0 @@ -import selectors from '../../helpers/selectors.js'; -import getBrowser from '../../helpers/puppeteer'; - -describe('InvoiceOut global invoice path', () => { - let browser; - let page; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - await page.loginAndModule('administrative', 'invoiceOut'); - await page.waitToClick('[icon="search"]'); - await page.waitForTimeout(1000); // index search needs time to return results - }); - - afterAll(async() => { - await browser.close(); - }); - - let invoicesBeforeOneClient; - let now = Date.vnNew(); - - it('should count the amount of invoices listed before globla invoces are made', async() => { - invoicesBeforeOneClient = await page.countElement(selectors.invoiceOutIndex.searchResult); - - expect(invoicesBeforeOneClient).toBeGreaterThanOrEqual(4); - }); - - it('should create a global invoice for charles xavier today', async() => { - await page.accessToSection('invoiceOut.global-invoicing'); - await page.waitToClick(selectors.invoiceOutGlobalInvoicing.oneClient); - await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.clientId, 'Charles Xavier'); - await page.pickDate(selectors.invoiceOutGlobalInvoicing.invoiceDate, now); - await page.pickDate(selectors.invoiceOutGlobalInvoicing.maxShipped, now); - await page.autocompleteSearch(selectors.invoiceOutGlobalInvoicing.printer, '1'); - await page.waitToClick(selectors.invoiceOutGlobalInvoicing.makeInvoice); - await page.waitForTimeout(1000); - }); -}); diff --git a/e2e/paths/09-invoice-out/05_negative_bases.spec.js b/e2e/paths/09-invoice-out/05_negative_bases.spec.js deleted file mode 100644 index 43ced2115e..0000000000 --- a/e2e/paths/09-invoice-out/05_negative_bases.spec.js +++ /dev/null @@ -1,29 +0,0 @@ -import getBrowser from '../../helpers/puppeteer'; - -describe('InvoiceOut negative bases path', () => { - let browser; - let page; - const httpRequests = []; - - beforeAll(async() => { - browser = await getBrowser(); - page = browser.page; - page.on('request', req => { - if (req.url().includes(`InvoiceOuts/negativeBases`)) - httpRequests.push(req.url()); - }); - await page.loginAndModule('administrative', 'invoiceOut'); - await page.accessToSection('invoiceOut.negative-bases'); - }); - - afterAll(async() => { - await browser.close(); - }); - - it('should show negative bases in a date range', async() => { - const request = httpRequests.find(req => - req.includes(`from`) && req.includes(`to`)); - - expect(request).toBeDefined(); - }); -}); diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html new file mode 100644 index 0000000000..da04c8e728 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/index.html @@ -0,0 +1,252 @@ + + + + + + + + + Transfer invoice to... + + + Show invoice... + + + + as PDF + + + as CSV + + + + + + Send invoice... + + + + Send PDF + + + Send CSV + + + + + + Delete Invoice + + + Book invoice + + + {{!$ctrl.invoiceOut.hasPdf ? 'Generate PDF invoice': 'Regenerate PDF invoice'}} + + + Show CITES letter + + + Refund... + + + + with warehouse + + + without warehouse + + + + + + + + + + + + + + + + + + + + + Are you sure you want to send it? + + + + + + Confirm + + + + + + + Are you sure you want to send it? + + + + + + Confirm + + + + + + transferInvoice + + + + + + + #{{id}} - {{::name}} + + + + + {{ ::description}} + + + + + + + {{::code}} - {{::description}} + + + + + + + + + + + + + Transfer client + + diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js new file mode 100644 index 0000000000..8ea4507ec4 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/index.js @@ -0,0 +1,191 @@ +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; + this.checked = true; + } + + get invoiceOut() { + return this._invoiceOut; + } + + set invoiceOut(value) { + this._invoiceOut = value; + if (value) + this.id = value.id; + } + + get hasInvoicing() { + return this.aclService.hasAny(['invoicing']); + } + + get isChecked() { + return this.checked; + } + + set isChecked(value) { + this.checked = value; + } + + $onInit() { + this.$http.get(`CplusRectificationTypes`, {filter: {order: 'description'}}) + .then(res => { + this.cplusRectificationTypes = res.data; + this.cplusRectificationType = res.data.filter(type => type.description == 'I – Por diferencias')[0].id; + }); + this.$http.get(`SiiTypeInvoiceOuts`, {filter: {where: {code: {like: 'R%'}}}}) + .then(res => { + this.siiTypeInvoiceOuts = res.data; + this.siiTypeInvoiceOut = res.data.filter(type => type.code == 'R4')[0].id; + }); + } + loadData() { + const filter = { + include: [ + { + relation: 'company', + scope: { + fields: ['id', 'code'] + } + }, { + relation: 'client', + scope: { + fields: ['id', 'name', 'email', 'hasToInvoiceByAddress'] + } + } + ] + }; + 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(() => { + const isInsideInvoiceOut = this.$state.current.name.startsWith('invoiceOut'); + if (isInsideInvoiceOut) + this.$state.go('invoiceOut.index'); + else + 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); + }); + } + + sendPdfInvoice($data) { + if (!$data.email) + return this.vnApp.showError(this.$t(`The email can't be empty`)); + + return this.vnEmail.send(`InvoiceOuts/${this.invoiceOut.ref}/invoice-email`, { + recipientId: this.invoiceOut.client.id, + recipient: $data.email + }); + } + + showCsvInvoice() { + this.vnReport.show(`InvoiceOuts/${this.invoiceOut.ref}/invoice-csv`, { + recipientId: this.invoiceOut.client.id + }); + } + + sendCsvInvoice($data) { + if (!$data.email) + return this.vnApp.showError(this.$t(`The email can't be empty`)); + + return this.vnEmail.send(`InvoiceOuts/${this.invoiceOut.ref}/invoice-csv-email`, { + recipientId: this.invoiceOut.client.id, + recipient: $data.email + }); + } + + showExportationLetter() { + this.vnReport.show(`InvoiceOuts/${this.invoiceOut.ref}/exportation-pdf`, { + recipientId: this.invoiceOut.client.id, + refFk: this.invoiceOut.ref + }); + } + + refundInvoiceOut(withWarehouse) { + const query = 'InvoiceOuts/refund'; + const params = {ref: this.invoiceOut.ref, withWarehouse: withWarehouse}; + this.$http.post(query, params).then(res => { + const tickets = res.data; + const refundTickets = tickets.map(ticket => ticket.id); + + this.vnApp.showSuccess(this.$t('The following refund tickets have been created', { + ticketId: refundTickets.join(',') + })); + if (refundTickets.length == 1) + this.$state.go('ticket.card.sale', {id: refundTickets[0]}); + }); + } + + transferInvoice() { + const params = { + id: this.invoiceOut.id, + refFk: this.invoiceOut.ref, + newClientFk: this.clientId, + cplusRectificationTypeFk: this.cplusRectificationType, + siiTypeInvoiceOutFk: this.siiTypeInvoiceOut, + invoiceCorrectionTypeFk: this.invoiceCorrectionType, + makeInvoice: this.checked + }; + + this.$http.get(`Clients/${this.clientId}`).then(response => { + const clientData = response.data; + const hasToInvoiceByAddress = clientData.hasToInvoiceByAddress; + + if (this.checked && hasToInvoiceByAddress) { + if (!window.confirm(this.$t('confirmTransferInvoice'))) + return; + } + + this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => { + const invoiceId = res.data; + this.vnApp.showSuccess(this.$t('Transferred invoice')); + this.$state.go('invoiceOut.card.summary', {id: invoiceId}); + }); + }); + } +} + +Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail']; + +ngModule.vnComponent('vnInvoiceOutDescriptorMenu', { + template: require('./index.html'), + controller: Controller, + bindings: { + invoiceOut: '<', + parentReload: '&' + } +}); diff --git a/modules/invoiceOut/front/descriptor-menu/index.spec.js b/modules/invoiceOut/front/descriptor-menu/index.spec.js new file mode 100644 index 0000000000..d2ccfa117e --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/index.spec.js @@ -0,0 +1,121 @@ +import './index'; + +describe('vnInvoiceOutDescriptorMenu', () => { + let controller; + let $httpBackend; + let $httpParamSerializer; + const invoiceOut = { + id: 1, + client: {id: 1101}, + ref: 'T1111111' + }; + + beforeEach(ngModule('invoiceOut')); + + beforeEach(inject(($componentController, _$httpParamSerializer_, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; + controller = $componentController('vnInvoiceOutDescriptorMenu', {$element: null}); + controller.invoiceOut = { + id: 1, + ref: 'T1111111', + client: {id: 1101} + }; + })); + + describe('createPdfInvoice()', () => { + it('should make a query to the createPdf() endpoint and show a success snackbar', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + + $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(); + + const expectedParams = { + recipientId: invoiceOut.client.id + }; + const serializedParams = $httpParamSerializer(expectedParams); + const expectedPath = `api/InvoiceOuts/${invoiceOut.ref}/invoice-csv?${serializedParams}`; + controller.showCsvInvoice(); + + expect(window.open).toHaveBeenCalledWith(expectedPath); + }); + }); + + describe('deleteInvoiceOut()', () => { + it(`should make a query and call showSuccess()`, () => { + controller.$state.reload = jest.fn(); + jest.spyOn(controller.vnApp, 'showSuccess'); + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond(); + controller.deleteInvoiceOut(); + $httpBackend.flush(); + + expect(controller.$state.reload).toHaveBeenCalled(); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + + it(`should make a query and call showSuccess() after state.go if the state wasn't in invoiceOut module`, () => { + jest.spyOn(controller.$state, 'go').mockReturnValue('ok'); + jest.spyOn(controller.vnApp, 'showSuccess'); + controller.$state.current.name = 'invoiceOut.card.something'; + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.id}/delete`).respond(); + controller.deleteInvoiceOut(); + $httpBackend.flush(); + + expect(controller.$state.go).toHaveBeenCalledWith('invoiceOut.index'); + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); + + describe('sendPdfInvoice()', () => { + it('should make a query to the email invoice endpoint and show a message snackbar', () => { + jest.spyOn(controller.vnApp, 'showMessage'); + + const $data = {email: 'brucebanner@gothamcity.com'}; + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.ref}/invoice-email`).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'); + + const $data = {email: 'brucebanner@gothamcity.com'}; + + $httpBackend.expectPOST(`InvoiceOuts/${invoiceOut.ref}/invoice-csv-email`).respond(); + controller.sendCsvInvoice($data); + $httpBackend.flush(); + + expect(controller.vnApp.showMessage).toHaveBeenCalled(); + }); + }); + + describe('refundInvoiceOut()', () => { + it('should make a query and show a success message', () => { + jest.spyOn(controller.vnApp, 'showSuccess'); + const params = {ref: controller.invoiceOut.ref}; + + $httpBackend.expectPOST(`InvoiceOuts/refund`, params).respond([{id: 1}, {id: 2}]); + controller.refundInvoiceOut(); + $httpBackend.flush(); + + expect(controller.vnApp.showSuccess).toHaveBeenCalled(); + }); + }); +}); diff --git a/modules/invoiceOut/front/descriptor-menu/locale/en.yml b/modules/invoiceOut/front/descriptor-menu/locale/en.yml new file mode 100644 index 0000000000..32ea03442b --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/locale/en.yml @@ -0,0 +1,7 @@ +The following refund tickets have been created: "The following refund tickets have been created: {{ticketIds}}" +Transfer invoice to...: Transfer invoice to... +Cplus Type: Cplus Type +transferInvoice: Transfer Invoice +destinationClient: Bill destination client +transferInvoiceInfo: New tickets from the destination customer will be generated in the default consignee. +confirmTransferInvoice: Destination customer has marked to bill by consignee, do you want to continue? \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml new file mode 100644 index 0000000000..92c1098782 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml @@ -0,0 +1,30 @@ +Show invoice...: Ver factura... +Send invoice...: Enviar factura... +Send PDF invoice: Enviar factura en PDF +Send CSV invoice: Enviar factura en CSV +as PDF: como PDF +as CSV: como CSV +Delete Invoice: Eliminar factura +Clone Invoice: Clonar factura +Book invoice: Asentar factura +Generate PDF invoice: Generar PDF factura +Show CITES letter: Ver carta CITES +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? +Are you sure you want to refund this invoice?: Estas seguro de querer abonar esta factura? +Create a refund ticket for each ticket on the current invoice: Crear un ticket abono por cada ticket de la factura actual +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 +The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}" +Refund...: Abono... +Transfer invoice to...: Transferir factura a... +Rectificative type: Tipo rectificativa +Transferred invoice: Factura transferida +transferInvoice: Transferir factura +destinationClient: Facturar cliente destino +transferInvoiceInfo: Los nuevos tickets del cliente destino serán generados en el consignatario por defecto. +confirmTransferInvoice: El cliente destino tiene marcado facturar por consignatario, ¿desea continuar? \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor-menu/style.scss b/modules/invoiceOut/front/descriptor-menu/style.scss new file mode 100644 index 0000000000..9e4cf42973 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-menu/style.scss @@ -0,0 +1,30 @@ +@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; + } + } + +} +@media screen and (min-width: 1000px) { + .transferInvoice { + min-width: $width-md; + } +} diff --git a/modules/invoiceOut/front/descriptor-popover/index.html b/modules/invoiceOut/front/descriptor-popover/index.html new file mode 100644 index 0000000000..2bd9121335 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-popover/index.html @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor-popover/index.js b/modules/invoiceOut/front/descriptor-popover/index.js new file mode 100644 index 0000000000..fc9899da03 --- /dev/null +++ b/modules/invoiceOut/front/descriptor-popover/index.js @@ -0,0 +1,9 @@ +import ngModule from '../module'; +import DescriptorPopover from 'salix/components/descriptor-popover'; + +class Controller extends DescriptorPopover {} + +ngModule.vnComponent('vnInvoiceOutDescriptorPopover', { + slotTemplate: require('./index.html'), + controller: Controller +}); diff --git a/modules/invoiceOut/front/descriptor/es.yml b/modules/invoiceOut/front/descriptor/es.yml new file mode 100644 index 0000000000..0e88a7dc7a --- /dev/null +++ b/modules/invoiceOut/front/descriptor/es.yml @@ -0,0 +1,2 @@ +Client card: Ficha del cliente +Invoice ticket list: Listado de tickets de la factura \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor/index.html b/modules/invoiceOut/front/descriptor/index.html new file mode 100644 index 0000000000..e1dc579ab2 --- /dev/null +++ b/modules/invoiceOut/front/descriptor/index.html @@ -0,0 +1,59 @@ + + + + + + + + + + + + + {{$ctrl.invoiceOut.client.name}} + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/modules/invoiceOut/front/descriptor/index.js b/modules/invoiceOut/front/descriptor/index.js new file mode 100644 index 0000000000..7eeb85ea6d --- /dev/null +++ b/modules/invoiceOut/front/descriptor/index.js @@ -0,0 +1,52 @@ +import ngModule from '../module'; +import Descriptor from 'salix/components/descriptor'; + +class Controller extends Descriptor { + get invoiceOut() { + return this.entity; + } + + set invoiceOut(value) { + this.entity = value; + } + + get hasInvoicing() { + return this.aclService.hasAny(['invoicing']); + } + + get filter() { + if (this.invoiceOut) + return JSON.stringify({refFk: this.invoiceOut.ref}); + + return null; + } + + loadData() { + const filter = { + include: [ + { + relation: 'company', + scope: { + fields: ['id', 'code'] + } + }, { + relation: 'client', + scope: { + fields: ['id', 'name', 'email'] + } + } + ] + }; + + return this.getData(`InvoiceOuts/${this.id}`, {filter}) + .then(res => this.entity = res.data); + } +} + +ngModule.vnComponent('vnInvoiceOutDescriptor', { + template: require('./index.html'), + controller: Controller, + bindings: { + invoiceOut: '<', + } +}); diff --git a/modules/invoiceOut/front/descriptor/index.spec.js b/modules/invoiceOut/front/descriptor/index.spec.js new file mode 100644 index 0000000000..987763b0a6 --- /dev/null +++ b/modules/invoiceOut/front/descriptor/index.spec.js @@ -0,0 +1,26 @@ +import './index'; + +describe('vnInvoiceOutDescriptor', () => { + let controller; + let $httpBackend; + + beforeEach(ngModule('invoiceOut')); + + beforeEach(inject(($componentController, _$httpBackend_) => { + $httpBackend = _$httpBackend_; + controller = $componentController('vnInvoiceOutDescriptor', {$element: null}); + })); + + describe('loadData()', () => { + it(`should perform a get query to store the invoice in data into the controller`, () => { + const id = 1; + const response = {id: 1}; + + $httpBackend.expectGET(`InvoiceOuts/${id}`).respond(response); + controller.id = id; + $httpBackend.flush(); + + expect(controller.invoiceOut).toEqual(response); + }); + }); +}); diff --git a/modules/invoiceOut/front/index.js b/modules/invoiceOut/front/index.js index a7209a0bdd..a5e51d4399 100644 --- a/modules/invoiceOut/front/index.js +++ b/modules/invoiceOut/front/index.js @@ -1,3 +1,7 @@ export * from './module'; import './main'; +import './summary'; +import './descriptor'; +import './descriptor-popover'; +import './descriptor-menu'; diff --git a/modules/invoiceOut/front/routes.json b/modules/invoiceOut/front/routes.json index 26dd2da037..7c7495cb98 100644 --- a/modules/invoiceOut/front/routes.json +++ b/modules/invoiceOut/front/routes.json @@ -25,6 +25,15 @@ "state": "invoiceOut.index", "component": "vn-invoice-out-index", "description": "InvoiceOut" + }, + { + "url": "/summary", + "state": "invoiceOut.card.summary", + "component": "vn-invoice-out-summary", + "description": "Summary", + "params": { + "invoice-out": "$ctrl.invoiceOut" + } } ] } diff --git a/modules/invoiceOut/front/summary/es.yml b/modules/invoiceOut/front/summary/es.yml new file mode 100644 index 0000000000..caff7ce993 --- /dev/null +++ b/modules/invoiceOut/front/summary/es.yml @@ -0,0 +1,13 @@ +Date: Fecha +Created: Creada +Due: Vencimiento +Booked: Asentado +General VAT: IVA general +Reduced VAT: IVA reducido +Shipped: F. envío +Type: Tipo +Rate: Tasa +Fee: Cuota +Taxable base: Base imp. +Tax breakdown: Desglose impositivo +Go to the Invoice Out: Ir a la factura emitida \ No newline at end of file diff --git a/modules/invoiceOut/front/summary/index.html b/modules/invoiceOut/front/summary/index.html new file mode 100644 index 0000000000..b837751586 --- /dev/null +++ b/modules/invoiceOut/front/summary/index.html @@ -0,0 +1,104 @@ + + + + + + + + {{$ctrl.summary.invoiceOut.ref}} - {{$ctrl.summary.invoiceOut.client.socialName}} + + + + + + + + + + + + + + + + + Tax breakdown + + + + Type + Taxable base + Rate + Fee + + + + + {{tax.name}} + {{tax.taxableBase | currency: 'EUR': 2}} + {{tax.rate}}% + {{tax.vat | currency: 'EUR': 2}} + + + + + + Ticket + + + + Ticket id + Alias + Shipped + Amount + + + + + + + {{ticket.id}} + + + + + {{ticket.nickname}} + + + {{ticket.shipped | date: 'dd/MM/yyyy' | dashIfEmpty}} + {{ticket.totalWithVat | currency: 'EUR': 2}} + + + + + + + + + + + + \ No newline at end of file diff --git a/modules/invoiceOut/front/summary/index.js b/modules/invoiceOut/front/summary/index.js new file mode 100644 index 0000000000..131f9ba8fe --- /dev/null +++ b/modules/invoiceOut/front/summary/index.js @@ -0,0 +1,34 @@ +import ngModule from '../module'; +import Summary from 'salix/components/summary'; +import './style.scss'; + +class Controller extends Summary { + get invoiceOut() { + return this._invoiceOut; + } + + set invoiceOut(value) { + this._invoiceOut = value; + if (value && value.id) { + this.loadData(); + this.loadTickets(); + } + } + + loadData() { + this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`) + .then(res => this.summary = res.data); + } + + loadTickets() { + this.$.$applyAsync(() => this.$.ticketsModel.refresh()); + } +} + +ngModule.vnComponent('vnInvoiceOutSummary', { + template: require('./index.html'), + controller: Controller, + bindings: { + invoiceOut: '<' + } +}); diff --git a/modules/invoiceOut/front/summary/index.spec.js b/modules/invoiceOut/front/summary/index.spec.js new file mode 100644 index 0000000000..44e3094ae5 --- /dev/null +++ b/modules/invoiceOut/front/summary/index.spec.js @@ -0,0 +1,31 @@ +import './index.js'; +import crudModel from 'core/mocks/crud-model'; + +describe('InvoiceOut', () => { + describe('Component summary', () => { + let controller; + let $httpBackend; + let $scope; + + beforeEach(ngModule('invoiceOut')); + + beforeEach(inject(($componentController, _$httpBackend_, $rootScope) => { + $httpBackend = _$httpBackend_; + $scope = $rootScope.$new(); + const $element = angular.element(''); + controller = $componentController('vnInvoiceOutSummary', {$element, $scope}); + controller._invoiceOut = {id: 1}; + controller.$.ticketsModel = crudModel; + })); + + describe('loadData()', () => { + it('should perform a query to set summary', () => { + $httpBackend.expect('GET', `InvoiceOuts/1/summary`).respond(200, 'the data you are looking for'); + controller.loadData(); + $httpBackend.flush(); + + expect(controller.summary).toEqual('the data you are looking for'); + }); + }); + }); +}); diff --git a/modules/invoiceOut/front/summary/style.scss b/modules/invoiceOut/front/summary/style.scss new file mode 100644 index 0000000000..e6e31fd94b --- /dev/null +++ b/modules/invoiceOut/front/summary/style.scss @@ -0,0 +1,6 @@ +@import "variables"; + + +vn-invoice-out-summary .summary { + max-width: $width-lg; +} \ No newline at end of file