version para Jenkins

This commit is contained in:
Carlos 2017-10-02 12:08:06 +02:00
commit 58a80d2062
453 changed files with 10858 additions and 2664 deletions

View File

@ -1,5 +1,9 @@
extends: google
extends: [eslint:recommended, google, plugin:jasmine/recommended]
installedESLint: true
plugins:
- jasmine
env:
jasmine: true
rules:
indent: [error, 4]
require-jsdoc: 0
@ -9,3 +13,6 @@ rules:
operator-linebreak: 0
radix: 0
guard-for-in: 0
camelcase: 0
default-case: 0
no-eq-null: 0

1
.gitignore vendored
View File

@ -4,3 +4,4 @@ build
npm-debug.log
debug.log
datasources.development.json
.idea

44
.vscode/launch.json vendored
View File

@ -5,7 +5,7 @@
"name": "Iniciar",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/app.js",
"program": "${workspaceRoot}/services/client/server/server.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
@ -37,50 +37,10 @@
"name": "Asociar al proceso",
"type": "node",
"request": "attach",
"processId": "${command.PickProcess}",
"processId": "${command:PickProcess}",
"port": 5858,
"sourceMaps": false,
"outFiles": []
},
{
"name": "Loopback",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/services/client/server/server.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": false,
"outFiles": []
},
{
"name": "gulp debug",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}\\@salix\\node_modules\\gulp\\bin\\gulp.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}\\@salix",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": false,
"outFiles": []
}
]
}

81
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,81 @@
#!/usr/bin/env groovy
import groovy.json.*;
def image;
def branchName = "${env.BRANCH_NAME}";
def branchNameTest = "preprod";
def branchNameProd = "master";
def prefixDocker = "test";
def dockerNginxName = ["nginx", "-p 80:80 --privileged --link test-auth:auth --link test-salix:salix --link test-client:client --link test-mailer:mailer --link test-production:production"]
def dockerAuthName = ["auth", "-p 3000:3000"]
def dockerSalixName = ["salix", "-p 3001:3001"]
def dockerClientName = ["client", "-p 3002:3002"]
def dockerMailerName = ["mailer", "-p 3003:3003"]
def dockerProductionName = ["production", "-p 3004:3004"]
def buildNumber = "${env.BUILD_NUMBER}";
def dockers = [dockerAuthName, dockerSalixName, dockerClientName, dockerMailerName, dockerProductionName, dockerNginxName]
node {
if (branchName == branchNameProd)
prefixDocker = "prod";
stage ('Checkout') {
checkout scm
}
stage ('install modules'){
sh "npm install"
}
stage ('build Project Salix'){
sh "gulp build"
}
for (int i = 0; i < dockers.size(); i++)
{
def element = dockers[i][0]
def ports = dockers[i][1]
stage ("docker ${element}")
{
stage ("Stopping ${prefixDocker}-${element} actual")
{
try
{
def returnDocker = sh (script: "docker stop ${prefixDocker}-${element}", returnStdout: true).trim();
echo "${returnDocker}";
returnDocker = sh (script: "docker rm ${prefixDocker}-${element}", returnStdout: false).trim();
//echo "${returnDocker}";
returnDocker = sh (script: "docker rmi ${prefixDocker}-${element}:${buildNumber-3}", returnStdout: true).trim();
echo "borran ${prefixDocker}-${element}:${buildNumber-3}";
//echo "${returnDocker}";
}catch(Exception _){
echo "Error Stage Stopping"
}
}
stage ("Create Docker Image ${element}") {
dir("./services/${element}"){
stage ("Install modules service ${element}"){
if (fileExists('./package.json'))
sh "npm i"
}
stage("Build image ${element}"){
image = docker.build("${prefixDocker}-${element}:${buildNumber}", ".")
}
}
}
stage ("Run Docker ${element}"){
image.run ("${ports} --name ${prefixDocker}-${element}")
}
/*
stage('docker registry'){
docker.withServer('tcp://harbor.verdnatura.es','docker-registry')
}
*/
}
}
}

64
README.md Normal file
View File

