This commit is contained in:
Javi Gallego 2018-03-14 14:46:03 +01:00
commit 1fbfbd6f7c
989 changed files with 126391 additions and 5580 deletions

6
.env.json Normal file
View File

@ -0,0 +1,6 @@
{
"salixHost": "localhost",
"salixPort": "3306",
"salixUser": "root",
"salixPassword": "root"
}

View File

@ -1,5 +1,10 @@
extends: google
installedESLint: true
extends: [eslint:recommended, google, plugin:jasmine/recommended]
parserOptions:
ecmaVersion: 2017
plugins:
- jasmine
env:
jasmine: true
rules:
indent: [error, 4]
require-jsdoc: 0
@ -9,3 +14,9 @@ rules:
operator-linebreak: 0
radix: 0
guard-for-in: 0
camelcase: 0
default-case: 0
no-eq-null: 0
no-console: 0
no-warning-comments: 0
no-empty: [error, allowEmptyCatch: true]

4
.gitignore vendored
View File

@ -1,6 +1,4 @@
node_modules
spliting.js
build
npm-debug.log
debug.log
datasources.development.json
docker-compose.yml

79
.vscode/launch.json vendored
View File

@ -2,85 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"name": "Iniciar",
"type": "node",
"request": "launch",
"program": "${workspaceRoot}/app.js",
"stopOnEntry": false,
"args": [],
"cwd": "${workspaceRoot}",
"preLaunchTask": null,
"runtimeExecutable": null,
"runtimeArgs": [
"--nolazy"
],
"env": {
"NODE_ENV": "development"
},
"console": "internalConsole",
"sourceMaps": false,
"outFiles": []
},
{
"name": "Asociar",
"type": "node",
"request": "attach",
"port": 5858,
"address": "localhost",
"restart": false,
"sourceMaps": false,
"outFiles": [],
"localRoot": "${workspaceRoot}",
"remoteRoot": null
},
{
"name": "Asociar al proceso",
"type": "node",
"request": "attach",
"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": []
"name": "Attach by Process ID",
"processId": "${command:PickProcess}"
}
]
}

13
Dockerfile Normal file
View File

@ -0,0 +1,13 @@
FROM node:8.9.4
COPY . /app
COPY ../loopback /loopback
WORKDIR /app
RUN npm install
RUN npm -g install pm2
CMD ["pm2-docker", "./server/server.js"]
EXPOSE 3000

60
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,60 @@
#!/usr/bin/env groovy
def branchName = "${env.BRANCH_NAME}";
def branchProduction = "master"
def branchTest = "test";
env.BRANCH_NAME = branchName;
env.TAG = "${env.BUILD_NUMBER}";
env.salixUser="${env.salixUser}";
env.salixPassword="${env.salixPassword}";
env.salixHost = "${env.productionSalixHost}";
env.salixPort = "${env.productionSalixPort}";
switch (branchName){
case branchTest:
env.NODE_ENV = "test";
env.salixHost = "${env.testSalixHost}";
env.salixPort = "${env.testSalixPort}";
break;
case branchProduction:
env.DOCKER_HOST = "tcp://172.16.255.29:2375";
env.NODE_ENV = "production"
break;
}
node
{
stage ('Print environment variables'){
echo "Branch ${branchName}, Build ${env.TAG}, salixHost ${env.salixHost}, NODE_ENV ${env.NODE_ENV} en docker Host ${env.DOCKER_HOST}"
}
stage ('Checkout') {
checkout scm
}
stage ('install modules'){
sh "npm install"
}
stage ('build Project'){
sh "gulp build"
}
stage ("docker")
{
stage ("install modules loopback service")
{
sh "cd ./services/loopback && npm install"
}
stage ("Stopping/Removing Docker")
{
sh "docker-compose down --rmi 'all'"
}
stage ("Generar dockers")
{
sh "docker-compose up -d --build"
}
}
}

17
LICENSE Normal file
View File

@ -0,0 +1,17 @@
Copyright (C) 2018 - Verdnatura Levante S.L.
This package is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
On Debian systems, the complete text of the GNU General Public
License can be found in "/usr/share/common-licenses/GPL-3".

73
README.md Normal file
View File

@ -0,0 +1,73 @@
# Salix
This project is an Enterprise resource planning (ERP) integrated management of core business processes, in real-time and mediated by software and technology developed with the stack listed below.
Salix is also the scientific name of a beautifull tree! :)
## Prerequisites
Required applications.
* Node.js = 8.9.4
* NGINX
* Docker
You will need to install globally the following items.
```
$ npm install -g karma-cli gulp webpack nodemon
```
## Getting Started // Installing
Pull from repository.
Run this commands on project root directory to install Node dependencies.
```
$ npm install
$ gulp install
```
Launch application in developer environment.
```
$ gulp
```
Also you can run backend and frontend as separately gulp tasks (including NGINX).
```
$ gulp client
$ gulp services
```
Manually reset fixtures.
```
$ gulp docker
```
## Running the unit 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 test
```
For end-to-end tests run from project's root.
```
$ gulp 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/)
* [Karma](https://karma-runner.github.io/)
* [Jasmine](https://jasmine.github.io/)
* [Nightmare](http://www.nightmarejs.org/)

View File

@ -1,10 +0,0 @@
{
"name": "@salix/auth",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "http://git.verdnatura.es:/salix"
}
}

View File

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

View File

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

View File

@ -1,6 +0,0 @@
{
"User": "User",
"Password": "Password",
"Do not close session": "Do not close session",
"Enter": "Enter"
}

View File

@ -0,0 +1,4 @@
User: User
Password: Password
Do not close session: Do not close session
Enter: Enter

View File

@ -1,6 +0,0 @@
{
"User": "Usuario",
"Password": "Contraseña",
"Do not close session": "No cerrar sesión",
"Enter": "Entrar"
}

View File

@ -0,0 +1,4 @@
User: Usuario
Password: Contraseña
Do not close session: No cerrar sesión
Enter: Entrar

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

@ -3,8 +3,19 @@
<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>
<vn-textfield
label="User"
model="$ctrl.user"
name="user"
vn-id="userField"
vn-focus>
</vn-textfield>
<vn-textfield
label="Password"
model="$ctrl.password"
name="password"
type="password">
</vn-textfield>
<div class="footer">
<vn-submit label="Enter"></vn-submit>
<div class="spinner-wrapper">
@ -14,5 +25,5 @@
</form>
</div>
</div>
<vn-snackbar></vn-snackbar>
<vn-snackbar vn-id="snackbar"></vn-snackbar>
</div>

View File

@ -1,38 +1,56 @@
import {module} from '../module';
import './login.scss';
import ngModule from '../module';
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');
/**
* A simple login form.
*/
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.focusUser();
this.showError('Please insert your user and password');
return;
}
this.loading = true;
model.appId = $window.location.href;
$http.post('/auth', this.model).then(
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: function(json) {
}
onLoginOk(json) {
this.loading = false;
let data = json.data;
$window.location = `${data.location}?access_token=${data.location}`;
},
onLoginErr: function(json) {
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.model.password = '';
this.password = '';
let message;
@ -47,11 +65,20 @@ function controller($http, $element, $window) {
message = 'Something went wrong';
}
this.showMessage(message);
},
showMessage: function(message) {
let snackbar = $element.find('vn-snackbar').controller('vnSnackbar');
snackbar.show({message: message});
this.showError(message);
this.focusUser();
}
focusUser() {
this.$.userField.select();
this.$.userField.focus();
}
showError(message) {
this.$.snackbar.showError({message: message});
}
});
}
Controller.$inject = ['$element', '$scope', '$window', '$http'];
ngModule.component('vnLogin', {
template: require('./login.html'),
controller: Controller
});

