Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2727-supplier_consumption
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Joan Sanchez 2021-05-03 14:03:41 +02:00
commit 1efe8cb683
35 changed files with 983 additions and 51 deletions

20
Jenkinsfile vendored
View File

@ -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}"
}

View File

@ -0,0 +1,2 @@
INSERT INTO salix.ACL (model, property, accessType, permission, principalType, principalId)
VALUES ('SupplierAddress', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -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
);

View File

@ -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'),

View File

@ -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

View File

@ -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"]'
},
};

View File

@ -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');
});
});

View File

@ -8,8 +8,8 @@
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
placeholder="Search by address"
info="You can search by address id or name"
placeholder="Search by consignee"
info="You can search by consignee id or name"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)"
auto-state="false">
@ -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'}">
<vn-none
class="vn-pr-sm"
ng-click="$ctrl.onStarClick($event)">
@ -78,7 +78,7 @@
<vn-float-button
vn-bind="+"
fixed-bottom-right
vn-tooltip="New address"
vn-tooltip="New consignee"
ui-sref="client.card.address.create"
icon="add"
label="Add">

View File

@ -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

View File

@ -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

View File

@ -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"
}

View File

@ -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"
}
}

View File

@ -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();
}
};

View File

@ -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"
}
}
}

View File

@ -153,6 +153,11 @@
"contacts": {
"type": "hasMany",
"model": "SupplierContact",
"foreignKey": "supplierFk"
},
"addresses": {
"type": "hasMany",
"model": "SupplierAddress",
"foreignKey": "supplierFk"
}
},

View File

@ -0,0 +1,109 @@
<vn-watcher
vn-id="watcher"
url="SupplierAddresses"
id-field="id"
data="$ctrl.address"
params="$ctrl.address"
insert-mode="true"
form="form">
</vn-watcher>
<vn-crud-model
auto-load="true"
url="Provinces/location"
data="provincesLocation"
order="id">
</vn-crud-model>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="$ctrl.address.nickname"
rule
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Street address"
ng-model="$ctrl.address.street"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-datalist vn-one
label="Postcode"
ng-model="$ctrl.address.postalCode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.country}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.address.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.address.provinceFk"
data="provincesLocation"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.address.phone"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Mobile"
ng-model="$ctrl.address.mobile"
rule>
</vn-textfield>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button
label="Cancel"
ui-sref="supplier.card.address.index">
</vn-button>
</vn-button-bar>
</form>
<!-- New postcode dialog -->
<vn-geo-postcode vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-geo-postcode>

View File

@ -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: '<'
}
});

View File

@ -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('<vn-supplier-address-create></vn-supplier-address-create>');
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);
});
});
});
});

View File

@ -0,0 +1,104 @@
<mg-ajax
path="SupplierAddresses/{{edit.params.addressId}}"
actions="$ctrl.address = edit.model"
options="mgEdit">
</mg-ajax>
<vn-watcher
vn-id="watcher"
url="SupplierAddresses"
id-field="id"
data="$ctrl.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" class="vn-w-md">
<vn-card class="vn-pa-lg">
<vn-horizontal>
<vn-textfield
vn-one
label="Name"
ng-model="$ctrl.address.nickname"
rule
vn-focus>
</vn-textfield>
<vn-textfield
vn-one
label="Street"
ng-model="$ctrl.address.street"
rule>
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-datalist vn-one
label="Postcode"
ng-model="$ctrl.address.postalCode"
selection="$ctrl.postcode"
url="Postcodes/location"
fields="['code','townFk']"
order="code, townFk"
value-field="code"
show-field="code"
rule>
<tpl-item>
{{code}} - {{town.name}} ({{town.province.name}},
{{town.province.country.country}})
</tpl-item>
<append>
<vn-icon-button
icon="add_circle"
vn-tooltip="New postcode"
ng-click="postcode.open()"
vn-acl="deliveryBoss"
vn-acl-action="remove">
</vn-icon-button>
</append>
</vn-datalist>
<vn-datalist vn-id="town" vn-one
label="City"
ng-model="$ctrl.address.city"
selection="$ctrl.town"
url="Towns/location"
fields="['id', 'name', 'provinceFk']"
show-field="name"
value-field="name">
<tpl-item>
{{name}}, {{province.name}}
({{province.country.country}})
</tpl-item>
</vn-datalist>
<vn-autocomplete vn-id="province" vn-one
label="Province"
ng-model="$ctrl.address.provinceFk"
url="Provinces/location"
fields="['id', 'name', 'countryFk']"
show-field="name"
value-field="id"
rule>
<tpl-item>{{name}} ({{country.country}})</tpl-item>
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Phone"
ng-model="$ctrl.address.phone"
rule>
</vn-textfield>
<vn-textfield
vn-one
label="Mobile"
ng-model="$ctrl.address.mobile"
rule>
</vn-textfield>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button label="Cancel" ui-sref="supplier.card.address.index"></vn-button>
</vn-button-bar>
</form>
<!-- New postcode dialog -->
<vn-geo-postcode vn-id="postcode"
on-response="$ctrl.onResponse($response)">
</vn-geo-postcode>

