Merge branch 'master' of https://gitea.verdnatura.es/verdnatura/salix
This commit is contained in:
commit
5978a4d6b7
|
@ -0,0 +1,4 @@
|
|||
node_modules
|
||||
print/node_modules
|
||||
front/node_modules
|
||||
services
|
|
@ -1,6 +0,0 @@
|
|||
{
|
||||
"salixHost": "localhost",
|
||||
"salixPort": "3306",
|
||||
"salixUser": "root",
|
||||
"salixPassword": "root"
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
node_modules
|
|
@ -1,12 +1,12 @@
|
|||
extends: [eslint:recommended, google, plugin:jasmine/recommended]
|
||||
parserOptions:
|
||||
ecmaVersion: 2017
|
||||
ecmaVersion: 2018
|
||||
sourceType: "module"
|
||||
plugins:
|
||||
- jasmine
|
||||
env:
|
||||
jasmine: true
|
||||
rules:
|
||||
indent: [error, 4]
|
||||
require-jsdoc: 0
|
||||
no-undef: 0
|
||||
max-len: 0
|
||||
|
@ -20,3 +20,15 @@ rules:
|
|||
no-console: 0
|
||||
no-warning-comments: 0
|
||||
no-empty: [error, allowEmptyCatch: true]
|
||||
complexity: 0
|
||||
max-depth: 0
|
||||
comma-dangle: 0
|
||||
bracketSpacing: 0
|
||||
space-infix-ops: 1
|
||||
no-invalid-this: 0
|
||||
space-before-function-paren: [error, never]
|
||||
prefer-const: 0
|
||||
curly: [error, multi-or-nest]
|
||||
indent: [error, 4]
|
||||
arrow-parens: [error, as-needed]
|
||||
jasmine/no-focused-tests: 0
|
||||
|
|
|
@ -1,4 +1,13 @@
|
|||
coverage
|
||||
node_modules
|
||||
build
|
||||
dist
|
||||
e2e/dms/*/
|
||||
!e2e/dms/c4c
|
||||
!e2e/dms/c81
|
||||
!e2e/dms/ecc
|
||||
npm-debug.log
|
||||
docker-compose.yml
|
||||
.eslintcache
|
||||
datasources.*.json
|
||||
print.*.json
|
||||
db.json
|
||||
junit.xml
|
|
@ -4,7 +4,13 @@
|
|||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach by Process ID",
|
||||
"name": "Attach",
|
||||
"restart": true,
|
||||
"timeout": 50000
|
||||
}, {
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach by process ID",
|
||||
"processId": "${command:PickProcess}"
|
||||
}
|
||||
]
|
||||
|
|
|
@ -2,5 +2,9 @@
|
|||
{
|
||||
// Carácter predeterminado de final de línea.
|
||||
"files.eol": "\n",
|
||||
"vsicons.presets.angular": false
|
||||
"vsicons.presets.angular": false,
|
||||
"eslint.autoFixOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll.eslint": true
|
||||
}
|
||||
}
|
41
Dockerfile
41
Dockerfile
|
@ -1,13 +1,38 @@
|
|||
FROM node:8.9.4
|
||||
FROM debian:stretch-slim
|
||||
ENV TZ Europe/Madrid
|
||||
|
||||
COPY . /app
|
||||
COPY ../loopback /loopback
|
||||
ARG DEBIAN_FRONTEND=noninteractive
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
curl \
|
||||
ca-certificates \
|
||||
gnupg2 \
|
||||
libfontconfig \
|
||||
&& curl -sL https://deb.nodesource.com/setup_10.x | bash - \
|
||||
&& apt-get install -y --no-install-recommends \
|
||||
nodejs \
|
||||
&& apt-get purge -y --auto-remove \
|
||||
gnupg2 \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& npm -g install pm2
|
||||
|
||||
WORKDIR /app
|
||||
WORKDIR /salix
|
||||
COPY package.json package-lock.json ./
|
||||
COPY loopback/package.json loopback/
|
||||
COPY print/package.json print/
|
||||
RUN npm install --only=prod
|
||||
RUN npm --prefix ./print install --only=prod ./print
|
||||
|
||||
RUN npm install
|
||||
RUN npm -g install pm2
|
||||
COPY loopback loopback
|
||||
COPY back back
|
||||
COPY modules modules
|
||||
COPY print print
|
||||
COPY \
|
||||
LICENSE \
|
||||
README.md \
|
||||
./
|
||||
|
||||
CMD ["pm2-docker", "./server/server.js"]
|
||||
CMD ["pm2-runtime", "./back/process.yml"]
|
||||
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=15s --timeout=10s \
|
||||
CMD curl -f http://localhost:3000/api/Applications/status || exit 1
|
||||
|
|
|
@ -1,60 +1,146 @@
|
|||
#!/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;
|
||||
pipeline {
|
||||
agent any
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
}
|
||||
|
||||
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}"
|
||||
environment {
|
||||
PROJECT_NAME = 'salix'
|
||||
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
|
||||
}
|
||||
stages {
|
||||
stage('Checkout') {
|
||||
checkout scm
|
||||
steps {
|
||||
script {
|
||||
if (!env.GIT_COMMITTER_EMAIL) {
|
||||
env.COMMITTER_EMAIL = sh(
|
||||
script: 'git --no-pager show -s --format="%ae"',
|
||||
returnStdout: true
|
||||
).trim()
|
||||
} else {
|
||||
env.COMMITTER_EMAIL = env.GIT_COMMITTER_EMAIL;
|
||||
}
|
||||
|
||||
stage ('install modules'){
|
||||
sh "npm install"
|
||||
switch (env.BRANCH_NAME) {
|
||||
case 'master':
|
||||
env.NODE_ENV = 'production'
|
||||
break
|
||||
case 'test':
|
||||
env.NODE_ENV = 'test'
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
stage ('build Project'){
|
||||
sh "gulp build"
|
||||
configFileProvider([
|
||||
configFile(fileId: "salix.groovy",
|
||||
variable: 'GROOVY_FILE')
|
||||
]) {
|
||||
load env.GROOVY_FILE
|
||||
}
|
||||
|
||||
stage ("docker")
|
||||
{
|
||||
stage ("install modules loopback service")
|
||||
{
|
||||
sh "cd ./services/loopback && npm install"
|
||||
sh 'printenv'
|
||||
}
|
||||
}
|
||||
stage('Install') {
|
||||
environment {
|
||||
NODE_ENV = ""
|
||||
}
|
||||
steps {
|
||||
nodejs('node-lts') {
|
||||
sh 'npm install --no-audit --prefer-offline'
|
||||
sh 'gulp install --ci'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Test') {
|
||||
environment {
|
||||
NODE_ENV = ""
|
||||
}
|
||||
parallel {
|
||||
stage('Frontend') {
|
||||
steps {
|
||||
nodejs('node-lts') {
|
||||
sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=1'
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Backend') {
|
||||
steps {
|
||||
nodejs('node-lts') {
|
||||
sh 'gulp backTestDockerOnce --junit --random'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
stage('Build') {
|
||||
when { anyOf {
|
||||
branch 'test'
|
||||
branch 'master'
|
||||
}}
|
||||
environment {
|
||||
CREDS = credentials('docker-registry')
|
||||
}
|
||||
steps {
|
||||
nodejs('node-lts') {
|
||||
sh 'gulp build'
|
||||
}
|
||||
|
||||
stage ("Stopping/Removing Docker")
|
||||
{
|
||||
sh "docker-compose down --rmi 'all'"
|
||||
sh 'docker login --username $CREDS_USR --password $CREDS_PSW $REGISTRY'
|
||||
sh 'docker-compose build --parallel'
|
||||
sh 'docker-compose push'
|
||||
}
|
||||
}
|
||||
stage('Deploy') {
|
||||
when { anyOf {
|
||||
branch 'test'
|
||||
branch 'master'
|
||||
}}
|
||||
steps {
|
||||
sh "docker stack deploy --with-registry-auth --compose-file docker-compose.yml ${env.STACK_NAME}"
|
||||
}
|
||||
}
|
||||
stage('Database') {
|
||||
when { anyOf {
|
||||
branch 'test'
|
||||
branch 'master'
|
||||
}}
|
||||
steps {
|
||||
configFileProvider([
|
||||
configFile(fileId: "config.${env.NODE_ENV}.ini",
|
||||
variable: 'MYSQL_CONFIG')
|
||||
]) {
|
||||
sh 'cp "$MYSQL_CONFIG" db/config.$NODE_ENV.ini'
|
||||
}
|
||||
|
||||
stage ("Generar dockers")
|
||||
{
|
||||
sh "docker-compose up -d --build"
|
||||
sh 'db/import-changes.sh -f $NODE_ENV'
|
||||
}
|
||||
}
|
||||
}
|
||||
post {
|
||||
always {
|
||||
script {
|
||||
if (!['master', 'test'].contains(env.BRANCH_NAME)) {
|
||||
try {
|
||||
junit 'junitresults.xml'
|
||||
junit 'junit.xml'
|
||||
} catch (e) {
|
||||
echo e.toString()
|
||||
}
|
||||
}
|
||||
|
||||
if (!env.COMMITTER_EMAIL) return
|
||||
try {
|
||||
mail(
|
||||
to: env.COMMITTER_EMAIL,
|
||||
subject: "Pipeline: ${env.JOB_NAME} (${env.BUILD_NUMBER}): ${currentBuild.currentResult}",
|
||||
body: "Check status at ${env.BUILD_URL}"
|
||||
)
|
||||
} catch (e) {
|
||||
echo e.toString()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
29
README.md
29
README.md
|
@ -8,13 +8,24 @@ Salix is also the scientific name of a beautifull tree! :)
|
|||
|
||||
Required applications.
|
||||
|
||||
* Node.js = 8.9.4
|
||||
* NGINX
|
||||
* Visual Studio Code
|
||||
* Node.js = 10.15.3 LTS
|
||||
* Docker
|
||||
|
||||
In Visual Studio Code we use the ESLint extension. Open Visual Studio Code, press Ctrl+P and paste the following command.
|
||||
```
|
||||
ext install dbaeumer.vscode-eslint
|
||||
```
|
||||
|
||||
You will need to install globally the following items.
|
||||
```
|
||||
$ npm install -g karma-cli gulp webpack nodemon
|
||||
# npm install -g jest gulp-cli nodemon
|
||||
```
|
||||
## Linux Only Prerequisites
|
||||
|
||||
Your user must be on the docker group to use it so you will need to run this command:
|
||||
```
|
||||
$ sudo usermod -a -G docker yourusername
|
||||
```
|
||||
|
||||
## Getting Started // Installing
|
||||
|
@ -32,12 +43,6 @@ 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
|
||||
|
@ -47,12 +52,12 @@ $ gulp docker
|
|||
|
||||
For client-side unit tests run from project's root.
|
||||
```
|
||||
$ karma start
|
||||
$ jest
|
||||
```
|
||||
|
||||
For server-side unit tests run from project's root.
|
||||
```
|
||||
$ npm run test
|
||||
$ gulp backTest
|
||||
```
|
||||
|
||||
For end-to-end tests run from project's root.
|
||||
|
@ -68,6 +73,6 @@ $ gulp e2e
|
|||
* [loopback](https://loopback.io/)
|
||||
* [docker](https://www.docker.com/)
|
||||
* [gulp.js](https://gulpjs.com/)
|
||||
* [Karma](https://karma-runner.github.io/)
|
||||
* [jest](https://jestjs.io/)
|
||||
* [Jasmine](https://jasmine.github.io/)
|
||||
* [Nightmare](http://www.nightmarejs.org/)
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = {
|
||||
presets: [
|
||||
'@babel/preset-env',
|
||||
],
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('acl', {
|
||||
description: 'Get the user information and permissions',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/acl`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.acl = async function(ctx) {
|
||||
let userId = ctx.req.accessToken.userId;
|
||||
let models = Self.app.models;
|
||||
|
||||
let user = await models.Account.findById(userId, {
|
||||
fields: ['id', 'name', 'nickname', 'email']
|
||||
});
|
||||
|
||||
let roles = await models.RoleMapping.find({
|
||||
fields: ['roleId'],
|
||||
where: {
|
||||
principalId: userId,
|
||||
principalType: 'USER'
|
||||
},
|
||||
include: [{
|
||||
relation: 'role',
|
||||
scope: {
|
||||
fields: ['name']
|
||||
}
|
||||
}]
|
||||
});
|
||||
|
||||
return {roles, user};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,67 @@
|
|||
const md5 = require('md5');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethod('login', {
|
||||
description: 'Login a user with username/email and password',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'user',
|
||||
type: 'String',
|
||||
description: 'The user name or email',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'password',
|
||||
type: 'String',
|
||||
description: 'The user name or email'
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/login`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.login = async function(user, password) {
|
||||
let token;
|
||||
let usesEmail = user.indexOf('@') !== -1;
|
||||
let User = Self.app.models.User;
|
||||
|
||||
let loginInfo = {password};
|
||||
|
||||
if (usesEmail)
|
||||
loginInfo.email = user;
|
||||
else
|
||||
loginInfo.username = user;
|
||||
|
||||
try {
|
||||
token = await User.login(loginInfo, 'user');
|
||||
} catch (err) {
|
||||
if (err.code != 'LOGIN_FAILED' || usesEmail)
|
||||
throw err;
|
||||
|
||||
let filter = {where: {name: user}};
|
||||
let instance = await Self.findOne(filter);
|
||||
|
||||
if (!instance || instance.password !== md5(password || ''))
|
||||
throw err;
|
||||
|
||||
let where = {id: instance.id};
|
||||
let userData = {
|
||||
id: instance.id,
|
||||
username: user,
|
||||
password: password,
|
||||
email: instance.email,
|
||||
created: instance.created,
|
||||
updated: instance.updated
|
||||
};
|
||||
await User.upsertWithWhere(where, userData);
|
||||
token = await User.login(loginInfo, 'user');
|
||||
}
|
||||
|
||||
return {token: token.id};
|
||||
};
|
||||
};
|
|
@ -0,0 +1,25 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('logout', {
|
||||
description: 'Logout a user with access token',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/logout`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.logout = async function(ctx) {
|
||||
await Self.app.models.User.logout(ctx.req.accessToken.id);
|
||||
return true;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('account login()', () => {
|
||||
describe('when credentials are correct', () => {
|
||||
it('should return the token', async() => {
|
||||
let response = await app.models.Account.login('employee', 'nightmare');
|
||||
|
||||
expect(response.token).toBeDefined();
|
||||
});
|
||||
|
||||
it('should return the token if the user doesnt exist but the client does', async() => {
|
||||
let response = await app.models.Account.login('PetterParker', 'nightmare');
|
||||
|
||||
expect(response.token).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('when credentials are incorrect', () => {
|
||||
it('should throw a 401 error', async() => {
|
||||
let error;
|
||||
|
||||
try {
|
||||
await app.models.Account.login('IDontExist', 'TotallyWrongPassword');
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.statusCode).toBe(401);
|
||||
expect(error.code).toBe('LOGIN_FAILED');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,42 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('account logout()', () => {
|
||||
it('should logout and remove token after valid login', async() => {
|
||||
let loginResponse = await app.models.Account.login('employee', 'nightmare');
|
||||
let accessToken = await app.models.AccessToken.findById(loginResponse.token);
|
||||
let ctx = {req: {accessToken: accessToken}};
|
||||
|
||||
let response = await app.models.Account.logout(ctx);
|
||||
let afterToken = await app.models.AccessToken.findById(loginResponse.token);
|
||||
|
||||
expect(response).toBeTruthy();
|
||||
expect(afterToken).toBeNull();
|
||||
});
|
||||
|
||||
it('should throw a 401 error when token is invalid', async() => {
|
||||
let error;
|
||||
let ctx = {req: {accessToken: {id: 'invalidToken'}}};
|
||||
|
||||
try {
|
||||
response = await app.models.Account.logout(ctx);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
expect(error.statusCode).toBe(401);
|
||||
});
|
||||
|
||||
it('should throw an error when no token is passed', async() => {
|
||||
let error;
|
||||
let ctx = {req: {accessToken: null}};
|
||||
|
||||
try {
|
||||
response = await app.models.Account.logout(ctx);
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('validateToken', {
|
||||
description: 'Validates the current logged user token',
|
||||
returns: {
|
||||
type: 'Boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/validateToken`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.validateToken = async function() {
|
||||
return true;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
const request = require('request-promise-native');
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('sendMessage', {
|
||||
description: 'Send a RocketChat message',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'to',
|
||||
type: 'String',
|
||||
required: true,
|
||||
description: 'user (@) or channel (#) to send the message'
|
||||
}, {
|
||||
arg: 'message',
|
||||
type: 'String',
|
||||
required: true,
|
||||
description: 'The message'
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/sendMessage`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.sendMessage = async(ctx, to, message) => {
|
||||
const models = Self.app.models;
|
||||
const accessToken = ctx.req.accessToken;
|
||||
const sender = await models.Account.findById(accessToken.userId);
|
||||
const recipient = to.replace('@', '');
|
||||
|
||||
if (sender.name != recipient)
|
||||
return sendMessage(to, `@${sender.name}: ${message}`);
|
||||
};
|
||||
|
||||
async function sendMessage(name, message) {
|
||||
const models = Self.app.models;
|
||||
const chatConfig = await models.ChatConfig.findOne();
|
||||
|
||||
if (!Self.token)
|
||||
Self.token = await login();
|
||||
|
||||
const uri = `${chatConfig.uri}/chat.postMessage`;
|
||||
return send(uri, {
|
||||
'channel': name,
|
||||
'text': message
|
||||
}).catch(async error => {
|
||||
if (error.statusCode === 401 && !Self.loginAttempted) {
|
||||
Self.token = await login();
|
||||
Self.loginAttempted = true;
|
||||
|
||||
return sendMessage(name, message);
|
||||
}
|
||||
|
||||
throw new Error(error.message);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a rocketchat token
|
||||
* @return {Object} userId and authToken
|
||||
*/
|
||||
async function login() {
|
||||
const models = Self.app.models;
|
||||
const chatConfig = await models.ChatConfig.findOne();
|
||||
const uri = `${chatConfig.uri}/login`;
|
||||
return send(uri, {
|
||||
user: chatConfig.user,
|
||||
password: chatConfig.password
|
||||
}).then(res => res.data);
|
||||
}
|
||||
|
||||
function send(uri, body) {
|
||||
if (process.env.NODE_ENV !== 'production') {
|
||||
return new Promise(resolve => {
|
||||
return resolve({statusCode: 200, message: 'Fake notification sent'});
|
||||
});
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
uri: uri,
|
||||
body: body,
|
||||
headers: {'content-type': 'application/json'},
|
||||
json: true
|
||||
};
|
||||
|
||||
if (Self.token) {
|
||||
options.headers['X-Auth-Token'] = Self.token.authToken;
|
||||
options.headers['X-User-Id'] = Self.token.userId;
|
||||
}
|
||||
|
||||
return request(options);
|
||||
}
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('chat sendMessage()', () => {
|
||||
it('should return a "Fake notification sent" as response', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 1}}};
|
||||
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
|
||||
|
||||
expect(response.statusCode).toEqual(200);
|
||||
expect(response.message).toEqual('Fake notification sent');
|
||||
});
|
||||
|
||||
it('should not return a response', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 18}}};
|
||||
let response = await app.models.Chat.sendMessage(ctx, '@salesPerson', 'I changed something');
|
||||
|
||||
expect(response).toBeUndefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,62 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('downloadFile', {
|
||||
description: 'Download a document',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: [
|
||||
{
|
||||
arg: 'body',
|
||||
type: 'file',
|
||||
root: true
|
||||
}, {
|
||||
arg: 'Content-Type',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}, {
|
||||
arg: 'Content-Disposition',
|
||||
type: 'String',
|
||||
http: {target: 'header'}
|
||||
}
|
||||
],
|
||||
http: {
|
||||
path: `/:id/downloadFile`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.downloadFile = async function(ctx, id) {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const dms = await Self.findById(id);
|
||||
|
||||
const hasReadRole = await models.DmsType.hasReadRole(ctx, dms.dmsTypeFk);
|
||||
if (!hasReadRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
const pathHash = storageConnector.getPathHash(dms.id);
|
||||
try {
|
||||
await models.Container.getFile(pathHash, dms.file);
|
||||
} catch (e) {
|
||||
if (e.code != 'ENOENT')
|
||||
throw e;
|
||||
|
||||
const error = new UserError(`File doesn't exists`);
|
||||
error.statusCode = 404;
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const stream = models.Container.downloadStream(pathHash, dms.file);
|
||||
|
||||
return [stream, dms.contentType, `filename="${dms.file}"`];
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('removeFile', {
|
||||
description: 'Makes a logical delete moving a file to a trash folder',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/removeFile`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.removeFile = async(ctx, id) => {
|
||||
const models = Self.app.models;
|
||||
const trashDmsType = await models.DmsType.findOne({where: {code: 'trash'}});
|
||||
const dms = await models.Dms.findById(id);
|
||||
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dms.dmsTypeFk);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
return dms.updateAttribute('dmsTypeFk', trashDmsType.id);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('dms downloadFile()', () => {
|
||||
let dmsId = 1;
|
||||
it('should return a response for an employee with text content-type', async() => {
|
||||
let workerId = 107;
|
||||
let ctx = {req: {accessToken: {userId: workerId}}};
|
||||
const result = await app.models.Dms.downloadFile(ctx, dmsId);
|
||||
|
||||
expect(result[1]).toEqual('text/plain');
|
||||
});
|
||||
|
||||
it(`should return an error for a user without enough privileges`, async() => {
|
||||
let clientId = 101;
|
||||
let ctx = {req: {accessToken: {userId: clientId}}};
|
||||
|
||||
let error;
|
||||
await app.models.Dms.downloadFile(ctx, dmsId).catch(e => {
|
||||
error = e;
|
||||
}).finally(() => {
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,19 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('dms removeFile()', () => {
|
||||
let dmsId = 1;
|
||||
|
||||
it(`should return an error for a user without enough privileges`, async() => {
|
||||
let clientId = 101;
|
||||
let ctx = {req: {accessToken: {userId: clientId}}};
|
||||
|
||||
let error;
|
||||
await app.models.Dms.removeFile(ctx, dmsId).catch(e => {
|
||||
error = e;
|
||||
}).finally(() => {
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,22 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('dms updateFile()', () => {
|
||||
it(`should return an error for a user without enough privileges`, async() => {
|
||||
let clientId = 101;
|
||||
let companyId = 442;
|
||||
let warehouseId = 1;
|
||||
let dmsTypeId = 14;
|
||||
|
||||
let dmsId = 1;
|
||||
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: dmsTypeId}};
|
||||
|
||||
let error;
|
||||
await app.models.Dms.updateFile(ctx, dmsId, warehouseId, companyId, dmsTypeId).catch(e => {
|
||||
error = e;
|
||||
}).finally(() => {
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,18 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('dms uploadFile()', () => {
|
||||
it(`should return an error for a user without enough privileges`, async() => {
|
||||
let clientId = 101;
|
||||
let ticketDmsTypeId = 14;
|
||||
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}};
|
||||
|
||||
let error;
|
||||
await app.models.Dms.uploadFile(ctx).catch(e => {
|
||||
error = e;
|
||||
}).finally(() => {
|
||||
expect(error.message).toEqual(`You don't have enough privileges`);
|
||||
});
|
||||
|
||||
expect(error).toBeDefined();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,150 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('updateFile', {
|
||||
description: 'updates a file properties or file',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
description: 'The document id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: 'The warehouse id'
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: 'The company id'
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: 'The dms type id'
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String'
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String'
|
||||
}, {
|
||||
arg: 'hasFileAttached',
|
||||
type: 'Boolean',
|
||||
description: 'True if has an attached file'
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/updateFile`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.updateFile = async(ctx, id, warehouseId, companyId,
|
||||
dmsTypeId, reference, description, hasFileAttached, options) => {
|
||||
const models = Self.app.models;
|
||||
|
||||
let tx;
|
||||
let myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, dmsTypeId);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
const dms = await Self.findById(id, null, myOptions);
|
||||
await dms.updateAttributes({
|
||||
dmsTypeFk: dmsTypeId,
|
||||
companyFk: companyId,
|
||||
warehouseFk: warehouseId,
|
||||
reference: reference,
|
||||
description: description
|
||||
}, myOptions);
|
||||
|
||||
if (hasFileAttached)
|
||||
await uploadNewFile(ctx, dms, myOptions);
|
||||
|
||||
if (tx) await tx.commit();
|
||||
return dms;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
async function uploadNewFile(ctx, dms, myOptions) {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const fileOptions = {};
|
||||
|
||||
const tempContainer = await getContainer('temp');
|
||||
const makeUpload = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const keys = Object.values(makeUpload.files);
|
||||
const files = keys.map(file => file[0]);
|
||||
const file = files[0];
|
||||
|
||||
if (file) {
|
||||
const oldExtension = storageConnector.getFileExtension(dms.file);
|
||||
const newExtension = storageConnector.getFileExtension(file.name);
|
||||
const fileName = `${dms.id}.${newExtension}`;
|
||||
|
||||
try {
|
||||
if (oldExtension != newExtension) {
|
||||
const pathHash = storageConnector.getPathHash(dms.id);
|
||||
|
||||
await models.Container.removeFile(pathHash, dms.file);
|
||||
}
|
||||
} catch (err) {}
|
||||
|
||||
const updatedDms = await dms.updateAttributes({
|
||||
contentType: file.type,
|
||||
file: fileName
|
||||
}, myOptions);
|
||||
|
||||
const pathHash = storageConnector.getPathHash(updatedDms.id);
|
||||
const container = await getContainer(pathHash);
|
||||
|
||||
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
|
||||
const destinationPath = `${container.client.root}/${pathHash}/${updatedDms.file}`;
|
||||
|
||||
fs.rename(originPath, destinationPath);
|
||||
|
||||
return updatedDms;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
*
|
||||
* @param {String} name Container name
|
||||
* @return {Object} Container instance
|
||||
*/
|
||||
async function getContainer(name) {
|
||||
const models = Self.app.models;
|
||||
let container;
|
||||
try {
|
||||
container = await models.Container.getContainer(name);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
container = await models.Container.createContainer({
|
||||
name: name
|
||||
});
|
||||
} else throw err;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,147 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
const fs = require('fs-extra');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('uploadFile', {
|
||||
description: 'Uploads a file and inserts into dms model',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'warehouseId',
|
||||
type: 'Number',
|
||||
description: 'The warehouse id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'companyId',
|
||||
type: 'Number',
|
||||
description: 'The company id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'dmsTypeId',
|
||||
type: 'Number',
|
||||
description: 'The dms type id',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'reference',
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'description',
|
||||
type: 'String',
|
||||
required: true
|
||||
}, {
|
||||
arg: 'hasFile',
|
||||
type: 'Boolean',
|
||||
description: 'True if has an attached file',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/uploadFile`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.uploadFile = async(ctx, options) => {
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const models = Self.app.models;
|
||||
const fileOptions = {};
|
||||
const args = ctx.args;
|
||||
|
||||
let tx;
|
||||
let myOptions = {};
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
|
||||
if (!hasWriteRole)
|
||||
throw new UserError(`You don't have enough privileges`);
|
||||
|
||||
// Upload file to temporary path
|
||||
const tempContainer = await getContainer('temp');
|
||||
const uploaded = await models.Container.upload(tempContainer.name, ctx.req, ctx.result, fileOptions);
|
||||
const files = Object.values(uploaded.files).map(file => {
|
||||
return file[0];
|
||||
});
|
||||
|
||||
const addedDms = [];
|
||||
for (const file of files) {
|
||||
const newDms = await createDms(ctx, file, myOptions);
|
||||
const pathHash = storageConnector.getPathHash(newDms.id);
|
||||
const container = await getContainer(pathHash);
|
||||
|
||||
const originPath = `${tempContainer.client.root}/${tempContainer.name}/${file.name}`;
|
||||
const destinationPath = `${container.client.root}/${pathHash}/${newDms.file}`;
|
||||
|
||||
await fs.rename(originPath, destinationPath);
|
||||
|
||||
addedDms.push(newDms);
|
||||
}
|
||||
|
||||
if (tx) await tx.commit();
|
||||
return addedDms;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
async function createDms(ctx, file, myOptions) {
|
||||
const models = Self.app.models;
|
||||
const storageConnector = Self.app.dataSources.storage.connector;
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
const myWorker = await models.Worker.findOne({where: {userFk: myUserId}}, myOptions);
|
||||
const args = ctx.args;
|
||||
|
||||
const newDms = await Self.create({
|
||||
workerFk: myWorker.id,
|
||||
dmsTypeFk: args.dmsTypeId,
|
||||
companyFk: args.companyId,
|
||||
warehouseFk: args.warehouseId,
|
||||
reference: args.reference,
|
||||
description: args.description,
|
||||
contentType: file.type,
|
||||
hasFile: args.hasFile
|
||||
}, myOptions);
|
||||
|
||||
let fileName = file.name;
|
||||
const extension = storageConnector.getFileExtension(fileName);
|
||||
fileName = `${newDms.id}.${extension}`;
|
||||
|
||||
return newDms.updateAttribute('file', fileName, myOptions);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns a container instance
|
||||
* If doesn't exists creates a new one
|
||||
*
|
||||
* @param {String} name Container name
|
||||
* @return {Object} Container instance
|
||||
*/
|
||||
async function getContainer(name) {
|
||||
const models = Self.app.models;
|
||||
let container;
|
||||
try {
|
||||
container = await models.Container.getContainer(name);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
container = await models.Container.createContainer({
|
||||
name: name
|
||||
});
|
||||
} else throw err;
|
||||
}
|
||||
|
||||
return container;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,48 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('send', {
|
||||
description: 'Send message to user',
|
||||
accessType: 'WRITE',
|
||||
accepts: [{
|
||||
arg: 'data',
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'recipientFk, message',
|
||||
http: {source: 'body'}
|
||||
}, {
|
||||
arg: 'context',
|
||||
type: 'object',
|
||||
http: function(ctx) {
|
||||
return ctx;
|
||||
}
|
||||
}],
|
||||
returns: {
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:recipient/send`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.send = async(ctx, data, options) => {
|
||||
const accessToken = ctx.options && ctx.options.accessToken || ctx.req && ctx.req.accessToken;
|
||||
const userId = accessToken.userId;
|
||||
const models = Self.app.models;
|
||||
const sender = await models.Account.findById(userId, null, options);
|
||||
const recipient = await models.Account.findById(data.recipientFk, null, options);
|
||||
|
||||
await Self.create({
|
||||
sender: sender.name,
|
||||
recipient: recipient.name,
|
||||
message: data.message
|
||||
}, options);
|
||||
|
||||
return await models.MessageInbox.create({
|
||||
sender: sender.name,
|
||||
recipient: recipient.name,
|
||||
finalRecipient: recipient.name,
|
||||
message: data.message
|
||||
}, options);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('message send()', () => {
|
||||
it('should return a response containing the same message in params', async() => {
|
||||
let ctx = {req: {accessToken: {userId: 1}}};
|
||||
let params = {
|
||||
recipientFk: 1,
|
||||
message: 'I changed something'
|
||||
};
|
||||
let response = await app.models.Message.send(ctx, params, {transaction: 'You'});
|
||||
|
||||
expect(response.message).toEqual(params.message);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('getConfig', {
|
||||
description: 'returns the information from UserConfig model for the active user',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
}, {
|
||||
arg: 'tableCode',
|
||||
type: 'String',
|
||||
description: `Code of the table you ask its configuration`
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getConfig`,
|
||||
verb: 'get'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getConfig = async ctx => {
|
||||
let userView = await Self.app.models.UserConfigView.findOne({
|
||||
where: {tableCode: ctx.args.tableCode, userFk: ctx.req.accessToken.userId}
|
||||
});
|
||||
|
||||
return userView;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('save', {
|
||||
description: 'returns the information from UserConfig model for the active user',
|
||||
accepts: [{
|
||||
arg: 'config',
|
||||
type: 'Object',
|
||||
required: true,
|
||||
description: `Code of the table you ask its configuration`,
|
||||
http: {source: 'body'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/save`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.save = async(ctx, config) => {
|
||||
let userView = await Self.app.models.UserConfigView.findOne({
|
||||
where: {tableCode: config.tableCode, userFk: ctx.req.accessToken.userId}
|
||||
});
|
||||
|
||||
if (userView)
|
||||
return userView.updateAttributes(config);
|
||||
|
||||
config.userFk = ctx.req.accessToken.userId;
|
||||
|
||||
return await Self.app.models.UserConfigView.create(config);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('getUserConfig', {
|
||||
description: 'returns the information from UserConfig model for the active user',
|
||||
accepts: [],
|
||||
returns: {
|
||||
type: 'object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/getUserConfig`,
|
||||
verb: 'get'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getUserConfig = async ctx => {
|
||||
let userConfig = await Self.app.models.UserConfig.findOne({
|
||||
where: {userFk: ctx.req.accessToken.userId}
|
||||
});
|
||||
|
||||
if (!userConfig) {
|
||||
let newConfig = {
|
||||
warehouseFk: 1,
|
||||
companyFk: 442,
|
||||
userFk: ctx.req.accessToken.userId
|
||||
};
|
||||
|
||||
userConfig = await Self.app.models.UserConfig.create(newConfig);
|
||||
}
|
||||
return userConfig;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,28 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethodCtx('setUserConfig', {
|
||||
description: 'Change worker of tickets state',
|
||||
accepts: [{
|
||||
arg: 'params',
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'warehouseFk, companyFk',
|
||||
http: {source: 'body'}
|
||||
}],
|
||||
returns: {
|
||||
arg: 'response',
|
||||
type: 'object'
|
||||
},
|
||||
http: {
|
||||
path: `/setUserConfig`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.setUserConfig = async(ctx, params) => {
|
||||
let token = ctx.req.accessToken;
|
||||
let currentUserId = token && token.userId;
|
||||
params.userFk = currentUserId;
|
||||
|
||||
return await Self.app.models.UserConfig.upsertWithWhere({userFk: currentUserId}, params);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('userConfig getUserConfig()', () => {
|
||||
it(`should return the configuration data of a given user`, async() => {
|
||||
await app.models.UserConfig.getUserConfig({req: {accessToken: {userId: 9}}})
|
||||
.then(response => {
|
||||
expect(response.warehouseFk).toEqual(1);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,74 @@
|
|||
{
|
||||
"Account": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Bank": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Country": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Company": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Container": {
|
||||
"dataSource": "storage"
|
||||
},
|
||||
"Chat": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"ChatConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Delivery": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Message": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"MessageInbox": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Province": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Warehouse": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Sip": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserConfigView": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"EmailUser": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Dms": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"DmsType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Town": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"Postcode": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhoneType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserPhone": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"UserLog": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,90 @@
|
|||
const md5 = require('md5');
|
||||
|
||||
module.exports = Self => {
|
||||
require('../methods/account/login')(Self);
|
||||
require('../methods/account/logout')(Self);
|
||||
require('../methods/account/acl')(Self);
|
||||
require('../methods/account/validate-token')(Self);
|
||||
|
||||
// Validations
|
||||
|
||||
Self.validatesUniquenessOf('name', {
|
||||
message: `A client with that Web User name already exists`
|
||||
});
|
||||
|
||||
Self.observe('before save', (ctx, next) => {
|
||||
if (ctx.currentInstance && ctx.currentInstance.id && ctx.data && ctx.data.password)
|
||||
ctx.data.password = md5(ctx.data.password);
|
||||
|
||||
next();
|
||||
});
|
||||
|
||||
Self.remoteMethod('getCurrentUserData', {
|
||||
description: 'Gets the current user data',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'ctx',
|
||||
type: 'Object',
|
||||
http: {source: 'context'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
verb: 'GET',
|
||||
path: '/getCurrentUserData'
|
||||
}
|
||||
});
|
||||
|
||||
Self.getCurrentUserData = async function(ctx) {
|
||||
let userId = ctx.req.accessToken.userId;
|
||||
|
||||
let account = await Self.findById(userId, {
|
||||
fields: ['id', 'name', 'nickname']
|
||||
});
|
||||
|
||||
let worker = await Self.app.models.Worker.findOne({
|
||||
fields: ['id'],
|
||||
where: {userFk: userId}
|
||||
});
|
||||
|
||||
return Object.assign(account, {workerId: worker.id});
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if user has a role.
|
||||
*
|
||||
* @param {Integer} userId The user id
|
||||
* @param {String} name The role name
|
||||
* @param {Object} options Options
|
||||
* @return {Boolean} %true if user has the role, %false otherwise
|
||||
*/
|
||||
Self.hasRole = async function(userId, name, options) {
|
||||
let roles = await Self.getRoles(userId, options);
|
||||
return roles.some(role => role == name);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get all user roles.
|
||||
*
|
||||
* @param {Integer} userId The user id
|
||||
* @param {Object} options Options
|
||||
* @return {Object} User role list
|
||||
*/
|
||||
Self.getRoles = async(userId, options) => {
|
||||
let result = await Self.rawSql(
|
||||
`SELECT r.name
|
||||
FROM account.user u
|
||||
JOIN account.roleRole rr ON rr.role = u.role
|
||||
JOIN account.role r ON r.id = rr.inheritsFrom
|
||||
WHERE u.id = ?`, [userId], options);
|
||||
|
||||
let roles = [];
|
||||
for (role of result)
|
||||
roles.push(role.name);
|
||||
|
||||
return roles;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"name": "Account",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.user"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number",
|
||||
"required": true
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"roleFk": {
|
||||
"type": "number",
|
||||
"mysql": {
|
||||
"columnName": "role"
|
||||
}
|
||||
},
|
||||
"nickname": {
|
||||
"type": "string"
|
||||
},
|
||||
"password": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"active": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"email": {
|
||||
"type": "string"
|
||||
},
|
||||
"created": {
|
||||
"type": "date"
|
||||
},
|
||||
"updated": {
|
||||
"type": "date"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"role": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "roleFk"
|
||||
},
|
||||
"emailUser": {
|
||||
"type": "hasOne",
|
||||
"model": "EmailUser",
|
||||
"foreignKey": "userFk"
|
||||
},
|
||||
"worker": {
|
||||
"type": "hasOne",
|
||||
"model": "Worker",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"property": "login",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "logout",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
},
|
||||
{
|
||||
"property": "validateToken",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$authenticated",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
{
|
||||
"name": "Bank",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "bank"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"bank": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"account": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"cash": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"entityFk": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"isActive": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"currencyFk": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"name": "ChatConfig",
|
||||
"description": "Chat API config",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "chatConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"uri": {
|
||||
"type": "String"
|
||||
},
|
||||
"user": {
|
||||
"type": "String"
|
||||
},
|
||||
"password": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/chat/sendMessage')(Self);
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"name": "Chat",
|
||||
"base": "VnModel",
|
||||
"acls": [{
|
||||
"property": "validations",
|
||||
"accessType": "EXECUTE",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
{
|
||||
"name": "Company",
|
||||
"description": "Companies",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "company"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"code": {
|
||||
"type": "String"
|
||||
},
|
||||
"expired": {
|
||||
"type": "date"
|
||||
}
|
||||
},
|
||||
|
||||
"scope": {
|
||||
"where" :{
|
||||
"expired": null
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"name": "Container",
|
||||
"base": "VnModel",
|
||||
"idInjection": true,
|
||||
"options": {
|
||||
"validateUpsert": true
|
||||
},
|
||||
"properties": {},
|
||||
"validations": [],
|
||||
"relations": {},
|
||||
"acls": [],
|
||||
"methods": []
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "Country",
|
||||
"description": "Worldwide countries",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "country"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"country": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"code": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"currency": {
|
||||
"type": "belongsTo",
|
||||
"model": "Currency",
|
||||
"foreignKey": "currencyFk"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/dms/downloadFile')(Self);
|
||||
require('../methods/dms/uploadFile')(Self);
|
||||
require('../methods/dms/removeFile')(Self);
|
||||
require('../methods/dms/updateFile')(Self);
|
||||
};
|
|
@ -0,0 +1,61 @@
|
|||
|
||||
{
|
||||
"name": "Dms",
|
||||
"description": "Documental Managment system",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "dms"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"file": {
|
||||
"type": "string"
|
||||
},
|
||||
"contentType": {
|
||||
"type": "string"
|
||||
},
|
||||
"reference": {
|
||||
"type": "string"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"hardCopyNumber": {
|
||||
"type": "Number"
|
||||
},
|
||||
"hasFile": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"created": {
|
||||
"type": "Date"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"dmsType": {
|
||||
"type": "belongsTo",
|
||||
"model": "DmsType",
|
||||
"foreignKey": "dmsTypeFk"
|
||||
},
|
||||
"worker": {
|
||||
"type": "belongsTo",
|
||||
"model": "Worker",
|
||||
"foreignKey": "workerFk"
|
||||
},
|
||||
"warehouse": {
|
||||
"type": "belongsTo",
|
||||
"model": "Warehouse",
|
||||
"foreignKey": "warehouseFk"
|
||||
},
|
||||
"company": {
|
||||
"type": "belongsTo",
|
||||
"model": "Company",
|
||||
"foreignKey": "companyFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
module.exports = Self => {
|
||||
/**
|
||||
* Checks if current user has
|
||||
* read privileges over a dms
|
||||
*
|
||||
* @param {Object} ctx - Request context
|
||||
* @param {Interger} id - DmsType id
|
||||
* @param {Object} options - Query options
|
||||
* @return {Boolean} True for user with read privileges
|
||||
*/
|
||||
Self.hasReadRole = async(ctx, id, options) => {
|
||||
const models = Self.app.models;
|
||||
const dmsType = await models.DmsType.findById(id, {
|
||||
include: {
|
||||
relation: 'readRole'
|
||||
}
|
||||
}, options);
|
||||
|
||||
return await hasRole(ctx, dmsType, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* write privileges over a dms
|
||||
*
|
||||
* @param {Object} ctx - Request context
|
||||
* @param {Interger} id - DmsType id
|
||||
* @param {Object} options - Query options
|
||||
* @return {Boolean} True for user with write privileges
|
||||
*/
|
||||
Self.hasWriteRole = async(ctx, id, options) => {
|
||||
const models = Self.app.models;
|
||||
const dmsType = await models.DmsType.findById(id, {
|
||||
include: {
|
||||
relation: 'writeRole'
|
||||
}
|
||||
}, options);
|
||||
|
||||
return await hasRole(ctx, dmsType, options);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if current user has
|
||||
* read or write privileges
|
||||
* @param {Object} ctx - Context
|
||||
* @param {Object} dmsType - Dms type [read/write]
|
||||
* @param {Object} options - Query options
|
||||
*/
|
||||
async function hasRole(ctx, dmsType, options) {
|
||||
const models = Self.app.models;
|
||||
const myUserId = ctx.req.accessToken.userId;
|
||||
|
||||
const readRole = dmsType.readRole() && dmsType.readRole().name;
|
||||
const writeRole = dmsType.writeRole() && dmsType.writeRole().name;
|
||||
const requiredRole = readRole || writeRole;
|
||||
|
||||
const hasRequiredRole = await models.Account.hasRole(myUserId, requiredRole, options);
|
||||
const isRoot = await models.Account.hasRole(myUserId, 'root', options);
|
||||
|
||||
if (isRoot || hasRequiredRole)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
|
@ -0,0 +1,47 @@
|
|||
{
|
||||
"name": "DmsType",
|
||||
"description": "Documental Managment system types",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "dmsType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
},
|
||||
"code": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"readRole": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "readRoleFk"
|
||||
},
|
||||
"writeRole": {
|
||||
"type": "belongsTo",
|
||||
"model": "Role",
|
||||
"foreignKey": "writeRoleFk"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}]
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
{
|
||||
"name": "EmailUser",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "account.emailUser"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"userFk": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"email": {
|
||||
"type": "string",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
{
|
||||
"name": "MessageInbox",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "messageInbox"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"sender": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"recipient": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"finalRecipient": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"message": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"remitter": {
|
||||
"type": "belongsTo",
|
||||
"model": "User",
|
||||
"foreignKey": "sender"
|
||||
},
|
||||
"receptor": {
|
||||
"type": "belongsTo",
|
||||
"model": "User",
|
||||
"foreignKey": "recipient"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/message/send')(Self);
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "Message",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "message"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "Identifier"
|
||||
},
|
||||
"sender": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"recipient": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"message": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"remitter": {
|
||||
"type": "belongsTo",
|
||||
"model": "User",
|
||||
"foreignKey": "sender"
|
||||
},
|
||||
"receptor": {
|
||||
"type": "belongsTo",
|
||||
"model": "User",
|
||||
"foreignKey": "recipient"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
return new UserError(`This postcode already exists`);
|
||||
return err;
|
||||
});
|
||||
};
|
|
@ -0,0 +1,50 @@
|
|||
{
|
||||
"name": "Postcode",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "postCode"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"code": {
|
||||
"id": true,
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"town": {
|
||||
"type": "belongsTo",
|
||||
"model": "Town",
|
||||
"foreignKey": "townFk"
|
||||
},
|
||||
"geo": {
|
||||
"type": "belongsTo",
|
||||
"model": "ZoneGeo",
|
||||
"foreignKey": "geoFk"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}],
|
||||
"scopes": {
|
||||
"location": {
|
||||
"include": {
|
||||
"relation": "town",
|
||||
"scope": {
|
||||
"include": {
|
||||
"relation": "province",
|
||||
"scope": {
|
||||
"include": {
|
||||
"relation": "country"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
module.exports = Self => {
|
||||
Self.validatesUniquenessOf('extension', {
|
||||
message: `The extension must be unique`
|
||||
});
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
{
|
||||
"name": "Sip",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "pbx.sip"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"userFk": {
|
||||
"type": "Number",
|
||||
"id": true,
|
||||
"description": "The user id",
|
||||
"mysql": {
|
||||
"columnName": "user_id"
|
||||
}
|
||||
},
|
||||
"extension": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "user_id"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('loopback model Account', () => {
|
||||
it('should return true if the user has the given role', async() => {
|
||||
let result = await app.models.Account.hasRole(1, 'employee');
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
|
||||
it('should return false if the user doesnt have the given role', async() => {
|
||||
let result = await app.models.Account.hasRole(1, 'administrator');
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('loopback model Company', () => {
|
||||
it('should check that the company FTH doesnt exists', async() => {
|
||||
let result = await app.models.Company.findOne({where: {code: 'FTH'}});
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,57 @@
|
|||
{
|
||||
"name": "Town",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "town"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"name": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"province": {
|
||||
"type": "belongsTo",
|
||||
"model": "Province",
|
||||
"foreignKey": "provinceFk"
|
||||
},
|
||||
"postcodes": {
|
||||
"type": "hasMany",
|
||||
"model": "Postcode",
|
||||
"foreignKey": "townFk"
|
||||
},
|
||||
"geo": {
|
||||
"type": "belongsTo",
|
||||
"model": "ZoneGeo",
|
||||
"foreignKey": "geoFk"
|
||||
}
|
||||
},
|
||||
"acls": [{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}],
|
||||
"scopes": {
|
||||
"location": {
|
||||
"include": [{
|
||||
"relation": "postcodes"
|
||||
},
|
||||
{
|
||||
"relation": "province",
|
||||
"scope": {
|
||||
"include": {
|
||||
"relation": "country"
|
||||
}
|
||||
}
|
||||
}],
|
||||
"fields": ["id", "name", "provinceFk"]
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/user-config-view/getConfig')(Self);
|
||||
require('../methods/user-config-view/save')(Self);
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
{
|
||||
"name": "UserConfigView",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.userConfigView"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"userFk": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"tableCode": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"configuration": {
|
||||
"type": "Object"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/user-config/setUserConfig')(Self);
|
||||
require('../methods/user-config/getUserConfig')(Self);
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
{
|
||||
"name": "UserConfig",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "userConfig"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"userFk": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"warehouseFk": {
|
||||
"type": "Number"
|
||||
},
|
||||
"companyFk": {
|
||||
"type": "Number"
|
||||
},
|
||||
"created": {
|
||||
"type": "Date"
|
||||
},
|
||||
"updated": {
|
||||
"type": "Date"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"warehouse": {
|
||||
"type": "belongsTo",
|
||||
"model": "Warehouse",
|
||||
"foreignKey": "warehouseFk"
|
||||
},
|
||||
"company": {
|
||||
"type": "belongsTo",
|
||||
"model": "Company",
|
||||
"foreignKey": "companyFk"
|
||||
},
|
||||
"account": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
{
|
||||
"name": "UserLog",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "userLog"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"forceId": false
|
||||
},
|
||||
"originFk": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"userFk": {
|
||||
"type": "Number"
|
||||
},
|
||||
"action": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
},
|
||||
"changedModel": {
|
||||
"type": "String"
|
||||
},
|
||||
"oldInstance": {
|
||||
"type": "Object"
|
||||
},
|
||||
"newInstance": {
|
||||
"type": "Object"
|
||||
},
|
||||
"creationDate": {
|
||||
"type": "Date"
|
||||
},
|
||||
"changedModelId": {
|
||||
"type": "Number"
|
||||
},
|
||||
"changedModelValue": {
|
||||
"type": "String"
|
||||
},
|
||||
"description": {
|
||||
"type": "String"
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
}
|
||||
},
|
||||
"scope": {
|
||||
"order": ["creationDate DESC", "id DESC"]
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"name": "UserPhoneType",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "userPhoneType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"code": {
|
||||
"id": true,
|
||||
"type": "String"
|
||||
},
|
||||
"description": {
|
||||
"type": "String"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.rewriteDbError(function(err) {
|
||||
if (err.code === 'ER_DUP_ENTRY')
|
||||
return new UserError(`This phone already exists`);
|
||||
return err;
|
||||
});
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"name": "UserPhone",
|
||||
"base": "Loggable",
|
||||
"log": {
|
||||
"model":"UserLog",
|
||||
"relation": "user"
|
||||
},
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "userPhone"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number"
|
||||
},
|
||||
"phone": {
|
||||
"type": "Number",
|
||||
"required": true
|
||||
},
|
||||
"typeFk": {
|
||||
"type": "String",
|
||||
"required": true
|
||||
}
|
||||
},
|
||||
"relations": {
|
||||
"user": {
|
||||
"type": "belongsTo",
|
||||
"model": "Account",
|
||||
"foreignKey": "userFk"
|
||||
},
|
||||
"type": {
|
||||
"type": "belongsTo",
|
||||
"model": "UserPhoneType",
|
||||
"foreignKey": "typeFk"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "user",
|
||||
"base": "User",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "salix.user"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"forceId": false
|
||||
},
|
||||
"username":{
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
"name": "Warehouse",
|
||||
"description": "Warehouses from where orders are sent",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "warehouse"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "Number",
|
||||
"forceId": false
|
||||
},
|
||||
"name": {
|
||||
"type": "String"
|
||||
},
|
||||
"isInventory": {
|
||||
"type": "Number"
|
||||
},
|
||||
"isManaged":{
|
||||
"type": "boolean"
|
||||
},
|
||||
"hasStowaway":{
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"acls": [
|
||||
{
|
||||
"accessType": "READ",
|
||||
"principalType": "ROLE",
|
||||
"principalId": "$everyone",
|
||||
"permission": "ALLOW"
|
||||
}
|
||||
],
|
||||
"scope" : {"where": {"isForTicket": {"neq": 0}}}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
apps:
|
||||
- script: ./loopback/server/server.js
|
||||
name: salix-back
|
||||
instances: 1
|
||||
max_restarts: 3
|
||||
restart_delay: 15000
|
|
@ -0,0 +1,39 @@
|
|||
require('require-yaml');
|
||||
|
||||
process.on('warning', warning => {
|
||||
console.log(warning.name);
|
||||
console.log(warning.message);
|
||||
console.log(warning.stack);
|
||||
});
|
||||
|
||||
let verbose = false;
|
||||
|
||||
if (process.argv[2] === '--v')
|
||||
verbose = true;
|
||||
|
||||
let Jasmine = require('jasmine');
|
||||
let jasmine = new Jasmine();
|
||||
let SpecReporter = require('jasmine-spec-reporter').SpecReporter;
|
||||
|
||||
let serviceSpecs = [
|
||||
`${__dirname}/**/*[sS]pec.js`,
|
||||
`${__dirname}/../loopback/**/*[sS]pec.js`,
|
||||
`${__dirname}/../modules/*/back/**/*.[sS]pec.js`
|
||||
];
|
||||
|
||||
jasmine.loadConfig({
|
||||
spec_dir: '.',
|
||||
spec_files: serviceSpecs,
|
||||
helpers: []
|
||||
});
|
||||
|
||||
jasmine.addReporter(new SpecReporter({
|
||||
spec: {
|
||||
// displayStacktrace: 'summary',
|
||||
displaySuccessful: verbose,
|
||||
displayFailedSpec: true,
|
||||
displaySpecDuration: true
|
||||
}
|
||||
}));
|
||||
|
||||
jasmine.execute();
|
|
@ -1 +0,0 @@
|
|||
export * from './src/auth';
|
|
@ -1,2 +0,0 @@
|
|||
import './module';
|
||||
import './login/login';
|
|
@ -1,29 +0,0 @@
|
|||
<div>
|
||||
<div class="box-wrapper">
|
||||
<div class="box">
|
||||
<img src="./logo.svg"/>
|
||||
<form name="form" ng-submit="$ctrl.submit()">
|
||||
<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">
|
||||
<vn-spinner enable="$ctrl.loading"></vn-spinner>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<vn-snackbar vn-id="snackbar"></vn-snackbar>
|
||||
</div>
|
|
@ -1,84 +0,0 @@
|
|||
import ngModule from '../module';
|
||||
import './style.scss';
|
||||
|
||||
/**
|
||||
* 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;
|
||||
let params = {
|
||||
user: this.user,
|
||||
password: this.password,
|
||||
location: this.$window.location.href
|
||||
};
|
||||
this.$http.post('/auth/login', params).then(
|
||||
json => this.onLoginOk(json),
|
||||
json => this.onLoginErr(json)
|
||||
);
|
||||
}
|
||||
onLoginOk(json) {
|
||||
this.loading = false;
|
||||
let data = json.data;
|
||||
let params = {
|
||||
token: data.token,
|
||||
continue: data.continue
|
||||
};
|
||||
this.$window.location = `${data.loginUrl}?${this.encodeUri(params)}`;
|
||||
}
|
||||
encodeUri(object) {
|
||||
let uri = '';
|
||||
for (var key in object)
|
||||
if (object[key]) {
|
||||
if (uri.length > 0)
|
||||
uri += '&';
|
||||
uri += encodeURIComponent(key) + '=' + encodeURIComponent(object[key]);
|
||||
}
|
||||
return uri;
|
||||
}
|
||||
onLoginErr(json) {
|
||||
this.loading = false;
|
||||
this.password = '';
|
||||
|
||||
let message;
|
||||
|
||||
switch (json.status) {
|
||||
case 401:
|
||||
message = 'Invalid credentials';
|
||||
break;
|
||||
case -1:
|
||||
message = 'Can\'t contact with server';
|
||||
break;
|
||||
default:
|
||||
message = 'Something went wrong';
|
||||
}
|
||||
|
||||
this.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
|
||||
});
|
|
@ -1,119 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
xmlns:ns="&#38;ns_sfw;"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
version="1.1"
|
||||
id="Capa_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
width="400"
|
||||
height="168.56424"
|
||||
viewBox="0 0 400 168.56424"
|
||||
enable-background="new 0 0 560 960"
|
||||
xml:space="preserve"
|
||||
inkscape:version="0.91 r13725"
|
||||
sodipodi:docname="logo.svg"><defs
|
||||
id="defs43" /><sodipodi:namedview
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1"
|
||||
objecttolerance="10"
|
||||
gridtolerance="10"
|
||||
guidetolerance="10"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:window-width="1920"
|
||||
inkscape:window-height="1016"
|
||||
id="namedview41"
|
||||
showgrid="false"
|
||||
fit-margin-top="0"
|
||||
fit-margin-left="0"
|
||||
fit-margin-right="0"
|
||||
fit-margin-bottom="0"
|
||||
inkscape:zoom="3.09"
|
||||
inkscape:cx="200"
|
||||
inkscape:cy="84.28212"
|
||||
inkscape:window-x="1920"
|
||||
inkscape:window-y="27"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="Capa_1" /><metadata
|
||||
id="metadata3"><ns:sfw><ns:slices /><ns:sliceSourceBounds
|
||||
height="212.103"
|
||||
width="503.32"
|
||||
y="-235.507"
|
||||
x="28.34"
|
||||
bottomLeftOrigin="true" /></ns:sfw><rdf:RDF><cc:Work
|
||||
rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><g
|
||||
id="g5"
|
||||
transform="matrix(0.79472445,0,0,0.79472445,-22.522491,-18.600526)"><g
|
||||
id="g7"><path
|
||||
d="M 51.517,90.859 28.34,23.407 l 18.318,0 9.479,34.665 0.776,2.839 c 1.158,4.151 2.041,7.632 2.65,10.44 0.336,-1.372 0.747,-2.992 1.234,-4.854 0.487,-1.862 1.174,-4.306 2.057,-7.328 l 9.942,-35.763 18.22,0 -23.361,67.453 -16.138,0 z"
|
||||
id="path9"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#3e3d3d;fill-rule:evenodd" /><path
|
||||
d="m 523.891,90.859 -16.591,0 c 0.036,-0.791 0.106,-1.614 0.209,-2.466 l 0.354,-2.653 c -2.932,2.285 -5.847,3.974 -8.737,5.071 -2.892,1.096 -5.879,1.644 -8.969,1.644 -4.8,0 -8.23,-1.32 -10.289,-3.957 -2.061,-2.639 -2.463,-6.202 -1.207,-10.688 1.154,-4.121 3.184,-7.47 6.091,-10.048 2.904,-2.577 6.718,-4.385 11.438,-5.423 2.602,-0.548 5.877,-1.144 9.821,-1.788 5.886,-0.915 9.079,-2.258 9.572,-4.027 l 0.337,-1.197 c 0.406,-1.449 0.171,-2.551 -0.706,-3.304 -0.873,-0.754 -2.383,-1.129 -4.521,-1.129 -2.325,0 -4.304,0.476 -5.93,1.43 -1.63,0.953 -2.868,2.353 -3.722,4.201 l -15.015,0 c 2.371,-5.631 5.874,-9.82 10.507,-12.573 4.636,-2.751 10.511,-4.128 17.628,-4.128 4.428,0 8.014,0.535 10.754,1.606 2.739,1.069 4.66,2.69 5.763,4.86 0.753,1.559 1.074,3.417 0.958,5.573 -0.112,2.157 -0.838,5.617 -2.173,10.386 l -5.283,18.874 c -0.633,2.254 -0.926,4.03 -0.884,5.327 0.043,1.295 0.416,2.141 1.118,2.537 l -0.523,1.872 z M 512.532,67.424 c -1.479,0.794 -3.889,1.528 -7.229,2.201 -1.618,0.304 -2.856,0.564 -3.71,0.777 -2.115,0.551 -3.698,1.255 -4.748,2.107 -1.052,0.854 -1.777,2 -2.179,3.435 -0.497,1.771 -0.346,3.199 0.452,4.285 0.801,1.084 2.104,1.626 3.911,1.626 2.787,0 5.284,-0.803 7.496,-2.406 2.208,-1.603 3.677,-3.703 4.402,-6.298 l 1.605,-5.727 z"
|
||||
id="path11"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#a3d131;fill-rule:evenodd" /><path
|
||||
d="m 441.489,90.859 13.951,-49.816 15.682,0 -2.441,8.716 c 2.699,-3.419 5.567,-5.915 8.604,-7.489 3.039,-1.569 6.566,-2.386 10.587,-2.448 l -4.518,16.13 c -0.677,-0.089 -1.354,-0.161 -2.029,-0.206 -0.673,-0.046 -1.315,-0.068 -1.927,-0.068 -2.505,0 -4.682,0.374 -6.525,1.121 -1.843,0.749 -3.471,1.901 -4.886,3.46 -0.902,1.038 -1.758,2.527 -2.558,4.466 -0.803,1.939 -1.808,5.076 -3.026,9.414 l -4.681,16.72 -16.233,0 z"
|
||||
id="path13"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#a3d131;fill-rule:evenodd" /><path
|
||||
d="m 447.466,41.043 -13.947,49.816 -15.961,0 1.923,-6.863 c -2.729,2.77 -5.509,4.812 -8.336,6.121 -2.823,1.309 -5.84,1.962 -9.048,1.962 -5.497,0 -9.239,-1.41 -11.23,-4.23 -1.99,-2.818 -2.208,-7.005 -0.655,-12.554 l 9.591,-34.251 16.325,0 -7.806,27.876 c -1.145,4.097 -1.424,6.917 -0.83,8.461 0.586,1.542 2.146,2.315 4.68,2.315 2.839,0 5.169,-0.94 6.993,-2.818 1.82,-1.881 3.329,-4.945 4.517,-9.194 l 7.459,-26.64 16.325,0 z"
|
||||
id="path15"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#a3d131;fill-rule:evenodd" /><path
|
||||
d="m 361.923,50.894 2.757,-9.85 6.663,0 3.942,-14.073 16.322,0 -3.938,14.073 8.351,0 -2.759,9.85 -8.352,0 -6.042,21.585 c -0.924,3.3 -1.107,5.483 -0.551,6.553 0.559,1.068 2.014,1.603 4.369,1.603 l 1.223,-0.023 0.869,-0.068 -2.914,10.408 c -1.805,0.329 -3.551,0.586 -5.239,0.765 -1.686,0.18 -3.294,0.271 -4.819,0.271 -5.658,0 -9.141,-1.382 -10.447,-4.145 -1.304,-2.763 -0.694,-8.648 1.824,-17.655 l 5.402,-19.293 -6.661,0 z"
|
||||
id="path17"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#a3d131;fill-rule:evenodd" /><path
|
||||
d="m 350.752,90.859 -16.594,0 c 0.037,-0.791 0.106,-1.614 0.21,-2.466 l 0.353,-2.653 c -2.934,2.285 -5.846,3.974 -8.737,5.071 -2.891,1.096 -5.881,1.644 -8.966,1.644 -4.805,0 -8.234,-1.32 -10.292,-3.957 -2.057,-2.639 -2.462,-6.202 -1.204,-10.688 1.152,-4.121 3.184,-7.47 6.088,-10.048 2.909,-2.577 6.72,-4.385 11.442,-5.423 2.599,-0.548 5.872,-1.144 9.818,-1.788 5.885,-0.915 9.077,-2.258 9.573,-4.027 l 0.338,-1.197 c 0.403,-1.449 0.171,-2.551 -0.706,-3.304 -0.873,-0.754 -2.383,-1.129 -4.525,-1.129 -2.324,0 -4.3,0.476 -5.93,1.43 -1.626,0.953 -2.867,2.353 -3.722,4.201 l -15.01,0 c 2.371,-5.631 5.873,-9.82 10.509,-12.573 4.634,-2.751 10.507,-4.128 17.626,-4.128 4.428,0 8.01,0.535 10.749,1.606 2.74,1.069 4.665,2.69 5.765,4.86 0.755,1.559 1.077,3.417 0.961,5.573 -0.117,2.157 -0.837,5.617 -2.178,10.386 l -5.281,18.874 c -0.633,2.254 -0.928,4.03 -0.883,5.327 0.045,1.295 0.417,2.141 1.119,2.537 l -0.523,1.872 z M 339.394,67.424 c -1.48,0.794 -3.893,1.528 -7.233,2.201 -1.616,0.304 -2.853,0.564 -3.71,0.777 -2.115,0.551 -3.696,1.255 -4.746,2.107 -1.052,0.854 -1.777,2 -2.18,3.435 -0.498,1.771 -0.345,3.199 0.456,4.285 0.796,1.084 2.102,1.626 3.908,1.626 2.786,0 5.285,-0.803 7.493,-2.406 2.212,-1.603 3.68,-3.703 4.407,-6.298 l 1.605,-5.727 z"
|
||||
id="path19"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#a3d131;fill-rule:evenodd" /><path
|
||||
d="m 243.068,23.405 -18.893,67.455 -16.32,0 1.835,-6.548 c -2.596,2.712 -5.274,4.716 -8.033,6.011 -2.763,1.297 -5.747,1.946 -8.957,1.946 -6.232,0 -10.613,-2.406 -13.151,-7.215 -2.532,-4.812 -2.708,-11.11 -0.53,-18.896 2.199,-7.851 5.936,-14.246 11.217,-19.196 5.283,-4.947 10.963,-7.421 17.043,-7.421 3.268,0 5.996,0.659 8.181,1.975 2.181,1.316 3.737,3.259 4.668,5.83 l 6.706,-23.94 16.234,0 z m -50.367,42.038 c -1.25,4.457 -1.374,7.868 -0.373,10.234 1.003,2.366 3.062,3.552 6.172,3.552 3.112,0 5.798,-1.169 8.054,-3.505 2.259,-2.335 4.017,-5.764 5.282,-10.281 1.172,-4.181 1.238,-7.416 0.198,-9.706 -1.036,-2.292 -3.097,-3.437 -6.178,-3.437 -2.901,0 -5.534,1.177 -7.901,3.526 -2.367,2.352 -4.117,5.557 -5.254,9.617"
|
||||
id="path21"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#3e3d3d;fill-rule:evenodd" /><path
|
||||
d="m 131.769,90.859 13.949,-49.816 15.682,0 -2.441,8.716 c 2.701,-3.419 5.569,-5.915 8.604,-7.489 3.039,-1.569 13.384,-2.386 17.405,-2.448 l -4.517,16.13 c -0.675,-0.089 -1.351,-0.161 -2.03,-0.206 -0.671,-0.046 -1.315,-0.068 -1.929,-0.068 -2.503,0 -11.494,0.374 -13.339,1.121 -1.845,0.749 -3.471,1.901 -4.886,3.46 -0.902,1.038 -1.756,2.527 -2.556,4.466 -0.803,1.939 -1.812,5.076 -3.029,9.414 l -4.682,16.72 -16.231,0 z"
|
||||
id="path23"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#3e3d3d;fill-rule:evenodd" /><path
|
||||
d="m 114.704,75.192 15.667,0 c -2.994,5.465 -7.055,9.691 -12.186,12.683 -5.126,2.994 -10.866,4.487 -17.218,4.487 -7.727,0 -13.177,-2.345 -16.355,-7.033 -3.176,-4.689 -3.63,-11.081 -1.364,-19.175 2.309,-8.247 6.396,-14.773 12.263,-19.585 5.868,-4.809 12.634,-7.215 20.3,-7.215 7.909,0 13.439,2.446 16.586,7.336 3.151,4.891 3.52,11.644 1.106,20.264 l -0.528,1.813 -0.373,1.079 -33.689,0 c -0.978,3.502 -0.97,6.176 0.027,8.022 0.994,1.843 2.961,2.765 5.895,2.765 2.169,0 4.086,-0.457 5.748,-1.372 1.66,-0.914 3.034,-2.27 4.121,-4.069 m -13.672,-14.543 18.577,-0.043 c 0.834,-3.195 0.687,-5.692 -0.446,-7.489 -1.133,-1.794 -3.116,-2.693 -5.961,-2.693 -2.689,0 -5.083,0.883 -7.183,2.648 -2.1,1.765 -3.759,4.292 -4.987,7.577"
|
||||
id="path25"
|
||||
inkscape:connector-curvature="0"
|
||||
style="clip-rule:evenodd;fill:#3e3d3d;fill-rule:evenodd" /><path
|
||||
d="m 279.482,91.578 -14.248,-41.467 -14.362,41.467 -16.14,0 21.281,-67.454 18.224,0 9.862,34.664 0.778,2.84 c 1.156,4.151 2.041,7.633 2.65,10.443 l 1.234,-4.857 c 0.487,-1.862 1.172,-4.304 2.057,-7.327 l 9.942,-35.763 18.222,0 -23.364,67.454 -16.136,0 z"
|
||||
id="path27"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#a3d131" /></g><g
|
||||
id="g29"><path
|
||||
d="m 122.886,124.71 c -4.603,-5.205 -9.749,-8.869 -15.438,-10.989 -5.693,-2.117 -12.352,-3.179 -19.98,-3.179 -13.804,0 -23.794,2.605 -29.97,7.81 -6.175,5.208 -9.263,12.169 -9.263,20.888 0,4.359 0.755,8.023 2.271,10.989 1.513,2.969 3.874,5.48 7.083,7.538 3.207,2.061 7.294,3.786 12.26,5.177 4.964,1.393 10.898,2.814 17.8,4.268 7.021,1.453 13.378,3.028 19.072,4.723 5.69,1.697 10.535,3.846 14.531,6.448 3.996,2.605 7.083,5.844 9.263,9.718 2.18,3.876 3.27,8.658 3.27,14.349 0,5.449 -1.09,10.234 -3.27,14.35 -2.18,4.117 -5.268,7.568 -9.263,10.353 -3.996,2.787 -8.81,4.876 -14.44,6.267 -5.631,1.391 -11.897,2.089 -18.799,2.089 -10.293,0 -19.557,-1.604 -27.79,-4.813 -8.236,-3.207 -15.802,-8.143 -22.705,-14.803 l 3.633,-4.541 c 6.054,6.176 12.956,10.838 20.707,13.985 7.748,3.15 16.529,4.723 26.337,4.723 12.107,0 21.643,-2.208 28.607,-6.63 6.961,-4.418 10.444,-11.17 10.444,-20.252 0,-4.601 -0.849,-8.506 -2.543,-11.715 -1.697,-3.207 -4.268,-5.932 -7.719,-8.174 -3.451,-2.239 -7.782,-4.178 -12.987,-5.813 -5.208,-1.635 -11.383,-3.179 -18.527,-4.632 -7.024,-1.453 -13.259,-2.966 -18.708,-4.541 -5.449,-1.572 -10.021,-3.57 -13.713,-5.993 -3.695,-2.421 -6.479,-5.387 -8.355,-8.9 -1.879,-3.511 -2.815,-7.93 -2.815,-13.259 0,-5.69 1.09,-10.745 3.27,-15.167 2.18,-4.419 5.236,-8.111 9.172,-11.08 3.934,-2.966 8.688,-5.236 14.258,-6.812 5.568,-1.572 11.806,-2.361 18.708,-2.361 8.355,0 15.649,1.212 21.887,3.633 6.235,2.424 11.957,6.297 17.165,11.625 l -3.453,4.721 z"
|
||||
id="path31"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f7931e" /><path
|
||||
d="m 142.321,234.599 55.58,-128.96 5.267,0 55.581,128.96 -6.902,0 -19.072,-44.5 -64.662,0 -19.072,44.5 -6.72,0 z m 58.304,-120.968 -30.696,71.019 60.847,0 -30.151,-71.019 z"
|
||||
id="path33"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f7931e" /><path
|
||||
d="m 280.18,234.599 0,-128.96 6.175,0 0,123.148 79.192,0 0,5.813 -85.367,0 z"
|
||||
id="path35"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f7931e" /><path
|
||||
d="m 387.523,234.599 0,-128.779 6.176,0 0,128.779 -6.176,0 z"
|
||||
id="path37"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f7931e" /><path
|
||||
d="m 420.58,105.639 47.588,60.666 47.77,-60.666 7.266,0 -51.402,65.388 49.949,63.572 -7.266,0 -46.316,-58.85 -46.135,58.85 -7.447,0 49.949,-63.572 -51.402,-65.388 7.446,0 z"
|
||||
id="path39"
|
||||
inkscape:connector-curvature="0"
|
||||
style="fill:#f7931e" /></g></g></svg>
|
Before Width: | Height: | Size: 12 KiB |
|
@ -1,50 +0,0 @@
|
|||
|
||||
vn-login > div {
|
||||
position: absolute;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
color: #333;
|
||||
font-size: 1.1em;
|
||||
font-weight: normal;
|
||||
background-color: #3c393b;
|
||||
|
||||
.box-wrapper {
|
||||
position: relative;
|
||||
max-width: 19em;
|
||||
margin: auto;
|
||||
height: inherit;
|
||||
}
|
||||
|
||||
.box {
|
||||
box-sizing: border-box;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
width: 100%;
|
||||
margin-top: -13.5em;
|
||||
padding: 3em;
|
||||
background-color: white;
|
||||
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;
|
||||
top: .3em;
|
||||
right: 4em;
|
||||
overflow: visible;
|
||||
}
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
import {ng} from 'vendor';
|
||||
import 'core';
|
||||
|
||||
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);
|
|
@ -1 +0,0 @@
|
|||
export * from './src/client';
|
|
@ -1,225 +0,0 @@
|
|||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
<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>
|
|
@ -1,16 +0,0 @@
|
|||
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
|
||||
});
|
|
@ -1,25 +0,0 @@
|
|||
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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,100 +0,0 @@
|
|||
<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>
|
|
@ -1,134 +0,0 @@
|
|||
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
|
||||
});
|
|
@ -1,55 +0,0 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,7 +0,0 @@
|
|||
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
|
|
@ -1,47 +0,0 @@
|
|||
<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>
|
|
@ -1,22 +0,0 @@
|
|||
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
|
||||
});
|
|
@ -1,46 +0,0 @@
|
|||
<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>
|
|
@ -1,8 +0,0 @@
|
|||
import ngModule from '../module';
|
||||
|
||||
ngModule.component('vnClientBasicData', {
|
||||
template: require('./basic-data.html'),
|
||||
bindings: {
|
||||
client: '<'
|
||||
}
|
||||
});
|
|
@ -1,14 +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: >-
|
||||
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
|
|
@ -1,64 +0,0 @@
|
|||
<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>
|
|
@ -1,57 +0,0 @@
|
|||
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: '<'
|
||||
}
|
||||
});
|
|
@ -1,80 +0,0 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,15 +0,0 @@
|
|||
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
|
|
@ -1,15 +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
|
||||
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
|
|
@ -1,16 +0,0 @@
|
|||
<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>
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue