diff --git a/CHANGELOG.md b/CHANGELOG.md index fb4ccc91b4..2b3c43005b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,17 +6,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -## [2334.01] - 2023-08-24 +## [2336.01] - 2023-09-07 ### Added -- (General -> Errores) Botón para enviar cau con los datos del error - ### Changed ### Fixed +## [2334.01] - 2023-08-24 + +### Added +- (General -> Errores) Botón para enviar cau con los datos del error + + ## [2332.01] - 2023-08-10 ### Added @@ -46,9 +50,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - (Clientes -> Razón social) Permite crear clientes con la misma razón social según el país -### Fixed - - ## [2328.01] - 2023-07-13 ### Added diff --git a/db/changes/233001/00-aclSaleTracking.sql b/db/changes/233001/00-aclSaleTracking.sql new file mode 100644 index 0000000000..6a699091a0 --- /dev/null +++ b/db/changes/233001/00-aclSaleTracking.sql @@ -0,0 +1,4 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('SaleTracking', 'deleteSaleGroupDetail', 'WRITE', 'ALLOW', 'ROLE', 'employee'), + ('SaleTracking', 'replaceOrCreate', 'WRITE', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/233601/.gitkeep b/db/changes/233601/.gitkeep new file mode 100644 index 0000000000..e69de29bb2 diff --git a/db/changes/233601/00-aclWorker.sql b/db/changes/233601/00-aclWorker.sql new file mode 100644 index 0000000000..e79d8f7384 --- /dev/null +++ b/db/changes/233601/00-aclWorker.sql @@ -0,0 +1,3 @@ +INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) + VALUES + ('Worker', 'search', 'READ', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/233601/00-addExpeditionState.sql b/db/changes/233601/00-addExpeditionState.sql new file mode 100644 index 0000000000..fb236b0c38 --- /dev/null +++ b/db/changes/233601/00-addExpeditionState.sql @@ -0,0 +1,2 @@ +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalId`) + VALUES ('ExpeditionState','addExpeditionState','WRITE','ALLOW','ROLE','delivery'); \ No newline at end of file diff --git a/db/changes/233601/00-createClaimReader.sql b/db/changes/233601/00-createClaimReader.sql new file mode 100644 index 0000000000..666bf232e3 --- /dev/null +++ b/db/changes/233601/00-createClaimReader.sql @@ -0,0 +1,32 @@ +INSERT INTO `account`.`role` (`id`, `name`, `description`, `hasLogin`) + VALUES ('claimViewer','Trabajadores que consulta las reclamaciones ',1); + +INSERT INTO `account`.`roleInherit` (`role`,`inheritsFrom`) + SELECT `r`.`id`, `r2`.`id` + FROM `account`.`role` `r` + JOIN `account`.`role` `r2` ON `r2`.`name` = 'claimViewer' + WHERE `r`.`name` IN ( + 'salesPerson', + 'buyer', + 'deliveryBoss', + 'handmadeBoss' + ) + +DELETE FROM `salix`.`ACL` + WHERE `model`= 'claim' + AND `property` IN ( + 'filter', + 'find', + 'findById', + 'getSummary' + ); + +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) + VALUES ('Claim','filter','READ','ALLOW','ROLE','claimViewer'); +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) + VALUES ('Claim','find','READ','ALLOW','ROLE','claimViewer'); +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) + VALUES ('Claim','findById','READ','ALLOW','ROLE','claimViewer'); +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalType`,`principalid`) + VALUES ('Claim','getSummary','READ','ALLOW','ROLE','claimViewer'); + diff --git a/db/changes/233601/00-department.sql b/db/changes/233601/00-department.sql new file mode 100644 index 0000000000..777d26b9f7 --- /dev/null +++ b/db/changes/233601/00-department.sql @@ -0,0 +1,3 @@ +UPDATE `vn`.`department` + SET code='VN' +WHERE name='VERDNATURA'; diff --git a/db/changes/233601/00-saleGroupDetail.sql b/db/changes/233601/00-saleGroupDetail.sql new file mode 100644 index 0000000000..d164787164 --- /dev/null +++ b/db/changes/233601/00-saleGroupDetail.sql @@ -0,0 +1,7 @@ +DELETE FROM `vn`.`saleGroupDetail` WHERE id IN (468106,468104,468107,468105,495210,495208,495207,495209,462879,462880,447186,450623,450622,455606,455605,455827,455829,455828,459067,460689,460691,460690,460692,462408,463403,463405,463404,463129,463127,463126,463128,468098,468096,468099,468097,468310,468314,468313,475654,468325,473248,474803,474739,475042,475052,475047,475041,475051,475046,475040,475050,475045,475039,475049,475044,475038,475048,475043,474888,474892,474890,474887,474891,474889,481109,481107,481105,481108,481106,481110,479008,490787,490792,490791,485295,485294,485293,485528,490796,487853,487959,491303,490789,490914,490913,492305,492310,492307,492304,492309,492306,492303,492308,494111,494110,494480,494482,494481,494483,495202,495200,495199,495201,497209,499765,499763,499767,499764,499768,499766,502014,502013,508820,508819,508818,463133,463131,463130,463132,468102,468100,468103,468101,468311,468316,468315,468327,474894,474898,474896,474893,474897,474895,495206,495204,495203,495205,499771,499769,499773,499770,499774,499772); +ALTER TABLE `vn`.`saleGroupDetail` ADD CONSTRAINT saleGroupDetail_UN UNIQUE KEY (saleFk); + +INSERT INTO `salix`.`ACL` (`model`,`property`,`accessType`,`permission`,`principalId`) + VALUES + ('SaleGroupDetail','deleteById','WRITE','ALLOW','employee'); + diff --git a/db/export-data.sh b/db/export-data.sh index 59edf6ebcb..7d346b235e 100755 --- a/db/export-data.sh +++ b/db/export-data.sh @@ -97,13 +97,6 @@ TABLES=( ) dump_tables ${TABLES[@]} -TABLES=( - postgresql - labour_agreement - media_type -) -dump_tables ${TABLES[@]} - TABLES=( sage TiposIva diff --git a/db/export-structure.sh b/db/export-structure.sh index a6888f2ac8..4ecbfa7e8c 100755 --- a/db/export-structure.sh +++ b/db/export-structure.sh @@ -7,7 +7,6 @@ SCHEMAS=( edi hedera pbx - postgresql sage salix stock @@ -23,7 +22,6 @@ IGNORETABLES=( --ignore-table=bs.productionIndicators --ignore-table=bs.VentasPorCliente --ignore-table=bs.v_ventas - --ignore-table=postgresql.currentWorkersStats --ignore-table=vn.accounting__ --ignore-table=vn.agencyModeZone --ignore-table=vn.agencyProvince diff --git a/db/tests/vn/workerTimeControlCheck.spec.js b/db/tests/vn/workerTimeControlCheck.spec.js index 9140628e62..0ca1429d4a 100644 --- a/db/tests/vn/workerTimeControlCheck.spec.js +++ b/db/tests/vn/workerTimeControlCheck.spec.js @@ -54,7 +54,6 @@ xdescribe('worker workerTimeControl_check()', () => { }); it('should throw an error if the worker with a special category has not finished the 9h break', async() => { - // dayBreak to 9h in postgresql.professional_category const workerId = 1110; const tabletId = 1; let stmts = []; @@ -91,7 +90,6 @@ xdescribe('worker workerTimeControl_check()', () => { }); it('should check f the worker with a special category has finished the 9h break', async() => { - // dayBreak to 9h in postgresql.professional_category const workerId = 1110; const tabletId = 1; let stmts = []; @@ -239,12 +237,6 @@ xdescribe('worker workerTimeControl_check()', () => { stmts.push('START TRANSACTION'); - stmt = new ParameterizedSQL(`INSERT INTO postgresql.calendar_employee(businessFk,calendar_state_id,date) - VALUES - (?,1,CURDATE())`, [ - workerId - ]); - stmts.push(stmt); stmt = new ParameterizedSQL(`INSERT INTO vn.workerTimeControl(userFk,timed,manual,direction) VALUES (?,TIMESTAMPADD(HOUR,-24,NOW()),0,"in"), diff --git a/e2e/helpers/extensions.js b/e2e/helpers/extensions.js index fe3ef08bd6..b6ea32a6cb 100644 --- a/e2e/helpers/extensions.js +++ b/e2e/helpers/extensions.js @@ -632,6 +632,7 @@ let actions = { await this.write(selector, value.toString()); break; case 'vn-autocomplete': + case 'vn-worker-autocomplete': if (value) await this.autocompleteSearch(selector, value.toString()); else @@ -667,6 +668,7 @@ let actions = { switch (tagName) { case 'vn-textfield': case 'vn-autocomplete': + case 'vn-worker-autocomplete': case 'vn-input-time': case 'vn-datalist': el = await input.$('input'); diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index 93288efa76..fe3633723d 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -187,7 +187,7 @@ export default { country: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.countryFk"]', userName: 'vn-client-create vn-textfield[ng-model="$ctrl.client.userName"]', email: 'vn-client-create vn-textfield[ng-model="$ctrl.client.email"]', - salesPerson: 'vn-client-create vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', + salesPerson: 'vn-client-create vn-worker-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', saveNewProvicenButton: '#saveProvince', saveNewCityButton: '#saveCity', saveNewPoscode: '#savePostcode', @@ -199,7 +199,7 @@ export default { email: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.email"]', phone: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.phone"]', mobile: 'vn-client-basic-data vn-textfield[ng-model="$ctrl.client.mobile"]', - salesPerson: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', + salesPerson: 'vn-client-basic-data vn-worker-autocomplete[ng-model="$ctrl.client.salesPersonFk"]', channel: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.contactChannelFk"]', transferor: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.transferorFk"]', businessType: 'vn-client-basic-data vn-autocomplete[ng-model="$ctrl.client.businessTypeFk"]', @@ -735,7 +735,7 @@ export default { }, createStateView: { state: 'vn-autocomplete[ng-model="$ctrl.stateFk"]', - worker: 'vn-autocomplete[ng-model="$ctrl.workerFk"]', + worker: 'vn-worker-autocomplete[ng-model="$ctrl.workerFk"]', saveStateButton: `button[type=submit]` }, claimsIndex: { @@ -781,12 +781,12 @@ export default { firstClaimReason: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]', firstClaimResult: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]', firstClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]', - firstClaimWorker: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.workerFk"]', + firstClaimWorker: 'vn-claim-development vn-horizontal:nth-child(1) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]', firstClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(1) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]', secondClaimReason: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimReasonFk"]', secondClaimResult: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResultFk"]', secondClaimResponsible: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimResponsibleFk"]', - secondClaimWorker: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.workerFk"]', + secondClaimWorker: 'vn-claim-development vn-horizontal:nth-child(2) vn-worker-autocomplete[ng-model="claimDevelopment.workerFk"]', secondClaimRedelivery: 'vn-claim-development vn-horizontal:nth-child(2) vn-autocomplete[ng-model="claimDevelopment.claimRedeliveryFk"]', saveDevelopmentButton: 'button[type=submit]' }, @@ -854,7 +854,7 @@ export default { }, createRouteView: { - worker: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.workerFk"]', + worker: 'vn-route-create vn-worker-autocomplete[ng-model="$ctrl.route.workerFk"]', createdDatePicker: 'vn-route-create vn-date-picker[ng-model="$ctrl.route.created"]', vehicleAuto: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.vehicleFk"]', agency: 'vn-route-create vn-autocomplete[ng-model="$ctrl.route.agencyModeFk"]', @@ -976,7 +976,7 @@ export default { street: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.street"]', user: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.name"]', email: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.email"]', - boss: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.bossFk"]', + boss: 'vn-worker-create vn-worker-autocomplete[ng-model="$ctrl.worker.bossFk"]', role: 'vn-worker-create vn-autocomplete[ng-model="$ctrl.worker.roleFk"]', iban: 'vn-worker-create vn-textfield[ng-model="$ctrl.worker.iban"]', createButton: 'vn-worker-create vn-submit[label="Create"]', diff --git a/e2e/paths/05-ticket/05_tracking_state.spec.js b/e2e/paths/05-ticket/05_tracking_state.spec.js index 949c9a8e6a..9ac3732872 100644 --- a/e2e/paths/05-ticket/05_tracking_state.spec.js +++ b/e2e/paths/05-ticket/05_tracking_state.spec.js @@ -59,7 +59,7 @@ describe('Ticket Create new tracking state path', () => { const result = await page .waitToGetProperty(selectors.createStateView.worker, 'value'); - expect(result).toEqual('salesPersonNick'); + expect(result).toEqual('salesPerson'); }); it(`should succesfully create a valid state`, async() => { diff --git a/front/core/components/autocomplete/index.html b/front/core/components/autocomplete/index.html index 69aac4d438..527ca77e72 100755 --- a/front/core/components/autocomplete/index.html +++ b/front/core/components/autocomplete/index.html @@ -18,7 +18,7 @@ +
- \ No newline at end of file + diff --git a/front/core/components/autocomplete/index.js b/front/core/components/autocomplete/index.js index 2539c4ef49..c78c293984 100755 --- a/front/core/components/autocomplete/index.js +++ b/front/core/components/autocomplete/index.js @@ -17,10 +17,9 @@ import './style.scss'; * @event change Thrown when value is changed */ export default class Autocomplete extends Field { - constructor($element, $, $compile, $transclude) { - super($element, $, $compile); + constructor($element, $, $transclude) { + super($element, $, $transclude); this.$transclude = $transclude; - this.$compile = $compile; this._selection = null; this.input = this.element.querySelector('input'); } @@ -153,7 +152,14 @@ export default class Autocomplete extends Field { filter.include = this.include; let json = encodeURIComponent(JSON.stringify(filter)); - this.$http.get(`${this.url}?filter=${json}`).then( + + let url; + if (this.url.includes('?')) + url = `${this.url}&filter=${json}`; + else + url = `${this.url}?filter=${json}`; + + this.$http.get(url).then( json => this.onSelectionRequest(json.data), () => this.onSelectionRequest() ); @@ -282,7 +288,7 @@ export default class Autocomplete extends Field { this.refreshSelection(); } } -Autocomplete.$inject = ['$element', '$scope', '$compile', '$transclude']; +Autocomplete.$inject = ['$element', '$scope', '$transclude']; ngModule.vnComponent('vnAutocomplete', { template: require('./index.html'), diff --git a/front/core/components/drop-down/index.html b/front/core/components/drop-down/index.html index ab868f7b5b..692b0c87c3 100644 --- a/front/core/components/drop-down/index.html +++ b/front/core/components/drop-down/index.html @@ -13,12 +13,12 @@ class="dropdown" ng-click="$ctrl.onContainerClick($event)"> -
{{$ctrl.statusText}}
- \ No newline at end of file + diff --git a/front/core/components/field/index.js b/front/core/components/field/index.js index 7ce8405553..ff92b6b872 100644 --- a/front/core/components/field/index.js +++ b/front/core/components/field/index.js @@ -3,8 +3,8 @@ import FormInput from '../form-input'; import './style.scss'; export default class Field extends FormInput { - constructor($element, $scope) { - super($element, $scope); + constructor($element, $scope, $transclude) { + super($element, $scope, $transclude); this.prefix = null; this.suffix = null; @@ -197,7 +197,7 @@ export default class Field extends FormInput { }); } } -Field.$inject = ['$element', '$scope']; +Field.$inject = ['$element', '$scope', '$transclude']; ngModule.vnComponent('vnField', { template: require('./index.html'), diff --git a/front/core/components/index.js b/front/core/components/index.js index e1b58374ef..eb40d5e1fe 100644 --- a/front/core/components/index.js +++ b/front/core/components/index.js @@ -51,6 +51,7 @@ import './textarea'; import './th'; import './treeview'; import './wday-picker'; +import './worker-autocomplete'; import './datalist'; import './contextmenu'; import './rating'; diff --git a/front/core/components/worker-autocomplete/index.html b/front/core/components/worker-autocomplete/index.html new file mode 100755 index 0000000000..95631e8aae --- /dev/null +++ b/front/core/components/worker-autocomplete/index.html @@ -0,0 +1,8 @@ + +
+ {{name}} +
+
+ {{nickname}}, {{code}} +
+
diff --git a/front/core/components/worker-autocomplete/index.js b/front/core/components/worker-autocomplete/index.js new file mode 100755 index 0000000000..70ca69e6e1 --- /dev/null +++ b/front/core/components/worker-autocomplete/index.js @@ -0,0 +1,40 @@ +import ngModule from '../../module'; +import Autocomplete from '../autocomplete'; + +export default class WorkerAutocomplete extends Autocomplete { + constructor(...args) { + super(...args); + } + + $onInit() { + super.$onInit(); + + let url = 'Workers/search'; + if (this.departments) { + const parameter = encodeURIComponent(JSON.stringify(this.departments)); + url = `Workers/search?departmentCodes=${parameter}`; + } + Object.assign(this, { + label: 'Worker', + url, + searchFunction: function({$search}) { + return {and: [ + {'active': {neq: false}}, + {or: [ + {'name': $search}, + {'nickname': {like: '%' + $search + '%'}}, + {'code': {like: $search + '%'}} + ]} + ]}; + }, + }); + } +} + +ngModule.vnComponent('vnWorkerAutocomplete', { + slotTemplate: require('./index.html'), + controller: WorkerAutocomplete, + bindings: { + departments: ' { - it('should return false for non-IBAN input', () => { - let isValid = validateIban('Pepinillos'); + it('should return false for invalid Spanish IBAN format', () => { + let isValid = validateIban('ES00 9999 0000 9999 0000 9999', 'ES'); expect(isValid).toBeFalsy(); }); - it('should return false for invalid spanish IBAN input', () => { - let isValid = validateIban('ES00 9999 0000 9999 0000 9999'); - - expect(isValid).toBeFalsy(); - }); - - it('should return true for valid spanish IBAN', () => { - let isValid = validateIban('ES91 2100 0418 4502 0005 1332'); + it('should return true for valid Spanish IBAN', () => { + let isValid = validateIban('ES91 2100 0418 4502 0005 1332', 'ES'); expect(isValid).toBeTruthy(); }); + + it('should return false for invalid Spanish IBAN with incorrect length', () => { + let isValid = validateIban('ES91210004184502000513', 'ES'); + + expect(isValid).toBeFalsy(); + }); + + it('should return false for invalid Spanish IBAN with incorrect module97 result', () => { + let isValid = validateIban('ES9121000418450200051331', 'ES'); + + expect(isValid).toBeFalsy(); + }); + + it('should return true for a non-Spanish countryCode', () => { + let isValid = validateIban('DE89370400440532013000', 'AT'); + + expect(isValid).toBeTruthy(); + }); + + it('should return true for null IBAN', () => { + let isValid = validateIban(null, 'ES'); + + expect(isValid).toBeTruthy(); + }); + + it('should return false for non-string IBAN', () => { + let isValid = validateIban(12345, 'ES'); + + expect(isValid).toBeFalsy(); + }); }); diff --git a/loopback/util/validateIban.js b/loopback/util/validateIban.js index 3ca09ef959..ed3e004260 100644 --- a/loopback/util/validateIban.js +++ b/loopback/util/validateIban.js @@ -1,6 +1,7 @@ -module.exports = function(iban) { +module.exports = function(iban, countryCode) { if (iban == null) return true; if (typeof iban != 'string') return false; + if (countryCode?.toLowerCase() != 'es') return true; iban = iban.toUpperCase(); iban = trim(iban); diff --git a/modules/claim/front/basic-data/index.html b/modules/claim/front/basic-data/index.html index 16e134c60a..10aa7623ae 100644 --- a/modules/claim/front/basic-data/index.html +++ b/modules/claim/front/basic-data/index.html @@ -25,15 +25,13 @@ - - + - +
@@ -66,16 +66,11 @@ show-field="description" rule> - - + --> - \ No newline at end of file + diff --git a/modules/claim/front/search-panel/index.html b/modules/claim/front/search-panel/index.html index 151a06c8e6..fbc527d60f 100644 --- a/modules/claim/front/search-panel/index.html +++ b/modules/claim/front/search-panel/index.html @@ -22,26 +22,18 @@ - - {{firstName}} {{name}} - - + - {{firstName}} {{name}} - + { }); async function ibanNeedsValidation(err, done) { - const filter = { - fields: ['code'], - where: {id: this.countryFk} - }; - const country = await Self.app.models.Country.findOne(filter); - const code = country ? country.code.toLowerCase() : null; - if (code != 'es') + if (!this.bankEntityFk) return done(); - if (!validateIban(this.iban)) + const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk); + const filter = { + fields: ['code'], + where: {id: bankEntity.countryFk} + }; + const country = await Self.app.models.Country.findOne(filter); + + if (!validateIban(this.iban, country?.code)) err(); done(); } diff --git a/modules/client/front/basic-data/index.html b/modules/client/front/basic-data/index.html index a5c8669790..e48b39fdc0 100644 --- a/modules/client/front/basic-data/index.html +++ b/modules/client/front/basic-data/index.html @@ -14,13 +14,13 @@ - @@ -59,18 +59,15 @@ - - - + - {{nickname}} diff --git a/modules/client/front/create/index.html b/modules/client/front/create/index.html index 3e7fdd9497..e6bf561ef7 100644 --- a/modules/client/front/create/index.html +++ b/modules/client/front/create/index.html @@ -15,18 +15,15 @@ rule vn-focus> - - {{firstName}} {{lastName}} - + departments="['VT']" + show-field="nickname"> + - - {{code}} - {{town.name}} ({{town.province.name}}, + {{code}} - {{town.name}} ({{town.province.name}}, {{town.province.country.country}}) @@ -82,7 +79,7 @@ - {{name}}, {{province.name}} + {{name}}, {{province.name}} ({{province.country.country}}) @@ -150,4 +147,4 @@ - \ No newline at end of file + diff --git a/modules/client/front/search-panel/index.html b/modules/client/front/search-panel/index.html index a02f93882f..2105d3a65e 100644 --- a/modules/client/front/search-panel/index.html +++ b/modules/client/front/search-panel/index.html @@ -14,17 +14,12 @@ vn-one label="Name" ng-model="filter.name"> - - {{firstName}} {{name}} - + diff --git a/modules/item/back/methods/item-shelving-sale/filter.js b/modules/item/back/methods/item-shelving-sale/filter.js index 12029d33d0..01a9f18566 100644 --- a/modules/item/back/methods/item-shelving-sale/filter.js +++ b/modules/item/back/methods/item-shelving-sale/filter.js @@ -28,11 +28,15 @@ module.exports = Self => { Object.assign(myOptions, options); const stmt = new ParameterizedSQL(` - SELECT iss.created, + SELECT + iss.id, + iss.created, iss.saleFk, iss.quantity, iss.userFk, + ish.id itemShelvingFk, ish.shelvingFk, + s.parkingFk, p.code, u.name FROM itemShelvingSale iss diff --git a/modules/item/back/models/item-shelving.json b/modules/item/back/models/item-shelving.json index 8628bfeeea..ff9ecabe3f 100644 --- a/modules/item/back/models/item-shelving.json +++ b/modules/item/back/models/item-shelving.json @@ -41,11 +41,6 @@ "type": "belongsTo", "model": "VnUser", "foreignKey": "userFk" - }, - "shelving": { - "type": "belongsTo", - "model": "Shelving", - "foreignKey": "shelvingFk" } } } diff --git a/modules/item/front/fixed-price-search-panel/index.html b/modules/item/front/fixed-price-search-panel/index.html index ebe2102772..347e65571f 100644 --- a/modules/item/front/fixed-price-search-panel/index.html +++ b/modules/item/front/fixed-price-search-panel/index.html @@ -54,11 +54,10 @@ vn-id="buyer" disabled="false" ng-model="$ctrl.filter.buyerFk" - url="Workers/activeWithRole" + url="TicketRequests/getItemTypeWorker" show-field="nickname" search-function="{firstName: $search}" value-field="id" - where="{role: {inq: ['logistic', 'buyer']}}" label="Buyer" on-change="$ctrl.addFilters()"> diff --git a/modules/item/front/index/index.js b/modules/item/front/index/index.js index 10bd4bbb7f..2bcc2302a6 100644 --- a/modules/item/front/index/index.js +++ b/modules/item/front/index/index.js @@ -44,8 +44,7 @@ class Controller extends Section { { field: 'buyerFk', autocomplete: { - url: 'Workers/activeWithRole', - where: `{role: {inq: ['logistic', 'buyer']}}`, + url: 'TicketRequests/getItemTypeWorker', searchFunction: '{firstName: $search}', showField: 'nickname', valueField: 'id', diff --git a/modules/item/front/request-search-panel/index.html b/modules/item/front/request-search-panel/index.html index 9d35fbca4a..6a51a429d6 100644 --- a/modules/item/front/request-search-panel/index.html +++ b/modules/item/front/request-search-panel/index.html @@ -22,12 +22,11 @@ - {{nickname}} + label="Atender"> @@ -46,18 +45,13 @@ - - {{firstName}} {{lastName}} - + departments="['VT']" + label="Salesperson"> + -
- @@ -46,7 +46,7 @@ ui-sref="order.card.summary({id: {{::order.id}}})" target="_blank"> - @@ -98,7 +98,7 @@ scroll-offset="100"> - Filter by selection - Exclude selection - Remove filter - Remove all filters - Copy value @@ -138,4 +138,4 @@ on-accept="$ctrl.onDelete()" question="All the selected elements will be deleted. Are you sure you want to continue?" message="Delete selected elements"> - \ No newline at end of file + diff --git a/modules/monitor/front/index/search-panel/index.html b/modules/monitor/front/index/search-panel/index.html index 0d24c41f91..25a3510ab6 100644 --- a/modules/monitor/front/index/search-panel/index.html +++ b/modules/monitor/front/index/search-panel/index.html @@ -1,7 +1,7 @@
@@ -43,16 +43,12 @@ label="Nickname" ng-model="filter.nickname"> - - {{firstName}} {{name}} - + - - + -
\ No newline at end of file + diff --git a/modules/route/back/methods/route/getSuggestedTickets.js b/modules/route/back/methods/route/getSuggestedTickets.js index c1a9c7cae1..e1b90d3590 100644 --- a/modules/route/back/methods/route/getSuggestedTickets.js +++ b/modules/route/back/methods/route/getSuggestedTickets.js @@ -24,7 +24,14 @@ module.exports = Self => { if (typeof options == 'object') Object.assign(myOptions, options); - const route = await Self.app.models.Route.findById(id, null, myOptions); + const route = await Self.app.models.Route.findById(id, { + include: { + relation: 'agencyMode', + scope: { + fields: ['name'] + } + } + }, myOptions); const zoneAgencyModes = await Self.app.models.ZoneAgencyMode.find({ where: { @@ -35,11 +42,13 @@ module.exports = Self => { const zoneIds = []; for (let zoneAgencyMode of zoneAgencyModes) zoneIds.push(zoneAgencyMode.zoneFk); + const minDate = new Date(route.created); minDate.setHours(0, 0, 0, 0); const maxDate = new Date(route.created); maxDate.setHours(23, 59, 59, 59); + let tickets = await Self.app.models.Ticket.find({ where: { zoneFk: {inq: zoneIds}, @@ -80,6 +89,12 @@ module.exports = Self => { ] }, myOptions); - return tickets; + return tickets.map(ticket => { + const simpleTicket = ticket.toJSON(); + return { + ...simpleTicket, + agencyName: route.agencyMode().name + }; + }); }; }; diff --git a/modules/route/front/basic-data/index.html b/modules/route/front/basic-data/index.html index ade9230e8b..7f9aef0d0a 100644 --- a/modules/route/front/basic-data/index.html +++ b/modules/route/front/basic-data/index.html @@ -8,20 +8,11 @@
- - -
{{::nickname}}
-
{{::name}}
-
-
+ - - + show-field="nickname"> + diff --git a/modules/route/front/search-panel/index.html b/modules/route/front/search-panel/index.html index a78d88d1cd..f3d392580f 100644 --- a/modules/route/front/search-panel/index.html +++ b/modules/route/front/search-panel/index.html @@ -1,7 +1,7 @@
@@ -15,16 +15,11 @@ - - + show-field="nickname"> + - Ticket Client Province - + - Population - + Population + PC Address @@ -39,7 +39,7 @@ - @@ -62,7 +62,7 @@ diff --git a/modules/supplier/back/models/supplier-account.js b/modules/supplier/back/models/supplier-account.js index 51da113ec3..691e725808 100644 --- a/modules/supplier/back/models/supplier-account.js +++ b/modules/supplier/back/models/supplier-account.js @@ -7,18 +7,18 @@ module.exports = Self => { }); async function ibanValidation(err, done) { - const supplier = await Self.app.models.Supplier.findById(this.supplierFk); + if (!this.bankEntityFk) + return done(); + + const bankEntity = await Self.app.models.BankEntity.findById(this.bankEntityFk); const filter = { fields: ['code'], - where: {id: supplier.countryFk} + where: {id: bankEntity.countryFk} }; const country = await Self.app.models.Country.findOne(filter); - const code = country ? country.code.toLowerCase() : null; - if (code != 'es') - return done(); - if (!validateIban(this.iban)) + if (!validateIban(this.iban, country?.code)) err(); done(); } diff --git a/modules/supplier/front/basic-data/index.html b/modules/supplier/front/basic-data/index.html index 9991908d4d..68e635a06f 100644 --- a/modules/supplier/front/basic-data/index.html +++ b/modules/supplier/front/basic-data/index.html @@ -15,29 +15,25 @@ rule vn-focus> - - + @@ -45,7 +41,7 @@ @@ -63,4 +59,4 @@ ng-click="watcher.loadOriginalData()"> - \ No newline at end of file + diff --git a/modules/supplier/front/consumption-search-panel/index.html b/modules/supplier/front/consumption-search-panel/index.html index 597c6edabb..5cba11d3c2 100644 --- a/modules/supplier/front/consumption-search-panel/index.html +++ b/modules/supplier/front/consumption-search-panel/index.html @@ -17,12 +17,11 @@ - {{nickname}} diff --git a/modules/ticket/back/methods/expedition-state/addExpeditionState.js b/modules/ticket/back/methods/expedition-state/addExpeditionState.js new file mode 100644 index 0000000000..f1199f1888 --- /dev/null +++ b/modules/ticket/back/methods/expedition-state/addExpeditionState.js @@ -0,0 +1,66 @@ +const UserError = require('vn-loopback/util/user-error'); + +module.exports = Self => { + Self.remoteMethod('addExpeditionState', { + description: 'Update an expedition state', + accessType: 'WRITE', + accepts: [ + { + arg: 'expeditions', + type: ['object'], + required: true, + description: 'Array of objects containing expeditionFk and stateCode' + } + ], + returns: { + type: 'boolean', + root: true + }, + http: { + path: `/addExpeditionState`, + verb: 'post' + } + }); + + Self.addExpeditionState = async(expeditions, 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; + } + let expeditionId; + try { + for (const expedition of expeditions) { + const expeditionStateType = await models.ExpeditionStateType.findOne( + { + fields: ['id'], + where: {code: expedition.stateCode} + }, myOptions + ); + if (!expeditionStateType) + throw new UserError(`Invalid state code: ${expedition.stateCode}.`); + + const typeFk = expeditionStateType.id; + expeditionId = expedition.expeditionFk; + + await models.ExpeditionState.create({ + expeditionFk: expedition.expeditionFk, + typeFk, + }, myOptions); + } + + if (tx) await tx.commit(); + } catch (e) { + if (tx) await tx.rollback(); + if (e instanceof UserError) + throw e; + + throw new UserError(`Invalid expedition id: ${expeditionId}.`); + } + }; +}; diff --git a/modules/ticket/back/methods/expedition-state/specs/addExpeditionState.spec.js b/modules/ticket/back/methods/expedition-state/specs/addExpeditionState.spec.js new file mode 100644 index 0000000000..819a43a605 --- /dev/null +++ b/modules/ticket/back/methods/expedition-state/specs/addExpeditionState.spec.js @@ -0,0 +1,75 @@ +const models = require('vn-loopback/server/server').models; + +describe('expeditionState addExpeditionState()', () => { + it('should update the expedition states', async() => { + const tx = await models.ExpeditionState.beginTransaction({}); + + try { + const options = {transaction: tx}; + const payload = [ + { + expeditionFk: 8, + stateCode: 'ON DELIVERY' + }, + ]; + + await models.ExpeditionState.addExpeditionState(payload, options); + + const expeditionState = await models.ExpeditionState.findOne({ + where: {id: 5} + }); + + expect(expeditionState.typeFk).toEqual(1); + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should throw an error an error when an stateCode does not exist', async() => { + const tx = await models.ExpeditionState.beginTransaction({}); + let error; + + try { + const options = {transaction: tx}; + const payload = [ + { + expeditionFk: 2, + stateCode: 'DUMMY' + } + ]; + await models.ExpeditionState.addExpeditionState(payload, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toContain('Invalid state code: DUMMY.'); + }); + + it('should throw an error when expeditionFk does not exist', async() => { + const tx = await models.ExpeditionState.beginTransaction({}); + let error; + try { + const options = {transaction: tx}; + const payload = [ + { + expeditionFk: 50, + stateCode: 'LOST' + } + ]; + + await models.ExpeditionState.addExpeditionState(payload, options); + + await tx.rollback(); + } catch (e) { + error = e; + await tx.rollback(); + } + + expect(error.message).toContain('Invalid expedition id: 50.'); + }); +}); diff --git a/modules/ticket/back/methods/sale-tracking/delete.js b/modules/ticket/back/methods/sale-tracking/delete.js new file mode 100644 index 0000000000..0b977e5d44 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/delete.js @@ -0,0 +1,69 @@ + +module.exports = Self => { + Self.remoteMethod('delete', { + description: 'Delete sale trackings and item shelving sales', + accessType: 'READ', + accepts: [ + { + arg: 'saleFk', + type: 'number', + description: 'The sale id' + }, + { + arg: 'stateCode', + type: 'string' + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/delete`, + verb: 'POST' + } + }); + + Self.delete = async(saleFk, stateCode, options) => { + const models = Self.app.models; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + if (stateCode === 'PREPARED') { + const itemShelvingSales = await models.ItemShelvingSale.find({where: {saleFk: saleFk}}, myOptions); + for (let itemShelvingSale of itemShelvingSales) + await itemShelvingSale.destroy(myOptions); + } + + const state = await models.State.findOne({ + where: {code: stateCode} + }, myOptions); + + const filter = { + where: { + saleFk: saleFk, + stateFk: state.id + } + }; + const saleTrackings = await models.SaleTracking.find(filter, myOptions); + for (let saleTracking of saleTrackings) + await saleTracking.destroy(myOptions); + + if (tx) await tx.commit(); + + return true; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/sale-tracking/filter.js b/modules/ticket/back/methods/sale-tracking/filter.js new file mode 100644 index 0000000000..2fa21cb1a8 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/filter.js @@ -0,0 +1,94 @@ + +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; + +module.exports = Self => { + Self.remoteMethod('filter', { + description: 'Returns a list with the lines of a ticket and its different states of preparation', + accessType: 'READ', + accepts: [ + { + arg: 'id', + type: 'number', + required: true, + description: 'The ticket id', + http: {source: 'path'} + }, + { + arg: 'filter', + type: 'object', + description: 'Filter defining where and paginated data' + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/:id/filter`, + verb: 'GET' + } + }); + + Self.filter = async(id, filter, options) => { + const conn = Self.dataSource.connector; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + const stmts = []; + let stmt; + + stmts.push('CALL cache.last_buy_refresh(FALSE)'); + + stmt = new ParameterizedSQL( + `SELECT t.clientFk, + t.shipped, + s.ticketFk, + s.itemFk, + s.quantity, + s.concept, + s.id saleFk, + i.image, + i.subName, + IF(stPrevious.saleFk,TRUE,FALSE) as isPreviousSelected, + stPrevious.isChecked as isPrevious, + stPrepared.isChecked as isPrepared, + stControled.isChecked as isControled, + sgd.id saleGroupDetailFk, + (MAX(sgd.id) IS NOT NULL) AS hasSaleGroupDetail, + p.code AS parkingCode, + i.value5, + i.value6, + i.value7, + i.value8, + i.value9, + i.value10 + FROM vn.ticket t + JOIN vn.sale s ON s.ticketFk = t.id + JOIN vn.item i ON i.id = s.itemFk + LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = t.warehouseFk + LEFT JOIN vn.state st ON TRUE + LEFT JOIN vn.saleTracking stPrevious ON stPrevious.saleFk = s.id + AND stPrevious.stateFk = (SELECT id FROM vn.state WHERE code = 'PREVIOUS_PREPARATION') + LEFT JOIN vn.saleTracking stPrepared ON stPrepared.saleFk = s.id + AND stPrepared.stateFk = (SELECT id FROM vn.state WHERE code = 'PREPARED') + LEFT JOIN vn.saleTracking stControled ON stControled.saleFk = s.id + AND stControled.stateFk = (SELECT id FROM vn.state s2 WHERE code = 'CHECKED') + LEFT JOIN vn.saleGroupDetail sgd ON sgd.saleFk = s.id + LEFT JOIN vn.saleGroup sg ON sg.id = sgd.saleGroupFk + LEFT JOIN vn.parking p ON p.id = sg.parkingFk + WHERE t.id = ? + GROUP BY s.id`, [id]); + + stmts.push(stmt); + + stmt.merge(Self.makeSuffix(filter)); + + const index = stmts.push(stmt) - 1; + const sql = ParameterizedSQL.join(stmts, ';'); + const result = await conn.executeStmt(sql, myOptions); + + return result[index]; + }; +}; diff --git a/modules/ticket/back/methods/sale-tracking/new.js b/modules/ticket/back/methods/sale-tracking/new.js new file mode 100644 index 0000000000..6a99c299c4 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/new.js @@ -0,0 +1,90 @@ + +module.exports = Self => { + Self.remoteMethodCtx('new', { + description: `Replaces the record or creates it if it doesn't exist`, + accessType: 'READ', + accepts: [ + { + arg: 'saleFk', + type: 'number', + description: 'The sale id' + }, + { + arg: 'isChecked', + type: 'boolean' + }, + { + arg: 'quantity', + type: 'number' + }, + { + arg: 'stateCode', + type: 'string' + } + ], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/new`, + verb: 'POST' + } + }); + + Self.new = async(ctx, saleFk, isChecked, quantity, stateCode, options) => { + const models = Self.app.models; + const userId = ctx.req.accessToken.userId; + const myOptions = {}; + let tx; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + try { + const state = await models.State.findOne({ + where: {code: stateCode} + }, myOptions); + + const saleTracking = await models.SaleTracking.findOne({ + where: { + saleFk: saleFk, + stateFk: state.id, + workerFk: userId + } + }, myOptions); + + let newSaleTracking; + if (saleTracking) { + newSaleTracking = await saleTracking.updateAttributes({ + saleFk: saleFk, + stateFk: state.id, + workerFk: userId, + isChecked: isChecked, + originalQuantity: quantity, + isScanned: null + }, myOptions); + } else { + newSaleTracking = await models.SaleTracking.create({ + saleFk: saleFk, + stateFk: state.id, + workerFk: userId, + isChecked: isChecked, + originalQuantity: quantity, + isScanned: null + }, myOptions); + } + + if (tx) await tx.commit(); + + return newSaleTracking; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/ticket/back/methods/sale-tracking/specs/delete.spec.js b/modules/ticket/back/methods/sale-tracking/specs/delete.spec.js new file mode 100644 index 0000000000..a8bcf56928 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/specs/delete.spec.js @@ -0,0 +1,30 @@ +const models = require('vn-loopback/server/server').models; + +describe('sale-tracking delete()', () => { + it('should delete a row of saleTracking and itemShelvingSale', async() => { + const tx = await models.SaleTracking.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const itemShelvingsBefore = await models.ItemShelvingSale.find(null, options); + const saleTrackingsBefore = await models.SaleTracking.find(null, options); + + const saleFk = 1; + const stateCode = 'PREPARED'; + const result = await models.SaleTracking.delete(saleFk, stateCode, options); + + const itemShelvingsAfter = await models.ItemShelvingSale.find(null, options); + const saleTrackingsAfter = await models.SaleTracking.find(null, options); + + expect(result).toEqual(true); + expect(saleTrackingsAfter.length).toBeLessThan(saleTrackingsBefore.length); + expect(itemShelvingsAfter.length).toBeLessThan(itemShelvingsBefore.length); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/sale-tracking/specs/filter.spec.js b/modules/ticket/back/methods/sale-tracking/specs/filter.spec.js new file mode 100644 index 0000000000..8a679f3a56 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/specs/filter.spec.js @@ -0,0 +1,23 @@ +const app = require('vn-loopback/server/server'); + +describe('sale-tracking filter()', () => { + it('should return 1 result filtering by ticket id', async() => { + const tx = await app.models.Claim.beginTransaction({}); + + try { + const options = {transaction: tx}; + + const id = 1; + const filter = {order: ['concept ASC', 'quantity DESC']}; + const result = await app.models.SaleTracking.filter(id, filter, options); + + expect(result.length).toEqual(4); + expect(result[0].ticketFk).toEqual(1); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/sale-tracking/specs/listSaleTracking.spec.js b/modules/ticket/back/methods/sale-tracking/specs/listSaleTracking.spec.js index d51c56874e..89f274c57b 100644 --- a/modules/ticket/back/methods/sale-tracking/specs/listSaleTracking.spec.js +++ b/modules/ticket/back/methods/sale-tracking/specs/listSaleTracking.spec.js @@ -1,6 +1,6 @@ const models = require('vn-loopback/server/server').models; -describe('ticket listSaleTracking()', () => { +describe('sale-tracking listSaleTracking()', () => { it('should call the listSaleTracking method and return the response', async() => { const tx = await models.SaleTracking.beginTransaction({}); diff --git a/modules/ticket/back/methods/sale-tracking/specs/new.spec.js b/modules/ticket/back/methods/sale-tracking/specs/new.spec.js new file mode 100644 index 0000000000..fcd3835821 --- /dev/null +++ b/modules/ticket/back/methods/sale-tracking/specs/new.spec.js @@ -0,0 +1,49 @@ +const models = require('vn-loopback/server/server').models; + +describe('sale-tracking new()', () => { + it('should update a saleTracking', async() => { + const tx = await models.SaleTracking.beginTransaction({}); + + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 55}}}; + + const saleFk = 1; + const isChecked = true; + const quantity = 20; + const stateCode = 'PREPARED'; + const result = await models.SaleTracking.new(ctx, saleFk, isChecked, quantity, stateCode, options); + + expect(result.isChecked).toBe(true); + expect(result.originalQuantity).toBe(20); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); + + it('should create a saleTracking', async() => { + const tx = await models.SaleTracking.beginTransaction({}); + + try { + const options = {transaction: tx}; + const ctx = {req: {accessToken: {userId: 1}}}; + + const saleFk = 1; + const isChecked = true; + const quantity = 20; + const stateCode = 'PREPARED'; + const result = await models.SaleTracking.new(ctx, saleFk, isChecked, quantity, stateCode, options); + + expect(result.isChecked).toBe(true); + expect(result.originalQuantity).toBe(20); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/ticket/back/methods/sale/salePreparingList.js b/modules/ticket/back/methods/sale/salePreparingList.js deleted file mode 100644 index e6e7d5164c..0000000000 --- a/modules/ticket/back/methods/sale/salePreparingList.js +++ /dev/null @@ -1,33 +0,0 @@ -module.exports = Self => { - Self.remoteMethodCtx('salePreparingList', { - description: 'Returns a list with the lines of a ticket and its different states of preparation', - accessType: 'READ', - accepts: [{ - arg: 'id', - type: 'number', - required: true, - description: 'The ticket id', - http: {source: 'path'} - }], - returns: { - type: ['object'], - root: true - }, - http: { - path: `/:id/salePreparingList`, - verb: 'GET' - } - }); - - Self.salePreparingList = async(ctx, id, options) => { - const myOptions = {}; - - if (typeof options == 'object') - Object.assign(myOptions, options); - - query = `CALL vn.salePreparingList(?)`; - const [sales] = await Self.rawSql(query, [id], myOptions); - - return sales; - }; -}; diff --git a/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js index 9ea859f7cf..f160cfaaca 100644 --- a/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js +++ b/modules/ticket/back/methods/ticket-request/getItemTypeWorker.js @@ -22,9 +22,13 @@ module.exports = Self => { } }); - Self.getItemTypeWorker = async filter => { + Self.getItemTypeWorker = async(filter, options) => { + const myOptions = {}; const conn = Self.dataSource.connector; + if (typeof options == 'object') + Object.assign(myOptions, options); + const query = `SELECT DISTINCT u.id, u.nickname FROM itemType it diff --git a/modules/ticket/back/model-config.json b/modules/ticket/back/model-config.json index 3c7e12eead..83d7303b73 100644 --- a/modules/ticket/back/model-config.json +++ b/modules/ticket/back/model-config.json @@ -20,6 +20,9 @@ "ExpeditionState": { "dataSource": "vn" }, + "ExpeditionStateType": { + "dataSource": "vn" + }, "Packaging": { "dataSource": "vn" }, diff --git a/modules/ticket/back/models/expedition-state-type.json b/modules/ticket/back/models/expedition-state-type.json new file mode 100644 index 0000000000..70207568bc --- /dev/null +++ b/modules/ticket/back/models/expedition-state-type.json @@ -0,0 +1,22 @@ +{ + "name": "ExpeditionStateType", + "base": "VnModel", + "options": { + "mysql": { + "table": "expeditionStateType" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "description": { + "type": "string" + }, + "code": { + "type": "string" + } + } +} \ No newline at end of file diff --git a/modules/ticket/back/models/expedition-state.js b/modules/ticket/back/models/expedition-state.js index af76af718a..496dd88d32 100644 --- a/modules/ticket/back/models/expedition-state.js +++ b/modules/ticket/back/models/expedition-state.js @@ -1,3 +1,4 @@ module.exports = function(Self) { require('../methods/expedition-state/filter')(Self); + require('../methods/expedition-state/addExpeditionState')(Self); }; diff --git a/modules/ticket/back/models/sale-tracking.js b/modules/ticket/back/models/sale-tracking.js index 4a46dd6b7b..54a2b5a1af 100644 --- a/modules/ticket/back/models/sale-tracking.js +++ b/modules/ticket/back/models/sale-tracking.js @@ -1,3 +1,6 @@ module.exports = Self => { + require('../methods/sale-tracking/filter')(Self); require('../methods/sale-tracking/listSaleTracking')(Self); + require('../methods/sale-tracking/new')(Self); + require('../methods/sale-tracking/delete')(Self); }; diff --git a/modules/ticket/back/models/sale.js b/modules/ticket/back/models/sale.js index bab201fddb..ae247fc242 100644 --- a/modules/ticket/back/models/sale.js +++ b/modules/ticket/back/models/sale.js @@ -1,6 +1,5 @@ module.exports = Self => { require('../methods/sale/getClaimableFromTicket')(Self); - require('../methods/sale/salePreparingList')(Self); require('../methods/sale/reserve')(Self); require('../methods/sale/deleteSales')(Self); require('../methods/sale/updatePrice')(Self); diff --git a/modules/ticket/front/sale-tracking/index.html b/modules/ticket/front/sale-tracking/index.html index 0309dde130..f05cf15fbd 100644 --- a/modules/ticket/front/sale-tracking/index.html +++ b/modules/ticket/front/sale-tracking/index.html @@ -1,11 +1,19 @@ + + + + @@ -13,7 +21,7 @@ - Is checked + Is checked Item Description Quantity @@ -23,76 +31,84 @@ - + + ng-class="{ + 'pink': sale.hasSaleGroupDetail, + 'none': !sale.hasSaleGroupDetail, + }" + class="circleState" + vn-tooltip="sale group detail" + vn-click-stop="$ctrl.clickSaleGroupDetail($index)"> - + - + - + - + - + - {{::sale.item.id}} + {{::sale.itemFk}}
- {{::sale.item.name}} - -

{{::sale.item.subName}}

+ {{::sale.concept}} + +

{{::sale.subName}}

{{::sale.quantity}} - {{::sale.saleGroupDetail.saleGroup.parking.code | dashIfEmpty}} + {{::sale.parkingCode | dashIfEmpty}} @@ -154,28 +170,35 @@ - - - + + Quantity Worker - Shelving - Parking + Shelving + Parking Created - {{::itemShelvingSale.quantity}} + + {{itemShelvingSale.quantity}} + + + + + - {{::itemShelvingSale.shelvingFk}} - {{::itemShelvingSale.code}} + + + + + + + + {{::itemShelvingSale.created | date: 'dd/MM/yyyy HH:mm'}} diff --git a/modules/ticket/front/sale-tracking/index.js b/modules/ticket/front/sale-tracking/index.js index 60b422cc01..6c0e7232ea 100644 --- a/modules/ticket/front/sale-tracking/index.js +++ b/modules/ticket/front/sale-tracking/index.js @@ -3,62 +3,6 @@ import Section from 'salix/components/section'; import './style.scss'; class Controller extends Section { - constructor($element, $) { - super($element, $); - this.filter = { - include: [ - { - relation: 'item' - }, - { - relation: 'saleTracking', - scope: { - fields: ['isChecked'] - } - }, - { - relation: 'saleGroupDetail', - scope: { - fields: ['saleGroupFk'], - include: { - relation: 'saleGroup', - scope: { - fields: ['parkingFk'], - include: { - relation: 'parking', - scope: { - fields: ['code'] - } - } - } - } - } - } - ] - }; - } - - get sales() { - return this._sales; - } - - set sales(value) { - this._sales = value; - if (value) { - const query = `Sales/${this.$params.id}/salePreparingList`; - this.$http.get(query) - .then(res => { - this.salePreparingList = res.data; - for (const salePreparing of this.salePreparingList) { - for (const sale of this.sales) { - if (salePreparing.saleFk == sale.id) - sale.preparingList = salePreparing; - } - } - }); - } - } - showItemDescriptor(event, sale) { this.quicklinks = { btnThree: { @@ -75,20 +19,145 @@ class Controller extends Section { } showSaleTracking(sale) { - this.saleId = sale.id; + this.saleId = sale.saleFk; this.$.saleTracking.show(); } showItemShelvingSale(sale) { - this.saleId = sale.id; + this.saleId = sale.saleFk; this.$.itemShelvingSale.show(); } + + clickSaleGroupDetail(index) { + const sale = this.sales[index]; + if (!sale.saleGroupDetailFk) return; + + return this.$http.delete(`SaleGroupDetails/${sale.saleGroupDetailFk}`) + .then(() => { + sale.hasSaleGroupDetail = false; + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + + clickPreviousSelected(index) { + const sale = this.sales[index]; + if (!sale.isPreviousSelected) { + this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false); + sale.isPreviousSelected = true; + } else { + this.saleTrackingDel(sale, 'PREVIOUS_PREPARATION'); + sale.isPreviousSelected = false; + sale.isPrevious = false; + } + } + + clickPrevious(index) { + const sale = this.sales[index]; + if (!sale.isPrevious) { + this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', true); + sale.isPrevious = true; + sale.isPreviousSelected = true; + } else { + this.saleTrackingNew(sale, 'PREVIOUS_PREPARATION', false); + sale.isPrevious = false; + } + } + + clickPrepared(index) { + const sale = this.sales[index]; + if (!sale.isPrepared) { + this.saleTrackingNew(sale, 'PREPARED', true); + sale.isPrepared = true; + } else { + this.saleTrackingDel(sale, 'PREPARED'); + sale.isPrepared = false; + } + } + + clickControled(index) { + const sale = this.sales[index]; + if (!sale.isControled) { + this.saleTrackingNew(sale, 'CHECKED', true); + sale.isControled = true; + } else { + this.saleTrackingDel(sale, 'CHECKED'); + sale.isControled = false; + } + } + + saleTrackingNew(sale, stateCode, isChecked) { + const params = { + saleFk: sale.saleFk, + isChecked: isChecked, + quantity: sale.quantity, + stateCode: stateCode + }; + this.$http.post(`SaleTrackings/new`, params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + + saleTrackingDel(sale, stateCode) { + const params = { + saleFk: sale.saleFk, + stateCode: stateCode + }; + this.$http.post(`SaleTrackings/delete`, params).then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + + updateQuantity(itemShelvingSale) { + const params = { + quantity: itemShelvingSale.quantity + }; + this.$http.patch(`ItemShelvingSales/${itemShelvingSale.id}`, params) + .then(() => { + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + + async updateShelving(itemShelvingSale) { + const params = { + shelvingFk: itemShelvingSale.shelvingFk + }; + const res = await this.$http.patch(`ItemShelvings/${itemShelvingSale.itemShelvingFk}`, params); + + const filter = { + fields: ['parkingFk'], + where: { + code: res.data.shelvingFk + } + }; + this.$http.get(`Shelvings/findOne`, {filter}) + .then(res => { + itemShelvingSale.parkingFk = res.data.parkingFk; + this.vnApp.showSuccess(this.$t('Data saved!')); + }); + } + + async updateParking(itemShelvingSale) { + const filter = { + fields: ['id'], + where: { + code: itemShelvingSale.shelvingFk + } + }; + const res = await this.$http.get(`Shelvings/findOne`, {filter}); + + const params = { + parkingFk: itemShelvingSale.parkingFk + }; + this.$http.patch(`Shelvings/${res.data.id}`, params) + .then(() => this.vnApp.showSuccess(this.$t('Data saved!'))); + } } ngModule.vnComponent('vnTicketSaleTracking', { template: require('./index.html'), controller: Controller, bindings: { - ticket: '<' + ticket: '<', + model: '
@@ -62,16 +62,12 @@ label="Nickname" ng-model="filter.nickname"> - - {{firstName}} {{name}} - + - - + ng-model="$ctrl.workerFk"> + @@ -43,4 +36,4 @@ ui-sref="ticket.card.tracking.index"> - \ No newline at end of file + diff --git a/modules/worker/back/methods/worker-time-control/sendMail.js b/modules/worker/back/methods/worker-time-control/sendMail.js index ab5e56a77f..66fb7cc23e 100644 --- a/modules/worker/back/methods/worker-time-control/sendMail.js +++ b/modules/worker/back/methods/worker-time-control/sendMail.js @@ -121,15 +121,18 @@ module.exports = Self => { `, [started, ended]); stmts.push(stmt); - stmt = new ParameterizedSQL(`INSERT INTO mail (receiver, subject, body) - SELECT CONCAT(u.name, '@verdnatura.es'), - CONCAT('Error registro de horas semana ', ?, ' año ', ?) , - CONCAT('No se ha podido enviar el registro de horas al empleado/s: ', GROUP_CONCAT(DISTINCT CONCAT('
', w.id, ' ', w.firstName, ' ', w.lastName))) - FROM tmp.timeControlError tce - JOIN vn.workerTimeControl wtc ON wtc.id = tce.id - JOIN worker w ON w.id = wtc.userFK - JOIN account.user u ON u.id = w.bossFk - GROUP BY w.bossFk`, [args.week, args.year]); + stmt = new ParameterizedSQL(` + INSERT INTO mail (receiver, subject, body) + SELECT CONCAT(u.name, '@verdnatura.es'), + CONCAT('Error registro de horas semana ', ?, ' año ', ?) , + CONCAT('No se ha podido enviar el registro de horas al empleado/s: ', + GROUP_CONCAT(DISTINCT CONCAT('
', w.id, ' ', w.firstName, ' ', w.lastName))) + FROM tmp.timeControlError tce + JOIN vn.workerTimeControl wtc ON wtc.id = tce.id + JOIN worker w ON w.id = wtc.userFK + JOIN account.user u ON u.id = w.bossFk + GROUP BY w.bossFk + `, [args.week, args.year]); stmts.push(stmt); stmt = new ParameterizedSQL(` @@ -177,10 +180,8 @@ module.exports = Self => { const workerTimeControlConfig = await models.WorkerTimeControlConfig.findOne(null, myOptions); for (let day of days[index]) { - if (!myOptions.transaction) { - tx = await Self.beginTransaction({}); - myOptions.transaction = tx; - } + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; try { workerFk = day.workerFk; if (day.timeWorkDecimal > 0 && day.timeWorkedDecimal == null @@ -365,13 +366,31 @@ module.exports = Self => { previousReceiver = day.receiver; } - if (tx) { - await tx.commit(); - delete myOptions.transaction; - } + if (tx) await tx.commit(); } catch (e) { + const stmts = []; + let stmt; + stmt = new ParameterizedSQL(` + INSERT INTO mail (receiver, subject, body) + SELECT CONCAT(u.name, '@verdnatura.es'), + CONCAT('Error registro de horas semana ', ?, ' año ', ?) , + CONCAT('No se ha podido enviar el registro de horas al empleado: ', + w.id, ' ', w.firstName, ' ', w.lastName, ' por el motivo: ', ?) + FROM worker w + JOIN account.user u ON u.id = w.bossFk + WHERE w.id = ? + `, [args.week, args.year, e.message, day.workerFk]); + stmts.push(stmt); + + const sql = ParameterizedSQL.join(stmts, ';'); + await conn.executeStmt(sql); + + previousWorkerFk = day.workerFk; + previousReceiver = day.receiver; + if (tx) await tx.rollback(); - throw e; + + continue; } } diff --git a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js index f440805592..3203dea827 100644 --- a/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js +++ b/modules/worker/back/methods/worker-time-control/weeklyHourRecordEmail.js @@ -51,7 +51,7 @@ module.exports = Self => { const salix = await models.Url.findOne({ where: { appName: 'salix', - environment: process.env.NODE_ENV || 'dev' + environment: process.env.NODE_ENV || 'development' } }, myOptions); @@ -61,7 +61,7 @@ module.exports = Self => { const url = `${salix.url}worker/${args.workerId}/time-control?timestamp=${timestamp}`; ctx.args.url = url; - Self.sendTemplate(ctx, 'weekly-hour-record'); + await Self.sendTemplate(ctx, 'weekly-hour-record'); return models.WorkerTimeControl.updateWorkerTimeControlMail(ctx, myOptions); }; diff --git a/modules/worker/back/methods/worker/activeWithInheritedRole.js b/modules/worker/back/methods/worker/activeWithInheritedRole.js index 9536b0f29b..19038405bd 100644 --- a/modules/worker/back/methods/worker/activeWithInheritedRole.js +++ b/modules/worker/back/methods/worker/activeWithInheritedRole.js @@ -1,7 +1,7 @@ module.exports = Self => { Self.remoteMethod('activeWithInheritedRole', { - description: 'Returns active workers with a role', + description: 'Returns active workers with an inherited role', accessType: 'READ', accepts: [{ arg: 'filter', @@ -24,7 +24,7 @@ module.exports = Self => { `SELECT DISTINCT w.id, w.firstName, w.lastName, u.name, u.nickname FROM worker w JOIN account.user u ON u.id = w.userFk - JOIN account.roleRole i ON i.role = u.role + JOIN account.roleRole i ON i.role = u.role JOIN account.role r ON r.id = i.inheritsFrom`; return Self.activeWorkers(query, filter); diff --git a/modules/worker/back/methods/worker/activeWithRole.js b/modules/worker/back/methods/worker/activeWithRole.js index 3924164585..c7f96e1511 100644 --- a/modules/worker/back/methods/worker/activeWithRole.js +++ b/modules/worker/back/methods/worker/activeWithRole.js @@ -1,7 +1,7 @@ module.exports = Self => { Self.remoteMethod('activeWithRole', { - description: 'Returns active workers with an inherited role', + description: 'Returns active workers with a role', accessType: 'READ', accepts: [{ arg: 'filter', diff --git a/modules/worker/back/methods/worker/search.js b/modules/worker/back/methods/worker/search.js new file mode 100644 index 0000000000..cd0a466ea7 --- /dev/null +++ b/modules/worker/back/methods/worker/search.js @@ -0,0 +1,69 @@ +const ParameterizedSQL = require('loopback-connector').ParameterizedSQL; +const mergeFilters = require('vn-loopback/util/filter').mergeFilters; + +module.exports = Self => { + Self.remoteMethodCtx('search', { + description: 'Returns an array of search results for a specified worker', + accepts: [{ + arg: 'filter', + type: 'object', + description: 'Filter to define conditions and paginate the data.', + required: true + }, + { + arg: 'departmentCodes', + type: ['string'], + description: 'Department codes to search workers', + }], + returns: { + type: ['object'], + root: true + }, + http: { + path: `/search`, + verb: 'GET' + } + }); + + Self.search = async(ctx, filter, departmentCodes) => { + const models = Self.app.models; + const conn = Self.dataSource.connector; + + if (departmentCodes) { + const departments = await models.Department.find({ + fields: ['id', 'sons'], + where: {code: {inq: departmentCodes}} + }); + + const allLeaves = await getAllLeaves(ctx, departments); + const where = {'departmentFk': {inq: allLeaves}}; + filter = mergeFilters(filter, {where}); + } + + const stmt = new ParameterizedSQL(` + SELECT * + FROM( + SELECT DISTINCT w.id, w.code, u.name, u.nickname, u.active, b.departmentFk + FROM worker w + JOIN account.user u ON u.id = w.id + JOIN business b ON b.workerFk = w.id + ) w`); + + stmt.merge(conn.makeSuffix(filter)); + return conn.executeStmt(stmt); + }; + + async function getAllLeaves(ctx, departments) { + const models = Self.app.models; + const leaves = []; + for (const department of departments) { + if (department.sons > 0) { + const subLeaves = await models.Department.getLeaves(ctx, department.id, null); + const grandLeaves = await getAllLeaves(ctx, subLeaves); + leaves.push(...grandLeaves); + } + leaves.push(department.id); + } + return leaves; + } +}; diff --git a/modules/worker/back/models/worker.js b/modules/worker/back/models/worker.js index b44703a88b..ccae3a6e6f 100644 --- a/modules/worker/back/models/worker.js +++ b/modules/worker/back/models/worker.js @@ -16,6 +16,7 @@ module.exports = Self => { require('../methods/worker/new')(Self); require('../methods/worker/deallocatePDA')(Self); require('../methods/worker/allocatePDA')(Self); + require('../methods/worker/search')(Self); require('../methods/worker/isAuthorized')(Self); Self.validatesUniquenessOf('locker', { diff --git a/modules/worker/front/basic-data/index.html b/modules/worker/front/basic-data/index.html index d89d88f2e8..2d85d018de 100644 --- a/modules/worker/front/basic-data/index.html +++ b/modules/worker/front/basic-data/index.html @@ -37,14 +37,11 @@
- - + - - + - - + - + @@ -78,12 +76,12 @@ label="Fill in days without physical check-ins" ng-model="$ctrl.department.hasToRefill"> - - + + - + diff --git a/package.json b/package.json index 2aa37379ed..dd833a8ad2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "salix-back", - "version": "23.34.01", + "version": "23.36.01", "author": "Verdnatura Levante SL", "description": "Salix backend", "license": "GPL-3.0",