Merge branch 'dev' of https://git.verdnatura.es:/salix into test

This commit is contained in:
Vicente Falco 2018-02-09 12:27:32 +01:00
commit e1a565ae1f
366 changed files with 57095 additions and 53005 deletions

BIN
.DS_Store vendored

Binary file not shown.

View File

@ -1,5 +1,6 @@
extends: [eslint:recommended, google, plugin:jasmine/recommended]
installedESLint: true
parserOptions:
ecmaVersion: 2017
plugins:
- jasmine
env:
@ -18,3 +19,4 @@ rules:
no-eq-null: 0
no-console: 0
no-warning-comments: 0
no-empty: [error, allowEmptyCatch: true]

5
.gitignore vendored
View File

@ -1,7 +1,4 @@
.DS_Store
node_modules
build
npm-debug.log
debug.log
datasources.development.json
.idea
docker-compose.yml

19
.vscode/launch.json vendored
View File

@ -2,25 +2,10 @@
"version": "0.2.0",
"configurations": [
{
"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": "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

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".

View File

@ -1,67 +1,64 @@
# Project Title
# Salix
Salix 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.
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
## Prerequisites
You will need to install globally the following items:
$ npm install -g karma
$ npm install -g karma-cli
$ npm install -g gulp
$ npm install -g webpack
$ npm install -g nodemon
Required applications.
install nginx globally.
* Node.js = 8.9.4
* NGINX
* Docker
## Getting Started // ### Installing
You will need to install globally the following items.
```
$ npm install -g karma-cli gulp webpack nodemon
```
## Getting Started // Installing
Pull from repository.
install nodejs v6.
Ask a senior developer for the datasources.development.json files required to run the project.
on root run:
Run this commands on project root directory to install Node dependencies.
```
$ npm install
$ gulp install
```
lauching nginx:
$ ./dev.sh
Launch application in developer environment.
```
$ gulp
```
launching frontend:
Also you can run backend and frontend as separately gulp tasks (including NGINX).
```
$ gulp client
or start nginx before client on sequence
$ gulp clientDev
launching backend:
$ gulp services
or start the local database before services on sequence
$ gulp serivcesDev
```
Manually reset local fixtures:
Manually reset fixtures.
```
$ gulp docker
```
to check docker images and containers status:
$ docker images
$ docker ps -a
## Running the unit tests
## Running the tests
for client-side unit tests run from project's root:
For client-side unit tests run from project's root.
```
$ karma start
```
for server-side unit tests run from project's root:
For server-side unit tests run from project's root.
```
$ npm run test
```
### Break down into end to end tests
Run local database plus e2e paths:
For end-to-end tests run from project's root.
```
$ gulp e2e
Just the e2e paths as the fixtures are untainted:
$ npm run e2e
```
## Built With
@ -71,12 +68,6 @@ $ npm run e2e
* [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
* [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": "https://git.verdnatura.es/salix"
}
}

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

View File

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

View File

@ -121,7 +121,8 @@
"menu": {
"description": "Credit",
"icon": "credit_card"
}
},
"acl": ["manager", "salesAssistant", "teamBoss", "teamManager"]
}, {
"url": "/create",
"state": "clientCard.credit.create",

View File

@ -3,9 +3,10 @@
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')" pad-medium>
<form name="form" ng-submit="watcher.submitGo('clientCard.addresses.list')" pad-medium>
<vn-card >
<vn-vertical pad-large>
<vn-title>Address</vn-title>
@ -13,7 +14,7 @@
<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="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>

View File

@ -4,7 +4,7 @@ export default class Controller {
constructor($state) {
this.address = {
clientFk: parseInt($state.params.id),
isEnabled: true
isActive: true
};
}
}

View File

@ -19,7 +19,7 @@ describe('Client', () => {
it('should define and set address property', () => {
expect(controller.address.clientFk).toBe(1234);
expect(controller.address.isEnabled).toBe(true);
expect(controller.address.isActive).toBe(true);
});
});
});

View File