View File

@ -12,10 +12,11 @@ vn-login > div {
.box-wrapper {
position: relative;
max-width: 22em;
max-width: 19em;
margin: auto;
height: inherit;
}
.box {
box-sizing: border-box;
position: absolute;
@ -27,15 +28,18 @@ vn-login > div {
box-shadow: 0 0 1em 0 rgba(1,1,1,.6);
border-radius: .5em;
}
img {
width: 100%;
padding-bottom: 1em;
}
.footer {
margin-top: 1em;
text-align: center;
position: relative;
}
.spinner-wrapper {
position: absolute;
width: 0;
@ -44,4 +48,3 @@ vn-login > div {
overflow: visible;
}
}

View File

@ -1,4 +1,14 @@
import {ng} from 'vendor';
import * as core from 'core';
import 'core';
export const module = ng.module('vnAuth', [core.NAME]);
let ngModule = ng.module('vnAuth', ['vnCore']);
export default ngModule;
config.$inject = ['$translatePartialLoaderProvider', '$httpProvider'];
export function config($translatePartialLoaderProvider, $httpProvider) {
$translatePartialLoaderProvider.addPart('auth');
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
ngModule.config(config);

View File

@ -1,10 +0,0 @@
{
"name": "@salix/client",
"version": "0.0.0",
"description": "",
"main": "index.js",
"repository": {
"type": "git",
"url": "http://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"
}
]
}

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

@ -0,0 +1,225 @@
{
"module": "client",
"name": "Clients",
"icon": "person",
"validations" : true,
"routes": [
{
"url": "/clients?q",
"state": "clients",
"component": "vn-client-index",
"acl": ["employee"]
},
{
"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": "$ctrl.client"
},
"menu": {
"description": "Basic data",
"icon": "settings"
}
},
{
"url": "/fiscal-data",
"state": "clientCard.fiscalData",
"component": "vn-client-fiscal-data",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Fiscal data",
"icon": "account_balance"
}
},
{
"url": "/billing-data",
"state": "clientCard.billingData",
"component": "vn-client-billing-data",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Pay method",
"icon": "icon-payment"
}
},
{
"url": "/addresses",
"state": "clientCard.addresses",
"component": "ui-view",
"abstract": true
},
{
"url": "/list",
"state": "clientCard.addresses.list",
"component": "vn-client-addresses",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Addresses",
"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": "$ctrl.client"
},
"menu": {
"description": "Web access",
"icon": "cloud"
}
},
{
"url": "/notes",
"state": "clientCard.notes",
"component": "ui-view",
"abstract": true
},
{
"url": "/list",
"state": "clientCard.notes.list",
"component": "vn-client-notes",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Notes",
"icon": "insert_drive_file"
}
},
{
"url": "/create",
"state": "clientCard.notes.create",
"component": "vn-note-create"
},
{
"url": "/credit",
"abstract": true,
"state": "clientCard.credit",
"component": "ui-view"
},
{
"url": "/list",
"state": "clientCard.credit.list",
"component": "vn-client-credit-list",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Credit",
"icon": "credit_card"
}
}, {
"url": "/create",
"state": "clientCard.credit.create",
"component": "vn-client-credit-create",
"params": {
"client": "$ctrl.client"
}
},
{
"url": "/greuge",
"abstract": true,
"state": "clientCard.greuge",
"component": "ui-view"
},
{
"url": "/list",
"state": "clientCard.greuge.list",
"component": "vn-client-greuge-list",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Greuge",
"icon": "work"
}
},
{
"url": "/create",
"state": "clientCard.greuge.create",
"component": "vn-client-greuge-create",
"params": {
"client": "$ctrl.client"
}
},
{
"url": "/mandate",
"state": "clientCard.mandate",
"component": "vn-client-mandate",
"menu": {
"description": "Mandate",
"icon": "pan_tool"
}
},
{
"url": "/invoices",
"state": "clientCard.invoices",
"component": "vn-client-invoices",
"menu": {
"description": "Invoices",
"icon": "icon-invoices"
}
},
{
"url": "/recovery",
"abstract": true,
"state": "clientCard.recovery",
"component": "ui-view"
},
{
"url": "/list",
"state": "clientCard.recovery.list",
"component": "vn-client-recovery-list",
"params": {
"client": "$ctrl.client"
},
"menu": {
"description": "Recovery",
"icon": "icon-recovery"
}
}, {
"url": "/create",
"state": "clientCard.recovery.create",
"component": "vn-client-recovery-create",
"params": {
"client": "$ctrl.client"
}
}, {
"url": "/summary",
"state": "clientCard.summary",
"component": "vn-client-summary",
"params": {
"client": "$ctrl.client"
}
}
]
}

View File