@ -0,0 +1,64 @@
# Project Title
One Paragraph of project description goes here
### Prerequisites
For testing purposes you will need to install globally the following items:
npm install -g karma
npm install -g karma-cli
## Getting Started // ### Installing
Pull from repo.
install nodejs v6 or above.
install nginx globally.
Ask a senior dev for the datasources.development.json files required to run the project.
on root run:
npm install
gulp install
lauching nginx:
./dev.sh
launching frontend:
gulp client
launching backend:
gulp services
## Running the tests
for client-side unit tests run from project's root:
karma start
for server-side unit tests run from project's root:
npm run testWatch or test for single run
### Break down into end to end tests
on root run:
npm run e2e
## Built With
* [angularjs](https://angularjs.org/)
* [nodejs](https://nodejs.org/)
* [webpack](https://webpack.js.org/)
* [loopback](https://loopback.io/)
* [docker](https://www.docker.com/)
* [gulp.js](https://gulpjs.com/)
## Versioning
We use [SourceTree](https://www.sourcetreeapp.com/) for versioning. For the versions available, see the [salix project](https://git.verdnatura.es).
## License
This project is licensed under the MIT License

View File

@ -5,6 +5,6 @@
"main": "index.js",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -1,4 +1,3 @@
export * from './module';
import './ngModule';
import './config';
export {component as Login} from './login/login';
import './login/login';

View File

@ -1,4 +1,4 @@
import {module} from './module';
import ngModule from './ngModule';
config.$inject = ['$translatePartialLoaderProvider', '$httpProvider'];
export function config($translatePartialLoaderProvider, $httpProvider) {
@ -7,4 +7,4 @@ export function config($translatePartialLoaderProvider, $httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
module.config(config);
ngModule.config(config);

26
client/auth/src/login/login.html Executable file → Normal file
View File

@ -1,18 +1,18 @@
<div>
<div class="box-wrapper">
<div class="box">
<img src="./logo.svg"/>
<form name="form" ng-submit="$ctrl.submit()">
<vn-textfield label="User" field="$ctrl.model.email"></vn-textfield>
<vn-password label="Password" field="$ctrl.model.password"></vn-password>
<div class="footer">
<vn-submit label="Enter"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
<div class="box">
<img src="./logo.svg"/>
<form name="form" ng-submit="$ctrl.submit()">
<vn-textfield vn-id="userField" label="User" model="$ctrl.user" vn-focus></vn-textfield>
<vn-textfield label="Password" model="$ctrl.password" type="password"></vn-textfield>
<div class="footer">
<vn-submit label="Enter"></vn-submit>
<div class="spinner-wrapper">
<vn-spinner enable="$ctrl.loading"></vn-spinner>
</div>
</form>
</div>
</div>
</form>
</div>
<vn-snackbar></vn-snackbar>
</div>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
</div>

View File

@ -1,57 +1,81 @@
import {module} from '../module';
import './login.scss';
import ngModule from '../ngModule';
import './style.scss';
export const component = {
template: require('./login.html'),
controller: controller
};
module.component('vnLogin', component);
controller.$inject = ['$http', '$element', '$window'];
function controller($http, $element, $window) {
Object.assign(this, {
submit: function() {
let model = this.model;
if (!(model && model.email && model.password)) {
this.showMessage('Please insert your email and password');
return;
}
this.loading = true;
model.appId = $window.location.href;
$http.post('/auth', this.model).then(
json => this.onLoginOk(json),
json => this.onLoginErr(json)
);
},
onLoginOk: function(json) {
this.loading = false;
let data = json.data;
$window.location = `${data.location}?access_token=${data.location}`;
},
onLoginErr: function(json) {
this.loading = false;
this.model.password = '';
let message;
switch (json.status) {
case 401:
message = 'Invalid credentials';
break;
case -1:
message = 'Can\'t contact with server';
break;
default:
message = 'Something went wrong';
}
this.showMessage(message);
},
showMessage: function(message) {
let snackbar = $element.find('vn-snackbar').controller('vnSnackbar');
snackbar.show({message: message});
export default class Controller {
constructor($element, $scope, $window, $http) {
this.$element = $element;
this.$ = $scope;
this.$window = $window;
this.$http = $http;
}
submit() {
if (!(this.user && this.password)) {
this.focusUser();
this.showMessage('Please insert your user and password');
return;
}
});
this.loading = true;
let params = {
user: this.user,
password: this.password,
location: this.$window.location.href
};
this.$http.post('/auth/login', params).then(
json => this.onLoginOk(json),
json => this.onLoginErr(json)
);
}
onLoginOk(json) {
this.loading = false;
let data = json.data;
let params = {
token: data.token,
continue: data.continue
};
this.$window.location = `${data.loginUrl}?${this.encodeUri(params)}`;
}
encodeUri(object) {
let uri = '';
for (var key in object)
if (object[key]) {
if (uri.length > 0)
uri += '&';
uri += encodeURIComponent(key) + '=' + encodeURIComponent(object[key]);
}
return uri;
}
onLoginErr(json) {
this.loading = false;
this.password = '';
this.focusUser();
let message;
switch (json.status) {
case 401:
message = 'Invalid credentials';
break;
case -1:
message = 'Can\'t contact with server';
break;
default:
message = 'Something went wrong';
}
this.showMessage(message);
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
showMessage(message) {
this.$.snackbar.show({message: message});
}
}
Controller.$inject = ['$element', '$scope', '$window', '$http'];
ngModule.component('vnLogin', {
template: require('./login.html'),
controller: Controller
});

View File

@ -44,4 +44,3 @@ vn-login > div {
overflow: visible;
}
}

View File

@ -1,4 +0,0 @@
import {ng} from 'vendor';
import * as core from 'core';
export const module = ng.module('vnAuth', [core.NAME]);

View File

@ -0,0 +1,5 @@
import {ng} from 'vendor';
import 'core';
let ngModule = ng.module('vnAuth', ['vnCore']);
export default ngModule;

View File

@ -5,6 +5,6 @@
"main": "index.js",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -1,75 +0,0 @@
{
"module": "client",
"routes": [
{
"url": "/clients",
"state": "clients",
"component": "vn-client-index"
}, {
"url": "/clients/:id",
"state": "clientCard",
"component": "vn-client-card"
}, {
"url": "/basic-data",
"state": "clientCard.basicData",
"component": "vn-client-basic-data",
"params": {
"client": "card.client"
},
"description": "Datos básicos",
"icon": "person"
}, {
"url": "/fiscal-data",
"state": "clientCard.fiscalData",
"component": "vn-client-fiscal-data",
"params": {
"client": "card.client"
},
"description": "Datos facturación",
"icon": "assignment"
}, {
"url": "/addresses",
"state": "clientCard.addresses",
"component": "vn-client-addresses",
"params": {
"client": "card.client"
},
"description": "Consignatarios",
"icon": "local_shipping"
}, {
"url": "/web-access",
"state": "clientCard.webAccess",
"component": "vn-client-web-access",
"params": {
"client": "card.client"
},
"description": "Acceso web",
"icon": "language"
}, {
"url": "/notes",
"state": "clientCard.notes",
"component": "vn-client-notes",
"params": {
"client": "card.client"
},
"description": "Notas",
"icon": "insert_drive_file"
}, {
"url": "/new-note",
"state": "clientCard.newNote",
"component": "vn-new-note"
},{
"url": "/create",
"state": "create",
"component": "vn-client-create"
}, {
"url": "/address/create",
"state": "clientCard.addressCreate",
"component": "vn-address-create"
}, {
"url": "/address/:addressId",
"state": "clientCard.addressEdit",
"component": "vn-address-edit"
}
]
}

110
client/client/routes.json Normal file
View File

@ -0,0 +1,110 @@
{
"module": "client",
"name": "Clients",
"icon": "/static/images/icon_client.png",
"validations" : true,
"routes": [
{
"url": "/clients",
"state": "clients",
"component": "vn-client-index"
}, {
"url": "/create",
"state": "create",
"component": "vn-client-create"
}, {
"url": "/clients/:id",
"state": "clientCard",
"abstract": true,
"component": "vn-client-card"
}, {
"url": "/basic-data",
"state": "clientCard.basicData",
"component": "vn-client-basic-data",
"params": {
"client": "card.client"
},
"menu": {
"description": "Datos básicos",
"icon": "person"
}
}, {
"url": "/fiscal-data",
"state": "clientCard.fiscalData",
"component": "vn-client-fiscal-data",
"params": {
"client": "card.client"
},
"menu": {
"description": "Datos fiscales",
"icon": "account_balance"
}
}, {
"url": "/billing-data",
"state": "clientCard.billingData",
"component": "vn-client-billing-data",
"params": {
"client": "card.client"
},
"menu": {
"description": "Datos facturación",
"icon": "assignment"
}
}, {
"url": "/addresses",
"state": "clientCard.addresses",
"component": "ui-view",
"abstract": true
}, {
"url": "/list",
"state": "clientCard.addresses.list",
"component": "vn-client-addresses",
"params": {
"client": "card.client"
},
"menu": {
"description": "Consignatarios",
"icon": "local_shipping"
}
}, {
"url": "/create",
"state": "clientCard.addresses.create",
"component": "vn-address-create"
}, {
"url": "/:addressId/edit",
"state": "clientCard.addresses.edit",
"component": "vn-address-edit"
}, {
"url": "/web-access",
"state": "clientCard.webAccess",
"component": "vn-client-web-access",
"params": {
"client": "card.client"
},
"menu": {
"description": "Acceso web",
"icon": "language"
}
}, {
"url": "/notes",
"state": "clientCard.notes",
"component": "ui-view",
"abstract": true
}, {
"url": "/list",
"state": "clientCard.notes.list",
"component": "vn-client-notes",
"params": {
"client": "card.client"
},
"menu": {
"description": "Notas",
"icon": "insert_drive_file"
}
}, {
"url": "/create",
"state": "clientCard.notes.create",
"component": "vn-note-create"
}
]
}

View File

@ -0,0 +1,46 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="$ctrl.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('clientCard.addresses')" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Address</vn-title>
<vn-horizontal>
<vn-check vn-one label="Default" field="$ctrl.address.isDefaultAddress"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Street address" field="$ctrl.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Postcode" field="$ctrl.address.postcode"></vn-textfield>
<vn-textfield vn-one label="Town/City" field="$ctrl.address.city"></vn-textfield>
<vn-autocomplete vn-one
field="$ctrl.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Province">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="$ctrl.address.agencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agency">
</vn-autocomplete>
<vn-textfield vn-one label="Phone" field="$ctrl.address.phone"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,16 @@
import ngModule from '../module';
export default class Controller {
constructor($state) {
this.address = {
clientFk: parseInt($state.params.id),
isEnabled: true
};
}
}
Controller.$inject = ['$state'];
ngModule.component('vnAddressCreate', {
template: require('./address-create.html'),
controller: Controller
});

View File

@ -0,0 +1,25 @@
import './address-create.js';
describe('Client', () => {
describe('Component vnAddressCreate', () => {
let $componentController;
let $state;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
$componentController = _$componentController_;
$state = _$state_;
$state.params.id = '1234';
}));
it('should define and set address property', () => {
let controller = $componentController('vnAddressCreate', {$state});
expect(controller.address.clientFk).toBe(1234);
expect(controller.address.isEnabled).toBe(true);
});
});
});

View File

@ -1,46 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="addressData.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="addressData.onSubmit()" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
<vn-horizontal>
<vn-check vn-one label="Predeterminado" field="addressData.address.default"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignatario" field="addressData.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Domicilio" field="addressData.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código Postal" field="addressData.address.postcode"></vn-textfield>
<vn-textfield vn-one label="Municipio" field="addressData.address.city"></vn-textfield>
<vn-autocomplete vn-one
field="addressData.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="addressData.address.agencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agencia">
</vn-autocomplete>
<vn-textfield vn-one label="Teléfono" field="addressData.address.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="addressData.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,26 +0,0 @@
import {module} from '../module';
class Controller {
constructor($scope, $state) {
this.$scope = $scope;
this.$state = $state;
this.address = {
client: parseInt($state.params.id),
enabled: true
};
}
onSubmit() {
this.$scope.watcher.submit().then(
() => this.$state.go('clientCard.addresses')
);
}
}
Controller.$inject = ['$scope', '$state'];
export const NAME = 'vnAddressCreate';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'addressData',
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,11 @@
{
"Street address": "Dirección postal",
"Default": "Predeterminado",
"Consignee": "Consignatario",
"Postcode": "Código postal",
"Town/City": "Ciudad",
"Province": "Provincia",
"Agency": "Agencia",
"Phone": "Teléfono",
"Mobile": "Móvil"
}

View File

@ -0,0 +1,49 @@
<mg-ajax path="/client/api/Addresses/{{edit.params.addressId}}" actions="$ctrl.address=edit.model" options="mgEdit"></mg-ajax>
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="$ctrl.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitBack()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Address</vn-title>
<vn-horizontal>
<vn-check vn-one label="enabled" field="$ctrl.address.isEnabled"></vn-check>
<vn-check vn-one label="Is equalizated" field="$ctrl.address.isEqualizated"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Street" field="$ctrl.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Postcode" field="$ctrl.address.postcode"></vn-textfield>
<vn-textfield vn-one label="City" field="$ctrl.address.city"></vn-textfield>
<vn-autocomplete vn-one
initial-data="$ctrl.address.province"
field="$ctrl.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Province">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="$ctrl.address.defaultAgencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agency">
</vn-autocomplete>
<vn-textfield vn-one label="Phone" field="$ctrl.address.phone"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,15 @@
import ngModule from '../module';
export default class Controller {
constructor($state) {
this.address = {
id: parseInt($state.params.addressId)
};
}
}
Controller.$inject = ['$state'];
ngModule.component('vnAddressEdit', {
template: require('./address-edit.html'),
controller: Controller
});

View File

@ -0,0 +1,24 @@
import './address-edit.js';
describe('Client', () => {
describe('Component vnAddressEdit', () => {
let $componentController;
let $state;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
$componentController = _$componentController_;
$state = _$state_;
$state.params.addressId = '1234';
}));
it('should define and set address property', () => {
let controller = $componentController('vnAddressEdit', {$state: $state});
expect(controller.address.id).toBe(1234);
});
});
});

View File

@ -1,48 +0,0 @@
<vn-watcher
vn-id="watcher"
get="true"
url="/client/api/Addresses"
id-field="id"
data="addressData.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitBack()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Consignatario</vn-title>
<vn-horizontal>
<vn-check vn-one label="Activo" field="addressData.address.enabled"></vn-check>
<vn-check vn-one label="Predeterminado" field="addressData.address.default"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignatario" field="addressData.address.consignee" vn-focus></vn-textfield>
<vn-textfield vn-one label="Domicilio" field="addressData.address.street"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código Postal" field="addressData.address.postcode"></vn-textfield>
<vn-textfield vn-one label="Municipio" field="addressData.address.city"></vn-textfield>
<vn-autocomplete vn-one
field="addressData.address.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
field="addressData.address.defaultAgencyFk"
url="/client/api/AgencyServices"
show-field="name"
value-field="id"
label="Agencia">
</vn-autocomplete>
<vn-textfield vn-one label="Teléfono" field="addressData.address.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="addressData.address.mobile"></vn-textfield>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,18 +0,0 @@
import {module} from '../module';
class Controller {
constructor($stateParams) {
this.address = {
id: $stateParams.addressId
};
}
}
Controller.$inject = ['$stateParams'];
export const NAME = 'vnAddressEdit';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'addressData',
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,4 @@
{
"Enabled": "Activo",
"Is equalizated": "Recargo de equivalencia"
}

View File

@ -0,0 +1,37 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/addressesList" options="mgIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-horizontal>
<vn-title vn-one>Addresses</vn-title>
</vn-horizontal>
<vn-horizontal ng-repeat="i in index.model track by i.id" class="pad-medium-top" style="align-items: center;">
<vn-auto style="border-radius: .5em;" class="pad-small border-solid"
ng-class="{'bg-dark-item': i.isDefaultAddress,'bg-opacity-item': !i.isEnabled && !i.isDefaultAddress}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h style="color:#FFA410;">
<i class="material-icons" ng-if="i.isDefaultAddress">star</i>
<i class="material-icons pointer" ng-if="!i.isDefaultAddress&&i.isEnabled" vn-tooltip="Set as default" tooltip-position="left" ng-click="$ctrl.setDefault(i.id)">star_border</i>
</vn-none>
<vn-auto>
<div><b>{{::i.consignee}}</b></div>
<div>{{::i.street}}</div>
<div>{{::i.city}}, {{::i.province}}</div>
<div>{{::i.phone}}, {{::i.mobile}}</div>
</vn-auto>
<a vn-empty ui-sref="clientCard.addresses.edit({addressId: {{i.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
</vn-horizontal>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-paging index="index"></vn-paging>
<vn-float-button
fixed-bottom-right
ui-sref="clientCard.addresses.create"
icon="add"
label="Add">
</vn-float-button>
</vn-vertical>

View File

@ -0,0 +1,19 @@
import ngModule from '../module';
class ClientAddresses {
constructor($http, $scope) {
this.$http = $http;
this.$scope = $scope;
}
setDefault(id) {
this.$http.patch(`/client/api/Addresses/${id}`, {id: id, isDefaultAddress: true}).then(() => {
this.$scope.index.accept();
});
}
}
ClientAddresses.$inject = ['$http', '$scope'];
ngModule.component('vnClientAddresses', {
template: require('./addresses.html'),
controller: ClientAddresses
});

View File

@ -1,32 +0,0 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/addressesList" options="vnIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-horizontal>
<vn-title vn-one>Consignatario</vn-title>
</vn-horizontal>
<vn-horizontal ng-repeat="i in index.model" class="pad-medium-top" style="align-items: center;">
<vn-auto style="border-radius: .5em;" class="pad-small border-solid"
ng-class="{'bg-dark-item': i.default,'bg-opacity-item': !i.enabled && !i.default}">
<vn-horizontal style="align-items: center;">
<vn-auto>
<div><b>{{i.consignee}}</b></div>
<div>{{i.street}}</div>
<div>{{i.city}}, {{i.province}}</div>
<div>{{i.phone}}, {{i.mobile}}</div>
</vn-auto>
<a vn-empty ui-sref="clientCard.addressEdit({addressId: {{i.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
</vn-horizontal>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-paging index="index"></vn-paging>
<vn-float-button
fixed-bottom-right
ui-sref="clientCard.addressCreate"
icon="add">
</vn-float-button>
</vn-vertical>

View File

@ -1,6 +0,0 @@
import {module} from '../module';
export const component = {
template: require('./index.html')
};
module.component('vnClientAddresses', component);

View File

@ -0,0 +1,4 @@
{
"Addresses": "Consignatarios",
"Set as default": "Establecer como predeterminado"
}

View File

@ -0,0 +1,45 @@
<mg-ajax path="/client/api/Clients/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Basic data</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Name" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi"></vn-textfield>
<vn-textfield autofocus vn-one label="Social name" field="$ctrl.client.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Phone" field="$ctrl.client.phone"></vn-textfield>
<vn-textfield vn-one label="Mobile" field="$ctrl.client.mobile"></vn-textfield>
<vn-textfield vn-one label="Fax" field="$ctrl.client.fax"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" field="$ctrl.client.email" info="You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main"></vn-textfield>
<vn-autocomplete vn-one
initial-data="$ctrl.client.salesPerson"
field="$ctrl.client.salesPersonFk"
url="/client/api/Clients/activeSalesPerson"
show-field="name"
value-field="id"
select-fields="surname"
label="Salesperson">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.contactChannel"
field="$ctrl.client.contactChannelFk"
url="/client/api/ContactChannels"
label="Channel">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,8 @@
import ngModule from '../module';
ngModule.component('vnClientBasicData', {
template: require('./basic-data.html'),
bindings: {
client: '<'
}
});

View File

@ -1,44 +0,0 @@
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="put">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Datos básicos</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="$ctrl.client.fi"></vn-textfield>
<vn-textfield autofocus vn-one label="Razón social" field="$ctrl.client.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Teléfono" field="$ctrl.client.phone"></vn-textfield>
<vn-textfield vn-one label="Móvil" field="$ctrl.client.mobile"></vn-textfield>
<vn-textfield vn-one label="Fax" field="$ctrl.client.fax"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" field="$ctrl.client.email"></vn-textfield>
<vn-autocomplete vn-one
field="$ctrl.client.salesPersonFk"
url="/client/api/Employees"
show-field="name"
value-field="id"
label="Comercial">
</vn-autocomplete>
<vn-autocomplete vn-one
field="$ctrl.client.chanelFK"
url="/client/api/Chanels"
show-field="name"
value-field="id"
label="Canal">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,9 +0,0 @@
import {module} from '../module';
export const component = {
template: require('./index.html'),
bindings: {
client: '<'
}
};
module.component('vnClientBasicData', component);

View File

@ -0,0 +1,13 @@
{
"Basic data": "Datos básicos",
"Name": "Nombre",
"Tax number": "NIF/CIF",
"Social name": "Razón social",
"Phone": "Teléfono",
"Mobile": "Móvil",
"Fax": "Fax",
"Email": "Correo electrónico",
"Salesperson": "Comercial",
"Channel": "Canal",
"You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main": "Puede guardar varios correos electrónicos encadenándolos mediante comas sin espacios, ejemplo: user@dominio.com,user2@dominio.com siendo el primer correo electrónico el principal"
}

View File

@ -0,0 +1,62 @@
<mg-ajax path="/client/api/Clients/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="$ctrl.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Billing information</vn-title>
<vn-horizontal>
<vn-autocomplete vn-two
vn-acl="administrative"
field="$ctrl.client.payMethodFk"
url="/client/api/PayMethods"
select-fields="ibanRequired"
initial-data="$ctrl.client.payMethod"
label="Forma de pago">
</vn-autocomplete>
<vn-textfield vn-two label="IBAN" field="$ctrl.client.iban" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Vencimiento" field="$ctrl.client.dueDay" vn-acl="administrative"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Descuento" field="$ctrl.client.discount" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Crédito" field="$ctrl.client.credit" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Crédito asegurado" field="$ctrl.client.creditInsurance" vn-acl="administrative"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-check vn-one label="Recargo de equivalencia" field="$ctrl.client.equalizationTax" vn-acl="administrative"></vn-check>
<vn-check vn-one label="Vies" field="$ctrl.client.vies" vn-acl="administrative"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Documentación</vn-title>
<vn-horizontal>
<vn-check vn-one label="Recibido core VNH" field="$ctrl.client.coreVnh" vn-acl="administrative"></vn-check>
<vn-check vn-one label="Recibido core VNL" field="$ctrl.client.coreVnl" vn-acl="administrative"></vn-check>
<vn-check vn-one label="Recibido B2B VNL" field="$ctrl.client.sepaVnl" vn-acl="administrative"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar" vn-acl="administrative"></vn-submit>
</vn-button-bar>
</form>
<vn-dialog
vn-id="send-mail"
on-response="$ctrl.returnDialog(response)">
<tpl-body>
<vn-vertical>
<vn-one text-center translate>Changed terms</vn-one>
<vn-one text-center translate>Notify customer?</vn-one>
</vn-vertical>
</tpl-body>
<tpl-buttons>
<button response="CANCEL" translate>No</button>
<button response="ACCEPT" translate>Yes, notify</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,58 @@
import ngModule from '../module';
export default class Controller {
constructor($scope, $http, vnApp, $translate) {
this.$ = $scope;
this.$http = $http;
this.vnApp = vnApp;
this.translate = $translate;
this.billData = {};
this.copyData();
}
$onChanges() {
this.copyData();
}
copyData() {
if (this.client) {
this.billData.payMethodFk = this.client.payMethodFk;
this.billData.iban = this.client.iban;
this.billData.dueDay = this.client.dueDay;
this.billData.discount = this.client.discount;
this.billData.credit = this.client.credit;
this.billData.creditInsurance = this.client.creditInsurance;
}
}
submit() {
return this.$.watcher.submit().then(
() => this.checkPaymentChanges());
}
checkPaymentChanges() {
let equals = true;
Object.keys(this.billData).forEach(
val => {
if (this.billData[val] !== this.client[val]) {
this.billData[val] = this.client[val];
equals = false;
}
}
);
if (!equals)
this.$.sendMail.show();
}
returnDialog(response) {
if (response === 'ACCEPT') {
this.$http.post(`/mailer/manuscript/payment-update/${this.client.id}`).then(
() => this.vnApp.showMessage(this.translate.instant('Notification sent!'))
);
}
}
}
Controller.$inject = ['$scope', '$http', 'vnApp', '$translate'];
ngModule.component('vnClientBillingData', {
template: require('./billing-data.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,85 @@
import './billing-data.js';
describe('Client', () => {
describe('Component vnClientBillingData', () => {
let $componentController;
let $httpBackend;
let $scope;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
let submit = jasmine.createSpy('submit').and.returnValue(Promise.resolve());
$scope.watcher = {submit};
let show = jasmine.createSpy('show');
$scope.sendMail = {show};
}));
describe('copyData()', () => {
it(`should define billData using client's data`, () => {
let controller = $componentController('vnClientBillingData', {$scope: $scope});
controller.client = {
credit: 1000000000000,
creditInsurance: null,
discount: 99,
dueDay: 0,
iban: null,
payMethodFk: 1
};
controller.billData = {};
controller.copyData(controller.client);
expect(controller.billData).toEqual(controller.client);
});
});
describe('submit()', () => {
it(`should call submit() on the watcher then receive a callback`, done => {
let controller = $componentController('vnClientBillingData', {$scope: $scope});
spyOn(controller, 'checkPaymentChanges');
controller.submit()
.then(() => {
expect(controller.$.watcher.submit).toHaveBeenCalledWith();
expect(controller.checkPaymentChanges).toHaveBeenCalledWith();
done();
});
});
});
describe('checkPaymentChanges()', () => {
it(`should not call sendMail.show() if there are no changes on billing data`, () => {
let controller = $componentController('vnClientBillingData', {$scope: $scope});
controller.billData = {marvelHero: 'Silver Surfer'};
controller.client = {marvelHero: 'Silver Surfer'};
controller.checkPaymentChanges();
expect(controller.$.sendMail.show).not.toHaveBeenCalled();
});
it(`should call sendMail.show() if there are changes on billing data object`, () => {
let controller = $componentController('vnClientBillingData', {$scope: $scope});
controller.billData = {marvelHero: 'Silver Surfer'};
controller.client = {marvelHero: 'Spider-Man'};
controller.checkPaymentChanges();
expect(controller.$.sendMail.show).toHaveBeenCalledWith();
});
});
describe('returnDialog()', () => {
it('should request to send notification email', () => {
let controller = $componentController('vnClientBillingData', {$scope: $scope});
controller.client = {id: '123'};
$httpBackend.when('POST', `/mailer/manuscript/payment-update/${controller.client.id}`).respond('done');
$httpBackend.expectPOST(`/mailer/manuscript/payment-update/${controller.client.id}`);
controller.returnDialog('ACCEPT');
$httpBackend.flush();
});
});
});
});

View File

@ -0,0 +1,8 @@
{
"Changed terms": "Has modificado las condiciones de pago",
"Notify customer?" : "¿Deseas notificar al cliente de dichos cambios?",
"No": "No",
"Yes, notify": "Sí, notificar",
"Notification sent!": "¡Notificación enviada!",
"Notification error": "Error al enviar notificación"
}

View File

@ -0,0 +1,18 @@
<vn-horizontal>
<mg-ajax
path="/client/api/Clients/{{edit.params.id}}/card"
actions="card.client = edit.model"
options="mgEdit">
</mg-ajax>
<vn-empty style="min-width: 18em; padding-left: 1em; padding-bottom: 1em;">
<vn-descriptor
client="card.client"
active="card.client.active"
class="display-block" >
</vn-descriptor>
<vn-left-menu></vn-left-menu>
</vn-empty>
<vn-auto>
<vn-vertical style="max-width: 70em; margin: 0 auto;" ui-view></vn-vertical>
</vn-auto>
</vn-horizontal>

View File

@ -0,0 +1,14 @@
import ngModule from '../module';
import './style.css';
export default class Controller {
constructor() {
this.client = null;
}
}
ngModule.component('vnClientCard', {
template: require('./card.html'),
controller: Controller,
controllerAs: 'card'
});

View File

@ -0,0 +1,24 @@
import './card.js';
describe('Client', () => {
describe('Component vnClientCard', () => {
let $componentController;
let $scope;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_;
$scope = $rootScope;
}));
it('should define and set client property to null in the module instance', () => {
let controller = $componentController('vnClientCard', {$scope: $scope});
expect(controller.client).toBeDefined();
expect(controller.client).toBe(null);
});
});
});

View File

@ -1,9 +0,0 @@
<vn-horizontal>
<vn-empty style="min-width: 18em; padding-left: 1em; padding-bottom: 1em;">
<vn-descriptor client="card.client" active="card.client.active" class="display-block" ></vn-descriptor>
<vn-left-menu items="card.items"></vn-left-menu>
</vn-empty>
<vn-auto>
<vn-vertical style="max-width: 70em; margin: 0 auto;" ui-view></vn-vertical>
</vn-auto>
</vn-horizontal>

View File

@ -1,26 +0,0 @@
import {module} from '../module';
import './style.css';
export const NAME = 'vnClientCard';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'card',
controller: function($http, $stateParams) {
this.client = null;
$http.get(`/client/api/Clients/${$stateParams.id}?filter[include][accountFk]`).then(
json => this.client = json.data
);
this.items = [];
routes.client.routes.forEach(i => {
if (i.description)
this.items.push({
description: i.description,
icon: i.icon,
href: i.state
});
});
}
};
COMPONENT.controller.$inject = ['$http', '$stateParams'];
module.component(NAME, COMPONENT);

View File

@ -1,25 +1,16 @@
export * from './module';
export {NAME as CLIENT_CARD,
COMPONENT as CLIENT_CARD_COMPONENT} from './card/index';
export {NAME as CLIENTS,
COMPONENT as CLIENTS_COMPONENT} from './index/index';
export {NAME as CLIENT_FISCAL_DATA_INDEX,
COMPONENT as CLIENT_FISCAL_DATA_INDEX_COMPONENT} from './fiscal-data/index';
export {NAME as CLIENT_DESCRIPTOR,
COMPONENT as CLIENT_DESCRIPTOR_COMPONENT} from './descriptor/index';
export {NAME as CLIENT_NOTES,
COMPONENT as CLIENT_NOTES_COMPONENT} from './notes/index';
export {NAME as CLIENT_SEARCH_PANEL,
COMPONENT as CLIENT_SEARCH_PANEL_COMPONENT} from './search-panel/index';
export {NAME as CLIENT_CREATE,
COMPONENT as CLIENT_CREATE_COMPONENT} from './create/index';
export {NAME as CLIENT_ADDRESS_EDIT_INDEX,
COMPONENT as CLIENT_ADDRESS_EDIT_INDEX_COMPONENT} from './address-edit/index';
export {NAME as NEW_NOTE_INDEX,
COMPONENT as NEW_NOTE_INDEX_COMPONENT} from './new-note/index';
import './addresses/index';
import './address-create/index';
import './basic-data/index';
import './web-access/index';
import './index/index';
import './card/card';
import './create/create';
import './basic-data/basic-data';
import './fiscal-data/fiscal-data';
import './billing-data/billing-data';
import './descriptor/descriptor';
import './search-panel/search-panel';
import './addresses/addresses';
import './address-create/address-create';
import './address-edit/address-edit';
import './notes/notes';
import './note-create/note-create';
import './web-access/web-access';

View File

@ -0,0 +1,32 @@
<mg-ajax path="/client/api/Clients/createUserProfile" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;">
<vn-card>
<vn-vertical pad-large>
<vn-title>Create client</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Name" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Business name" field="$ctrl.client.socialName"></vn-textfield>
<vn-textfield vn-one label="User name" field="$ctrl.client.userName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" field="$ctrl.client.email" info="You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Create and edit"></vn-submit>
<vn-button label="Create" ng-click="watcher.submitBack()"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -1,23 +1,22 @@
import {module} from '../module';
import ngModule from '../module';
class Controller {
export default class Controller {
constructor($scope, $state) {
this.$scope = $scope;
this.$ = $scope;
this.$state = $state;
this.client = {
active: true
};
}
onSubmit() {
this.$scope.watcher.submit().then(
this.$.watcher.submit().then(
json => this.$state.go('clientCard.basicData', {id: json.data.id})
);
}
}
Controller.$inject = ['$scope', '$state'];
export const component = {
template: require('./index.html'),
ngModule.component('vnClientCreate', {
template: require('./create.html'),
controller: Controller
};
module.component('vnClientCreate', component);
});

View File

@ -0,0 +1,46 @@
import './create.js';
describe('Client', () => {
describe('Component vnClientCreate', () => {
let $componentController;
let $scope;
let $state;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$state_) => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$state = _$state_;
$scope.watcher = {
submit: () => {
return {
then: callback => {
callback({data: {id: '1234'}});
}
};
}
};
}));
it('should define and set scope, state and client properties', () => {
let controller = $componentController('vnClientCreate', {$scope: $scope});
expect(controller.$).toBe($scope);
expect(controller.$state).toBe($state);
expect(controller.client.active).toBe(true);
});
describe('onSubmit()', () => {
it(`should call submit() on the watcher then expect a callback`, () => {
let controller = $componentController('vnClientCreate', {$scope: $scope});
spyOn($state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('clientCard.basicData', {id: '1234'});
});
});
});
});