@ -1,11 +0,0 @@
{
"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,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

@ -1,4 +1,4 @@
<mg-ajax path="/client/api/Addresses/{{edit.params.addressId}}" actions="$ctrl.address=edit.model" options="mgEdit"></mg-ajax>
<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"
@ -6,20 +6,20 @@
data="$ctrl.address"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitBack()" pad-medium>
<form name="form" ng-submit="$ctrl.submit()" pad-medium>
<vn-card>
<vn-vertical pad-large>
<vn-title>Address</vn-title>
<vn-horizontal>
<vn-one>
<vn-check label="Enabled" field="$ctrl.address.isEnabled"></vn-check>
<vn-check label="Enabled" field="$ctrl.address.isActive"></vn-check>
</vn-one>
<vn-one>
<vn-check label="Is equalizated" field="$ctrl.address.isEqualizated" vn-acl="administrative"></vn-check>
</vn-one>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-one label="Consignee" field="$ctrl.address.consignee" vn-focus></vn-textfield>
<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>
@ -36,8 +36,8 @@
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
initial-data="$ctrl.address.defaultAgency"
field="$ctrl.address.defaultAgencyFk"
initial-data="$ctrl.address.agency"
field="$ctrl.address.agencyFk"
url="/client/api/AgencyModes"
show-field="name"
value-field="id"
@ -46,6 +46,46 @@
<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"
order = "description ASC"
filter-search="{where: {description: {regexp: 'search'}} }"
>
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete>
<vn-textfield vn-three label="Description" model="observation.description"></vn-textfield>
<vn-one pad-medium-top>
<vn-icon
pointer
medium-grey
icon="remove_circle_outline"
ng-click="$ctrl.removeObservation($index)"
>
</vn-icon>
<vn-icon
pointer
margin-medium-left
orange
icon="add_circle"
ng-if = "observation.showAddIcon && observationsTypes.model.length > $ctrl.observations.length"
ng-click="$ctrl.addObservation()"
></vn-icon>
</vn-one>
</vn-horizontal>
</vn-one>
</vn-vertical>
</vn-card>
<vn-button-bar>

View File

@ -1,13 +1,139 @@
import ngModule from '../module';
export default class Controller {
constructor($state) {
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 = [];
}
_setIconAdd() {
if (this.observations.length) {
this.observations.map(element => {
element.showAddIcon = false;
return true;
});
this.observations[this.observations.length - 1].showAddIcon = true;
} else {
this.addObservation();
}
}
_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, showAddIcon: true});
this._setIconAdd();
}
removeObservation(index) {
let item = this.observations[index];
if (item) {
this.observations.splice(index, 1);
this._setIconAdd();
if (item.id) {
this.observationsRemoved.push(item.id);
this._setDirtyForm();
}
}
}
_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() {
this._unsetDirtyForm();
let submitWatcher = this.$scope.watcher.dataChanged();
let submitObservations;
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);
}
}
submitObservations = 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 (submitWatcher && !submitObservations) {
this.$scope.watcher.submit().then(() => {
this.$state.go('clientCard.addresses.list', {id: this.$state.params.id});
});
} else if (!submitWatcher && submitObservations) {
this._submitObservations(observationsObj).then(() => {
this.$state.go('clientCard.addresses.list', {id: this.$state.params.id});
});
} else if (submitWatcher && submitObservations) {
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')
);
}
}
$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'];
Controller.$inject = ['$state', '$scope', '$http', '$q', '$translate', 'vnApp'];
ngModule.component('vnAddressEdit', {
template: require('./address-edit.html'),

View File

@ -5,20 +5,101 @@ describe('Client', () => {
let $componentController;
let $state;
let controller;
let $httpBackend;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_) => {
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$state = _$state_;
$state.params.addressId = '1234';
$httpBackend = _$httpBackend_;
$state.params.addressId = '1';
controller = $componentController('vnAddressEdit', {$state: $state});
}));
it('should define and set address property', () => {
expect(controller.address.id).toBe(1234);
expect(controller.address.id).toEqual(1);
});
describe('_setIconAdd()', () => {
it('should set the propertie sowAddIcon from all observations to false less last one that be true', () => {
controller.observations = [
{id: 1, description: 'Spiderman rocks', showAddIcon: true},
{id: 2, description: 'Batman sucks', showAddIcon: true},
{id: 3, description: 'Ironman rules', showAddIcon: false}
];
controller._setIconAdd();
expect(controller.observations[0].showAddIcon).toBeFalsy();
expect(controller.observations[1].showAddIcon).toBeFalsy();
expect(controller.observations[2].showAddIcon).toBeTruthy();
});
});
describe('addObservation()', () => {
it('should add one empty observation into controller observations collection and call _setIconAdd()', () => {
controller.observations = [];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.addObservation();
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.observations.length).toEqual(1);
expect(controller.observations[0].id).toBe(undefined);
expect(controller.observations[0].showAddIcon).toBeTruthy();
});
});
describe('removeObservation(index)', () => {
it('should remove an observation that occupies the position in the index given and call _setIconAdd()', () => {
let index = 2;
controller.observations = [
{id: 1, description: 'Spiderman rocks', showAddIcon: false},
{id: 2, description: 'Batman sucks', showAddIcon: false},
{id: 3, description: 'Ironman rules', showAddIcon: true}
];
spyOn(controller, '_setIconAdd').and.callThrough();
controller.removeObservation(index);
expect(controller._setIconAdd).toHaveBeenCalledWith();
expect(controller.observations.length).toEqual(2);
expect(controller.observations[0].showAddIcon).toBeFalsy();
expect(controller.observations[1].showAddIcon).toBeTruthy();
expect(controller.observations[index]).toBe(undefined);
});
});
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,4 +0,0 @@
{
"Enabled": "Activo",
"Is equalizated": "Recargo de equivalencia"
}

View File

@ -0,0 +1,5 @@
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

View File

@ -1,25 +1,39 @@
<mg-ajax path="/client/api/Clients/{{index.params.id}}/addressesList" options="mgIndex"></mg-ajax>
<mg-ajax path="/client/api/Clients/{{index.params.id}}/listAddresses" 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.items track by i.id" class="pad-medium-top" style="align-items: center;">
<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': i.isDefaultAddress,'bg-opacity-item': !i.isEnabled && !i.isDefaultAddress}">
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="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>
<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>
<div><b>{{::i.consignee}}</b></div>
<div>{{::i.street}}</div>
<div>{{::i.city}}, {{::i.province}}</div>
<div>{{::i.phone}}, {{::i.mobile}}</div>
<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>
<a vn-auto ui-sref="clientCard.addresses.edit({addressId: {{i.id}}})">
<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>
@ -27,9 +41,7 @@
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-paging index="index" total="index.model.total"></vn-paging>
<vn-float-button
fixed-bottom-right
ui-sref="clientCard.addresses.create"