@ -0,0 +1,45 @@
<vn-watcher
vn-id="watcher"
url="/client/api/Addresses"
id-field="id"
data="$ctrl.address"
save="post"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('clientCard.addresses.list')" margin-medium>
<vn-card 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.nickname" 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.postalCode"></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.agencyModeFk"
url="/client/api/AgencyModes"
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-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),
isActive: 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 controller;
let $componentController;
let $state;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
$componentController = _$componentController_;
$state = _$state_;
$state.params.id = '1234';
controller = $componentController('vnAddressCreate', {$state});
}));
it('should define and set address property', () => {
expect(controller.address.clientFk).toBe(1234);
expect(controller.address.isActive).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,9 @@
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,100 @@
<mg-ajax
path="/client/api/Addresses/{{edit.params.addressId}}"
actions="$ctrl.address = edit.model; $ctrl._setIconAdd();"
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="$ctrl.submit()" margin-medium>
<vn-card pad-large>
<vn-title>Address</vn-title>
<vn-horizontal>
<vn-check vn-one label="Enabled" field="$ctrl.address.isActive"></vn-check>
<vn-check
vn-one label="Is equalizated"
field="$ctrl.address.isEqualizated"
vn-acl="administrative, salesAssistant">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.nickname" 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.postalCode"></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
initial-data="$ctrl.address.agencyMode"
field="$ctrl.address.agencyModeFk"
url="/client/api/AgencyModes"
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-one margin-medium-top>
<vn-title>Notes</vn-title>
<mg-ajax path="/client/api/ObservationTypes" options="mgIndex as observationsTypes"></mg-ajax>
<vn-horizontal ng-repeat="observation in $ctrl.observations track by $index">
<vn-autocomplete
vn-one
initial-data="observation.observationType"
field="observation.observationTypeFk"
data="observationsTypes.model"
show-field="description"
label="Observation type">
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete>
<vn-textfield
vn-two
margin-large-right
label="Description"
model="observation.description"
rule="addressObservation.description">
</vn-textfield>
<vn-auto pad-medium-top>
<vn-icon
pointer
medium-grey
vn-tooltip="Remove note"
tooltip-position = "left"
icon="remove_circle_outline"
ng-click="$ctrl.removeObservation($index)">
</vn-icon>
</vn-one>
</vn-horizontal>
</vn-one>
<vn-one>
<vn-icon
pointer
margin-medium-left
vn-tooltip="Add note"
tooltip-position = "right"
orange
icon="add_circle"
ng-if="observationsTypes.model.length > $ctrl.observations.length"
ng-click="$ctrl.addObservation()">
</vn-icon>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,134 @@
import ngModule from '../module';
export default class Controller {
constructor($state, $scope, $http, $q, $translate, vnApp) {
this.$state = $state;
this.$scope = $scope;
this.$http = $http;
this.$q = $q;
this.$translate = $translate;
this.vnApp = vnApp;
this.address = {
id: parseInt($state.params.addressId)
};
this.observations = [];
this.observationsOld = {};
this.observationsRemoved = [];
}
_setDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setDirty();
}
}
_unsetDirtyForm() {
if (this.$scope.form) {
this.$scope.form.$setPristine();
}
}
addObservation() {
this.observations.push({observationTypeFk: null, addressFk: this.address.id, description: null});
}
removeObservation(index) {
let item = this.observations[index];
if (item) {
this.observations.splice(index, 1);
if (item.id) {
this.observationsRemoved.push(item.id);
this._setDirtyForm();
}
}
if (this.observations.length === 0 && Object.keys(this.observationsOld).length === 0) {
this._unsetDirtyForm();
}
}
_submitObservations(objectObservations) {
return this.$http.post(`/client/api/AddressObservations/crudAddressObservations`, objectObservations);
}
_observationsEquals(ob1, ob2) {
return ob1.id === ob2.id && ob1.observationTypeFk === ob2.observationTypeFk && ob1.description === ob2.description;
}
submit() {
if (this.$scope.form.$invalid) {
return false;
}
let canWatcherSubmit = this.$scope.watcher.dataChanged();
let canObservationsSubmit;
let repeatedTypes = false;
let types = [];
let observationsObj = {
delete: this.observationsRemoved,
create: [],
update: []
};
for (let i = 0; i < this.observations.length; i++) {
let observation = this.observations[i];
let isNewObservation = observation.id === undefined;
if (observation.observationTypeFk && types.indexOf(observation.observationTypeFk) !== -1) {
repeatedTypes = true;
break;
}
if (observation.observationTypeFk)
types.push(observation.observationTypeFk);
if (isNewObservation && observation.observationTypeFk && observation.description) {
observationsObj.create.push(observation);
} else if (!isNewObservation && !this._observationsEquals(this.observationsOld[observation.id], observation)) {
observationsObj.update.push(observation);
}
}
canObservationsSubmit = observationsObj.update.length > 0 || observationsObj.create.length > 0 || observationsObj.delete.length > 0;
if (repeatedTypes) {
this.vnApp.showMessage(
this.$translate.instant('The observation type must be unique')
);
} else if (canWatcherSubmit && !canObservationsSubmit) {
this.$scope.watcher.submit().then(() => {
this.$state.go('clientCard.addresses.list', {id: this.$state.params.id});
});
} else if (!canWatcherSubmit && canObservationsSubmit) {
this._submitObservations(observationsObj).then(() => {
this.$state.go('clientCard.addresses.list', {id: this.$state.params.id});
});
} else if (canWatcherSubmit && canObservationsSubmit) {
this.$q.all([this.$scope.watcher.submit(), this._submitObservations(observationsObj)]).then(() => {
this.$state.go('clientCard.addresses.list', {id: this.$state.params.id});
});
} else {
this.vnApp.showMessage(
this.$translate.instant('No changes to save')
);
}
this._unsetDirtyForm();
}
$onInit() {
let filter = {
where: {addressFk: this.address.id},
include: {relation: 'observationType'}
};
this.$http.get(`/client/api/AddressObservations?filter=${JSON.stringify(filter)}`).then(res => {
this.observations = res.data;
res.data.forEach(item => {
this.observationsOld[item.id] = Object.assign({}, item);
});
});
}
}
Controller.$inject = ['$state', '$scope', '$http', '$q', '$translate', 'vnApp'];
ngModule.component('vnAddressEdit', {
template: require('./address-edit.html'),
controller: Controller
});

View File

@ -0,0 +1,55 @@
import './address-edit.js';
describe('Client', () => {
describe('Component vnAddressEdit', () => {
let $componentController;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
$state.params.addressId = '1';
controller = $componentController('vnAddressEdit', {$state: $state});
}));
it('should define and set address property', () => {
expect(controller.address.id).toEqual(1);
});
describe('_observationsEquals', () => {
it('should return true if two observations are equals independent of control attributes', () => {
let ob1 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: true};
let ob2 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: false};
let equals = controller._observationsEquals(ob2, ob1);
expect(equals).toBeTruthy();
});
it('should return false if two observations are not equals independent of control attributes', () => {
let ob1 = {id: 1, observationTypeFk: 1, description: 'Spiderman rocks', showAddIcon: true};
let ob2 = {id: 1, observationTypeFk: 1, description: 'Spiderman sucks', showAddIcon: true};
let equals = controller._observationsEquals(ob2, ob1);
expect(equals).toBeFalsy();
});
});
describe('$onInit()', () => {
it('should perform a GET query to receive the address observations', () => {
let filter = {where: {addressFk: 1}, include: {relation: 'observationType'}};
let res = ['some notes'];
$httpBackend.when('GET', `/client/api/AddressObservations?filter=${JSON.stringify(filter)}`).respond(res);
$httpBackend.expectGET(`/client/api/AddressObservations?filter=${JSON.stringify(filter)}`);
controller.$onInit();
$httpBackend.flush();
});
});
});
});

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,7 @@
Enabled: Activo
Is equalizated: Recargo de equivalencia
Observation type: Tipo de observación
Description: Descripción
The observation type must be unique: El tipo de observación ha de ser único
Add note: Añadir nota
Remove note: Quitar nota

