diff --git a/Jenkinsfile b/Jenkinsfile index 85914ad62..d411ddb22 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -13,15 +13,6 @@ pipeline { stage('Checkout') { steps { script { - if (!env.GIT_COMMITTER_EMAIL) { - env.COMMITTER_EMAIL = sh( - script: 'git --no-pager show -s --format="%ae"', - returnStdout: true - ).trim() - } else { - env.COMMITTER_EMAIL = env.GIT_COMMITTER_EMAIL; - } - switch (env.BRANCH_NAME) { case 'master': env.NODE_ENV = 'production' @@ -41,7 +32,7 @@ pipeline { load env.GROOVY_FILE } - sh 'printenv' + setEnv() } } stage('Install') { @@ -86,16 +77,14 @@ pipeline { branch 'master' }} environment { - CREDS = credentials('docker-registry') + CREDENTIALS = credentials('docker-registry') } steps { nodejs('node-v12') { sh 'gulp build' } - sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY' - sh 'docker-compose build --parallel' - sh 'docker-compose push' + dockerBuild() } } stage('Deploy') { @@ -103,6 +92,9 @@ pipeline { branch 'test' branch 'master' }} + environment { + DOCKER_HOST = "${env.SWARM_HOST}" + } steps { sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}" } diff --git a/db/changes/10310-mothersDay/00-ACL.sql b/db/changes/10310-mothersDay/00-ACL.sql new file mode 100644 index 000000000..a2ae1e40f --- /dev/null +++ b/db/changes/10310-mothersDay/00-ACL.sql @@ -0,0 +1,2 @@ +INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId) +VALUES ('SupplierAddress', '*', '*', 'ALLOW', 'ROLE', 'employee'); diff --git a/db/changes/10310-mothersDay/00-supplierAddress.sql b/db/changes/10310-mothersDay/00-supplierAddress.sql new file mode 100644 index 000000000..e15a25a81 --- /dev/null +++ b/db/changes/10310-mothersDay/00-supplierAddress.sql @@ -0,0 +1,18 @@ +CREATE TABLE `vn`.`supplierAddress` +( + id INT NULL AUTO_INCREMENT, + supplierFk INT NULL, + nickname VARCHAR(40) NULL, + street VARCHAR(255) NULL, + provinceFk SMALLINT(6) UNSIGNED NULL, + postalCode VARCHAR(10) NULL, + city VARCHAR(50) NULL, + phone VARCHAR(15) NULL, + mobile VARCHAR(15) NULL, + CONSTRAINT supplierAddress_pk + PRIMARY KEY (id), + CONSTRAINT supplierAddress_province_fk + FOREIGN KEY (provinceFk) REFERENCES province (id) + ON UPDATE CASCADE +); + diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index b215d0edd..f386ad780 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -1244,6 +1244,15 @@ INSERT INTO `vn`.`supplierActivity`(`code`, `name`) ('flowerPlants', 'Wholesale of flowers and plants'), ('vegetablesFruits', 'Fruit and vegetable trade'); +INSERT INTO `vn`.`supplierAddress`(`id`, `supplierFk`, `nickname`, `street`, `provinceFk`, `postalCode`, `city`, `phone`, `mobile`) + VALUES + (1, 1, 'Ace Chemicals', 'The Midtown', 1, '46000', 'Gotham', '111111111', '222222222'), + (2, 1, 'Arkham Asylum', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'), + (3, 2, 'Wayne Tower', 'Grand Avenue', 1, '46000', 'Gotham', '111111111', '222222222'), + (4, 2, 'Bank of Gotham', 'Founders Island', 1, '46000', 'Gotham', '111111111', '222222222'), + (5, 442, 'GCR building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'), + (6, 442, 'The Gotham Tonight building', 'Bristol district', 1, '46000', 'Gotham', '111111111', '222222222'); + INSERT INTO `vn`.`supplier`(`id`, `name`, `nickname`,`account`,`countryFk`,`nif`,`isFarmer`,`commission`, `created`, `isActive`, `street`, `city`, `provinceFk`, `postCode`, `payMethodFk`, `payDemFk`, `payDay`, `taxTypeSageFk`, `withholdingSageFk`, `transactionTypeSageFk`, `workerFk`, `supplierActivityFk`) VALUES (1, 'Plants SL', 'Plants nick', 4100000001, 1, '06089160W', 0, 0, CURDATE(), 1, 'supplier address 1', 'PONTEVEDRA', 1, 15214, 1, 1, 15, 4, 1, 1, 18, 'flowerPlants'), diff --git a/docker-compose.yml b/docker-compose.yml index 24e1446e9..f8d1c808b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,6 +9,9 @@ services: - 80 deploy: replicas: 2 + placement: + constraints: + - node.role == worker back: image: registry.verdnatura.es/salix-back:${BRANCH_NAME:?} build: . @@ -31,6 +34,9 @@ services: - /mnt/storage/image:/var/lib/salix/image deploy: replicas: ${BACK_REPLICAS:?} + placement: + constraints: + - node.role == worker configs: datasources: external: true diff --git a/e2e/helpers/selectors.js b/e2e/helpers/selectors.js index dd80f0021..f8e0cbf47 100644 --- a/e2e/helpers/selectors.js +++ b/e2e/helpers/selectors.js @@ -1097,17 +1097,6 @@ export default { clientButton: 'vn-supplier-descriptor vn-icon[icon="person"]', entriesButton: 'vn-supplier-descriptor vn-icon[icon="icon-entry"]', }, - supplierContact: { - anyContact: 'vn-supplier-contact > form > vn-card > div', - addNewContact: 'vn-supplier-contact vn-icon[icon="add_circle"]', - thirdContactName: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.name"]', - thirdContactPhone: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.phone"]', - thirdContactMobile: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.mobile"]', - thirdContactEmail: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.email"]', - thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]', - saveButton: 'vn-supplier-contact button[type="submit"]', - thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]' - }, supplierBasicData: { alias: 'vn-supplier-basic-data vn-textfield[ng-model="$ctrl.supplier.nickname"]', isSerious: 'vn-supplier-basic-data vn-check[ng-model="$ctrl.supplier.isSerious"]', @@ -1132,5 +1121,36 @@ export default { payDem: 'vn-supplier-billing-data vn-autocomplete[ng-model="$ctrl.supplier.payDemFk"]', payDay: 'vn-supplier-billing-data vn-input-number[ng-model="$ctrl.supplier.payDay"]', saveButton: 'vn-supplier-billing-data button[type=submit]' - } + }, + supplierAddress: { + anyAddress: 'vn-supplier-address-index a', + thirdAddress: 'vn-supplier-address-index vn-card > div:nth-child(3) > a', + newAddress: 'vn-supplier-address-index vn-float-button[icon="add"]', + newNickname: 'vn-supplier-address-create vn-textfield[ng-model="$ctrl.address.nickname"]', + newStreet: 'vn-supplier-address-create vn-textfield[ng-model="$ctrl.address.street"]', + newPostcode: 'vn-supplier-address-create vn-datalist[ng-model="$ctrl.address.postalCode"]', + newCity: 'vn-supplier-address-create vn-datalist[ng-model="$ctrl.address.city"]', + newProvince: 'vn-supplier-address-create vn-autocomplete[ng-model="$ctrl.address.provinceFk"]', + newPhone: 'vn-supplier-address-create vn-textfield[ng-model="$ctrl.address.phone"]', + newMobile: 'vn-supplier-address-create vn-textfield[ng-model="$ctrl.address.mobile"]', + editNickname: 'vn-supplier-address-edit vn-textfield[ng-model="$ctrl.address.nickname"]', + editStreet: 'vn-supplier-address-edit vn-textfield[ng-model="$ctrl.address.street"]', + editPostcode: 'vn-supplier-address-edit vn-datalist[ng-model="$ctrl.address.postalCode"]', + editCity: 'vn-supplier-address-edit vn-datalist[ng-model="$ctrl.address.city"]', + editProvince: 'vn-supplier-address-edit vn-autocomplete[ng-model="$ctrl.address.provinceFk"]', + editPhone: 'vn-supplier-address-edit vn-textfield[ng-model="$ctrl.address.phone"]', + editMobile: 'vn-supplier-address-edit vn-textfield[ng-model="$ctrl.address.mobile"]', + saveButton: 'button[type="submit"]' + }, + supplierContact: { + anyContact: 'vn-supplier-contact > form > vn-card > div', + addNewContact: 'vn-supplier-contact vn-icon[icon="add_circle"]', + thirdContactName: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.name"]', + thirdContactPhone: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.phone"]', + thirdContactMobile: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.mobile"]', + thirdContactEmail: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.email"]', + thirdContactNotes: 'vn-supplier-contact div:nth-child(3) vn-textfield[ng-model="contact.observation"]', + saveButton: 'vn-supplier-contact button[type="submit"]', + thirdContactDeleteButton: 'vn-supplier-contact div:nth-child(3) vn-icon-button[icon="delete"]' + }, }; diff --git a/e2e/paths/13-supplier/05_billing_data.spec.js b/e2e/paths/13-supplier/04_billing_data.spec.js similarity index 100% rename from e2e/paths/13-supplier/05_billing_data.spec.js rename to e2e/paths/13-supplier/04_billing_data.spec.js diff --git a/e2e/paths/13-supplier/05_address.spec.js b/e2e/paths/13-supplier/05_address.spec.js new file mode 100644 index 000000000..5bccba3ee --- /dev/null +++ b/e2e/paths/13-supplier/05_address.spec.js @@ -0,0 +1,79 @@ +import selectors from '../../helpers/selectors.js'; +import getBrowser from '../../helpers/puppeteer'; + +describe('Supplier address path', () => { + let browser; + let page; + + beforeAll(async() => { + browser = await getBrowser(); + page = browser.page; + await page.loginAndModule('employee', 'supplier'); + await page.accessToSearchResult('1'); + await page.accessToSection('supplier.card.address.index'); + }); + + afterAll(async() => { + await browser.close(); + }); + + it('should count the addresses before creating one', async() => { + const count = await page.countElement(selectors.supplierAddress.anyAddress); + + expect(count).toEqual(2); + }); + + it('should open the new address form by clicking the add button', async() => { + await page.waitToClick(selectors.supplierAddress.newAddress); + await page.waitForState('supplier.card.address.create'); + }); + + it('should create a new address', async() => { + await page.write(selectors.supplierAddress.newNickname, 'Darkest dungeon'); + await page.write(selectors.supplierAddress.newStreet, 'Wayne manor'); + await page.write(selectors.supplierAddress.newPostcode, '46000'); + await page.write(selectors.supplierAddress.newCity, 'Valencia'); + await page.autocompleteSearch(selectors.supplierAddress.newProvince, 'Province one'); + await page.write(selectors.supplierAddress.newPhone, '888888888'); + await page.write(selectors.supplierAddress.newMobile, '444444444'); + await page.waitToClick(selectors.supplierAddress.saveButton); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + + it('should have been redirected to the addresses index', async() => { + await page.waitForState('supplier.card.address.index'); + }); + + it('should count the addresses and find one more now', async() => { + const count = await page.countElement(selectors.supplierAddress.anyAddress); + + expect(count).toEqual(3); + }); + + it('should open the edit address form by clicking the new address', async() => { + await page.waitToClick(selectors.supplierAddress.thirdAddress); + await page.waitForState('supplier.card.address.edit'); + }); + + it('should edit the address', async() => { + await page.overwrite(selectors.supplierAddress.editNickname, 'Wayne manor'); + await page.overwrite(selectors.supplierAddress.editStreet, '1007 Mountain Drive'); + await page.overwrite(selectors.supplierAddress.editPostcode, '46000'); + await page.overwrite(selectors.supplierAddress.editCity, 'Valencia'); + await page.autocompleteSearch(selectors.supplierAddress.editProvince, 'Province one'); + await page.overwrite(selectors.supplierAddress.editPhone, '777777777'); + await page.overwrite(selectors.supplierAddress.editMobile, '555555555'); + await page.waitToClick(selectors.supplierAddress.saveButton); + const message = await page.waitForSnackbar(); + + expect(message.text).toContain('Data saved!'); + }); + + it('should check the address has now the expected data', async() => { + let thirdAddress = await page.waitToGetProperty(selectors.supplierAddress.thirdAddress, 'innerText'); + + expect(thirdAddress).toContain('Wayne manor'); + }); +}); diff --git a/e2e/paths/13-supplier/04_contact.spec.js b/e2e/paths/13-supplier/06_contact.spec.js similarity index 100% rename from e2e/paths/13-supplier/04_contact.spec.js rename to e2e/paths/13-supplier/06_contact.spec.js diff --git a/modules/client/front/address/index/index.html b/modules/client/front/address/index/index.html index 51b310128..ebdc44724 100644 --- a/modules/client/front/address/index/index.html +++ b/modules/client/front/address/index/index.html @@ -8,8 +8,8 @@ @@ -26,7 +26,7 @@ ui-sref="client.card.address.edit({addressId: {{::address.id}}})" class="vn-pa-sm border-solid border-radius" ng-class="{'item-disabled': !address.isActive}" - translate-attr="{title: 'Edit address'}"> + translate-attr="{title: 'Edit consignee'}"> @@ -78,7 +78,7 @@ diff --git a/modules/client/front/address/locale/es.yml b/modules/client/front/address/locale/es.yml index 06d9e76f7..e1383fab5 100644 --- a/modules/client/front/address/locale/es.yml +++ b/modules/client/front/address/locale/es.yml @@ -1,8 +1,8 @@ # Index Set as default: Establecer como predeterminado Active first to set as default: Active primero para marcar como predeterminado -Search by address: Buscar por consignatario -You can search by address id or name: Puedes buscar por el id o nombre del consignatario +Search by consignee: Buscar por consignatario +You can search by consignee id or name: Puedes buscar por el id o nombre del consignatario # Edit Enabled: Activo Is equalizated: Recargo de equivalencia diff --git a/modules/client/front/locale/es.yml b/modules/client/front/locale/es.yml index 82cbb129e..1a5a570a7 100644 --- a/modules/client/front/locale/es.yml +++ b/modules/client/front/locale/es.yml @@ -36,9 +36,9 @@ Clients: Clientes New client: Nuevo cliente Fiscal data: Datos fiscales Billing data: Forma de pago -Addresses: Consignatarios -New address: Nuevo consignatario -Edit address: Editar consignatario +Consignees: Consignatarios +New consignee: Nuevo consignatario +Edit consignee: Editar consignatario Web access: Acceso web Notes: Notas New note: Nueva nota diff --git a/modules/client/front/routes.json b/modules/client/front/routes.json index a2d559645..4bd4086e0 100644 --- a/modules/client/front/routes.json +++ b/modules/client/front/routes.json @@ -102,7 +102,7 @@ "url": "/index?q", "state": "client.card.address.index", "component": "vn-client-address-index", - "description": "Addresses", + "description": "Consignees", "params": { "client": "$ctrl.client" } diff --git a/modules/supplier/back/model-config.json b/modules/supplier/back/model-config.json index 62c580aca..7febc17b4 100644 --- a/modules/supplier/back/model-config.json +++ b/modules/supplier/back/model-config.json @@ -1,8 +1,14 @@ { + "PayDem": { + "dataSource": "vn" + }, "Supplier": { "dataSource": "vn" }, - "PayDem": { + "SupplierAddress": { + "dataSource": "vn" + }, + "SupplierAccount": { "dataSource": "vn" }, "SupplierLog": { @@ -10,8 +16,5 @@ }, "SupplierContact": { "dataSource": "vn" - }, - "SupplierAccount": { - "dataSource": "vn" } } diff --git a/modules/supplier/back/models/supplier-address.js b/modules/supplier/back/models/supplier-address.js new file mode 100644 index 000000000..ca08fa719 --- /dev/null +++ b/modules/supplier/back/models/supplier-address.js @@ -0,0 +1,16 @@ +module.exports = Self => { + Self.validateAsync('postalCode', hasValidPostcode, { + message: `The postcode doesn't exist. Please enter a correct one` + }); + + async function hasValidPostcode(err, done) { + if (!this.postalCode) + return done(); + + const models = Self.app.models; + const postcode = await models.Postcode.findById(this.postalCode); + + if (!postcode) err(); + done(); + } +}; diff --git a/modules/supplier/back/models/supplier-address.json b/modules/supplier/back/models/supplier-address.json new file mode 100644 index 000000000..302f15e4b --- /dev/null +++ b/modules/supplier/back/models/supplier-address.json @@ -0,0 +1,55 @@ +{ + "name": "SupplierAddress", + "description": "Supplier addresses", + "base": "Loggable", + "log": { + "model": "SupplierLog", + "relation": "supplier", + "showField": "name" + }, + "options": { + "mysql": { + "table": "supplierAddress" + } + }, + "properties": { + "id": { + "type": "number", + "id": true, + "description": "Identifier" + }, + "nickname": { + "type": "string", + "required": true + }, + "street": { + "type": "string", + "required": true + }, + "city": { + "type": "string", + "required": true + }, + "postalCode": { + "type": "string" + }, + "phone": { + "type": "string" + }, + "mobile": { + "type": "string" + } + }, + "relations": { + "province": { + "type": "belongsTo", + "model": "Province", + "foreignKey": "provinceFk" + }, + "supplier": { + "type": "belongsTo", + "model": "Supplier", + "foreignKey": "supplierFk" + } + } +} \ No newline at end of file diff --git a/modules/supplier/back/models/supplier.json b/modules/supplier/back/models/supplier.json index 867ac4f33..30ed721a5 100644 --- a/modules/supplier/back/models/supplier.json +++ b/modules/supplier/back/models/supplier.json @@ -153,6 +153,11 @@ "contacts": { "type": "hasMany", "model": "SupplierContact", + "foreignKey": "supplierFk" + }, + "addresses": { + "type": "hasMany", + "model": "SupplierAddress", "foreignKey": "supplierFk" } }, diff --git a/modules/supplier/front/address/create/index.html b/modules/supplier/front/address/create/index.html new file mode 100644 index 000000000..4d66b70f0 --- /dev/null +++ b/modules/supplier/front/address/create/index.html @@ -0,0 +1,109 @@ + + + + +
+ + + + + + + + + + + {{code}} - {{town.name}} ({{town.province.name}}, + {{town.province.country.country}}) + + + + + + + + + {{name}}, {{province.name}} + ({{province.country.country}}) + + + + {{name}} ({{country.country}}) + + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/modules/supplier/front/address/create/index.js b/modules/supplier/front/address/create/index.js new file mode 100644 index 000000000..21b845881 --- /dev/null +++ b/modules/supplier/front/address/create/index.js @@ -0,0 +1,74 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + constructor($element, $) { + super($element, $); + + this.address = { + supplierFk: this.$params.id + }; + } + + onSubmit() { + this.$.watcher.submit().then(res => { + this.$state.go('supplier.card.address.index'); + }); + } + + get town() { + return this._town; + } + + // Town auto complete + set town(selection) { + this._town = selection; + + if (!selection) return; + + const province = selection.province; + const postcodes = selection.postcodes; + + if (!this.address.provinceFk) + this.address.provinceFk = province.id; + + if (postcodes.length === 1) + this.address.postalCode = postcodes[0].code; + } + + get postcode() { + return this._postcode; + } + + // Postcode auto complete + set postcode(selection) { + this._postcode = selection; + + if (!selection) return; + + const town = selection.town; + const province = town.province; + + if (!this.address.city) + this.address.city = town.name; + + if (!this.address.provinceFk) + this.address.provinceFk = province.id; + } + + onResponse(response) { + this.address.postalCode = response.code; + this.address.city = response.city; + this.address.provinceFk = response.provinceFk; + } +} + +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnSupplierAddressCreate', { + template: require('./index.html'), + controller: Controller, + bindings: { + supplier: '<' + } +}); diff --git a/modules/supplier/front/address/create/index.spec.js b/modules/supplier/front/address/create/index.spec.js new file mode 100644 index 000000000..026de3769 --- /dev/null +++ b/modules/supplier/front/address/create/index.spec.js @@ -0,0 +1,102 @@ +import './index'; +import watcher from 'core/mocks/watcher'; + +describe('Supplier', () => { + describe('Component vnSupplierAddressCreate', () => { + let $scope; + let controller; + let $element; + let $state; + + beforeEach(ngModule('supplier')); + + beforeEach(inject(($componentController, $rootScope, _$state_) => { + $scope = $rootScope.$new(); + $state = _$state_; + $state.params.id = '1234'; + $element = angular.element(''); + controller = $componentController('vnSupplierAddressCreate', {$element, $scope}); + controller.$.watcher = watcher; + controller.$.watcher.submit = () => { + return { + then: callback => { + callback({data: {id: 124}}); + } + }; + }; + controller.supplier = {id: 1}; + })); + + describe('onSubmit()', () => { + it('should perform a PATCH and then redirect to the main section', () => { + jest.spyOn(controller.$state, 'go'); + controller.onSubmit(); + + expect(controller.$state.go).toHaveBeenCalledWith('supplier.card.address.index'); + }); + }); + + describe('town() setter', () => { + it(`should set provinceFk property`, () => { + controller.town = { + provinceFk: 1, + code: 46001, + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + }, + postcodes: [] + }; + + expect(controller.address.provinceFk).toEqual(1); + }); + + it(`should set provinceFk property and fill the postalCode if there's just one`, () => { + controller.town = { + provinceFk: 1, + code: 46001, + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + }, + postcodes: [{code: '46001'}] + }; + + expect(controller.address.provinceFk).toEqual(1); + expect(controller.address.postalCode).toEqual('46001'); + }); + }); + + describe('postcode() setter', () => { + it(`should set the town and province properties`, () => { + controller.postcode = { + townFk: 1, + code: 46001, + town: { + id: 1, + name: 'New York', + province: { + id: 1, + name: 'New york', + country: { + id: 2, + name: 'USA' + } + } + } + }; + + expect(controller.address.city).toEqual('New York'); + expect(controller.address.provinceFk).toEqual(1); + }); + }); + }); +}); diff --git a/modules/supplier/front/address/edit/index.html b/modules/supplier/front/address/edit/index.html new file mode 100644 index 000000000..dd4cbb4d2 --- /dev/null +++ b/modules/supplier/front/address/edit/index.html @@ -0,0 +1,104 @@ + + + + + +
+ + + + + + + + + + + {{code}} - {{town.name}} ({{town.province.name}}, + {{town.province.country.country}}) + + + + + + + + + {{name}}, {{province.name}} + ({{province.country.country}}) + + + + {{name}} ({{country.country}}) + + + + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/modules/supplier/front/address/edit/index.js b/modules/supplier/front/address/edit/index.js new file mode 100644 index 000000000..4c7450666 --- /dev/null +++ b/modules/supplier/front/address/edit/index.js @@ -0,0 +1,62 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; + +export default class Controller extends Section { + onSubmit() { + this.$.watcher.submit() + .then(() => this.$state.go('supplier.card.address.index')); + } + + get town() { + return this._town; + } + + // Town auto complete + set town(selection) { + const oldValue = this._town; + this._town = selection; + + if (!selection || !oldValue) return; + + const province = selection.province; + const postcodes = selection.postcodes; + + if (!this.address.provinceFk) + this.address.provinceFk = province.id; + + if (!this.address.postalCode && postcodes.length === 1) + this.address.postalCode = postcodes[0].code; + } + + get postcode() { + return this._postcode; + } + + // Postcode auto complete + set postcode(selection) { + const oldValue = this._postcode; + this._postcode = selection; + + if (!selection || !oldValue) return; + + const town = selection.town; + const province = town.province; + + if (!this.address.city) + this.address.city = town.name; + + if (!this.address.provinceFk) + this.address.provinceFk = province.id; + } + + onResponse(response) { + this.address.postalCode = response.code; + this.address.city = response.city; + this.address.provinceFk = response.provinceFk; + } +} + +ngModule.vnComponent('vnSupplierAddressEdit', { + template: require('./index.html'), + controller: Controller +}); diff --git a/modules/supplier/front/address/edit/index.spec.js b/modules/supplier/front/address/edit/index.spec.js new file mode 100644 index 000000000..991163baa --- /dev/null +++ b/modules/supplier/front/address/edit/index.spec.js @@ -0,0 +1,39 @@ +import './index'; +import watcher from 'core/mocks/watcher'; + +describe('Supplier', () => { + describe('Component vnSupplierAddressEdit', () => { + let $scope; + let controller; + let $element; + let $state; + + beforeEach(ngModule('supplier')); + + beforeEach(inject(($componentController, $rootScope, _$state_) => { + $scope = $rootScope.$new(); + $state = _$state_; + $state.params.addressId = '1'; + $element = angular.element(''); + controller = $componentController('vnSupplierAddressEdit', {$element, $scope}); + controller.address = {id: 1}; + controller.$.watcher = watcher; + controller.$.watcher.submit = () => { + return { + then: callback => { + callback({data: {id: 124}}); + } + }; + }; + })); + + describe('onSubmit()', () => { + it('should perform a PATCH and then redirect to the main section', () => { + jest.spyOn(controller.$state, 'go'); + controller.onSubmit(); + + expect(controller.$state.go).toHaveBeenCalledWith('supplier.card.address.index'); + }); + }); + }); +}); diff --git a/modules/supplier/front/address/index/index.html b/modules/supplier/front/address/index/index.html new file mode 100644 index 000000000..cb7b3d56c --- /dev/null +++ b/modules/supplier/front/address/index/index.html @@ -0,0 +1,64 @@ + + + + + + + + + + + + + diff --git a/modules/supplier/front/address/index/index.js b/modules/supplier/front/address/index/index.js new file mode 100644 index 000000000..c3985a0c1 --- /dev/null +++ b/modules/supplier/front/address/index/index.js @@ -0,0 +1,46 @@ +import ngModule from '../../module'; +import Section from 'salix/components/section'; +import './style.scss'; + +class Controller extends Section { + constructor($element, $) { + super($element, $); + this.filter = { + fields: [ + 'id', + 'nickname', + 'street', + 'city', + 'provinceFk', + 'phone', + 'mobile', + 'postalCode' + ], + order: ['nickname ASC'], + include: [{ + relation: 'province', + scope: { + fields: ['id', 'name'] + } + }] + }; + } + + exprBuilder(param, value) { + switch (param) { + case 'search': + return /^\d+$/.test(value) + ? {id: value} + : {nickname: {like: `%${value}%`}}; + } + } +} +Controller.$inject = ['$element', '$scope']; + +ngModule.vnComponent('vnSupplierAddressIndex', { + template: require('./index.html'), + controller: Controller, + bindings: { + supplier: '<' + } +}); diff --git a/modules/supplier/front/address/index/index.spec.js b/modules/supplier/front/address/index/index.spec.js new file mode 100644 index 000000000..086d3a9fa --- /dev/null +++ b/modules/supplier/front/address/index/index.spec.js @@ -0,0 +1,34 @@ +import './index'; + +describe('Supplier', () => { + describe('Component vnSupplierAddressIndex', () => { + let controller; + let $scope; + let $stateParams; + + beforeEach(ngModule('supplier')); + + beforeEach(inject(($componentController, $rootScope, _$stateParams_) => { + $stateParams = _$stateParams_; + $stateParams.id = 1; + $scope = $rootScope.$new(); + const $element = angular.element(''); + controller = $componentController('vnSupplierAddressIndex', {$element, $scope}); + controller.supplier = {id: 1}; + })); + + describe('exprBuilder()', () => { + it('should return a filter based on a search by id', () => { + const filter = controller.exprBuilder('search', '123'); + + expect(filter).toEqual({id: '123'}); + }); + + it('should return a filter based on a search by name', () => { + const filter = controller.exprBuilder('search', 'Arkham Chemicals'); + + expect(filter).toEqual({nickname: {like: '%Arkham Chemicals%'}}); + }); + }); + }); +}); diff --git a/modules/supplier/front/address/index/style.scss b/modules/supplier/front/address/index/style.scss new file mode 100644 index 000000000..44ce07b3c --- /dev/null +++ b/modules/supplier/front/address/index/style.scss @@ -0,0 +1,21 @@ +@import "variables"; +@import "./effects"; + +vn-supplier-address-index { + .address { + padding-bottom: $spacing-md; + + &:last-child { + padding-bottom: 0; + } + & > a { + @extend %clickable; + box-sizing: border-box; + display: flex; + align-items: center; + width: 100%; + color: inherit; + overflow: hidden; + } + } +} \ No newline at end of file diff --git a/modules/supplier/front/address/locale/es.yml b/modules/supplier/front/address/locale/es.yml new file mode 100644 index 000000000..30009fa87 --- /dev/null +++ b/modules/supplier/front/address/locale/es.yml @@ -0,0 +1,18 @@ +# Index +Search by address: Buscar por dirección +You can search by address id or name: Puedes buscar por el id o nombre de la dirección + +# Create +Street address: Dirección postal +Postcode: Código postal +Town/City: Ciudad +Province: Provincia +Phone: Teléfono +Mobile: Móvil + +# Common +Fiscal name: Nombre fiscal +Street: Dirección fiscal +Addresses: Direcciones +New address: Nueva dirección +Edit address: Editar dirección \ No newline at end of file diff --git a/modules/supplier/front/index.js b/modules/supplier/front/index.js index 9c5cd4195..dc131ef4a 100644 --- a/modules/supplier/front/index.js +++ b/modules/supplier/front/index.js @@ -15,3 +15,6 @@ import './log'; import './consumption'; import './consumption-search-panel'; import './billing-data'; +import './address/index'; +import './address/create'; +import './address/edit'; diff --git a/modules/supplier/front/routes.json b/modules/supplier/front/routes.json index 5dc6a29b0..9a0dee48b 100644 --- a/modules/supplier/front/routes.json +++ b/modules/supplier/front/routes.json @@ -12,6 +12,7 @@ {"state": "supplier.card.basicData", "icon": "settings"}, {"state": "supplier.card.fiscalData", "icon": "account_balance"}, {"state": "supplier.card.billingData", "icon": "icon-payment"}, + {"state": "supplier.card.address.index", "icon": "icon-delivery"}, {"state": "supplier.card.account", "icon": "contact_support"}, {"state": "supplier.card.contact", "icon": "contact_phone"}, {"state": "supplier.card.log", "icon": "history"}, @@ -49,7 +50,8 @@ "params": { "supplier": "$ctrl.supplier" } - }, { + }, + { "url": "/basic-data", "state": "supplier.card.basicData", "component": "vn-supplier-basic-data", @@ -58,7 +60,8 @@ "params": { "supplier": "$ctrl.supplier" } - }, { + }, + { "url": "/fiscal-data", "state": "supplier.card.fiscalData", "component": "vn-supplier-fiscal-data", @@ -67,7 +70,8 @@ "supplier": "$ctrl.supplier" }, "acl": ["administrative"] - }, { + }, + { "url" : "/log", "state": "supplier.card.log", "component": "vn-supplier-log", @@ -100,7 +104,8 @@ "supplier": "$ctrl.supplier" }, "acl": ["administrative"] - },{ + }, + { "url": "/account", "state": "supplier.card.account", "component": "vn-supplier-account", @@ -109,6 +114,36 @@ "supplier": "$ctrl.supplier" }, "acl": ["administrative"] + }, + { + "url": "/address", + "state": "supplier.card.address", + "component": "ui-view", + "abstract": true + }, + { + "url": "/index?q", + "state": "supplier.card.address.index", + "component": "vn-supplier-address-index", + "description": "Addresses", + "params": { + "supplier": "$ctrl.supplier" + } + }, + { + "url": "/create", + "state": "supplier.card.address.create", + "component": "vn-supplier-address-create", + "description": "New address", + "params": { + "supplier": "$ctrl.supplier" + } + }, + { + "url": "/:addressId/edit", + "state": "supplier.card.address.edit", + "component": "vn-supplier-address-edit", + "description": "Edit address" } ] } \ No newline at end of file diff --git a/modules/ticket/front/descriptor-menu/index.js b/modules/ticket/front/descriptor-menu/index.js index 8ea08898f..17ed36ab5 100644 --- a/modules/ticket/front/descriptor-menu/index.js +++ b/modules/ticket/front/descriptor-menu/index.js @@ -28,8 +28,11 @@ class Controller extends Section { if (!value) return; this.loadData().then(() => { - if (this.$params.sendSMS) - this.showSMSDialog(); + if (this.$params.sendSMS) { + this.showSMSDialog({ + message: this.$params.message + }); + } }); } diff --git a/modules/ticket/front/descriptor/index.html b/modules/ticket/front/descriptor/index.html index af625f5d4..85cca1c4f 100644 --- a/modules/ticket/front/descriptor/index.html +++ b/modules/ticket/front/descriptor/index.html @@ -2,7 +2,11 @@ module="ticket" description="$ctrl.ticket.client.name"> - +
diff --git a/modules/ticket/front/summary/index.html b/modules/ticket/front/summary/index.html index bc75c2cc7..6c25f2665 100644 --- a/modules/ticket/front/summary/index.html +++ b/modules/ticket/front/summary/index.html @@ -19,7 +19,11 @@ ng-click="$ctrl.setOkState()" vn-tooltip="Change ticket state to 'Ok'"> - + diff --git a/modules/ticket/front/summary/index.js b/modules/ticket/front/summary/index.js index 714460221..5887be153 100644 --- a/modules/ticket/front/summary/index.js +++ b/modules/ticket/front/summary/index.js @@ -40,6 +40,11 @@ class Controller extends Summary { }); } + get isTicketModule() { + const path = this.$state.getCurrentPath()[1]; + return path.state.name === 'ticket'; + } + get isEditable() { try { return !this.ticket.ticketState.state.alertLevel; diff --git a/print/templates/email/osticket-report/osticket-report.html b/print/templates/email/osticket-report/osticket-report.html index 749c06128..431ee2631 100644 --- a/print/templates/email/osticket-report/osticket-report.html +++ b/print/templates/email/osticket-report/osticket-report.html @@ -45,10 +45,10 @@ {{$t('author')}} - {{$t('dated')}} - {{$t('ticketSubject')}} + {{$t('dated')}} + {{$t('ticketSubject')}} {{$t('ticketDescription')}} - {{$t('resolution')}} + {{$t('resolution')}}