View File

@ -1,28 +0,0 @@
<mg-ajax path="/client/api/Clients" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" margin-medium>
<div style="max-width: 70em; margin: 0 auto;">
<vn-card>
<vn-vertical pad-large>
<vn-title>Crear Cliente</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" field="$ctrl.client.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Razón social" field="$ctrl.client.socialName"></vn-textfield>
<vn-one></vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Crear y continuar"></vn-submit>
<vn-button label="Crear" ng-click="watcher.submitBack()"></vn-button>
</vn-button-bar>
</div>
</form>

View File

@ -0,0 +1,10 @@
{
"Name": "Nombre",
"Tax number": "NIF/CIF",
"Business name": "Razón social",
"User name": "Nombre de usuario",
"Email": "Correo electrónico",
"Create and edit": "Crear y editar",
"Create": "Crear",
"You can save multiple emails by chaining them using comma without spaces, example: user@domain.com,user2@domain.com the first email will be considered as the main": "Puede guardar varios correos electrónicos encadenándolos mediante comas sin espacios, ejemplo: user@dominio.com,user2@dominio.com siendo el primer correo electrónico el principal"
}

View File

@ -5,10 +5,10 @@
<i class="material-icons descriptor-icon">person</i>
</vn-one>
<vn-vertical vn-two>
<div class="margin-none">{{descriptor.client.id}}</div>
<div class="margin-none">{{descriptor.client.name}}</div>
<div class="margin-none">{{descriptor.client.phone}}</div>
<vn-switch label="Activo" model="descriptor.active"></vn-switch>
<div class="margin-none">{{::$ctrl.client.id}}</div>
<div class="margin-none">{{$ctrl.client.name}}</div>
<div class="margin-none">{{$ctrl.client.phone}}</div>
<vn-switch label="Activo" model="$ctrl.active"></vn-switch>
</vn-vertical>
</vn-horizontal>
</vn-vertical>

View File

@ -0,0 +1,27 @@
import ngModule from '../module';
import './style.css';
export default class Controller {
constructor($scope, $http) {
this.$http = $http;
}
set active(value) {
if (this._active !== value && this._active !== undefined)
this.$http.put(`/client/api/Clients/${this.client.id}/activate`);
this._active = value;
}
get active() {
return this._active;
}
}
Controller.$inject = ['$scope', '$http'];
ngModule.component('vnDescriptor', {
template: require('./descriptor.html'),
controller: Controller,
bindings: {
client: '<',
active: '<'
}
});

View File

@ -0,0 +1,32 @@
import './descriptor.js';
describe('Client', () => {
describe('Component vnDescriptor', () => {
let $componentController;
let $scope;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
}));
describe('set active', () => {
it('should check if active is defined and diferent from the new value', () => {
let controller = $componentController('vnDescriptor', {$scope: $scope});
controller.client = {id: 1};
expect(controller._active).toBe(undefined);
controller.active = false;
expect(controller._active).toBe(false);
controller.active = true;
expect(controller._active).toBe(true);
});
});
});
});

View File

@ -1,21 +0,0 @@
import {module} from '../module';
import './style.css';
export const NAME = 'vnDescriptor';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'descriptor',
bindings: {
client: '<',
active: '<'
},
controller: function($http, $scope) {
var self = this;
$scope.$watch('descriptor.active', function(newValue, oldValue) {
if (oldValue !== undefined)
$http.put(`/client/api/Clients/${self.client.id}/activate`);
});
}
};
COMPONENT.controller.$inject = ['$http', '$scope'];
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,45 @@
<mg-ajax path="/client/api/Clients/{{patch.params.id}}" options="vnPatch"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
form="form"
save="patch">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Fiscal data</vn-title>
<vn-horizontal>
<vn-check vn-one label="Facturar" field="$ctrl.client.hasToInvoice"></vn-check>
<vn-check vn-one label="Factura impresa" field="$ctrl.client.invoiceByEmail"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Domicilio fiscal" field="$ctrl.client.street" vn-focus></vn-textfield>
<vn-textfield vn-one label="Municipio" field="$ctrl.client.city"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código postal" field="$ctrl.client.postcode"></vn-textfield>
<vn-autocomplete vn-one
initial-data="$ctrl.client.province"
field="$ctrl.client.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.country"
field="$ctrl.client.countryFk"
url="/client/api/Countries"
show-field="name"
value-field="id"
label="País">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,8 @@
import ngModule from '../module';
ngModule.component('vnClientFiscalData', {
template: require('./fiscal-data.html'),
bindings: {
client: '<'
}
});

View File

@ -1,73 +0,0 @@
<mg-ajax path="/client/api/Clients/{{put.params.id}}" options="vnPut"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="fiscal.client"
form="form"
save="put">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Datos fiscales y de facturación</vn-title>
<vn-horizontal>
<vn-check vn-one label="Facturar" field="fiscal.client.hasToInvoice"></vn-check>
<vn-check vn-one label="Factura impresa" field="fiscal.client.invoiceByEmail"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Domicilio fiscal" field="fiscal.client.street" vn-focus></vn-textfield>
<vn-textfield vn-one label="Municipio" field="fiscal.client.city"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Código postal" field="fiscal.client.postcode"></vn-textfield>
<vn-autocomplete vn-one
field="fiscal.client.provinceFk"
url="/client/api/Provinces"
show-field="name"
value-field="id"
label="Provincia">
</vn-autocomplete>
<vn-autocomplete vn-one
field="fiscal.client.countryFk"
url="/client/api/Countries"
show-field="name"
value-field="id"
label="País">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Información de facturación</vn-title>
<vn-horizontal>
<vn-textfield vn-two label="IBAN" field="fiscal.client.iban"></vn-textfield>
<vn-autocomplete vn-two
field="fiscal.client.payMethodFk"
url="/client/api/PayMethods"
show-field="name"
value-field="id"
label="Forma de pago">
</vn-autocomplete>
<vn-textfield vn-one label="Vencimiento" field="fiscal.client.dueDay"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Crédito" field="fiscal.client.credit"></vn-textfield>
<vn-textfield vn-one label="Crédito asegurado" field="fiscal.client.creditInsurance"></vn-textfield>
<vn-check vn-three label="Recargo de equivalencia" field="fiscal.client.surcharge"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-card margin-small-bottom>
<vn-vertical pad-large>
<vn-title>Documentación</vn-title>
<vn-horizontal>
<vn-check vn-one label="Recibido core VNH" field="fiscal.client.coreVnh"></vn-check>
<vn-check vn-one label="Recibido core VNL" field="fiscal.client.coreVnl"></vn-check>
<vn-check vn-one label="Recibido B2B VNL" field="fiscal.client.sepaVnl"></vn-check>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,11 +0,0 @@
import {module} from '../module';
export const NAME = 'vnClientFiscalData';
export const COMPONENT = {
template: require('./index.html'),
controllerAs: 'fiscal',
bindings: {
client: '<'
}
};
module.component(NAME, COMPONENT);

View File

@ -1,21 +1,21 @@
<mg-ajax path="/client/api/Clients/filter" options="vnIndex"></mg-ajax>
<mg-ajax path="/client/api/Clients/filter" options="mgIndex"></mg-ajax>
<div margin-medium>
<div style="max-width: 40em; margin: 0 auto;">
<vn-card>
<vn-horizontal pad-medium>
<vn-searchbar
vn-auto
<vn-searchbar vn-auto
index="index"
on-search="index.accept()"
on-search="$ctrl.search(index)"
advanced="true"
search="$ctrl.model.search"
popover="vn-client-search-panel">
</vn-searchbar>
</vn-horizontal>
</vn-card>
<vn-card margin-medium-top>
<vn-item-client ng-repeat="client in index.model" title="View client" client="client"></vn-item-client>
<vn-item-client ng-repeat="client in index.model.instances" title="View client" client="client"></vn-item-client>
</vn-card>
<vn-paging index="index"></vn-paging>
<vn-paging index="index" total="index.model.count"></vn-paging>
</div>
<a ui-sref="create" fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>

View File

@ -1,9 +1,18 @@
import {module} from '../module';
import ngModule from '../module';
import './style.css';
import './item-client';
export const NAME = 'vnClientIndex';
export const COMPONENT = {
template: require('./index.html')
};
module.component(NAME, COMPONENT);
export default class Controller {
constructor() {
this.model = {};
}
search(index) {
index.filter.search = this.model.search;
index.accept();
}
}
ngModule.component('vnClientIndex', {
template: require('./index.html'),
controller: Controller
});

View File

@ -0,0 +1,37 @@
import './index.js';
describe('Client', () => {
describe('Component vnClientIndex', () => {
let $componentController;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject(_$componentController_ => {
$componentController = _$componentController_;
}));
it('should define and set model property as an empty object', () => {
let controller = $componentController('vnClientIndex');
expect(controller.model).toEqual({});
});
describe('search()', () => {
it(`should set model's search to the search input`, () => {
let controller = $componentController('vnClientIndex');
controller.model.search = 'batman';
let index = {
filter: {},
accept: () => {
return 'accepted';
}
};
controller.search(index);
expect(index.filter.search).toBe('batman');
});
});
});
});

View File

@ -1,7 +1,7 @@
<a ui-sref="clientCard.basicData({ id: {{itemClient.client.id}} })" pad-medium border-solid-bottom>
<div class="vn-item-client-name">{{itemClient.client.name}}</div>
<div>Id Cliente: <b>{{itemClient.client.id}}</b></div>
<div>Teléfono: <b>{{itemClient.client.phone | phone}}</b></div>
<div>Población: <b>{{itemClient.client.city}}</b></div>
<div>email: <b>{{itemClient.client.email}}</b></div>
<a ui-sref="clientCard.basicData({ id: {{$ctrl.client.id}} })" pad-medium border-solid-bottom>
<div class="vn-item-client-name">{{$ctrl.client.name}}</div>
<div><span translate>Client id</span>: <b>{{$ctrl.client.id}}</b></div>
<div><span translate>Phone</span>: <b>{{$ctrl.client.phone | phone}}</b></div>
<div><span translate>Town/City</span>: <b>{{$ctrl.client.city}}</b></div>
<div><span translate>Email</span>: <b>{{$ctrl.client.email}}</b></div>
</a>

View File

@ -1,11 +1,8 @@
import {module} from '../module';
import ngModule from '../module';
export const NAME = 'vnItemClient';
export const COMPONENT = {
ngModule.component('vnItemClient', {
template: require('./item-client.html'),
controllerAs: 'itemClient',
bindings: {
client: '<'
}
};
module.component(NAME, COMPONENT);
});

View File

@ -0,0 +1,7 @@
{
"Client id": "Id cliente",
"Phone": "Teléfono",
"Town/City": "Ciudad",
"Email": "Correo electrónico",
"Create client": "Crear cliente"
}

View File

@ -1 +1,4 @@
{}
{
"Client": "Client",
"Clients": "Clients"
}

View File

@ -1 +1,5 @@
{}
{
"Client": "Cliente",
"Clients": "Clientes",
"Fiscal data": "Datos Fiscales"
}

View File

@ -1,5 +1,5 @@
import {ng} from 'vendor';
import * as core from 'core';
import 'core';
export const NAME = 'client';
export const module = ng.module(NAME, []);
const ngModule = ng.module('client', ['vnCore']);
export default ngModule;

View File

@ -1,18 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/ClientObservations"
id-field="id"
data="newNote.note"
form="form">
</vn-watcher>
<form name="form" ng-submit="newNote.onSubmit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Nueva nota</vn-title>
<vn-textarea label="Nueva nota" model="newNote.note.text" vn-focus padd-medium-top></vn-textarea>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
</vn-button-bar>
</form>

View File

@ -1,27 +0,0 @@
import template from './index.html';
import {module} from '../module';
class Controller {
constructor($element, $state) {
this.element = $element[0];
this.$state = $state;
this.note = {
client: $state.params.id,
text: null
};
}
onSubmit() {
this.element.querySelector('vn-watcher').$ctrl.submit().then(
() => this.$state.go('clientCard.notes')
);
}
}
Controller.$inject = ['$element', '$state'];
export const NAME = 'vnNewNote';
export const COMPONENT = {
template: template,
controllerAs: 'newNote',
controller: Controller
};
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,4 @@
{
"New note": "Nueva nota",
"Note": "Nota"
}

View File

@ -0,0 +1,18 @@
<vn-watcher
vn-id="watcher"
url="/client/api/ClientObservations"
id-field="id"
data="$ctrl.note"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('clientCard.notes.list')" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>New note</vn-title>
<vn-textarea label="Note" model="$ctrl.note.text" vn-focus padd-medium-top></vn-textarea>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,16 @@
import ngModule from '../module';
export default class Controller {
constructor($state) {
this.note = {
clientFk: parseInt($state.params.id),
text: null
};
}
}
Controller.$inject = ['$state'];
ngModule.component('vnNoteCreate', {
template: require('./note-create.html'),
controller: Controller
});

View File

@ -0,0 +1,25 @@
import './note-create.js';
describe('Client', () => {
describe('Component vnNoteCreate', () => {
let $componentController;
let $state;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
$componentController = _$componentController_;
$state = _$state_;
$state.params.id = '1234';
}));
it('should define clientFk using $state.params.id', () => {
let controller = $componentController('vnNoteCreate', {$state: $state});
expect(controller.note.clientFk).toBe(1234);
expect(controller.note.client).toBe(undefined);
});
});
});

View File

@ -1,17 +0,0 @@
<vn-card ng-show="observation.observations.length" pad-medium>
<vn-vertical pad-large>
<vn-title>Notas</vn-title>
<vn-horizontal ng-repeat="n in observation.observations" margin-small-bottom style="align-items: center;">
<vn-auto style="border-radius: .3em;" class="pad-small border-solid">
<div class="notes-date">{{n.creationTime | date:'dd/MM/yyyy HH:mm'}}</div>
<div class="notes-date">{{n.employeeFk}}</div>
<div>{{n.text}}</div>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-float-button
fixed-bottom-right
ng-click="observation.newObservation()"
icon="add">
</vn-float-button>

View File

@ -1,34 +0,0 @@
import './style.css';
import template from './index.html';
import {module} from '../module';
export const NAME = 'vnClientNotes';
export const COMPONENT = {
template: template,
controllerAs: 'observation',
bindings: {
client: '<'
},
controller: function($http, $state) {
this.$onChanges = function(changes) {
if (this.client) {
this.getObservation(this.client.id);
}
};
this.getObservation = function(clientId) {
let json = JSON.stringify({where: {clientFk: this.client.id}, order: 'creationTime DESC'});
$http.get(`/client/api/clientObservations?filter=${json}`).then(
json => {
this.observations = json.data;
}
);
};
this.newObservation = () => {
$state.go("clientCard.newNote", {id: this.client.id});
};
}
};
COMPONENT.controller.$inject = ['$http', '$state'];
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,3 @@
{
"Notes": "Notas"
}

View File

@ -0,0 +1,17 @@
<vn-card ng-show="$ctrl.observations.length" pad-medium>
<vn-vertical pad-large>
<vn-title>Notes</vn-title>
<vn-horizontal ng-repeat="n in $ctrl.observations" margin-small-bottom style="align-items: center;">
<vn-auto style="border-radius: .3em;" class="pad-small border-solid">
<div class="notes-date">{{::n.created | date:'dd/MM/yyyy HH:mm'}}</div>
<div class="notes-date">{{::n.employee.name}}</div>
<div>{{::n.text}}</div>
</vn-auto>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-float-button
fixed-bottom-right
ng-click="$ctrl.newObservation()"
icon="add">
</vn-float-button>

View File

@ -0,0 +1,34 @@
import ngModule from '../module';
import './style.css';
export default class Controller {
constructor($http, $state) {
this.$http = $http;
this.$state = $state;
}
$onChanges() {
if (this.client) {
this.getObservation(this.client.id);
}
}
getObservation(clientId) {
let json = JSON.stringify({where: {clientFk: this.client.id}, order: 'created DESC'});
this.$http.get(`/client/api/clientObservations?filter=${json}`).then(
json => {
this.observations = json.data;
}
);
}
newObservation() {
this.$state.go("clientCard.notes.create", {id: this.client.id});
}
}
Controller.$inject = ['$http', '$state'];
ngModule.component('vnClientNotes', {
template: require('./notes.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,55 @@
import './notes.js';
describe('Client', () => {
describe('Component vnClientNotes', () => {
let $componentController;
let $state;
let $httpBackend;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
}));
describe('$onChanges()', () => {
it(`should call getObservation() with the client id`, () => {
let controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
controller.client = {
id: 1234
};
spyOn(controller, 'getObservation').and.returnValue();
controller.$onChanges();
expect(controller.getObservation).toHaveBeenCalledWith(1234);
});
});
describe('$getObservation()', () => {
it(`should request to GET the client notes`, () => {
let controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
controller.client = {id: '1234'};
let json = JSON.stringify({where: {clientFk: '1234'}, order: 'created DESC'});
$httpBackend.when('GET', `/client/api/clientObservations?filter=${json}`).respond('ok');
$httpBackend.expectGET(`/client/api/clientObservations?filter=${json}`, {Accept: 'application/json, text/plain, */*'});
controller.getObservation();
$httpBackend.flush();
});
});
describe('$newObservation()', () => {
it(`should redirect the user to the newObservation view`, () => {
let controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
controller.client = {id: '1234'};
spyOn(controller.$state, 'go');
controller.newObservation();
expect(controller.$state.go).toHaveBeenCalledWith('clientCard.notes.create', Object({id: '1234'}));
});
});
});
});

View File

@ -1,26 +0,0 @@
<div pad-large style="min-width: 30em;" ng-show="$ctrl.formVisibility">
<form name="form" ng-submit="form.$valid && $ctrl.onSearch()" ng-keyup="$ctrl.getKeyPressed($event)">
<vn-horizontal>
<vn-textfield vn-one label="Id Cliente" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="NIF/CIF" model="$ctrl.filter.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Nombre" model="$ctrl.filter.name"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Razon Social" model="$ctrl.filter.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Población" model="$ctrl.filter.city"></vn-textfield>
<vn-textfield vn-one label="Código Postal" model="$ctrl.filter.postcode"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" model="$ctrl.filter.email"></vn-textfield>
<vn-textfield vn-one label="Teléfono" model="$ctrl.filter.phone"></vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -1,28 +0,0 @@
import {module} from '../module';
export const NAME = 'vnClientSearchPanel';
export const COMPONENT = {
template: require('./index.html'),
controller: function($scope, $window) {
this.filter = {id: null, fi: null, name: null, socialName: null, city: null, postcode: null, email: null, phone: null};
this.formVisibility = true;
this.onSearch = () => {
this.setStorageValue();
this.onSubmit(this.filter);
};
this.getKeyPressed = function(event) {
if (event.which === 27)
this.formVisibility = false;
};
this.$onChanges = () => {
var value = JSON.parse($window.sessionStorage.getItem('filter'));
if (value !== undefined)
this.filter = value;
};
this.setStorageValue = () => {
$window.sessionStorage.setItem('filter', JSON.stringify(this.filter));
};
}
};
module.component(NAME, COMPONENT);

View File

@ -0,0 +1,10 @@
{
"Client id": "Id cliente",
"Tax number": "NIF/CIF",
"Name": "Nombre",
"Social name": "Razon social",
"Town/City": "Ciudad",
"Postcode": "Código postal",
"Email": "Correo electrónico",
"Phone": "Teléfono"
}

View File

@ -0,0 +1,25 @@
<div pad-large style="min-width: 30em">
<form ng-submit="$ctrl.onSearch()">
<vn-horizontal>
<vn-textfield vn-one label="Client id" model="$ctrl.filter.id" vn-focus></vn-textfield>
<vn-textfield vn-one label="Tax number" model="$ctrl.filter.fi"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Name" model="$ctrl.filter.name"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Social name" model="$ctrl.filter.socialName"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Town/City" model="$ctrl.filter.city"></vn-textfield>
<vn-textfield vn-one label="Postcode" model="$ctrl.filter.postcode"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Email" model="$ctrl.filter.email"></vn-textfield>
<vn-textfield vn-one label="Phone" model="$ctrl.filter.phone"></vn-textfield>
</vn-horizontal>
<vn-horizontal margin-large-top>
<vn-submit label="Search"></vn-submit>
</vn-horizontal>
</form>
</div>

View File

@ -0,0 +1,27 @@
import ngModule from '../module';
export default class Controller {
constructor($window) {
this.$window = $window;
// onSubmit() is defined by @vnSearchbar
this.onSubmit = () => {};
}
onSearch() {
this.setStorageValue();
this.onSubmit(this.filter);
}
$onChanges() {
var value = JSON.parse(this.$window.sessionStorage.getItem('filter'));
if (value !== undefined)
this.filter = value;
}
setStorageValue() {
this.$window.sessionStorage.setItem('filter', JSON.stringify(this.filter));
}
}
Controller.$inject = ['$window'];
ngModule.component('vnClientSearchPanel', {
template: require('./search-panel.html'),
controller: Controller
});

View File

@ -0,0 +1,42 @@
import './search-panel.js';
describe('Client', () => {
describe('Component vnClientSearchPanel', () => {
let $componentController;
let $window;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$window_) => {
$componentController = _$componentController_;
$window = _$window_;
}));
describe('onSearch()', () => {
it(`should call setStorageValue() and onSubmit()`, () => {
let controller = $componentController('vnClientSearchPanel', {$window: $window});
spyOn(controller, 'setStorageValue');
spyOn(controller, 'onSubmit');
controller.setStorageValue();
controller.onSubmit();
expect(controller.setStorageValue).toHaveBeenCalledWith();
expect(controller.onSubmit).toHaveBeenCalledWith();
});
});
describe('$onChanges()', () => {
it(`should set filter properties using the search values`, () => {
let controller = $componentController('vnClientSearchPanel', {$window: $window});
expect(controller.filter).not.toBeDefined();
spyOn(JSON, 'parse').and.returnValue({data: 'data'});
controller.$onChanges();
expect(controller.filter).toBe(JSON.parse({data: 'data'}));
});
});
});
});

View File

@ -1,34 +0,0 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Accounts"
id-field="id"
data="$ctrl.client.account"
to="$ctrl.account"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Web access</vn-title>
<vn-check label="Acceso Web" field="$ctrl.account.active"></vn-check>
<vn-textfield label="Usuario" class="margin-medium-top" field="$ctrl.account.name" vn-focus></vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Guardar"></vn-submit>
<vn-button label="Cambiar contraseña" vn-dialog="change-pass"></vn-button>
</vn-button-bar>
</form>
<vn-dialog
vn-id="change-pass"
on-open="$ctrl.onPassOpen()"
on-response="$ctrl.onPassChange(response)">
<dbody>
<vn-password label="New password" model="$ctrl.newPassword"></vn-password>
<vn-password label="Repeat password" model="$ctrl.repeatPassword"></vn-password>
</dbody>
<buttons>
<button response="CANCEL" translate>Cancel</button>
<button response="ACCEPT" translate>Change password</button>
</buttons>
</vn-dialog>

View File

@ -1,48 +0,0 @@
import {module} from '../module';
class Controller {
constructor($scope, $http, vnAppLogger) {
this.$scope = $scope;
this.$http = $http;
this.vnAppLogger = vnAppLogger;
}
$onChanges() {
if(this.client)
this.account = this.client.account;
}
onPassOpen() {
this.newPassword = '';
this.repeatPassword = '';
this.$scope.$apply();
}
onPassChange(response) {
if(response == 'ACCEPT')
try {
if(!this.newPassword)
throw new Error(`Passwords can't be empty`);
if(this.newPassword != this.repeatPassword)
throw new Error(`Passwords don't match`);
let account = {
password: this.newPassword
};
this.$http.put(`/client/api/Accounts/${this.client.id}`, account);
}
catch(e) {
this.vnAppLogger.showError(e.message);
return false;
}
return true;
}
}
Controller.$inject = ['$scope', '$http', 'vnAppLogger'];
module.component('vnClientWebAccess', {
template: require('./index.html'),
bindings: {
client: '<'
},
controller: Controller
});

View File

@ -1,4 +1,6 @@
{
"User": "Usuario",
"Enable web access": "Habilitar acceso web",
"Web access": "Acceso web",
"New password": "Nueva contraseña",
"Repeat password": "Repetir contraseña",

View File

@ -0,0 +1,41 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Accounts"
id-field="id"
data="$ctrl.account"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Web access</vn-title>
<vn-check label="Enable web access" field="$ctrl.account.active"></vn-check>
<vn-textfield label="User" class="margin-medium-top" field="$ctrl.account.name" vn-focus></vn-textfield>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
<vn-button ng-if="$ctrl.canChangePassword" label="Change password" vn-dialog="change-pass"></vn-button>
</vn-button-bar>
</form>
<vn-dialog
vn-id="change-pass"
on-open="$ctrl.onPassOpen()"
on-response="$ctrl.onPassChange(response)">
<tpl-body>
<vn-textfield
type="password"
label="New password"
model="$ctrl.newPassword">
</vn-textfield>
<vn-textfield
type="password"
label="Repeat password"
model="$ctrl.repeatPassword">
</vn-textfield>
</tpl-body>
<tpl-buttons>
<button response="CANCEL" translate>Cancel</button>
<button response="ACCEPT" translate>Change password</button>
</tpl-buttons>
</vn-dialog>

View File

@ -0,0 +1,60 @@
import ngModule from '../module';
export default class Controller {
constructor($scope, $http, vnApp) {
this.$ = $scope;
this.$http = $http;
this.vnApp = vnApp;
this.canChangePassword = false;
}
$onChanges() {
if (this.client) {
this.account = this.client.account;
this.isCustomer();
}
}
isCustomer() {
if (this.client && this.client.id) {
this.$http.get(`/client/api/Clients/${this.client.id}/getRoleCustomer`).then(res => {
this.canChangePassword = (res.data) ? res.data.isCustomer : false;
});
} else {
this.canChangePassword = false;
}
}
onPassOpen() {
this.newPassword = '';
this.repeatPassword = '';
this.$.$apply();
}
onPassChange(response) {
if (response == 'ACCEPT' && this.canChangePassword)
try {
if (!this.newPassword)
throw new Error(`Passwords can't be empty`);
if (this.newPassword != this.repeatPassword)
throw new Error(`Passwords don't match`);
let account = {
password: this.newPassword
};
this.$http.patch(`/client/api/Accounts/${this.client.id}`, account);
} catch (e) {
this.vnApp.showError(e.message);
return false;
}
return true;
}
}
Controller.$inject = ['$scope', '$http', 'vnApp'];
ngModule.component('vnClientWebAccess', {
template: require('./web-access.html'),
controller: Controller,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,96 @@
import './web-access.js';
describe('Component VnClientWebAccess', () => {
let $componentController;
let $httpBackend;
let $scope;
let vnApp;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _vnApp_) => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
vnApp = _vnApp_;
spyOn(vnApp, 'showError');
}));
describe('$onChanges()', () => {
it(`should pass client's account data to account then call isCustomer function`, () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
spyOn(controller, 'isCustomer');
controller.client = {client: 'Bruce Wayne', account: 'Wayne Industries'};
controller.account = {};
controller.$onChanges();
expect(controller.account).toBe('Wayne Industries');
expect(controller.isCustomer).toHaveBeenCalledWith();
});
});
describe('isCustomer()', () => {
it(`should perform a query if client is defined with an ID`, () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
controller.client = {id: '1234'};
controller.isCustomer();
$httpBackend.when('GET', `/client/api/Clients/${controller.client.id}/getRoleCustomer`).respond('ok');
$httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/getRoleCustomer`);
$httpBackend.flush();
});
});
describe('onPassOpen()', () => {
it('should set passwords to empty values', () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
controller.newPassword = 'm24x8';
controller.repeatPassword = 'm24x8';
controller.onPassOpen();
expect(controller.newPassword).toBe('');
expect(controller.repeatPassword).toBe('');
});
});
describe('onPassChange()', () => {
it('should request to update the password', () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
controller.client = {id: '1234'};
controller.newPassword = 'm24x8';
controller.repeatPassword = 'm24x8';
controller.canChangePassword = true;
$httpBackend.when('PATCH', '/client/api/Accounts/1234').respond('done');
$httpBackend.expectPATCH('/client/api/Accounts/1234', {password: 'm24x8'});
controller.onPassChange('ACCEPT');
$httpBackend.flush();
});
describe(`when password is empty`, () => {
it(`should throw Passwords can't be empty error`, () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
controller.client = {id: '1234'};
controller.newPassword = '';
controller.canChangePassword = true;
controller.onPassChange('ACCEPT');
expect(vnApp.showError).toHaveBeenCalledWith(`Passwords can't be empty`);
});
});
describe(`when passwords don't match`, () => {
it(`should throw Passwords don't match error`, () => {
let controller = $componentController('vnClientWebAccess', {$scope: $scope});
controller.client = {id: '1234'};
controller.newPassword = 'm24x8';
controller.canChangePassword = true;
controller.repeatPassword = 'notMatchingPassword';
controller.onPassChange('ACCEPT');
expect(vnApp.showError).toHaveBeenCalledWith(`Passwords don't match`);
});
});
});
});

View File

@ -5,6 +5,6 @@
"main": "index.js",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
"url": "https://git.verdnatura.es/salix"
}
}

View File

@ -0,0 +1,14 @@
<vn-vertical ng-click="$ctrl.showDropDown = true">
<vn-textfield vn-one label="{{$ctrl.label}}" model="$ctrl.displayValue" readonly="$ctrl.readonly"></vn-textfield>
<vn-drop-down vn-one
items="$ctrl.items"
show="$ctrl.showDropDown"
selected="$ctrl.field"
filter="true"
load-more="$ctrl.getItems()"
show-load-more="$ctrl.maxRow"
filter-action="$ctrl.findItems(search)"
item-width="$ctrl.width"
multiple="$ctrl.multiple"
><vn-item ng-transclude="tplItem">{{$parent.item.name}}</vn-item></vn-drop-down>
</vn-vertical>

View File

@ -0,0 +1,304 @@
import {module} from '../module';
import Component from '../lib/component';
import './style.scss';
class Autocomplete extends Component {
constructor($element, $scope, $http, $timeout) {
super($element);
this.$element = $element;
this.$scope = $scope;
this.$http = $http;
this.$timeout = $timeout;
this._showDropDown = false;
this.finding = false;
this.findMore = false;
this._value = null;
this._field = null;
this._preLoad = false;
this.maxRow = 10;
this.showField = this.showField || 'name';
this.valueField = this.valueField || 'id';
this.items = this.data || [];
this.displayValueMultiCheck = [];
this._multiField = [];
this.readonly = true;
}
get showDropDown() {
return this._showDropDown;
}
set showDropDown(value) {
if (value && this.url && !this._preLoad) {
this._preLoad = true;
this.getItems();
}
this._showDropDown = value;
}
get displayValue() {
return this._value;
}
set displayValue(value) {
let val = (value === undefined || value === '') ? null : value;
if (this.multiple && val) {
let index = this.displayValueMultiCheck.indexOf(val);
if (index === -1)
this.displayValueMultiCheck.push(val);
else
this.displayValueMultiCheck.splice(index, 1);
this._value = this.displayValueMultiCheck.join(', ');
} else {
this._value = val;
}
if (value === null) {
this.field = null;
if (this.multiple && this.items.length) {
this.displayValueMultiCheck = [];
this.items.map(item => {
item.checked = false;
return item;
});
}
}
}
get field() {
return this.multiple ? this._multiField : this._field;
}
set field(value) {
this.finding = true;
if (value && value.hasOwnProperty(this.valueField)) {
this._field = value[this.valueField];
if (this.multiple) {
this.setMultiField(value[this.valueField]);
}
} else {
this.setValue(value);
}
if (value && value.hasOwnProperty(this.showField))
this.displayValue = value[this.showField];
this.finding = false;
if (this.onChange)
this.onChange({item: this._field});
}
set initialData(value) {
if (value) {
this.field = value;
}
}
setMultiField(val) {
if (val && typeof val === 'object' && val[this.valueField]) {
val = val[this.valueField];
}
if (val === null) {
this._multiField = [];
} else {
let index = this._multiField.indexOf(val);
if (index === -1) {
this._multiField.push(val);
} else {
this._multiField.splice(index, 1);
}
}
}
setValue(value) {
if (value) {
let data = this.items;
if (data && data.length)
for (let i = 0; i < data.length; i++)
if (data[i][this.valueField] === value) {
this.showItem(data[i]);
return;
}
this.requestItem(value);
} else {
this._field = null;
this.setMultiField(null);
this.displayValue = '';
}
}
requestItem(value) {
if (!value) return;
let where = {};
where[this.valueField] = value;
let filter = {
fields: this.getRequestFields(),
where: where
};
let json = JSON.stringify(filter);
this.$http.get(`${this.url}?filter=${json}`).then(
json => this.onItemRequest(json.data),
json => this.onItemRequest(null)
);
}
onItemRequest(data) {
if (data && data.length > 0)
this.showItem(data[0]);
else
this.showItem(null);
}
showItem(item) {
this.displayValue = item ? item[this.showField] : '';
this.field = item;
}
getRequestFields() {
let fields = {};
fields[this.valueField] = true;
fields[this.showField] = true;
if (this._selectFields)
for (let field of this._selectFields)
fields[field] = true;
return fields;
}
findItems(search) {
if (!this.url)
return this.items ? this.items : [];
if (search && !this.finding) {
this.maxRow = false;
let filter = {where: {name: {regexp: search}}};
let json = JSON.stringify(filter);
this.finding = true;
this.$http.get(`${this.url}?filter=${json}`).then(
json => {
this.items = [];
json.data.forEach(
el => {
if (this.multiple) {
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
}
this.items.push(el);
}
);
this.finding = false;
},
() => {
this.finding = false;
}
);
} else if (!search && !this.finding) {
this.maxRow = 10;
this.items = [];
this.getItems();
}
}
getItems() {
let filter = {};
if (this.maxRow) {
if (this.items) {
filter.skip = this.items.length;
}
filter.limit = this.maxRow;
filter.order = 'name ASC';
}
let json = JSON.stringify(filter);
this.$http.get(`${this.url}?filter=${json}`).then(
json => {
if (json.data.length)
json.data.forEach(
el => {
if (this.multiple) {
el.checked = this.field.indexOf(el[this.valueField]) !== -1;
}
this.items.push(el);
}
);
else
this.maxRow = false;
}
);
}
$onInit() {
this.findMore = this.url && this.maxRow;
this.mouseFocus = false;
this.focused = false;
this.$element.bind('mouseover', e => {
this.$timeout(() => {
this.mouseFocus = true;
this.showDropDown = this.focused;
});
});
this.$element.bind('mouseout', () => {
this.$timeout(() => {
this.mouseFocus = false;
this.showDropDown = this.focused;
});
});
this.$element.bind('focusin', e => {
this.$timeout(() => {
this.focused = true;
this.showDropDown = true;
});
});
this.$element.bind('focusout', e => {
this.$timeout(() => {
this.focused = false;
this.showDropDown = this.mouseFocus;
});
});
let rectangle = this.$element[0].getBoundingClientRect();
this.width = Math.round(rectangle.width) - 10;
}
$onDestroy() {
this.$element.unbind('mouseover');
this.$element.unbind('mouseout');
this.$element.unbind('focusin');
this.$element.unbind('focusout');
}
}
Autocomplete.$inject = ['$element', '$scope', '$http', '$timeout'];
module.component('vnAutocomplete', {
template: require('./autocomplete.html'),
controller: Autocomplete,
bindings: {
url: '@?',
showField: '@?',
valueField: '@?',
selectFields: '@?',
initialData: '<?',
onChange: '&?',
data: '<?',
itemAs: '@?',
field: '=',
label: '@',
itemTemplate: '@?',
multiple: '@?'
},
transclude: {
tplItem: '?tplItem'
}
});

View File

@ -0,0 +1,200 @@
import './autocomplete.js';
describe('Component vnAutocomplete', () => {
let $componentController;
let $scope;
let $httpBackend;
let $timeout;
let $element;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$timeout = _$timeout_;
$element = angular.element('<div></div>');
}));
describe('showDropDown() setter', () => {
it(`should set _showDropDown value`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller._showDropDown = '';
controller.showDropDown = 'some value';
expect(controller._showDropDown).toEqual('some value');
});
it(`should set _showDropDown value`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller._showDropDown = '';
controller.showDropDown = 'some value';
expect(controller._showDropDown).toEqual('some value');
});
});
describe('displayValue() setter', () => {
it(`should display value in a formated way`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
let value = 'some value';
controller.displayValue = value;
expect(controller._value).toEqual(value);
});
describe('when the autocomeplete is multiple', () => {
it(`should display values separated with commas`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.multiple = true;
controller.displayValue = 'some value';
controller.displayValue = 'another value';
expect(controller._value).toEqual('some value, another value');
});
});
});
describe('field() setter', () => {
describe('when value is an object', () => {
it(`should set _field controllers property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
});
it(`should set _multifield controllers property `, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.multiple = true;
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
expect(controller._multiField[0]).toEqual(1);
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._multiField).toEqual([]);
expect(controller._field).toEqual(1);
});
it(`should set _multifield value and remove it if called a second type with same value`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.multiple = true;
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._field).toEqual(1);
expect(controller._multiField[0]).toEqual(1);
controller.field = {id: 1, name: 'Bruce Wayne'};
expect(controller._multiField).toEqual([]);
expect(controller._field).toEqual(1);
});
it(`should set displayValue finding an existing item in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = {id: 2, name: 'Bruce Wayne'};
expect(controller.displayValue).toEqual('Bruce Wayne');
});
});
describe('when value is a number', () => {
it(`should set _field controller property finding an existing item in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 2;
expect(controller._field).toEqual(2);
});
it(`should set _multifield value and remove it if called a second type with same value finding an existing item in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = [{id: 1, name: 'Batman'}, {id: 2, name: 'Bruce Wayne'}];
controller.multiple = true;
controller.field = 2;
expect(controller._multiField[0]).toEqual(2);
controller.field = 2;
expect(controller._multiField).toEqual([]);
});
it(`should perform a query if the item id isn't present in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}, {url: 'test.com'});
$httpBackend.whenGET('test.com?filter={"fields":{"id":true,"name":true},"where":{"id":3}}').respond();
$httpBackend.expectGET('test.com?filter={"fields":{"id":true,"name":true},"where":{"id":3}}');
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
$httpBackend.flush();
});
it(`should set displayValue finding an existing item in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 2;
expect(controller.displayValue).toEqual('Bruce Wayne');
});
it(`should set field performing a query as the item id isn't present in the controller.items property`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}, {url: 'test.com'});
$httpBackend.whenGET('test.com?filter={"fields":{"id":true,"name":true},"where":{"id":3}}').respond();
$httpBackend.expectGET('test.com?filter={"fields":{"id":true,"name":true},"where":{"id":3}}');
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
$httpBackend.flush();
});
});
});
describe('findItem()', () => {
it(`should return items array if the controller does not provide a url and nither it has items`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = ['Batman', 'Bruce Wayne'];
controller.findItems('some search value');
expect(controller.items.length).toEqual(2);
});
it(`should perform a query with the search value if the finding flag is false`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.items = ['Batman', 'Bruce Wayne'];
controller.findItems('Gotham');
expect(controller.items.length).toEqual(2);
});
it(`should perform a query with the search value if the finding flag is false`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}, {url: 'test.com'});
let search = 'The Joker';
let json = JSON.stringify({where: {name: {regexp: search}}});
$httpBackend.whenGET(`test.com?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`test.com?filter=${json}`);
controller.findItems(search);
$httpBackend.flush();
expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'});
});
it(`should perform a query with the search value if the finding flag is false`, () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout}, {url: 'test.com'});
let search = 'The Joker';
let json = JSON.stringify({where: {name: {regexp: search}}});
$httpBackend.whenGET(`test.com?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`test.com?filter=${json}`);
controller.findItems(search);
$httpBackend.flush();
expect(controller.items[0]).toEqual({id: 3, name: 'The Joker'});
});
// siguiente test el de Multiple!
});
});