View File

@ -0,0 +1,47 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/listAddresses" options="mgIndex"></mg-ajax>
<vn-vertical pad-medium>
<vn-card pad-large>
<vn-title vn-one>Addresses</vn-title>
<vn-horizontal ng-repeat="address in index.model.items track by address.id" class="pad-medium-top" style="align-items: center;">
<vn-one border-radius class="pad-small border-solid"
ng-class="{'bg-dark-item': address.isDefaultAddress,'bg-opacity-item': !address.isActive && !address.isDefaultAddress}">
<vn-horizontal style="align-items: center;">
<vn-none pad-medium-h style="color:#FFA410;">
<i class="material-icons" ng-if="address.isDefaultAddress">star</i>
<i class="material-icons"
vn-tooltip="Active first to set as default"
tooltip-position="left"
ng-if="!address.isActive">star_border</i>
<i class="material-icons pointer"
ng-if="address.isActive && !address.isDefaultAddress"
vn-tooltip="Set as default"
tooltip-position="left"
ng-click="$ctrl.setDefault(address)">star_border</i>
</vn-none>
<vn-one border-solid-right>
<div><b>{{::address.nickname}}</b></div>
<div>{{::address.street}}</div>
<div>{{::address.city}}, {{::address.province}}</div>
<div>{{::address.phone}}, {{::address.mobile}}</div>
</vn-one>
<vn-vertical vn-one pad-medium-h>
<vn-one ng-repeat="observation in address.observations track by $index" ng-class="{'pad-small-top': $index}">
<b margin-medium-right>{{::observation.observationType.description}}:</b>
<span>{{::observation.description}}</span>
</vn-one>
</vn-vertical>
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{::address.id}}})">
<vn-icon-button icon="edit"></vn-icon-button>
</a>
</vn-horizontal>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-paging index="index" total="index.model.total"></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,22 @@
import ngModule from '../module';
class ClientAddresses {
constructor($http, $scope) {
this.$http = $http;
this.$scope = $scope;
}
setDefault(address) {
if (address.isActive) {
let params = {isDefaultAddress: true};
this.$http.patch(`/client/api/Addresses/${address.id}`, params).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,2 @@
Set as default: Establecer como predeterminado
Active first to set as default: Active primero para marcar como predeterminado

View File

@ -0,0 +1,46 @@
<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()" margin-medium>
<vn-card pad-large>
<vn-title>Basic data</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Comercial Name" field="$ctrl.client.name" vn-focus></vn-textfield>
<vn-textfield vn-one label="Contact" field="$ctrl.client.contact"></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="Email"
field="$ctrl.client.email"
info="You can save multiple emails">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<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="name"
label="Salesperson"
where="{or: [{firstName: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
</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-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,14 @@
Comercial Name: Nombre comercial
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: >-
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
Contact: Contacto

View File

@ -0,0 +1,64 @@
<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 pad-large>
<vn-title>Pay method</vn-title>
<vn-horizontal>
<vn-autocomplete vn-two
vn-acl="administrative, salesAssistant"
field="$ctrl.client.payMethodFk"
url="/client/api/PayMethods"
select-fields="ibanRequired"
initial-data="$ctrl.client.payMethod"
label="Pay method">
</vn-autocomplete>
<vn-textfield
vn-two label="IBAN"
field="$ctrl.client.iban"
vn-acl="administrative, salesAssistant">
</vn-textfield>
<vn-textfield
vn-one label="Due day"
field="$ctrl.client.dueDay"
vn-acl="administrative, salesAssistant">
</vn-textfield>
</vn-horizontal>
<vn-horizontal margin-medium-bottom>
<vn-one>
<vn-check
label="Received core VNH"
field="$ctrl.client.hasCoreVnh"
vn-acl="administrative, salesAssistant">
</vn-check>
</vn-one>
<vn-one>
<vn-check
label="Received core VNL"
field="$ctrl.client.hasCoreVnl"
vn-acl="administrative, salesAssistant">
</vn-check>
</vn-one>
<vn-one>
<vn-check
label="Received B2B VNL"
field="$ctrl.client.hasSepaVnl"
vn-acl="administrative, salesAssistant">
</vn-check>
</vn-one>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save" vn-acl="administrative, salesAssistant"></vn-submit>
</vn-button-bar>
</form>
<vn-confirm
vn-id="send-mail"
on-response="$ctrl.returnDialog(response)"
question="Changed terms"
message="Notify customer?">
</vn-confirm>

View File

@ -0,0 +1,57 @@
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;
}
}
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/notification/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,80 @@
import './billing-data.js';
describe('Client', () => {
describe('Component vnClientBillingData', () => {
let $componentController;
let $httpBackend;
let $scope;
let controller;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$scope = $rootScope.$new();
let submit = jasmine.createSpy('submit').and.returnValue(Promise.resolve());
$scope.watcher = {submit};
let show = jasmine.createSpy('show');
$scope.sendMail = {show};
controller = $componentController('vnClientBillingData', {$scope: $scope});
}));
describe('copyData()', () => {
it(`should define billData using client's data`, () => {
controller.client = {
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 => {
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`, () => {
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`, () => {
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', () => {
controller.client = {id: '123'};
$httpBackend.when('POST', `/mailer/notification/payment-update/${controller.client.id}`).respond('done');
$httpBackend.expectPOST(`/mailer/notification/payment-update/${controller.client.id}`);
controller.returnDialog('ACCEPT');
$httpBackend.flush();
});
});
});
});

View File

@ -0,0 +1,15 @@
Changed terms: Payment terms have changed
Notify customer?: Do you want to notify customer?
No: No
Yes, notify: Yes, notify
Notification sent!: Notification sent!
Notification error: Error while sending notification
Yes, propagate: Yes, propagate
Equivalent tax spreaded: Equivalent tax spreaded
Invoice by address: Invoice by address
Equalization tax: Equalization tax
Due day: Due day
Received core VNH: VNH core received
Received core VNL: VNL core received
Received B2B VNL: VNL B2B received
Save: Save

View File

@ -0,0 +1,15 @@
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
Yes, propagate: Si, propagar
Equivalent tax spreaded: Recargo de equivalencia propagado
Invoice by address: Facturar por consignatario
Equalization tax: Recargo de equivalencia
Due day: Vencimiento
Received core VNH: Recibido core VNH
Received core VNL: Recibido core VNL
Received B2B VNL: Recibido B2B VNL
Save: Guardar

View File

@ -0,0 +1,16 @@
<vn-main-block>
<mg-ajax
path="/client/api/Clients/{{edit.params.id}}/card"
actions="$ctrl.client = edit.model"
options="mgEdit">
</mg-ajax>
<vn-horizontal>
<vn-auto class="left-block">
<vn-client-descriptor client="$ctrl.client"></vn-client-descriptor>
<vn-left-menu></vn-left-menu>
</vn-auto>
<vn-one>
<vn-vertical ui-view></vn-vertical>
</vn-one>
</vn-horizontal>
</vn-main-block>

View File

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

View File

@ -0,0 +1,24 @@
import './card.js';
describe('Client', () => {
describe('Component vnClientCard', () => {
let $componentController;
let $scope;
let controller;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope) => {
$componentController = _$componentController_;
$scope = $rootScope;
controller = $componentController('vnClientCard', {$scope: $scope});
}));
it('should define and set client property to null in the module instance', () => {
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,3 +0,0 @@
vn-descriptor{
font-family: raleway-bold;
}

View File

@ -1,25 +1,25 @@
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';
import './credit-list/credit-list';
import './credit-create/credit-create';
import './greuge-list/greuge-list';
import './greuge-create/greuge-create';
import './mandate/mandate';
import './invoices/invoices';
import './summary/client-summary';
import './recovery-list/recovery-list';
import './recovery-create/recovery-create';

View File

@ -0,0 +1,37 @@
<mg-ajax path="/client/api/Clients/createWithUser" 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 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="Web user" 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"></vn-textfield>
<vn-autocomplete vn-one
field="$ctrl.client.salesPersonFk"
url="/client/api/Clients/activeSalesPerson"
show-field="name"
value-field="id"
select-fields="name"
label="Salesperson"
where="{or: [{firstName: {regexp: 'search'}}, {name: {regexp: 'search'}}]}">
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Create"></vn-submit>
</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,45 @@
import './create.js';
describe('Client', () => {
describe('Component vnClientCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
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'}});
}
};
}
};
controller = $componentController('vnClientCreate', {$scope: $scope});
}));
it('should define and set scope, state and client properties', () => {
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`, () => {
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,11 @@
Name: Nombre
Tax number: NIF/CIF
Business name: Razón social
Web user: Usuario Web
Email: Correo electrónico
Create and edit: Crear y editar
Create: Crear
You can save multiple emails: >-
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,18 @@
<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.submitGo('clientCard.credit.list')" pad-medium>
<vn-card pad-large>
<vn-title>Add credit</vn-title>
<vn-horizontal>
<vn-textfield vn-one label="Credit" field="$ctrl.client.credit" type="number" vn-focus></vn-textfield>
</vn-horizontal>
</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('vnClientCreditCreate', {
template: require('./credit-create.html'),
bindings: {
client: '<'
}
});

View File

@ -0,0 +1 @@
Add credit: Añadir crédito

View File

@ -0,0 +1,29 @@
<mg-ajax path="/client/api/ClientCredits/filter" options="vnIndexNonAuto"></mg-ajax>
<vn-vertical pad-medium>
<vn-card pad-large>
<vn-vertical>
<vn-title>Credit</vn-title>
<vn-grid-header on-order="$ctrl.onOrder(field, order)">
<vn-column-header vn-one pad-medium-h field="amount" text="Credit"></vn-column-header>
<vn-column-header vn-two pad-medium-h field="created" text="Since" default-order="ASC"></vn-column-header>
<vn-column-header vn-two pad-medium-h field="worker.firstName" text="Employee" order-locked></vn-column-header>
</vn-grid-header>
<vn-one class="list list-content">
<vn-horizontal
vn-one class="list list-element text-center"
pad-small-bottom
ng-repeat="credit in index.model.instances track by credit.id">
<vn-one pad-medium-h>{{::credit.amount | number:2}} €</vn-one>
<vn-two pad-medium-h>{{::credit.created | date:'dd/MM/yyyy HH:mm' }}</vn-two>
<vn-two pad-medium-h>{{::credit.worker.firstName}} {{::credit.worker.name}}</vn-two>
</vn-horizontal>
</vn-one>
<vn-one class="text-center pad-small-v" ng-if="index.model.count === 0" translate>No results</vn-one>
<vn-horizontal vn-one class="list list-footer"></vn-horizontal>
<vn-paging vn-one margin-large-top index="index" total="index.model.count"></vn-paging>
</vn-vertical>
</vn-card>
</vn-vertical>
<a ui-sref="clientCard.credit.create" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -0,0 +1,7 @@
import ngModule from '../module';
import FilterClientList from '../filter-client-list';
ngModule.component('vnClientCreditList', {
template: require('./credit-list.html'),
controller: FilterClientList
});

View File

@ -0,0 +1,3 @@
Since : Desde
Employee : Empleado
No results: Sin resultados

View File

@ -0,0 +1,54 @@
<vn-card margin-medium-v class="vn-descriptor">
<vn-horizontal class="header">
<a translate-attr="{title: 'Return to module index'}" ui-sref="clients">
<vn-icon icon="chevron_left"></vn-icon>
</a>
<vn-icon icon="person"></vn-icon>
<a translate-attr="{title: 'Preview'}" ui-sref="clientCard.summary">
<vn-icon icon="desktop_windows"></vn-icon>
</a>
</vn-horizontal>
<div pad-medium>
<h6>{{$ctrl.client.name}}</h6>
<div>
<vn-label translate>Id</vn-label> {{$ctrl.client.id}}
</div>
<div>
<vn-label translate>Phone</vn-label> {{$ctrl.client.phone | phone}}
</div>
<div>
<vn-label translate>Credit</vn-label> {{$ctrl.client.credit | currency:'€':2}}
</div>
<div>
<vn-label translate>Secured credit</vn-label>
<span ng-if="$ctrl.client.creditInsurance">{{$ctrl.client.creditInsurance | currency:'€':2}}</span>
<span ng-if="!$ctrl.client.creditInsurance">-</span>
</div>
</div>
<div class="footer">
<vn-icon
vn-tooltip="Client inactive"
tooltip-position = "right"
icon="person"
ng-class="{bright: $ctrl.client.isActive == false}">
</vn-icon>
<vn-icon
vn-tooltip="Client Frozen"
tooltip-position = "right"
icon="mail"
ng-class="{bright: $ctrl.client.isFreezed == true}">
</vn-icon>
<vn-icon
vn-tooltip="Web Account inactive"
tooltip-position = "right"
icon="phone"
ng-class="{bright: $ctrl.client.account.active == false}">
</vn-icon>
<vn-icon
vn-tooltip="Client has debt"
tooltip-position = "right"
icon="power"
ng-class="{bright: $ctrl.clientDebt < 0}">
</vn-icon>
</div>
</vn-card>

View File

@ -0,0 +1,36 @@
import ngModule from '../module';
class ClientDescriptor {
constructor($http) {
this.$http = $http;
}
_getClientDebt(clientFk) {
this.$http.get(`/client/api/Clients/${clientFk}/getDebt`)
.then(response => {
this.clientDebt = response.data.debt;
});
}
_getClient(clientFk) {
this.$http.get(`/client/api/Clients/${clientFk}/card`)
.then(response => {
Object.assign(this.client, response.data);
});
}
$onChanges(changes) {
if (changes.client && this.client) {
this._getClient(this.client.id);
this._getClientDebt(this.client.id);
}
}
}
ClientDescriptor.$inject = ['$http'];
ngModule.component('vnClientDescriptor', {
template: require('./descriptor.html'),
bindings: {
client: '<'
},
controller: ClientDescriptor
});

View File

@ -0,0 +1,45 @@
import './descriptor.js';
describe('Descriptor', () => {
describe('Component vnClientDescriptor', () => {
let $componentController;
let $httpBackend;
let controller;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientDescriptor');
}));
describe('_getClientDebt()', () => {
it(`should call _getClientDebt() and define the clientDebt value on the controller`, () => {
controller.client = {};
let response = {debt: 100};
$httpBackend.whenGET(`/client/api/Clients/101/getDebt`).respond(response);
$httpBackend.expectGET(`/client/api/Clients/101/getDebt`);
controller._getClientDebt(101);
$httpBackend.flush();
expect(controller.clientDebt).toEqual(100);
});
});
describe('_getClient()', () => {
it(`should call _getClient() and define the client value on the controller`, () => {
controller.client = {};
let response = {id: 101, name: 'Batman'};
$httpBackend.whenGET(`/client/api/Clients/101/card`).respond(response);
$httpBackend.expectGET(`/client/api/Clients/101/card`);
controller._getClient(101);
$httpBackend.flush();
expect(controller.client.name).toEqual('Batman');
});
});
});
});

View File

@ -1,15 +0,0 @@
<vn-card>
<vn-vertical class="margin-medium" pad-medium-top pad-medium-bottom>
<vn-horizontal>
<vn-one>
<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>
</vn-vertical>
</vn-horizontal>
</vn-vertical>
</vn-card>

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

@ -1,3 +0,0 @@
.descriptor-icon{
font-size:60px;
}

View File

@ -0,0 +1,9 @@
import FilterList from 'core/src/lib/filter-list';
export default class FilterClientList extends FilterList {
constructor($scope, $timeout, $state) {
super($scope, $timeout, $state);
this.modelName = 'clientFk';
}
}
FilterClientList.$inject = ['$scope', '$timeout', '$state'];

View File

@ -0,0 +1,141 @@
<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 pad-large>
<vn-title>Fiscal data</vn-title>
<vn-horizontal>
<vn-textfield
vn-two
vn-focus
label="Social name"
field="$ctrl.client.socialName"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield>
<vn-textfield
vn-one
label="Tax number"
field="$ctrl.client.fi"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield>
<vn-check
vn-one
label="Is equalizated"
field="$ctrl.client.isEqualizated"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-two
label="Street"
field="$ctrl.client.street"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield>
<vn-textfield
vn-one
label="City"
field="$ctrl.client.city"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
label="Postcode"
field="$ctrl.client.postcode"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</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="Province"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-autocomplete>
<vn-autocomplete
vn-one
initial-data="$ctrl.client.country"
field="$ctrl.client.countryFk"
url="/client/api/Countries"
show-field="country"
value-field="id"
label="Country"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal margin-small-bottom>
<vn-check
vn-one
label="Active"
field="$ctrl.client.isActive"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-one
label="Invoice by address"
field="$ctrl.client.hasToInvoiceByAddress"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-one
label="Verified data"
field="$ctrl.client.isTaxDataChecked"
vn-acl="administrative, salesAssistant, salesAssistant">
</vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-check
vn-one
label="Has to invoice"
field="$ctrl.client.hasToInvoice"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-one
label="Invoice by mail"
field="$ctrl.client.isToBeMailed"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
<vn-check
vn-one
label="Vies"
field="$ctrl.client.isVies"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-check>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit
label="Save"
vn-acl="administrative, salesAssistant, salesPerson"
acl-conditional-to-salesPerson="{{!$ctrl.client.isTaxDataChecked}}">
</vn-submit>
</vn-button-bar>
</form>
<vn-confirm
vn-id="propagate-isEqualizated"
question="You changed the equalization tax"
message="Do you want to spread the change?"
on-response="$ctrl.returnDialogEt(response)">
</vn-confirm>

View File

@ -0,0 +1,60 @@
import ngModule from '../module';
export default class ClientFiscalData {
constructor($scope, $http, vnApp, $translate) {
this.$ = $scope;
this.$http = $http;
this.vnApp = vnApp;
this.translate = $translate;
this.isEqualizated = undefined;
this.copyData();
}
$onChanges() {
this.copyData();
}
copyData() {
if (this.client) {
this.isEqualizated = this.client.isEqualizated;
}
}
buyerHaspermissions() {
if (!this.client) return true;
return !this.client.isTaxDataChecked;
}
submit() {
return this.$.watcher.submit().then(
() => this.checkEtChanges());
}
checkEtChanges() {
let equals = this.isEqualizated == this.client.isEqualizated;
this.isEqualizated = this.client.isEqualizated;
if (!equals)
this.$.propagateIsEqualizated.show();
}
returnDialogEt(response) {
if (response === 'ACCEPT') {
this.$http.patch(`/client/api/Clients/${this.client.id}/addressesPropagateRe`, {isEqualizated: this.client.isEqualizated}).then(
res => {
if (res.data)
this.vnApp.showMessage(this.translate.instant('Equivalent tax spreaded'));
}
);
}
}
}
ClientFiscalData.$inject = ['$scope', '$http', 'vnApp', '$translate'];
ngModule.component('vnClientFiscalData', {
template: require('./fiscal-data.html'),
controller: ClientFiscalData,
bindings: {
client: '<'
}
});

View File

@ -0,0 +1,31 @@
import './fiscal-data.js';
describe('Client', () => {
describe('Component vnClientFiscalData', () => {
let $componentController;
let $httpBackend;
let $scope;
let controller;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$scope = $rootScope.$new();
controller = $componentController('vnClientFiscalData', {$scope: $scope});
}));
describe('returnDialogEt()', () => {
it('should request to patch the propagation of tax status', () => {
controller.client = {id: 123, isEqualizated: false};
$httpBackend.when('PATCH', `/client/api/Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated}).respond('done');
$httpBackend.expectPATCH(`/client/api/Clients/${controller.client.id}/addressesPropagateRe`, {isEqualizated: controller.client.isEqualizated});
controller.returnDialogEt('ACCEPT');
$httpBackend.flush();
});
});
});
});

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