View File

@ -5,10 +5,13 @@ class ClientAddresses {
this.$http = $http;
this.$scope = $scope;
}
setDefault(id) {
this.$http.patch(`/client/api/Addresses/${id}`, {id: id, isDefaultAddress: true}).then(() => {
this.$scope.index.accept();
});
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'];

View File

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

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

@ -19,8 +19,7 @@
<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"
>
info="You can save multiple emails">
</vn-textfield>
</vn-horizontal>
<vn-horizontal>
@ -30,10 +29,9 @@
url="/client/api/Clients/activeSalesPerson"
show-field="name"
value-field="id"
select-fields="surname"
select-fields="name"
label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {surname: {regexp: 'search'}}]}}"
>
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.contactChannel"

View File

@ -1,13 +0,0 @@
{
"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 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",
"Contact": "Contacto"
}

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

@ -41,9 +41,7 @@ export default class Controller {
returnDialog(response) {
if (response === 'ACCEPT') {
this.$http.post(`/mailer/notification/payment-update/${this.client.id}`).then(
() => {
this.vnApp.showMessage(this.translate.instant('Notification sent!'));
}
() => this.vnApp.showMessage(this.translate.instant('Notification sent!'))
);
}
}

View File

@ -14,6 +14,7 @@ describe('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};

View File

@ -1,19 +0,0 @@
{
"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",
"You changes the equivalent tax": "Has cambiado el recargo de equivalencia",
"Do you want to spread the change to their consignees?" : "¿Deseas propagar el cambio a sus consignatarios?",
"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,17 @@
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
You changes the equivalent tax: Has cambiado el recargo de equivalencia
Do you want to spread the change to their consignees?: ¿Deseas propagar el cambio a sus consignatarios?
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

@ -1,4 +1,4 @@
<mg-ajax path="/client/api/Clients/createUserProfile" options="vnPost"></mg-ajax>
<mg-ajax path="/client/api/Clients/createWithUser" options="vnPost"></mg-ajax>
<vn-watcher
vn-id="watcher"
data="$ctrl.client"
@ -19,16 +19,16 @@
<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-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="surname"
select-fields="name"
label="Salesperson"
filter-search="{where: {or: [{name: {regexp: 'search'}}, {surname: {regexp: 'search'}}]}}"
></vn-autocomplete>
filter-search="{where: {or: [{name: {regexp: 'search'}}, {name: {regexp: 'search'}}]}}">
</vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>

View File

@ -1,10 +0,0 @@
{
"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

@ -0,0 +1,11 @@
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: >-
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

@ -1,3 +0,0 @@
{
"Add credit": "Añadir crédito"
}

View File

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

View File

@ -5,7 +5,7 @@
<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="employee.name" text="Employee" order-locked></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
@ -14,7 +14,7 @@
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.employee.name}} {{::credit.employee.surname}}</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>

View File

@ -1,9 +1,7 @@
import ngModule from '../module';
import FilterClientList from '../filterClientList';
class ClientCreditList extends FilterClientList {}
ngModule.component('vnClientCreditList', {
template: require('./credit-list.html'),
controller: ClientCreditList
controller: FilterClientList
});

View File

@ -1,5 +0,0 @@
{
"Since" : "Desde",
"Employee" : "Empleado",
"No results": "Sin resultados"
}

View File

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

View File

@ -1,40 +1,8 @@
// Generic object to list models, related to the client, with mgCrud
export default class FilterClientList {
import FilterList from '../../core/src/lib/filterList';
export default class FilterClientList extends FilterList {
constructor($scope, $timeout, $state) {
this.$ = $scope;
this.$timeout = $timeout;
this.$state = $state;
this.waitingMgCrud = 0;
this.clientFk = $state.params.id;
}
onOrder(field, order) {
this.filter(`${field} ${order}`);
}
filter(order) {
if (this.$.index && this.clientFk) {
this.waitingMgCrud = 0;
this.$.index.filter = {
page: 1,
size: 10,
clientFk: this.clientFk
};
if (order) {
this.$.index.filter.order = order;
}
this.$.index.accept();
} else if (!this.clientFk) {
throw new Error('Error: ClientFk not found');
} else if (this.waitingMgCrud > 3) {
throw new Error('Error: Magic Crud is not loaded');
} else {
this.waitingMgCrud++;
this.$timeout(() => {
this.filter(order);
}, 250);
}
super($scope, $timeout, $state);
this.modelName = 'clientFk';
}
}
FilterClientList.$inject = ['$scope', '$timeout', '$state'];

View File

@ -10,9 +10,9 @@
<vn-vertical pad-large>
<vn-title>Fiscal data</vn-title>
<vn-horizontal>
<vn-textfield autofocus vn-two label="Social name" field="$ctrl.client.socialName" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi" vn-acl="administrative"></vn-textfield>
<vn-check vn-one label="Is equalizated" field="$ctrl.client.isEqualizated" vn-acl="administrative"></vn-check>
<vn-textfield autofocus vn-two label="Social name" field="$ctrl.client.socialName" vn-acl="administrative"></vn-textfield>
<vn-textfield vn-one label="Tax number" field="$ctrl.client.fi" vn-acl="administrative"></vn-textfield>
<vn-check vn-one label="Is equalizated" field="$ctrl.client.isEqualizated" vn-acl="administrative"></vn-check>
</vn-horizontal>
<vn-horizontal>
<vn-textfield vn-two label="Street" field="$ctrl.client.street" vn-focus vn-acl="administrative"></vn-textfield>
@ -27,18 +27,16 @@
show-field="name"
value-field="id"
label="Province"
vn-acl="administrative"
>
vn-acl="administrative">
</vn-autocomplete>
<vn-autocomplete vn-one
initial-data="$ctrl.client.country"
field="$ctrl.client.countryFk"
url="/client/api/Countries"
show-field="name"
show-field="country"
value-field="id"
label="Country"
vn-acl="administrative"
>
vn-acl="administrative">
</vn-autocomplete>
</vn-horizontal>
<vn-horizontal margin-small-bottom>
@ -64,17 +62,14 @@
</vn-one>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>
<vn-submit label="Save" vn-acl="administrative"></vn-submit>
</vn-button-bar>
</form>
<vn-dialog
vn-id="propagate-isEqualizated"
on-response="$ctrl.returnDialogEt(response)"
>
on-response="$ctrl.returnDialogEt(response)">
<tpl-body>
<vn-vertical>
<vn-one text-center translate>You changes the equivalen

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

@ -9,20 +9,17 @@
<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>
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>
@ -36,3 +33,4 @@
<a ui-sref="clientCard.greuge.create" fixed-bottom-right>
<vn-float-button icon="add"></vn-float-button>
</a>

View File

@ -1,9 +1,7 @@
import ngModule from '../module';
import FilterClientList from '../filterClientList';
class ClientGreugeList extends FilterClientList {}
ngModule.component('vnClientGreugeList', {
template: require('./greuge-list.html'),
controller: ClientGreugeList
controller: FilterClientList
});

View File

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

View File

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

View File

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

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

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

View File

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

View File

@ -1,25 +0,0 @@
{
"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"
}

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

View File

@ -1,5 +0,0 @@
{
"Company": "Empresa",
"Register date": "Fecha alta",
"End date": "Fecha baja"
}

View File

@ -0,0 +1,3 @@
Company: Empresa
Register date: Fecha alta
End date: Fecha baja

View File

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

View File

@ -0,0 +1,2 @@
New note: Nueva nota
Note: Nota

View File

@ -3,6 +3,7 @@
url="/client/api/ClientObservations"
id-field="id"
data="$ctrl.note"
save="post"
form="form">
</vn-watcher>
<form name="form" ng-submit="watcher.submitGo('clientCard.notes.list')" pad-medium>

View File

@ -2,12 +2,12 @@
<vn-vertical pad-large>
<vn-title>Notes</vn-title>
<vn-one
ng-repeat="n in $ctrl.observations"
pad-small border-solid
border-radius
margin-small-bottom style="align-items: center;">
ng-repeat="n in $ctrl.observations"
pad-small border-solid
border-radius
margin-small-bottom style="align-items: center;">
<vn-horizontal>
<vn-one >{{::n.employee.name}} {{::n.employee.surname}}</vn-one>
<vn-one >{{::n.worker.firstName}} {{::n.worker.name}}</vn-one>
<vn-auto>{{::n.created | date:'dd/MM/yyyy HH:mm'}}</vn-auto>
</vn-horizontal>
<vn-horizontal>

View File

@ -15,7 +15,8 @@ describe('Client', () => {
$componentController = _$componentController_;
$state = _$state_;
$httpBackend = _$httpBackend_;
controller = $componentController('vnClientNotes', {$httpBackend: $httpBackend, $state: $state});
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
controller = $componentController('vnClientNotes', {$state: $state});
}));
describe('$onChanges()', () => {

View File

@ -1,10 +0,0 @@
{
"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,8 @@
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

@ -1,41 +0,0 @@
import './search-panel.js';
describe('Client', () => {
describe('Component vnClientSearchPanel', () => {
let $componentController;
let sessionStorage;
let controller;
beforeEach(() => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _sessionStorage_) => {
$componentController = _$componentController_;
sessionStorage = _sessionStorage_;
controller = $componentController('vnClientSearchPanel', {sessionStorage: sessionStorage});
}));
// describe('onSearch()', () => {
// it('should call setStorageValue() and onSubmit()', () => {
// 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', () => {
// expect(controller.filter).not.toBeDefined();
// spyOn(sessionStorage, 'get').and.returnValue({data: 'data'});
// controller.$onChanges();
// expect(controller.filter).toBe(sessionStorage.get({data: 'data'}));
// });
// });
});
});

View File

@ -1,7 +0,0 @@
{
"User": "Usuario",
"Enable web access": "Habilitar acceso web",
"New password": "Nueva contraseña",
"Repeat password": "Repetir contraseña",
"Change password": "Cambiar contraseña"
}

View File

@ -0,0 +1,5 @@
User: Usuario
Enable web access: Habilitar acceso web
New password: Nueva contraseña
Repeat password: Repetir contraseña
Change password: Cambiar contraseña

View File

@ -16,7 +16,7 @@ export default class Controller {
isCustomer() {
if (this.client && this.client.id) {
this.$http.get(`/client/api/Clients/${this.client.id}/getRoleCustomer`).then(res => {
this.$http.get(`/client/api/Clients/${this.client.id}/hasCustomerRole`).then(res => {
this.canChangePassword = (res.data) ? res.data.isCustomer : false;
});
} else {

View File

@ -15,6 +15,7 @@ describe('Component VnClientWebAccess', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
vnApp = _vnApp_;
spyOn(vnApp, 'showError');
controller = $componentController('vnClientWebAccess', {$scope: $scope});
@ -37,8 +38,8 @@ describe('Component VnClientWebAccess', () => {
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.when('GET', `/client/api/Clients/${controller.client.id}/hasCustomerRole`).respond('ok');
$httpBackend.expectGET(`/client/api/Clients/${controller.client.id}/hasCustomerRole`);
$httpBackend.flush();
});
});

View File

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

View File

@ -18,5 +18,5 @@
item-width="$ctrl.width"
multiple="$ctrl.multiple"
parent = "$ctrl.element"
><vn-item ng-transclude="tplItem">{{$parent.item.name}}</vn-item></vn-drop-down>
><vn-item ng-transclude="tplItem">{{$parent.item[$ctrl.showField]}}</vn-item></vn-drop-down>
</vn-vertical>

View File

@ -2,7 +2,6 @@ import {module} from '../module';
import Component from '../lib/component';
import copyObject from '../lib/copy';
import './style.scss';
import { log } from 'util';
class Autocomplete extends Component {
constructor($element, $scope, $http, $timeout, $filter) {
@ -20,9 +19,8 @@ class Autocomplete extends Component {
this._field = null;
this._preLoad = false;
this.maxRow = 10;
this.showField = this.showField || 'name';
this.valueField = this.valueField || 'id';
this.order = this.order || 'name ASC';
this.showField = 'name';
this.valueField = 'id';
this.items = copyObject(this.data) || [];
this.displayValueMultiCheck = [];
this._multiField = [];
@ -195,6 +193,10 @@ class Autocomplete extends Component {
return fields;
}
getOrder() {
return this.order ? this.order : `${this.showField} ASC`;
}
findItems(search) {
if (this.url && search && !this.finding) {
this.maxRow = false;
@ -208,7 +210,7 @@ class Autocomplete extends Component {
Object.assign(filter.where, this.filter.where);
}
}
filter.order = this.order;
filter.order = this.getOrder();
let json = JSON.stringify(filter);
this.finding = true;
this.$http.get(`${this.url}?filter=${json}`).then(
@ -252,7 +254,7 @@ class Autocomplete extends Component {
filter.skip = this.items.length;
}
filter.limit = this.maxRow;
filter.order = this.order;
filter.order = this.getOrder();
}
if (this.filter) {
Object.assign(filter, this.filter);

View File

@ -16,6 +16,7 @@ describe('Component vnAutocomplete', () => {
$componentController = _$componentController_;
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$element = angular.element('<div></div>');
controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
@ -120,7 +121,6 @@ describe('Component vnAutocomplete', () => {
it(`should perform a query if the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
$httpBackend.whenGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`).respond();
$httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`);
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
@ -136,7 +136,6 @@ describe('Component vnAutocomplete', () => {
it(`should set field performing a query as the item id isn't present in the controller.items property`, () => {
controller.url = 'test.com';
$httpBackend.whenGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`).respond();
$httpBackend.expectGET(`${controller.url}?filter={"fields":{"id":true,"name":true},"where":{"id":3}}`);
controller.items = [{id: 1, name: 'test1'}, {id: 2, name: 'Bruce Wayne'}];
controller.field = 3;
@ -150,7 +149,7 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@ -163,8 +162,8 @@ describe('Component vnAutocomplete', () => {
let controller = $componentController('vnAutocomplete', {$scope, $element, $httpBackend, $timeout});
controller.url = 'test.com';
let search = 'The Joker';
controller.filterSearch = "{where: {surname: {regexp: 'search'}}}";
let json = JSON.stringify({where: {surname: {regexp: search}}, order: controller.order});
controller.filterSearch = "{where: {name: {regexp: 'search'}}}";
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);
@ -177,7 +176,7 @@ describe('Component vnAutocomplete', () => {
controller.url = 'test.com';
let search = 'Joker';
controller.multiple = true;
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.order});
let json = JSON.stringify({where: {name: {regexp: search}}, order: controller.getOrder()});
$httpBackend.whenGET(`${controller.url}?filter=${json}`).respond([{id: 3, name: 'The Joker'}, {id: 4, name: 'Joker'}]);
$httpBackend.expectGET(`${controller.url}?filter=${json}`);
controller.findItems(search);

View File

@ -9,7 +9,8 @@ describe('Directive acl', () => {
});
compile = (hasPermissions, _element) => {
inject(($compile, $rootScope, aclService, _$timeout_) => {
inject(($compile, $rootScope, aclService, _$timeout_, _$httpBackend_) => {
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
spyOn(aclService, 'aclPermission').and.returnValue(hasPermissions);
scope = $rootScope.$new();
$timeout = _$timeout_;

View File

@ -20,8 +20,9 @@ describe('Directive dialog', () => {
});
};
beforeEach(angular.mock.inject(_$componentController_ => {
beforeEach(angular.mock.inject((_$componentController_, _$httpBackend_) => {
$componentController = _$componentController_;
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element('<div></div>');
controller = $componentController('vnDialog', {$element});
}));

View File

@ -8,12 +8,11 @@ describe('Directive focus', () => {
});
compile = (_element, _childElement) => {
inject(($compile, $rootScope) => {
inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
if (_childElement) {
let childElement = angular.element(_childElement);
$element[0] < childElement;
$element[0].firstChild.focus = jasmine.createSpy(focus);
}
$element[0].focus = jasmine.createSpy('focus');

View File

@ -8,8 +8,9 @@ describe('Directive vnId', () => {
});
compile = _element => {
inject(($compile, $rootScope) => {
inject(($compile, $rootScope, _$httpBackend_) => {
$scope = $rootScope.$new();
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$element = angular.element(_element);
$compile($element)($scope);
$scope.$digest();

View File

@ -8,8 +8,9 @@ describe('Directive validation', () => {
});
compile = (_element, validations, value) => {
inject(($compile, $rootScope, aclService, _$timeout_, $window) => {
inject(($compile, $rootScope, aclService, _$timeout_, $window, _$httpBackend_) => {
$window.validations = validations;
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
scope = $rootScope.$new();
scope.user = {name: value};
element = angular.element(_element);

View File

@ -11,7 +11,8 @@ describe('Component vnDropDown', () => {
angular.mock.module('client');
});
beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_) => {
beforeEach(angular.mock.inject((_$componentController_, _$timeout_, _$filter_, _$httpBackend_) => {
_$httpBackend_.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$componentController = _$componentController_;
$element = angular.element('<div><ul><li></li></ul></div>');
$timeout = _$timeout_;
@ -258,19 +259,22 @@ describe('Component vnDropDown', () => {
});
});
// describe('setScrollPosition()', () => {
// it(`should call child.scrollIntoView if defined `, () => {
// $element[0].firstChild.setAttribute('class', 'dropdown');
// let child = $element[0].firstChild.firstChild;
describe('setScrollPosition()', () => {
it(`should call child.scrollIntoView if defined `, () => {
$element[0].firstChild.setAttribute('class', 'dropdown');
$element[0].firstChild.firstChild.setAttribute('class', 'active');
let child = $element[0].firstChild.firstChild;
spyOn(child, 'getBoundingClientRect').and.returnValue({top: 100});
let container = $element[0].firstChild;
spyOn(container, 'getBoundingClientRect').and.returnValue({top: 10, height: 70});
child.scrollIntoView = () => {};
spyOn(child, 'scrollIntoView');
controller._activeOption = 0;
controller.setScrollPosition();
// child.scrollIntoView = () => {};
// spyOn(child, 'scrollIntoView');
// controller._activeOption = 0;
// controller.setScrollPosition();
// expect(child.scrollIntoView).toHaveBeenCalledWith();
// });
// });
expect(child.scrollIntoView).toHaveBeenCalledWith();
});
});
describe('selectItem()', () => {
it(`should pass item to selected and set controller._show to false`, () => {

View File

@ -15,6 +15,7 @@ describe('Component vnIconMenu', () => {
beforeEach(angular.mock.inject((_$componentController_, $rootScope, _$httpBackend_, _$timeout_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$timeout = _$timeout_;
$scope = $rootScope.$new();
$element = angular.element('<div></div>');

View File

@ -10,9 +10,9 @@ export default class App {
constructor($rootScope) {
this.loaderStatus = 0;
this.$rootScope = $rootScope;
this.timeout = window.snackbarTimeout || 2000;
}
show(message) {
this.timeout = window.snackbarTimeout || 2000;
if (this.snackbar) this.snackbar.show({message: message, timeout: this.timeout});
}
showMessage(message) {

View File

@ -0,0 +1,40 @@
// Generic object to list models
export default class FilterList {
constructor($scope, $timeout, $state) {
this.$ = $scope;
this.$timeout = $timeout;
this.$state = $state;
this.waitingMgCrud = 0;
this.modelId = $state.params.id;
}
onOrder(field, order) {
this.filter(`${field} ${order}`);
}
filter(order) {
if (this.$.index && this.modelId && this.modelName) {
this.waitingMgCrud = 0;
this.$.index.filter = {
page: 1,
size: 10
};
this.$.index.filter[this.modelName] = this.modelId;
if (order) {
this.$.index.filter.order = order;
}
this.$.index.accept();
} else if (!this.modelId || !this.modelName) {
throw new Error('Error: model not found');
} else if (this.waitingMgCrud > 3) {
throw new Error('Error: Magic Crud is not loaded');
} else {
this.waitingMgCrud++;
this.$timeout(() => {
this.filter(order);
}, 250);
}
}
}

View File

@ -6,6 +6,7 @@ import './app';
import './interceptor';
import './aclService';
import './storageServices';
import './filterList';
export * from './util';
export {default as splitingRegister} from './splitingRegister';

View File

@ -3,6 +3,9 @@ import isEqual from './equals';
export default function getModifiedData(object, objectOld) {
var newObject = {};
if (objectOld === null) {
return object;
}
for (var k in object) {
var val = object[k];
var valOld = objectOld[k] === undefined ? null : objectOld[k];

View File

@ -1,59 +1,59 @@
import {module} from '../module';
import splitingRegister from './splitingRegister';
factory.$inject = ['$translatePartialLoader', '$http', '$window', '$ocLazyLoad', '$q', '$translate'];
export function factory($translatePartialLoader, $http, $window, $ocLazyLoad, $q, $translate) {
factory.$inject = ['$http', '$window', '$ocLazyLoad', '$translatePartialLoader', '$translate'];
export function factory($http, $window, $ocLazyLoad, $translatePartialLoader, $translate) {
class ModuleLoader {
constructor() {
this._loadedModules = {};
this._loaded = {};
}
load(moduleName, validations) {
if (this._loadedModules[moduleName])
return;
let loaded = this._loaded;
this._loadedModules[moduleName] = true;
if (loaded[moduleName])
return loaded[moduleName];
loaded[moduleName] = Promise.resolve(true);
let deps = splitingRegister.getDependencies(moduleName);
let modules = splitingRegister.modules;
let promises = [];
let depPromises = [];
if (deps)
for (let dep of deps)
depPromises.push(this.load(dep, validations));
loaded[moduleName] = new Promise((resolve, reject) => {
Promise.all(depPromises)
.then(() => {
let promises = [];
// FIXME: https://github.com/angular-translate/angular-translate/pull/1674
$translatePartialLoader.addPart(moduleName);
promises.push($translate.refresh());
if (validations)
promises.push(new Promise(resolve => {
$http.get(`/${moduleName}/validations`).then(
json => this.onValidationsReady(json, resolve),
json => resolve()
);
}));
for (let dep of deps) {
this._loadedModules[dep] = true;
promises.push(modules[dep]());
if (validations)
promises.push(new Promise(resolve => {
$http.get(`/${dep}/validations`).then(
json => this.onValidationsReady(json, resolve),
json => resolve()
);
splitingRegister.modules[moduleName](resolve);
}));
$translatePartialLoader.addPart(dep);
// FIXME: https://github.com/angular-translate/angular-translate/pull/1674
// promises.push($translate.refresh());
setTimeout(() => $translate.refresh(), 500);
}
let ocDeps = deps.map(item => {
return {name: item};
Promise.all(promises)
.then(() => {
this._loaded[moduleName] = true;
resolve($ocLazyLoad.load({name: moduleName}));
})
.catch(reject);
})
.catch(reject);
});
return new Promise(resolve => {
Promise.all(promises).then(
() => resolve($ocLazyLoad.load(ocDeps))
);
});
}
parseValidation(val) {
switch (val.validation) {
case 'custom':
// TODO: Reemplazar eval
val.bindedFunction = eval(`(${val.bindedFunction})`);
break;
case 'format':
val.with = new RegExp(val.with);
break;
}
return loaded[moduleName];
}
onValidationsReady(json, resolve) {
let entities = json.data;
@ -69,6 +69,17 @@ export function factory($translatePartialLoader, $http, $window, $ocLazyLoad, $q
Object.assign($window.validations, json.data);
resolve();
}
parseValidation(val) {
switch (val.validation) {
case 'custom':
// TODO: Replace eval
val.bindedFunction = eval(`(${val.bindedFunction})`);
break;
case 'format':
val.with = new RegExp(val.with);
break;
}
}
}
return new ModuleLoader();

View File

@ -1,29 +1,11 @@
class SplitingRegister {
constructor() {
this._graph = null;
this._modules = {};
this.graph = null;
this.modules = {};
}
get modules() {
return this._modules;
}
getDependencies(dependency) {
var array = [];
array.push(dependency);
var first = this._graph[dependency];
if (first)
while (first.length > 0) {
dependency = first.shift();
array = array.concat(this.getDependencies(dependency));
}
return array;
}
registerGraph(graph) {
this._graph = graph;
}
register(moduleName, loader) {
this._modules[moduleName] = loader;
getDependencies(moduleName) {
return this.graph[moduleName];
}
}

View File

@ -1,9 +0,0 @@
{
"Accept": "Accept",
"Cancel": "Cancel",
"Close": "Close",
"Clear": "Clear",
"Save": "Save",
"Add": "Add",
"Search": "Search"
}

View File

@ -0,0 +1,7 @@
Accept: Accept
Cancel: Cancel
Close: Close
Clear: Clear
Save: Save
Add: Add
Search: Search

View File

@ -1,11 +0,0 @@
{
"Accept": "Aceptar",
"Cancel": "Cancelar",
"Close": "Cerrar",
"Clear": "Borrar",
"Save": "Guardar",
"Add": "Añadir",
"Search": "Buscar",
"Show More": "Ver más",
"No more results" : "No hay más resultados"
}

View File

@ -0,0 +1,9 @@
Accept: Aceptar
Cancel: Cancelar
Close: Cerrar
Clear: Borrar
Save: Guardar
Add: Añadir
Search: Buscar
Show More: Ver más
No more results : No hay más resultados

View File

@ -1,6 +0,0 @@
{
"Are you sure exit without saving?": "¿Seguro que quieres salir sin guardar?",
"Unsaved changes will be lost": "Los cambios que no hayas guardado se perderán",
"No changes to save": "No hay cambios que guardar",
"Some fields are invalid": "Algunos campos no son válidos"
}

View File

@ -0,0 +1,4 @@
Are you sure exit without saving?: ¿Seguro que quieres salir sin guardar?
Unsaved changes will be lost: Los cambios que no hayas guardado se perderán
No changes to save: No hay cambios que guardar
Some fields are invalid: Algunos campos no son válidos

View File

@ -47,7 +47,7 @@ export default class Watcher extends Component {
fetchData() {
let id = this.data[this.idField];
// return new Promise((resolve, reject) => {
// return new Promise((resolve, reject) => {
this.$http.get(`${this.url}/${id}`).then(
json => {
this.data = copyObject(json.data);
@ -55,26 +55,33 @@ export default class Watcher extends Component {
}
// json => reject(json)
);
// });
// });
}
/**
* Submits the data and goes back in the history.
*
* @return {Promise} The request promise
*/
submitBack() {
return this.submit().then(
() => this.window.history.back()
);
}
/**
* Submits the data and goes another state.
*
* @param {String} state The state name
* @param {Object} params The request params
* @return {Promise} The request promise
*/
submitGo(state, params) {
return this.submit().then(
() => this.$state.go(state, params || {})
);
}
/**
* Submits the data to the server.
*
@ -94,9 +101,11 @@ export default class Watcher extends Component {
(resolve, reject) => this.noChanges(reject)
);
}
let changedData = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post') ? this.copyInNewObject(this.data) : getModifiedData(this.data, this.orgData);
let changedData = (this.$attrs.save && this.$attrs.save.toLowerCase() === 'post')
? this.copyInNewObject(this.data)
: getModifiedData(this.data, this.orgData);
if (this.save) {
if (this.save && this.save.accept) {
this.save.model = changedData; // this.copyInNewObject(changedData);
return new Promise((resolve, reject) => {
this.save.accept().then(
@ -108,7 +117,7 @@ export default class Watcher extends Component {
// XXX: Alternative when mgCrud is not used
let id = this.orgData[this.idField];
let id = this.idField ? this.orgData[this.idField] : null;
if (id) {
return new Promise((resolve, reject) => {
@ -120,7 +129,7 @@ export default class Watcher extends Component {
}
return new Promise((resolve, reject) => {
this.$http.post(this.url, this.data).then(
this.$http.post(this.url, changedData).then(
json => this.writeData(json, resolve),
json => reject(json)
);
@ -149,6 +158,8 @@ export default class Watcher extends Component {
updateOriginalData() {
this.orgData = this.copyInNewObject(this.data);
if (this.form && this.form.$dirty)
this.form.$setPristine();
}
copyInNewObject(data) {
@ -181,7 +192,7 @@ export default class Watcher extends Component {
}
dataChanged() {
if (this.form && !this.form.$dirty) return false;
if (this.form && this.form.$dirty) return true;
let newData = this.copyInNewObject(this.data);
return !isEqual(newData, this.orgData);
}

View File

@ -25,6 +25,7 @@ describe('Component vnWatcher', () => {
vnApp = _vnApp_;
$transitions = _$transitions_;
$httpBackend = _$httpBackend_;
$httpBackend.when('GET', /\/locale\/\w+\/[a-z]{2}\.json/).respond({});
$translate = _$translate_;
$attrs = {
save: "patch"
@ -144,7 +145,7 @@ describe('Component vnWatcher', () => {
describe('when controller.save()', () => {
it(`should set controller.save.model property`, () => {
controller.save = {};
controller.save = {accept: () => {}};
controller.data = {originalInfo: 'original data', info: 'new data'};
controller.orgData = {originalInfo: 'original data'};
controller.submit();

View File

@ -14,7 +14,10 @@ class ItemCard {
{relation: "origin"},
{relation: "ink"},
{relation: "producer"},
{relation: "intrastat"}
{relation: "intrastat"},
{relation: "expence"},
{relation: "taxClass"},
{relation: "itemTag", scope: {order: "priority ASC", include: {relation: "tag"}}}
]
};
this.$http.get(`/item/api/Items/${this.$state.params.id}?filter=${JSON.stringify(filter)}`).then(

View File

@ -0,0 +1,33 @@
import './item-card.js';
describe('Item', () => {
describe('Component vnItemCard', () => {
let $componentController;
let $httpBackend;
let $state;
let controller;
beforeEach(() => {
angular.mock.module('item');
});
beforeEach(angular.mock.inject((_$componentController_, _$state_, _$httpBackend_) => {
$componentController = _$componentController_;
$httpBackend = _$httpBackend_;
$state = _$state_;
controller = $componentController('vnItemCard', {$state: $state});
}));
describe('$onInit()', () => {
it('should request to patch the propagation of tax status', () => {
controller.client = {id: 123, isEqualizated: false};
$httpBackend.whenGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"taxClass"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}').respond({data: 'item'});
$httpBackend.expectGET('/item/api/Items/undefined?filter={"include":[{"relation":"itemType"},{"relation":"origin"},{"relation":"ink"},{"relation":"producer"},{"relation":"intrastat"},{"relation":"expence"},{"relation":"taxClass"},{"relation":"itemTag","scope":{"order":"priority ASC","include":{"relation":"tag"}}}]}');
controller.$onInit();
$httpBackend.flush();
expect(controller.item).toEqual({data: 'item'});
});
});
});
});

View File

@ -31,12 +31,10 @@
order="description ASC"
filter-search="{where: {description: {regexp: 'search'}} }"
>
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
<tpl-item>{{$parent.$parent.item.description}}</tpl-item>
</vn-autocomplete>
<vn-textfield vn-one label="Relevancy" field="$ctrl.item.relevancy" type="number"></vn-textfield>
</vn-horizontal>
<vn-horizontal>
<vn-autocomplete vn-one
url="/item/api/Origins"
@ -45,9 +43,13 @@
value-field="id"
field="$ctrl.item.originFk"
></vn-autocomplete>
<vn-one></vn-one>
<vn-autocomplete vn-one
url="/item/api/Expences"
label="Expence"
field="$ctrl.item.expenceFk"
initial-data="$ctrl.item.expence"
></vn-autocomplete>
</vn-horizontal>
</vn-vertical>
</vn-card>
<vn-button-bar>

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