View File

@ -1,10 +0,0 @@
<div class="mdl-textfield mdl-js-textfield mdl-textfield--floating-label">
<input type="text"
class="mdl-textfield__input"
ng-keydown="$ctrl.onKeydown($event)"
ng-click="$ctrl.onClick($event)"
ng-keyup="$ctrl.onKeyup($event)"
ng-focus="$ctrl.onFocus($event)"
ng-blur="$ctrl.onBlur($event)"/>
<label class="mdl-textfield__label">{{$ctrl.label | translate}}</label>
</div>

View File

@ -1,356 +0,0 @@
import {module} from '../module';
import Component from '../lib/component';
import './style.scss';
/**
* Combobox like component with search and partial data loading features.
*/
export default class Autocomplete extends Component {
constructor($element, $scope, $http, vnPopover) {
super($element);
this.input = $element[0].querySelector('input');
this.item = null;
this.data = null;
this.popover = null;
this.popoverData = null;
this.timeoutId = null;
this.lastSearch = null;
this.lastRequest = null;
this.currentRequest = null;
this.moreData = false;
this.activeOption = -1;
this.maxRows = 10;
this.requestDelay = 350;
this.locked = false;
this.$http = $http;
this.$scope = $scope;
this.vnPopover = vnPopover;
componentHandler.upgradeElement($element[0].firstChild);
this.requestItem();
}
set field(value) {
this.locked = true;
this.setValue(value);
this.locked = false;
}
get field() {
return this.value;
}
mdlUpdate() {
let mdlField = this.element.firstChild.MaterialTextfield;
if (mdlField)
mdlField.updateClasses_();
}
loadData(textFilter) {
textFilter = textFilter ? textFilter : '';
if (this.lastSearch === textFilter) {
this.popoverDataReady();
return;
}
this.lastSearch = textFilter;
let lastRequest = this.lastRequest;
let requestWillSame = lastRequest !== null
&& !this.moreData
&& textFilter.substr(0, lastRequest.length) === lastRequest;
if (requestWillSame)
this.localFilter(textFilter);
else
this.requestData(textFilter, false);
}
getRequestFields() {
let fields = {};
fields[this.valueField] = true;
fields[this.showField] = true;
return fields;
}
requestData(textFilter, append) {
let where = {};
let skip = 0;
if (textFilter)
where[this.showField] = {regexp: textFilter};
if (append && this.data)
skip = this.data.length;
let filter = {
fields: this.getRequestFields(),
where: where,
order: `${this.showField} ASC`,
skip: skip,
limit: this.maxRows
};
this.lastRequest = textFilter ? textFilter : '';
let json = JSON.stringify(filter);
if (this.currentRequest)
this.currentRequest.resolve();
this.currentRequest = this.$http.get(`${this.url}?filter=${json}`);
this.currentRequest.then(
json => this.onRequest(json.data, append),
json => this.onRequest([])
);
}
onRequest(data, append) {
this.currentRequest = null;
this.moreData = data.length >= this.maxRows;
if (!append || !this.data)
this.data = data;
else
this.data = this.data.concat(data);
this.setPopoverData(this.data);
}
localFilter(textFilter) {
let regex = new RegExp(textFilter, 'i');
let data = this.data.filter(item => {
return regex.test(item[this.showField]);
});
this.setPopoverData(data);
}
setPopoverData(data) {
this.popoverData = data;
this.popoverDataReady();
}
popoverDataReady() {
if (this.hasFocus)
this.showPopover();
}
showPopover() {
if (!this.data) return;
let fragment = this.document.createDocumentFragment();
let data = this.popoverData;
for (let i = 0; i < data.length; i++) {
let li = this.document.createElement('li');
li.appendChild(this.document.createTextNode(data[i][this.showField]));
fragment.appendChild(li);
}
if (this.moreData) {
let li = this.document.createElement('li');
li.appendChild(this.document.createTextNode('Load more'));
li.className = 'load-more';
fragment.appendChild(li);
}
if (this.popover) {
this.popover.innerHTML = '';
this.popover.appendChild(fragment);
} else {
let popover = this.document.createElement('ul');
popover.addEventListener('click',
e => this.onPopoverClick(e));
popover.addEventListener('mousedown',
e => this.onPopoverMousedown(e));
popover.className = 'vn-autocomplete';
popover.appendChild(fragment);
this.vnPopover.show(popover, this.input);
this.popover = popover;
}
}
hidePopover() {
if (!this.popover) return;
this.activeOption = -1;
this.vnPopover.hide();
this.popover = null;
}
selectPopoverOption(index) {
if (!this.popover || index === -1) return;
if (index < this.popoverData.length) {
this.selectOptionByDataIndex(this.popoverData, index);
this.hidePopover();
} else
this.requestData(this.lastRequest, true);
}
onPopoverClick(event) {
let childs = this.popover.childNodes;
for (let i = 0; i < childs.length; i++)
if (childs[i] === event.target) {
this.selectPopoverOption(i);
break;
}
}
onPopoverMousedown(event) {
// Prevents input from loosing focus
event.preventDefault();
}
onClick(event) {
if (!this.popover)
this.showPopover();
}
onFocus() {
this.hasFocus = true;
this.input.select();
if (this.data)
this.showPopover();
else
this.loadData();
}
onBlur() {
this.hasFocus = false;
this.restoreShowValue();
this.hidePopover();
}
onKeydown(event) {
switch (event.keyCode) {
case 13: // Enter
this.selectPopoverOption(this.activeOption);
break;
case 27: // Escape
this.restoreShowValue();
this.input.select();
break;
case 38: // Arrow up
this.activateOption(this.activeOption - 1);
break;
case 40: // Arrow down
this.activateOption(this.activeOption + 1);
break;
default:
return;
}
event.preventDefault();
}
onKeyup(event) {
if (!this.isKeycodePrintable(event.keyCode)) return;
if (this.timeoutId) clearTimeout(this.timeoutId);
this.timeoutId = setTimeout(() => this.onTimeout(), this.requestDelay);
}
onTimeout() {
this.loadData(this.input.value);
this.timeoutId = null;
}
isKeycodePrintable(keyCode) {
return keyCode === 32 // Spacebar
|| keyCode === 8 // Backspace
|| (keyCode > 47 && keyCode < 58) // Numbers
|| (keyCode > 64 && keyCode < 91) // Letters
|| (keyCode > 95 && keyCode < 112) // Numpad
|| (keyCode > 185 && keyCode < 193) // ;=,-./`
|| (keyCode > 218 && keyCode < 223); // [\]'
}
restoreShowValue() {
this.putItem(this.item);
}
requestItem() {
if (!this.value) return;
let where = {};
where[this.valueField] = this.value;
let filter = {
fields: this.getRequestFields(),
where: where
};
let json = JSON.stringify(filter);
this.$http.get(`${this.url}?filter=${json}`).then(
json => this.onItemRequest(json.data),
json => this.onItemRequest(null)
);
}
onItemRequest(data) {
if (data && data.length > 0)
this.showItem(data[0]);
else
this.showItem(null);
}
activateOption(index) {
if (!this.popover)
this.showPopover();
let popover = this.popover;
let childs = popover.childNodes;
let len = this.popoverData.length;
if (this.activeOption >= 0)
childs[this.activeOption].className = '';
if (index >= len)
index = 0;
else if (index < 0)
index = len - 1;
if (index >= 0) {
let opt = childs[index];
let top = popover.scrollTop;
let height = popover.clientHeight;
if (opt.offsetTop + opt.offsetHeight > top + height)
top = opt.offsetTop + opt.offsetHeight - height;
else if (opt.offsetTop < top)
top = opt.offsetTop;
opt.className = 'active';
popover.scrollTop = top;
}
this.activeOption = index;
}
setValue(value) {
this.value = value;
if (value) {
let data = this.data;
if (data)
for (let i = 0; i < data.length; i++)
if (data[i][this.valueField] == value) {
this.putItem(data[i]);
return;
}
this.requestItem();
} else
this.putItem(null);
}
selectOptionByIndex(index) {
this.selectOptionByDataIndex(this.data, index);
}
selectOptionByDataIndex(data, index) {
if (data && index >= 0 && index < data.length)
this.putItem(data[index]);
else
this.putItem(null);
}
putItem(item) {
this.showItem(item);
let value = item ? item[this.valueField] : undefined;
if (!this.locked) {
this.value = value;
setTimeout(
() => this.$scope.$apply());
}
}
showItem(item) {
this.input.value = item ? item[this.showField] : '';
this.item = item;
this.mdlUpdate();
}
}
Autocomplete.$inject = ['$element', '$scope', '$http', 'vnPopover'];
module.component('vnAutocomplete', {
template: require('./index.html'),
bindings: {
url: '@',
showField: '@',
valueField: '@',
field: '=',
label: '@'
},
controller: Autocomplete
});

Some files were not shown because too many files have changed in this diff Show More