@ -0,0 +1,4 @@
No: No
Yes, notify: Sí, notificar
You changed the equalization tax: Has cambiado el recargo de equivalencia
Do you want to spread the change: ¿Deseas propagar el cambio a sus consignatarios?

View File

@ -0,0 +1,44 @@
<mg-ajax path="/client/api/greuges" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.greuge"
form="form"
save="post">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()" pad-medium>
<vn-card pad-large>
<vn-title>Add Greuge</vn-title>
<vn-horizontal>
<vn-textfield
vn-one
margin-medium-right
label="Amount"
field="$ctrl.greuge.amount"
step="1"
vn-focus>
</vn-textfield>
<vn-date-picker vn-one
label="Date"
model="$ctrl.greuge.shipped"
ini-options="{enableTime: true, dateFormat: 'd-m-Y h:i', time_24hr: true}">
</vn-date-picker>
</vn-horizontal>
<vn-horizontal>
<vn-textfield
vn-one
argin-medium-right
label="Comment"
field="$ctrl.greuge.description">
</vn-textfield>
<vn-autocomplete
vn-one
field="$ctrl.greuge.greugeTypeFk"
url="/client/api/greugeTypes"
label="Type">
</vn-autocomplete>
</vn-horizontal>
</vn-card>
<vn-button-bar>
<vn-submit label="Save"></vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,25 @@
import ngModule from '../module';
class ClientGreugeCreate {
constructor($scope, $state, $filter) {
this.$ = $scope;
this.$state = $state;
this.greuge = {
shipped: $filter('date')(new Date(), 'yyyy-MM-dd HH:mm')
};
}
onSubmit() {
this.greuge.clientFk = this.$state.params.id;
this.$.watcher.submit().then(
() => {
this.$state.go('clientCard.greuge.list');
}
);
}
}
ClientGreugeCreate.$inject = ['$scope', '$state', '$filter'];
ngModule.component('vnClientGreugeCreate', {
template: require('./greuge-create.html'),
controller: ClientGreugeCreate
});