View File

@ -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
});

View File

@ -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('<vn-supplier-address-edit></vn-supplier-address-edit>');
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');
});
});
});
});

View File

@ -0,0 +1,64 @@
<vn-crud-model
vn-id="model"
url="Suppliers/{{$ctrl.$params.id}}/addresses"
filter="$ctrl.filter"
limit="10"
data="$ctrl.addresses"
auto-load="true">
</vn-crud-model>
<vn-portal slot="topbar">
<vn-searchbar
placeholder="Search by address"
info="You can search by address id or name"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)"
auto-state="false">
</vn-searchbar>
</vn-portal>
<vn-data-viewer
model="model"
class="vn-w-md">
<vn-card class="vn-pa-md">
<div
ng-repeat="address in $ctrl.addresses"
class="address">
<a
ui-sref="supplier.card.address.edit({addressId: {{::address.id}}})"
class="vn-pa-sm border-solid border-radius"
translate-attr="{title: 'Edit address'}">
<vn-one
style="overflow: hidden; min-width: 14em;">
<div class="ellipsize"><b>{{::address.nickname}} - #{{::address.id}}</b></div>
<div class="ellipsize" name="street">{{::address.street}}</div>
<div class="ellipsize">
<span ng-show="::address.postalCode">{{::address.postalCode}} -</span>
<span ng-show="::address.city">{{::address.city}},</span>
{{::address.province.name}}
</div>
<div class="ellipsize">
{{::address.phone}}<span ng-if="::address.mobile">, </span>
{{::address.mobile}}
</div>
</vn-one>
<vn-vertical
vn-one
ng-if="address.observations.length"
class="vn-hide-narrow vn-px-md border-solid-left"
style="height: 6em; overflow: auto;">
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'vn-pt-sm': $index}">
<b>{{::observation.observationType.description}}:</b>
<span>{{::observation.description}}</span>
</vn-one>
</vn-vertical>
</a>
</div>
</vn-card>
</vn-data-viewer>
<vn-float-button
vn-bind="+"
fixed-bottom-right
vn-tooltip="New address"
ui-sref="supplier.card.address.create"
icon="add"
label="Add">
</vn-float-button>

View File

@ -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: '<'
}
});

View File

@ -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('<vn-supplier-address-index></vn-supplier-address-index>');
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%'}});
});
});
});
});

View File

@ -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;
}
}
}

View File

@ -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

View File

@ -15,3 +15,6 @@ import './log';
import './consumption';
import './consumption-search-panel';
import './billing-data';
import './address/index';
import './address/create';
import './address/edit';

View File

@ -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"
}
]
}

View File

@ -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
});
}
});
}

View File

@ -2,7 +2,11 @@
module="ticket"
description="$ctrl.ticket.client.name">
<slot-dot-menu>
<vn-ticket-descriptor-menu ticket-id="$ctrl.ticket.id" parent-reload="$ctrl.cardReload()"/>
<vn-ticket-descriptor-menu
vn-id="descriptorMenu"
ticket-id="$ctrl.ticket.id"
parent-reload="$ctrl.cardReload()"
/>
</slot-dot-menu>
<slot-body>
<div class="attributes">

View File

@ -19,7 +19,11 @@
ng-click="$ctrl.setOkState()"
vn-tooltip="Change ticket state to 'Ok'">
</vn-button>
<vn-ticket-descriptor-menu ticket-id="$ctrl.summary.id" parent-reload="$ctrl.reload()"/>
<vn-ticket-descriptor-menu
ng-if="!$ctrl.isTicketModule"
ticket-id="$ctrl.summary.id"
parent-reload="$ctrl.reload()"
/>
</h5>
<vn-horizontal>
<vn-one>

View File

@ -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;

View File

@ -45,10 +45,10 @@
<thead>
<tr>
<th width="5%">{{$t('author')}}</th>
<th width="5%">{{$t('dated')}}</th>
<th width="30%">{{$t('ticketSubject')}}</th>
<th width="15%">{{$t('dated')}}</th>
<th width="25%">{{$t('ticketSubject')}}</th>
<th width="30%">{{$t('ticketDescription')}}</th>
<th width="30%">{{$t('resolution')}}</th>
<th width="20%">{{$t('resolution')}}</th>
</tr>
</thead>
<tbody v-for="ticket in technician.tickets">