diff --git a/back/methods/dms/uploadFile.js b/back/methods/dms/uploadFile.js index fb4b2e5b8..a4212b804 100644 --- a/back/methods/dms/uploadFile.js +++ b/back/methods/dms/uploadFile.js @@ -9,35 +9,35 @@ module.exports = Self => { accepts: [ { arg: 'warehouseId', - type: 'Number', + type: 'number', description: 'The warehouse id', required: true }, { arg: 'companyId', - type: 'Number', + type: 'number', description: 'The company id', required: true }, { arg: 'dmsTypeId', - type: 'Number', + type: 'number', description: 'The dms type id', required: true }, { arg: 'reference', - type: 'String', + type: 'string', required: true }, { arg: 'description', - type: 'String', + type: 'string', required: true }, { arg: 'hasFile', - type: 'Boolean', + type: 'boolean', description: 'True if has an attached file', required: true }], returns: { - type: 'Object', + type: 'object', root: true }, http: { diff --git a/db/changes/10430-ash/00-aclAgencyTerm.sql b/db/changes/10430-ash/00-aclAgencyTerm.sql new file mode 100644 index 000000000..d3e11e53e --- /dev/null +++ b/db/changes/10430-ash/00-aclAgencyTerm.sql @@ -0,0 +1,2 @@ +INSERT INTO salix.ACL (model,property,accessType,principalId) + VALUES ('AgencyTerm','*','*','administrative'); diff --git a/db/changes/10430-ash/00-agencyTerm.sql b/db/changes/10430-ash/00-agencyTerm.sql new file mode 100644 index 000000000..9822b160b --- /dev/null +++ b/db/changes/10430-ash/00-agencyTerm.sql @@ -0,0 +1,10 @@ +CREATE TABLE `vn`.`agencyTermConfig` ( + `expenceFk` varchar(10) DEFAULT NULL, + `vatAccountSupported` varchar(15) DEFAULT NULL, + `vatPercentage` decimal(28,10) DEFAULT NULL, + `transaction` varchar(50) DEFAULT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8; + +INSERT INTO `vn`.`agencyTermConfig` +(`expenceFk`, `vatAccountSupported`, `vatPercentage`, `transaction`) +VALUES('6240000000', '4721000015', 21.0000000000, 'Adquisiciones intracomunitarias de servicios'); \ No newline at end of file diff --git a/db/changes/10430-ash/00-deliveryBoss.sql b/db/changes/10430-ash/00-deliveryBoss.sql new file mode 100644 index 000000000..a4bcdcd0c --- /dev/null +++ b/db/changes/10430-ash/00-deliveryBoss.sql @@ -0,0 +1,3 @@ +UPDATE `account`.`user` +SET `role` = 57 +WHERE id IN (2294, 4365, 7294); \ No newline at end of file diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index 50a12a094..d87dc046a 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -167,9 +167,11 @@ INSERT INTO `vn`.`accountingType`(`id`, `description`, `receiptDescription`,`cod INSERT INTO `vn`.`bank`(`id`, `bank`, `account`, `cash`, `entityFk`, `isActive`, `currencyFk`) VALUES - (1, 'Pay on receipt', '5720000001', 3, 0, 1, 1), - (2, 'Cash', '5700000001', 2, 0, 1, 1), - (3, 'Compensation', '4000000000', 8, 0, 1, 1); + (1, 'Pay on receipt', '5720000001', 3, 0, 1, 1), + (2, 'Cash', '5700000001', 2, 0, 1, 1), + (3, 'Compensation', '4000000000', 8, 0, 1, 1), + (3117, 'Caixa Rural d''Algemesi', '5720000000', 8, 3117, 1, 1); + INSERT INTO `vn`.`deliveryMethod`(`id`, `code`, `description`) VALUES @@ -522,6 +524,8 @@ INSERT INTO `vn`.`expence`(`id`, `taxTypeFk`, `name`, `isWithheld`) (4751000000, 1, 'Retenciones', 1), (4751000000, 6, 'Retencion', 0), (6210000567, 0, 'Alquiler VNH', 0), + (6240000000, 1, 'Transportes de ventas rutas', 0), + (6240000000, 4, 'Transportes de ventas rutas', 0), (7001000000, 1, 'Mercaderia', 0), (7050000000, 1, 'Prestacion de servicios', 1); @@ -2453,6 +2457,28 @@ INSERT INTO `bs`.`defaulter` (`clientFk`, `amount`, `created`, `defaulterSinced` (1107, 500, CURDATE(), CURDATE()), (1109, 500, CURDATE(), CURDATE()); +INSERT INTO `vn`.`agencyTerm` (`agencyFk`, `minimumPackages`, `kmPrice`, `packagePrice`, `routePrice`, `minimumKm`, `minimumM3`, `m3Price`) + VALUES + (1, 0, 0.00, 0.00, NULL, 0, 0.00, 0), + (3, 0, 0.00, 3.05, NULL, 0, 0.00, 0), + (2, 60, 0.00, 0.00, NULL, 0, 5.00, 33); + +UPDATE `vn`.`agency` +SET `supplierFk`=1 +WHERE `id`=1; +UPDATE `vn`.`agency` +SET `supplierFk`=1 +WHERE `id`=2; +UPDATE `vn`.`agency` +SET `supplierFk`=2 +WHERE `id`=3; + +UPDATE `vn`.`route` +SET `invoiceInFk`=1 +WHERE `id`=1; +UPDATE `vn`.`route` +SET `invoiceInFk`=2 +WHERE `id`=2; INSERT INTO `bs`.`salesPerson` (`workerFk`, `year`, `month`, `portfolioWeight`) VALUES (18, YEAR(CURDATE()), MONTH(CURDATE()), 807.23), diff --git a/e2e/paths/03-worker/05_calendar.spec.js b/e2e/paths/03-worker/05_calendar.spec.js index 08ef71f13..e97b7fe7c 100644 --- a/e2e/paths/03-worker/05_calendar.spec.js +++ b/e2e/paths/03-worker/05_calendar.spec.js @@ -9,7 +9,7 @@ describe('Worker calendar path', () => { browser = await getBrowser(); page = browser.page; await page.loginAndModule('hr', 'worker'); - await page.accessToSearchResult('Hank Pym'); + await page.accessToSearchResult('Charles Xavier'); await page.accessToSection('worker.card.calendar'); }); @@ -18,12 +18,6 @@ describe('Worker calendar path', () => { }); describe('as hr', () => { - it('should check 5 total holidays have been used so far before testing anything', async() => { - const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - - expect(result).toContain(' 5 '); - }); - it('should set two days as holidays on the calendar and check the total holidays increased by 1.5', async() => { await page.waitToClick(selectors.workerCalendar.holidays); await page.waitForTimeout(reasonableTimeBetweenClicks); @@ -56,14 +50,14 @@ describe('Worker calendar path', () => { const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - expect(result).toContain(' 6.5 '); + expect(result).toContain(' 1.5 '); }); }); describe(`as salesBoss`, () => { - it(`should log in and get to Hank's calendar`, async() => { + it(`should log in and get to Charles Xavier's calendar`, async() => { await page.loginAndModule('salesBoss', 'worker'); - await page.accessToSearchResult('Hank Pym'); + await page.accessToSearchResult('Charles Xavier'); await page.accessToSection('worker.card.calendar'); }); @@ -101,14 +95,14 @@ describe('Worker calendar path', () => { it('should check the total holidays used are back to what it was', async() => { const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - expect(result).toContain(' 5 '); + expect(result).toContain(' 0 '); }); }); - describe(`as Hank`, () => { + describe(`as Charles Xavier`, () => { it(`should log in and get to his calendar`, async() => { - await page.loginAndModule('HankPym', 'worker'); - await page.accessToSearchResult('Hank Pym'); + await page.loginAndModule('CharlesXavier', 'worker'); + await page.accessToSearchResult('Charles Xavier'); await page.accessToSection('worker.card.calendar'); }); @@ -122,7 +116,7 @@ describe('Worker calendar path', () => { it('should check the total holidays used are now the initial ones', async() => { const result = await page.waitToGetProperty(selectors.workerCalendar.totalHolidaysUsed, 'innerText'); - expect(result).toContain(' 5 '); + expect(result).toContain(' 0 '); }); it('should use the year selector to go to the previous year', async() => { diff --git a/loopback/locale/es.json b/loopback/locale/es.json index aa86e8ab6..e7505e60f 100644 --- a/loopback/locale/es.json +++ b/loopback/locale/es.json @@ -220,5 +220,6 @@ "Can't transfer claimed sales": "No puedes transferir lineas reclamadas", "You don't have privileges to create pay back": "No tienes permisos para crear un abono", "The item is required": "El artículo es requerido", - "The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo" + "The agency is already assigned to another autonomous": "La agencia ya está asignada a otro autónomo", + "reference duplicated": "Referencia duplicada" } \ No newline at end of file diff --git a/modules/entry/front/basic-data/index.html b/modules/entry/front/basic-data/index.html index f75834045..a05630dd6 100644 --- a/modules/entry/front/basic-data/index.html +++ b/modules/entry/front/basic-data/index.html @@ -79,7 +79,6 @@ { + Self.remoteMethod('createInvoiceIn', { + description: 'Creates an invoiceIn from one or more agency terms', + accessType: 'WRITE', + accepts: [{ + arg: 'rows', + type: ['object'], + required: true, + description: `The rows from which the invoiceIn will be created`, + }, + { + arg: 'dms', + type: ['object'], + required: true, + description: 'The dms file attached' + }], + returns: { + type: 'object', + root: true + }, + http: { + path: `/createInvoiceIn`, + verb: 'POST' + } + }); + + Self.createInvoiceIn = async(rows, dms, options) => { + const models = Self.app.models; + + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const [firstRow] = rows; + const [firstDms] = dms; + + const reference = await models.Dms.findById([firstDms.id], null, myOptions); + + const newInvoiceIn = await models.InvoiceIn.create({ + supplierFk: firstRow.supplierFk, + supplierRef: reference.reference, + issued: firstRow.created, + booked: firstRow.created, + operated: firstRow.created, + bookEntried: firstRow.created, + dmsFk: firstDms.id, + }, myOptions); + + const expence = await models.AgencyTermConfig.findOne(null, myOptions); + + const [taxTypeSage] = await Self.rawSql(` + SELECT IFNULL(s.taxTypeSageFk, CodigoIva) value + FROM vn.supplier s + JOIN sage.TiposIva ti ON TRUE + JOIN vn.agencyTermConfig atg + WHERE s.id = ? + AND ti.CuentaIvaSoportado = atg.vatAccountSupported + AND ti.PorcentajeIva = atg.vatPercentage + `, [firstRow.supplierFk], myOptions); + + const [transactionTypeSage] = await Self.rawSql(` + SELECT IFNULL(s.transactionTypeSageFk, tt.CodigoTransaccion) value + FROM vn.supplier s + JOIN sage.TiposTransacciones tt ON TRUE + JOIN vn.agencyTermConfig atg + WHERE s.id = ? + AND tt.Transaccion = atg.transaction + `, [firstRow.supplierFk], myOptions); + + await models.InvoiceInTax.create({ + invoiceInFk: newInvoiceIn.id, + taxableBase: firstRow.totalPrice, + expenseFk: expence.expenceFk, + taxTypeSageFk: taxTypeSage.value, + transactionTypeSageFk: transactionTypeSage.value + }, myOptions); + + await Self.rawSql(`CALL invoiceInDueDay_calculate(?)`, [newInvoiceIn.id], myOptions); + + for (let agencyTerm of rows) { + const route = await models.Route.findById(agencyTerm.routeFk, null, myOptions); + await Self.rawSql(` + UPDATE vn.route SET invoiceInFk = ? WHERE id = ? + `, [newInvoiceIn.id, route.id], myOptions); + } + + if (tx) await tx.commit(); + + return newInvoiceIn; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/route/back/methods/agency-term/filter.js b/modules/route/back/methods/agency-term/filter.js new file mode 100644 index 000000000..0bca9ddf7 --- /dev/null +++ b/modules/route/back/methods/agency-term/filter.js @@ -0,0 +1,77 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethodCtx('filter', { + description: 'Find all instances of the model matched by filter from the data source.', + accessType: 'READ', + accepts: [ + { + arg: 'filter', + type: 'object', + description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string', + http: {source: 'query'} + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/filter`, + verb: 'GET' + } + }); + + Self.filter = async(ctx, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const stmts = []; + const stmt = new ParameterizedSQL( + `SELECT * + FROM ( + SELECT r.id routeFk, + r.created, + r.agencyModeFk, + am.name agencyModeName, + am.agencyFk, + a.name agencyAgreement, + SUM(t.packages) packages, + r.m3, + r.kmEnd - r.kmStart kmTotal, + CAST(IFNULL(ate.routePrice, + (ate.kmPrice * (GREATEST(r.kmEnd - r.kmStart , ate.minimumKm)) + + GREATEST(r.m3 , ate.minimumM3) * ate.m3Price) + + ate.packagePrice * SUM(t.packages) ) + AS DECIMAL(10,2)) price, + r.invoiceInFk, + a.supplierFk, + s.name supplierName + FROM vn.route r + LEFT JOIN vn.agencyMode am ON r.agencyModeFk = am.id + LEFT JOIN vn.agency a ON am.agencyFk = a.id + LEFT JOIN vn.ticket t ON t.routeFk = r.id + LEFT JOIN vn.agencyTerm ate ON ate.agencyFk = a.id + LEFT JOIN vn.supplier s ON s.id = a.supplierFk + WHERE r.created > DATE_ADD(CURDATE(), INTERVAL -2 MONTH) AND a.supplierFk IS NOT NULL + GROUP BY r.id + ) a` + ); + + stmt.merge(conn.makeSuffix(filter)); + + const agencyTerm = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + const models = Self.app.models; + for (let agencyTerm of result) + agencyTerm.route = await models.Route.findById(agencyTerm.routeFk); + + return agencyTerm === 0 ? result : result[agencyTerm]; + }; +}; + diff --git a/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js b/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js new file mode 100644 index 000000000..81686144d --- /dev/null +++ b/modules/route/back/methods/agency-term/specs/createInvoiceIn.spec.js @@ -0,0 +1,48 @@ +const models = require('vn-loopback/server/server').models; + +// Include after #3638 export database +xdescribe('AgencyTerm createInvoiceIn()', () => { + const rows = [ + { + routeFk: 2, + supplierFk: 1, + created: '2022-03-02T23:00:00.000Z', + totalPrice: 165 + } + ]; + const dms = [ + { + id: 6 + } + ]; + + it('should make an invoiceIn', async() => { + const tx = await models.AgencyTerm.beginTransaction({}); + const options = {transaction: tx}; + + try { + const invoiceInId = 10; + const invoiceInDueDayId = 11; + const invoiceInTaxId = 12; + + const oldInvoiceIn = await models.InvoiceIn.findById(invoiceInId, null, options); + const oldInvoiceInDueDay = await models.InvoiceInDueDay.findById(invoiceInDueDayId, null, options); + const oldInvoiceInTax = await models.InvoiceInTax.findById(invoiceInTaxId, null, options); + + await models.AgencyTerm.createInvoiceIn(rows, dms, options); + + const [newInvoiceIn] = await models.InvoiceIn.rawSql('SELECT MAX(id) id FROM invoiceIn', null, options); + const [newInvoiceInDueDay] = await models.InvoiceInDueDay.rawSql('SELECT MAX(id) id FROM invoiceInDueDay', null, options); + const [newInvoiceInTax] = await models.InvoiceInTax.rawSql('SELECT MAX(id) id FROM invoiceInTax', null, options); + + expect(newInvoiceIn.id).toBeGreaterThan(oldInvoiceIn.id); + expect(newInvoiceInDueDay.id).toBeGreaterThan(oldInvoiceInDueDay.id); + expect(newInvoiceInTax.id).toBeGreaterThan(oldInvoiceInTax.id); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/route/back/methods/agency-term/specs/filter.spec.js b/modules/route/back/methods/agency-term/specs/filter.spec.js new file mode 100644 index 000000000..69a7f987c --- /dev/null +++ b/modules/route/back/methods/agency-term/specs/filter.spec.js @@ -0,0 +1,26 @@ +const models = require('vn-loopback/server/server').models; + +describe('AgencyTerm filter()', () => { + const authUserId = 9; + + it('should return all the tickets matching the filter', async() => { + const tx = await models.AgencyTerm.beginTransaction({}); + + try { + const options = {transaction: tx}; + const filter = {}; + const ctx = {req: {accessToken: {userId: authUserId}}}; + + const agencyTerms = await models.AgencyTerm.filter(ctx, filter, options); + const firstAgencyTerm = agencyTerms[0]; + + expect(firstAgencyTerm.routeFk).toEqual(1); + expect(agencyTerms.length).toEqual(3); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/route/back/model-config.json b/modules/route/back/model-config.json index 0dfe1d02a..31aaad9f5 100644 --- a/modules/route/back/model-config.json +++ b/modules/route/back/model-config.json @@ -1,4 +1,10 @@ { + "AgencyTerm": { + "dataSource": "vn" + }, + "AgencyTermConfig": { + "dataSource": "vn" + }, "Route": { "dataSource": "vn" }, diff --git a/modules/route/back/models/agency-term-config.json b/modules/route/back/models/agency-term-config.json new file mode 100644 index 000000000..c94fc266b --- /dev/null +++ b/modules/route/back/models/agency-term-config.json @@ -0,0 +1,24 @@ +{ + "name": "AgencyTermConfig", + "base": "VnModel", + "options": { + "mysql": { + "table": "agencyTermConfig" + } + }, + "properties": { + "expenceFk": { + "type": "string", + "id": true + }, + "vatAccountSupported": { + "type": "string" + }, + "vatPercentage": { + "type": "number" + }, + "transaction": { + "type": "string" + } + } +} diff --git a/modules/route/back/models/agency-term.js b/modules/route/back/models/agency-term.js new file mode 100644 index 000000000..8a0eea655 --- /dev/null +++ b/modules/route/back/models/agency-term.js @@ -0,0 +1,4 @@ +module.exports = Self => { + require('../methods/agency-term/filter')(Self); + require('../methods/agency-term/createInvoiceIn')(Self); +}; diff --git a/modules/route/back/models/agency-term.json b/modules/route/back/models/agency-term.json new file mode 100644 index 000000000..4e13c2a18 --- /dev/null +++ b/modules/route/back/models/agency-term.json @@ -0,0 +1,37 @@ +{ + "name": "AgencyTerm", + "base": "VnModel", + "options": { + "mysql": { + "table": "agencyTerm" + } + }, + "properties": { + "agencyFk": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "minimumPackages": { + "type": "number" + }, + "kmPrice": { + "type": "number" + }, + "packagePrice": { + "type": "number" + }, + "routePrice": { + "type": "number" + }, + "minimumKm": { + "type": "number" + }, + "minimumM3": { + "type": "number" + }, + "m3Price": { + "type": "number" + } + } +} diff --git a/modules/route/front/agency-term/createInvoiceIn/index.html b/modules/route/front/agency-term/createInvoiceIn/index.html new file mode 100644 index 000000000..8f270378f --- /dev/null +++ b/modules/route/front/agency-term/createInvoiceIn/index.html @@ -0,0 +1,108 @@ + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
diff --git a/modules/route/front/agency-term/createInvoiceIn/index.js b/modules/route/front/agency-term/createInvoiceIn/index.js new file mode 100644 index 000000000..0198ab80f --- /dev/null +++ b/modules/route/front/agency-term/createInvoiceIn/index.js @@ -0,0 +1,120 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; +import UserError from 'core/lib/user-error'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + this.dms = { + files: [], + hasFile: false, + hasFileAttached: false + }; + } + + get route() { + return this._route; + } + + set route(value) { + this._route = value; + + this.setDefaultParams(); + this.getAllowedContentTypes(); + } + + $onChanges() { + if (this.$params && this.$params.q) + this.params = JSON.parse(this.$params.q); + } + + getAllowedContentTypes() { + this.$http.get('DmsContainers/allowedContentTypes').then(res => { + const contentTypes = res.data.join(', '); + this.allowedContentTypes = contentTypes; + }); + } + + get contentTypesInfo() { + return this.$t('ContentTypesInfo', { + allowedContentTypes: this.allowedContentTypes + }); + } + + setDefaultParams() { + const params = {filter: { + where: {code: 'invoiceIn'} + }}; + this.$http.get('DmsTypes/findOne', {params}).then(res => { + const dmsType = res.data && res.data; + const companyId = this.vnConfig.companyFk; + const warehouseId = this.vnConfig.warehouseFk; + const defaultParams = { + warehouseId: warehouseId, + companyId: companyId, + dmsTypeId: dmsType.id, + description: this.params.supplierName + }; + + this.dms = Object.assign(this.dms, defaultParams); + }); + } + + onSubmit() { + if (this.dms.files.length > 1) throw new UserError('You cannot attach more than one document'); + const query = `dms/uploadFile`; + const options = { + method: 'POST', + url: query, + params: this.dms, + headers: { + 'Content-Type': undefined + }, + transformRequest: files => { + const formData = new FormData(); + formData.append(files[0].name, files[0]); + return formData; + }, + data: this.dms.files + }; + this.$http(options).then(res => { + if (res) { + const addedDms = res.data; + this.$.watcher.updateOriginalData(); + + const params = { + rows: this.params.rows, + dms: addedDms + }; + + this.$http.post('AgencyTerms/createInvoiceIn', params) + .then(() => { + this.$state.go('route.agencyTerm.index'); + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + }); + } + + onFileChange(files) { + let hasFileAttached = false; + + if (files.length > 0) + hasFileAttached = true; + + this.$.$applyAsync(() => { + this.dms.hasFileAttached = hasFileAttached; + }); + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnAgencyTermCreateInvoiceIn', { + template: require('./index.html'), + controller: Controller, + bindings: { + route: '<' + } +}); diff --git a/modules/route/front/agency-term/createInvoiceIn/index.spec.js b/modules/route/front/agency-term/createInvoiceIn/index.spec.js new file mode 100644 index 000000000..d6d9883a7 --- /dev/null +++ b/modules/route/front/agency-term/createInvoiceIn/index.spec.js @@ -0,0 +1,107 @@ +import './index'; +import watcher from 'core/mocks/watcher.js'; + +describe('AgencyTerm', () => { + describe('Component vnAgencyTermCreateInvoiceIn', () => { + let controller; + let $scope; + let $httpBackend; + let $httpParamSerializer; + + beforeEach(ngModule('route')); + + beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$httpParamSerializer_) => { + $scope = $rootScope.$new(); + $httpBackend = _$httpBackend_; + $httpParamSerializer = _$httpParamSerializer_; + const $element = angular.element(''); + controller = $componentController('vnAgencyTermCreateInvoiceIn', {$element}); + controller._route = { + id: 1 + }; + })); + + describe('$onChanges()', () => { + it('should update the params data when $params.q is defined', () => { + controller.$params = {q: '{"supplierName": "Plants SL","rows": null}'}; + + const params = {q: '{"supplierName": "Plants SL", "rows": null}'}; + const json = JSON.parse(params.q); + + controller.$onChanges(); + + expect(controller.params).toEqual(json); + }); + }); + + describe('route() setter', () => { + it('should set the ticket data and then call setDefaultParams() and getAllowedContentTypes()', () => { + jest.spyOn(controller, 'setDefaultParams'); + jest.spyOn(controller, 'getAllowedContentTypes'); + controller.route = { + id: 1 + }; + + expect(controller.route).toBeDefined(); + expect(controller.setDefaultParams).toHaveBeenCalledWith(); + expect(controller.getAllowedContentTypes).toHaveBeenCalledWith(); + }); + }); + + describe('getAllowedContentTypes()', () => { + it('should make an HTTP GET request to get the allowed content types', () => { + const expectedResponse = ['image/png', 'image/jpg']; + $httpBackend.expect('GET', `DmsContainers/allowedContentTypes`).respond(expectedResponse); + controller.getAllowedContentTypes(); + $httpBackend.flush(); + + expect(controller.allowedContentTypes).toBeDefined(); + expect(controller.allowedContentTypes).toEqual('image/png, image/jpg'); + }); + }); + + describe('setDefaultParams()', () => { + it('should perform a GET query and define the dms property on controller', () => { + const params = {filter: { + where: {code: 'invoiceIn'} + }}; + const serializedParams = $httpParamSerializer(params); + $httpBackend.expect('GET', `DmsTypes/findOne?${serializedParams}`).respond({id: 1, code: 'invoiceIn'}); + controller.params = {supplierName: 'Plants SL'}; + controller.setDefaultParams(); + $httpBackend.flush(); + + expect(controller.dms).toBeDefined(); + expect(controller.dms.dmsTypeId).toEqual(1); + }); + }); + + describe('onSubmit()', () => { + it('should make an HTTP POST request to save the form data', () => { + controller.$.watcher = watcher; + + jest.spyOn(controller.$.watcher, 'updateOriginalData'); + const files = [{id: 1, name: 'MyFile'}]; + controller.dms = {files}; + const serializedParams = $httpParamSerializer(controller.dms); + const query = `dms/uploadFile?${serializedParams}`; + controller.params = {rows: null}; + + $httpBackend.expect('POST', query).respond({}); + $httpBackend.expect('POST', 'AgencyTerms/createInvoiceIn').respond({}); + controller.onSubmit(); + $httpBackend.flush(); + }); + }); + + describe('onFileChange()', () => { + it('should set dms hasFileAttached property to true if has any files', () => { + const files = [{id: 1, name: 'MyFile'}]; + controller.onFileChange(files); + $scope.$apply(); + + expect(controller.dms.hasFileAttached).toBeTruthy(); + }); + }); + }); +}); diff --git a/modules/route/front/agency-term/createInvoiceIn/style.scss b/modules/route/front/agency-term/createInvoiceIn/style.scss new file mode 100644 index 000000000..73f136fc1 --- /dev/null +++ b/modules/route/front/agency-term/createInvoiceIn/style.scss @@ -0,0 +1,7 @@ +vn-ticket-request { + .vn-textfield { + margin: 0!important; + max-width: 100px; + } +} + diff --git a/modules/route/front/agency-term/index/index.html b/modules/route/front/agency-term/index/index.html new file mode 100644 index 000000000..bb18af250 --- /dev/null +++ b/modules/route/front/agency-term/index/index.html @@ -0,0 +1,143 @@ + + + + + +
+
+
Total
+ + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + Id + + Date + + Agency route + + Agency Agreement + + Packages + + M3 + + Km + + Price + + Received + + Autonomous +
+ + + + + {{::agencyTerm.routeFk}} + + {{::agencyTerm.created | date:'dd/MM/yyyy'}}{{::agencyTerm.agencyModeName | dashIfEmpty}}{{::agencyTerm.agencyAgreement | dashIfEmpty}}{{::agencyTerm.packages | dashIfEmpty}}{{::agencyTerm.m3 | dashIfEmpty}}{{::agencyTerm.kmTotal | dashIfEmpty}}{{::agencyTerm.price | dashIfEmpty}} + + {{::agencyTerm.invoiceInFk}} + + + + {{::agencyTerm.supplierName}} + + + + +
+
+
+
+ + + + + + + + + + + + + +
+ + + + +
diff --git a/modules/route/front/agency-term/index/index.js b/modules/route/front/agency-term/index/index.js new file mode 100644 index 000000000..ece7f18da --- /dev/null +++ b/modules/route/front/agency-term/index/index.js @@ -0,0 +1,122 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.smartTableOptions = { + activeButtons: { + search: true + }, + columns: [ + { + field: 'agencyFk', + autocomplete: { + url: 'AgencyModes', + showField: 'name', + valueField: 'name' + } + }, + { + field: 'agencyAgreement', + autocomplete: { + url: 'Agencies', + showField: 'name', + valueField: 'name' + } + }, + { + field: 'supplierFk', + autocomplete: { + url: 'Suppliers', + showField: 'name', + valueField: 'name', + } + } + ] + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'agencyFk': + return {'a.agencyModeName': value}; + case 'supplierFk': + return {'a.supplierName': value}; + case 'routeFk': + return {'a.routeFk': value}; + case 'created': + case 'agencyAgreement': + case 'packages': + case 'm3': + case 'kmTotal': + case 'price': + case 'invoiceInFk': + return {[`a.${param}`]: value}; + } + } + + get checked() { + const agencyTerms = this.$.model.data || []; + const checkedAgencyTerms = []; + for (let agencyTerm of agencyTerms) { + if (agencyTerm.checked) + checkedAgencyTerms.push(agencyTerm); + } + + return checkedAgencyTerms; + } + + get totalChecked() { + return this.checked.length; + } + + get totalPrice() { + let totalPrice = 0; + + if (this.checked.length > 0) { + for (let agencyTerm of this.checked) + totalPrice += agencyTerm.price; + + return totalPrice; + } + + return totalPrice; + } + + preview(route) { + this.routeSelected = route; + this.$.summary.show(); + } + + createInvoiceIn() { + const rowsToCreateInvoiceIn = []; + const supplierFk = this.checked[0].supplierFk; + + for (let agencyTerm of this.checked) { + let hasSameSupplier = supplierFk == agencyTerm.supplierFk; + if (hasSameSupplier) { + rowsToCreateInvoiceIn.push({ + routeFk: agencyTerm.routeFk, + supplierFk: agencyTerm.supplierFk, + created: agencyTerm.created, + totalPrice: this.totalPrice}); + } else { + this.vnApp.showError(this.$t('Two autonomous cannot be counted at the same time')); + return false; + } + } + const params = JSON.stringify({ + supplierName: this.checked[0].supplierName, + rows: rowsToCreateInvoiceIn + }); + this.$state.go('route.agencyTerm.createInvoiceIn', {q: params}); + } +} + +ngModule.vnComponent('vnAgencyTermIndex', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/route/front/agency-term/index/index.spec.js b/modules/route/front/agency-term/index/index.spec.js new file mode 100644 index 000000000..55c40daa5 --- /dev/null +++ b/modules/route/front/agency-term/index/index.spec.js @@ -0,0 +1,94 @@ +import './index.js'; +import crudModel from 'core/mocks/crud-model'; + +describe('AgencyTerm', () => { + describe('Component vnAgencyTermIndex', () => { + let controller; + let $window; + + beforeEach(ngModule('route')); + + beforeEach(inject(($componentController, _$window_) => { + $window = _$window_; + const $element = angular.element(''); + controller = $componentController('vnAgencyTermIndex', {$element}); + controller.$.model = crudModel; + controller.$.model.data = [ + {supplierFk: 1, totalPrice: null}, + {supplierFk: 1}, + {supplierFk: 2} + ]; + })); + + describe('checked() getter', () => { + it('should return the checked lines', () => { + const data = controller.$.model.data; + data[0].checked = true; + + const checkedRows = controller.checked; + + const firstCheckedRow = checkedRows[0]; + + expect(firstCheckedRow.supplierFk).toEqual(1); + }); + }); + + describe('totalCheked() getter', () => { + it('should return the total checked lines', () => { + const data = controller.$.model.data; + data[0].checked = true; + + const checkedRows = controller.totalChecked; + + expect(checkedRows).toEqual(1); + }); + }); + + describe('preview()', () => { + it('should show the summary dialog', () => { + controller.$.summary = {show: () => {}}; + jest.spyOn(controller.$.summary, 'show'); + + let event = new MouseEvent('click', { + view: $window, + bubbles: true, + cancelable: true + }); + const route = {id: 1}; + + controller.preview(event, route); + + expect(controller.$.summary.show).toHaveBeenCalledWith(); + }); + }); + + describe('createInvoiceIn()', () => { + it('should throw an error if more than one autonomous are checked', () => { + jest.spyOn(controller.vnApp, 'showError'); + const data = controller.$.model.data; + data[0].checked = true; + data[2].checked = true; + + controller.createInvoiceIn(); + + expect(controller.vnApp.showError).toHaveBeenCalled(); + }); + + it('should call the function go() on $state to go to the file management', () => { + jest.spyOn(controller.$state, 'go'); + const data = controller.$.model.data; + data[0].checked = true; + + controller.createInvoiceIn(); + + delete data[0].checked; + const params = JSON.stringify({ + supplierName: data[0].supplierName, + rows: [data[0]] + }); + + expect(controller.$state.go).toHaveBeenCalledWith('route.agencyTerm.createInvoiceIn', {q: params}); + }); + }); + }); +}); diff --git a/modules/route/front/agency-term/index/style.scss b/modules/route/front/agency-term/index/style.scss new file mode 100644 index 000000000..eaa1a16ed --- /dev/null +++ b/modules/route/front/agency-term/index/style.scss @@ -0,0 +1,32 @@ +@import "variables"; + +vn-item-product { + display: block; + + .id { + background-color: $color-main; + color: $color-font-dark; + margin-bottom: 0; + } + .image { + height: 112px; + width: 112px; + + & > img { + max-height: 100%; + max-width: 100%; + border-radius: 3px; + } + } + vn-label-value:first-of-type section{ + margin-top: 9px; + } +} + +table { + img { + border-radius: 50%; + width: 50px; + height: 50px; + } +} \ No newline at end of file diff --git a/modules/route/front/agency-term/locale/es.yml b/modules/route/front/agency-term/locale/es.yml new file mode 100644 index 000000000..0f6797188 --- /dev/null +++ b/modules/route/front/agency-term/locale/es.yml @@ -0,0 +1,5 @@ +Agency route: Agencia ruta +Agency Agreement: Acuerdo agencia +Autonomous: Autónomos +Two autonomous cannot be counted at the same time: Dos autonónomos no pueden ser contabilizados al mismo tiempo +You cannot attach more than one document: No puedes adjuntar más de un documento \ No newline at end of file diff --git a/modules/route/front/index.js b/modules/route/front/index.js index 10ccb0634..d5becd8dd 100644 --- a/modules/route/front/index.js +++ b/modules/route/front/index.js @@ -11,4 +11,6 @@ import './create'; import './basic-data'; import './log'; import './tickets'; +import './agency-term/index'; +import './agency-term/createInvoiceIn'; import './ticket-popup'; diff --git a/modules/route/front/routes.json b/modules/route/front/routes.json index beb444abc..0d66a5697 100644 --- a/modules/route/front/routes.json +++ b/modules/route/front/routes.json @@ -3,10 +3,11 @@ "name": "Routes", "icon": "icon-delivery", "validations" : true, - "dependencies": ["client", "worker", "ticket"], + "dependencies": ["client", "worker", "ticket", "supplier", "invoiceIn"], "menus": { "main": [ - {"state": "route.index", "icon": "icon-delivery"} + {"state": "route.index", "icon": "icon-delivery"}, + {"state": "route.agencyTerm.index", "icon": "contact_support"} ], "card": [ {"state": "route.card.basicData", "icon": "settings"}, @@ -37,6 +38,26 @@ "state": "route.card", "abstract": true, "component": "vn-route-card" + }, { + "url": "/agency-term", + "abstract": true, + "state": "route.agencyTerm", + "component": "ui-view" + }, { + "url": "/index", + "state": "route.agencyTerm.index", + "component": "vn-agency-term-index", + "description": "Autonomous", + "acl": ["administrative"] + },{ + "url": "/createInvoiceIn?q", + "state": "route.agencyTerm.createInvoiceIn", + "component": "vn-agency-term-create-invoice-in", + "description": "File management", + "params": { + "route": "$ctrl.route" + }, + "acl": ["administrative"] }, { "url": "/summary", "state": "route.card.summary", diff --git a/modules/ticket/back/methods/ticket/componentUpdate.js b/modules/ticket/back/methods/ticket/componentUpdate.js index 2294e6d25..d9fe259ea 100644 --- a/modules/ticket/back/methods/ticket/componentUpdate.js +++ b/modules/ticket/back/methods/ticket/componentUpdate.js @@ -116,8 +116,8 @@ module.exports = Self => { if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); - const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions); - if (!isProductionBoss) { + const isDeliveryBoss = await models.Account.hasRole(userId, 'deliveryBoss', myOptions); + if (!isDeliveryBoss) { const zoneShipped = await models.Agency.getShipped( args.landed, args.addressFk, diff --git a/modules/ticket/back/methods/ticket/isEditable.js b/modules/ticket/back/methods/ticket/isEditable.js index a444691ac..5b9a397a1 100644 --- a/modules/ticket/back/methods/ticket/isEditable.js +++ b/modules/ticket/back/methods/ticket/isEditable.js @@ -31,10 +31,10 @@ module.exports = Self => { }, myOptions); const isSalesAssistant = await Self.app.models.Account.hasRole(userId, 'salesAssistant', myOptions); - const isProductionBoss = await Self.app.models.Account.hasRole(userId, 'productionBoss', myOptions); + const isDeliveryBoss = await Self.app.models.Account.hasRole(userId, 'deliveryBoss', myOptions); const isBuyer = await Self.app.models.Account.hasRole(userId, 'buyer', myOptions); - const isValidRole = isSalesAssistant || isProductionBoss || isBuyer; + const isValidRole = isSalesAssistant || isDeliveryBoss || isBuyer; let alertLevel = state ? state.alertLevel : null; let ticket = await Self.app.models.Ticket.findById(id, { diff --git a/modules/ticket/back/methods/ticket/priceDifference.js b/modules/ticket/back/methods/ticket/priceDifference.js index e0ffac55a..0e8cc2e06 100644 --- a/modules/ticket/back/methods/ticket/priceDifference.js +++ b/modules/ticket/back/methods/ticket/priceDifference.js @@ -78,8 +78,8 @@ module.exports = Self => { if (!isEditable) throw new UserError(`The sales of this ticket can't be modified`); - const isProductionBoss = await models.Account.hasRole(userId, 'productionBoss', myOptions); - if (!isProductionBoss) { + const isDeliveryBoss = await models.Account.hasRole(userId, 'deliveryBoss', myOptions); + if (!isDeliveryBoss) { const zoneShipped = await models.Agency.getShipped( args.landed, args.addressId, diff --git a/modules/zone/front/delivery-days/index.js b/modules/zone/front/delivery-days/index.js index 21c65678f..e4d5e72e9 100644 --- a/modules/zone/front/delivery-days/index.js +++ b/modules/zone/front/delivery-days/index.js @@ -69,14 +69,14 @@ class Controller extends Section { if (!$events.length) return; const day = $days[0]; - const zonesIds = []; + const zoneIds = []; for (let event of $events) - zonesIds.push(event.zoneFk); + zoneIds.push(event.zoneFk); this.$.zoneEvents.show($event.target); const params = { - zonesId: zonesIds, + zoneIds: zoneIds, date: day }; diff --git a/modules/zone/front/delivery-days/index.spec.js b/modules/zone/front/delivery-days/index.spec.js index 3d71bc93f..63c87fbea 100644 --- a/modules/zone/front/delivery-days/index.spec.js +++ b/modules/zone/front/delivery-days/index.spec.js @@ -110,7 +110,7 @@ describe('Zone Component vnZoneDeliveryDays', () => { {zoneFk: 8} ]; const params = { - zonesId: [1, 2, 8], + zoneIds: [1, 2, 8], date: [day][0] }; const response = [{id: 1, hour: ''}]; diff --git a/storage/pdfs/invoice/.keep b/storage/pdfs/invoice/.keep new file mode 100644 index 000000000..e69de29bb