View File

@ -0,0 +1,39 @@
import './greuge-create.js';
describe('Client', () => {
describe('Component vnClientGreugeCreate', () => {
let $componentController;
let $scope;
let $state;
let controller;
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();
}
};
}
};
controller = $componentController('vnClientGreugeCreate', {$scope: $scope});
}));
describe('onSubmit()', () => {
it('should call the function go() on $state to go to the greuges list', () => {
spyOn($state, 'go');
controller.onSubmit();
expect(controller.$state.go).toHaveBeenCalledWith('clientCard.greuge.list');
});
});
});
});

View File

@ -0,0 +1,38 @@
<mg-ajax path="/client/api/greuges/filter" options="vnIndexNonAuto"></mg-ajax>
<mg-ajax path="/client/api/greuges/{{edit.params.id}}/sumAmount" options="mgEdit"></mg-ajax>
<vn-vertical pad-medium>
<vn-card pad-large>
<vn-vertical>
<vn-title>Greuge</vn-title>
<vn-grid-header on-order="$ctrl.onOrder(field, order)">
<vn-column-header vn-one pad-medium-h field="shipped" text="Date" default-order="ASC"></vn-column-header>
<vn-column-header vn-two pad-medium-h field="description" text="Comment"></vn-column-header>
<vn-column-header vn-one pad-medium-h field="amount" text="Amount"></vn-column-header>
<vn-column-header vn-one pad-medium-h field="greugeTypeFk" text="Type"></vn-column-header>
</vn-grid-header>
<vn-one class="list list-content">
<vn-horizontal
class="list list-element text-center"
pad-small-bottom
ng-repeat="greuge in index.model.instances track by greuge.id">
<vn-one pad-medium-h>{{::greuge.shipped | date:'dd/MM/yyyy HH:mm' }}</vn-one>
<vn-two pad-medium-h>{{::greuge.description}}</vn-two>
<vn-one pad-medium-h>{{::greuge.amount | number:2}} €</vn-one>
<vn-one pad-medium-h>{{::greuge.greugeType.name}}</vn-one>
</vn-horizontal>
</vn-one>
<vn-one class="text-center pad-small-v" ng-if="index.model.count === 0" translate>No results</vn-one>
<vn-horizontal vn-one class="list list-footer text-center">
<vn-one pad-medium-h></vn-one>
<vn-two pad-medium-h></vn-two>
<vn-one pad-medium-h ng-if="index.model.count > 0">{{edit.model.sumAmount | number:2}} €</vn-one>
<vn-one pad-medium-h></vn-one>
</vn-horizontal>
<vn-paging margin-large-top vn-one index="index" total="index.model.count"></vn-paging>
</vn-vertical>
</vn-card>
</vn-vertical>
<a ui-sref="clientCard.greuge.create" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -0,0 +1,7 @@
import ngModule from '../module';
import FilterClientList from '../filter-client-list';
ngModule.component('vnClientGreugeList', {
template: require('./greuge-list.html'),
controller: FilterClientList
});

View File

@ -0,0 +1,5 @@
Date: Fecha
Comment: Comentario
Amount: Importe
Type: Tipo
Add Greuge: Añadir Greuge

View File

@ -1,23 +1,32 @@
<mg-ajax path="/client/api/Clients/filter" options="vnIndex"></mg-ajax>
<mg-ajax path="/client/api/Clients/filter" options="vnIndexNonAuto"></mg-ajax>
<div margin-medium>
<div style="max-width: 40em; margin: 0 auto;">
<div class="vn-list">
<vn-card>
<vn-horizontal pad-medium>
<vn-searchbar
vn-auto
<vn-searchbar vn-one
index="index"
on-search="index.accept()"
on-search="$ctrl.search(index)"
advanced="true"
popover="vn-client-search-panel">
popover="vn-client-search-panel"
ignore-keys = "['page', 'size', 'search']">
</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"
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>
</a>
</div>
<a ui-sref="create" fixed-bottom-right>
<vn-float-button icon="person_add"></vn-float-button>
</a>
<vn-dialog class="dialog-summary"
vn-id="dialog-summary-client">
<tpl-body>
<vn-client-summary client="$ctrl.clientSelected"></vn-client-summary>
</tpl-body>
</vn-dialog>

View File

@ -1,9 +1,22 @@
import {module} from '../module';
import './style.css';
import ngModule from '../module';
import './item-client';
export const NAME = 'vnClientIndex';
export const COMPONENT = {
template: require('./index.html')
};
module.component(NAME, COMPONENT);
export default class Controller {
constructor($scope) {
this.$scope = $scope;
this.clientSelected = null;
}
search(index) {
index.accept();
}
openSummary(client) {
this.clientSelected = client;
this.$scope.dialogSummaryClient.show();
}
}
Controller.$inject = ['$scope'];
ngModule.component('vnClientIndex', {
template: require('./index.html'),
controller: Controller
});

View File

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

View File

@ -1,7 +1,21 @@
<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}} })"
translate-attr="{title: 'View client'}"
class="vn-list-item">
<vn-horizontal ng-click="$ctrl.onClick($event)">
<vn-one>
<h6>{{::$ctrl.client.name}}</h6>
<div><vn-label translate>Id</vn-label> {{::$ctrl.client.id}}</div>
<div><vn-label translate>Phone</vn-label> {{::$ctrl.client.phone | phone}}</div>
<div><vn-label translate>Town/City</vn-label> {{::$ctrl.client.city}}</div>
<div><vn-label translate>Email</vn-label> {{::$ctrl.client.email}}</div>
</vn-one>
<vn-horizontal class="buttons">
<vn-icon
ng-click="$ctrl.preview($event)"
vn-tooltip="Preview"
icon="desktop_windows">
</vn-icon>
</vn-horizontal>
</vn-horizontal>
</a>

View File

@ -1,11 +1,24 @@
import {module} from '../module';
import ngModule from '../module';
export const NAME = 'vnItemClient';
export const COMPONENT = {
class Controller {
onClick(event) {
if (event.defaultPrevented)
event.stopImmediatePropagation();
}
preview(event) {
event.preventDefault();
this.list.openSummary(this.client);
}
}
ngModule.component('vnItemClient', {
template: require('./item-client.html'),
controllerAs: 'itemClient',
controller: Controller,
bindings: {
client: '<'
},
require: {
list: '^vnClientIndex'
}
};
module.component(NAME, COMPONENT);
});

View File

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

View File

@ -0,0 +1,3 @@
vn-item-client {
display: block;
}

View File

@ -0,0 +1,42 @@
<mg-ajax path="/client/api/InvoiceOuts/filter" options="vnIndexNonAuto"></mg-ajax>
<mg-ajax path="/client/api/InvoiceOuts/{{edit.params.id}}/sumAmount" options="mgEdit"></mg-ajax>
<vn-vertical pad-medium>
<vn-card pad-large>
<vn-vertical>
<vn-title>Invoices</vn-title>
<vn-vertical style="text-align: center;">
<vn-grid-header on-order="$ctrl.onOrder(field, order)">
<vn-column-header vn-one field="ref" text="Reference" default-order="ASC"></vn-column-header>
<vn-column-header vn-one field="issued" text="Issue date"></vn-column-header>
<vn-column-header vn-one field="dued" text="Due date"></vn-column-header>
<vn-column-header vn-one field="amount" text="Amount"></vn-column-header>
</vn-grid-header>
<vn-vertical>
<vn-one
ng-if="index.model.count > 0"
class="list list-content">
<vn-horizontal
class="list list-element"
pad-small-bottom
ng-repeat="invoice in index.model.instances track by greuge.id">
<vn-one>{{::invoice.ref}}</vn-one>
<vn-one>{{::invoice.issued | date:'dd/MM/yyyy' }}</vn-one>
<vn-one>{{::invoice.dued | date:'dd/MM/yyyy' }}</vn-one>
<vn-one>{{::invoice.amount | currency:'€':2}}</vn-one>
</vn-horizontal>
</vn-one>
<vn-one
ng-if="index.model.count === 0"
pad-small-v translate>
No results
</vn-one>
</vn-vertical>
<vn-horizontal vn-one class="list list-footer">
<vn-three></vn-three>
<vn-one>{{edit.model.sumAmount | currency:'€':2}}</vn-one>
</vn-horizontal>
</vn-vertical>
<vn-paging margin-large-top vn-one index="index" total="index.model.count"></vn-paging>
</vn-vertical>
</vn-card>
</vn-vertical>

View File

@ -0,0 +1,15 @@
import ngModule from '../module';
import FilterClientList from '../filter-client-list';
class Controller extends FilterClientList {
constructor($scope, $timeout, $state, $stateParams) {
super($scope, $timeout, $state);
$scope.$stateParams = $stateParams;
}
}
Controller.$inject = ['$scope', '$timeout', '$state', '$stateParams'];
ngModule.component('vnClientInvoices', {
template: require('./invoices.html'),
controller: Controller
});

View File

@ -0,0 +1,5 @@
Invoices: Facturas
Reference: Referencia
Issue date: Fecha de emisión
Due date: Vencimiento
Amount: Total

View File

@ -1 +0,0 @@
{}

View File

@ -0,0 +1,2 @@
Client: Client
Clients: Clients

View File

@ -1 +0,0 @@
{}

View File

@ -0,0 +1,23 @@
Active: Activo
Client: Cliente
Clients: Clientes
Basic data: Datos básicos
Fiscal data: Datos Fiscales
Addresses: Consignatarios
Web access: Acceso web
Notes: Notas
Has to invoice: Factura
Invoice by mail: Factura impresa
Country: País
Street: Domicilio fiscal
City: Municipio
Postcode: Código postal
Province: Provincia
Save: Guardar
Pay method : Forma de pago
Address: Consignatario
Credit : Crédito
Secured credit: Crédito asegurado
Verified data: Datos comprobados
Mandate: Mandato
Amount: Importe

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