Compare commits

..

1 Commits

Author SHA1 Message Date
Javier Segarra 35f0997020 test
gitea/salix/pipeline/head There was a failure building this commit Details
2024-01-04 14:34:43 +01:00
4993 changed files with 197465 additions and 224159 deletions

View File

@ -1,6 +1,4 @@
node_modules
print/node_modules
front
db
e2e
storage
front/node_modules
services

View File

@ -36,7 +36,3 @@ rules:
jasmine/no-focused-tests: 0
jasmine/prefer-toHaveBeenCalledWith: 0
arrow-spacing: ["error", { "before": true, "after": true }]
no-restricted-syntax:
- "error"
- selector: "NewExpression[callee.name='Date']"
message: "Use Date.vnNew() instead of new Date()."

1
.gitignore vendored
View File

@ -10,4 +10,3 @@ print.*.json
db.json
junit.xml
.DS_Store
storage

View File

@ -1,33 +0,0 @@
const fs = require('fs');
const path = require('path');
function getCurrentBranchName(p = process.cwd()) {
if (!fs.existsSync(p)) return false;
const gitHeadPath = path.join(p, '.git', 'HEAD');
if (!fs.existsSync(gitHeadPath))
return getCurrentBranchName(path.resolve(p, '..'));
const headContent = fs.readFileSync(gitHeadPath, 'utf-8');
return headContent.trim().split('/')[2];
}
const branchName = getCurrentBranchName();
if (branchName) {
const msgPath = `.git/COMMIT_EDITMSG`;
const msg = fs.readFileSync(msgPath, 'utf-8');
const reference = branchName.match(/^\d+/);
const referenceTag = `refs #${reference}`;
if (!msg.includes(referenceTag) && reference) {
const splitedMsg = msg.split(':');
if (splitedMsg.length > 1) {
const finalMsg = splitedMsg[0] + ': ' + referenceTag + splitedMsg.slice(1).join(':');
fs.writeFileSync(msgPath, finalMsg);
}
}
}

View File

@ -1,8 +0,0 @@
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
echo "Running husky commit-msg hook"
npx --no-install commitlint --edit
echo "Adding reference tag to commit message"
node .husky/addReferenceTag.js

File diff suppressed because it is too large Load Diff

61
Dockerfile Normal file
View File

@ -0,0 +1,61 @@
FROM debian:bookworm-slim
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
# NodeJs
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
ca-certificates \
gnupg2 \
graphicsmagick \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& npm install -g npm@9.6.6
# Puppeteer
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Extra dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/* \
&& npm -g install pm2
# Salix
WORKDIR /salix
COPY print/package.json print/package-lock.json print/
RUN npm --prefix ./print install --omit=dev ./print
COPY package.json package-lock.json ./
COPY loopback/package.json loopback/
RUN npm install --omit=dev
COPY loopback loopback
COPY back back
COPY modules modules
COPY print print
COPY \
LICENSE \
README.md \
./
CMD ["pm2-runtime", "./back/process.yml"]
HEALTHCHECK --interval=15s --timeout=10s \
CMD curl -f http://localhost:3000/api/Applications/status || exit 1

308
Jenkinsfile vendored
View File

@ -1,258 +1,144 @@
#!/usr/bin/env groovy
def PROTECTED_BRANCH
def FROM_GIT
def RUN_TESTS
def RUN_BUILD
def BRANCH_ENV = [
test: 'test',
master: 'production'
]
node {
stage('Setup') {
env.BACK_REPLICAS = 1
env.NODE_ENV = BRANCH_ENV[env.BRANCH_NAME] ?: 'dev'
PROTECTED_BRANCH = [
'dev',
'test',
'master'
].contains(env.BRANCH_NAME)
FROM_GIT = env.JOB_NAME.startsWith('gitea/')
RUN_TESTS = !PROTECTED_BRANCH && FROM_GIT
RUN_BUILD = PROTECTED_BRANCH && FROM_GIT
// https://www.jenkins.io/doc/book/pipeline/jenkinsfile/#using-environment-variables
echo "NODE_NAME: ${env.NODE_NAME}"
echo "WORKSPACE: ${env.WORKSPACE}"
configFileProvider([
configFile(fileId: 'salix.properties',
variable: 'PROPS_FILE')
]) {
def props = readProperties file: PROPS_FILE
props.each {key, value -> env."${key}" = value }
props.each {key, value -> echo "${key}: ${value}" }
}
if (PROTECTED_BRANCH) {
configFileProvider([
configFile(fileId: "salix.branch.${env.BRANCH_NAME}",
variable: 'BRANCH_PROPS_FILE')
]) {
def props = readProperties file: BRANCH_PROPS_FILE
props.each {key, value -> env."${key}" = value }
props.each {key, value -> echo "${key}: ${value}" }
}
}
}
}
pipeline {
agent any
options {
disableConcurrentBuilds()
}
tools {
nodejs 'node-v20'
}
environment {
PROJECT_NAME = 'salix'
STACK_NAME = "${env.PROJECT_NAME}-${env.BRANCH_NAME}"
}
stages {
stage('Checkout') {
steps {
script {
switch (env.BRANCH_NAME) {
case 'master':
env.NODE_ENV = 'production'
env.BACK_REPLICAS = 4
break
case 'test':
env.NODE_ENV = 'test'
env.BACK_REPLICAS = 2
break
}
}
configFileProvider([
configFile(fileId: "salix.groovy",
variable: 'GROOVY_FILE')
]) {
load env.GROOVY_FILE
}
setEnv()
}
}
stage('Install') {
environment {
NODE_ENV = ''
NODE_ENV = ""
}
parallel {
stage('Back') {
steps {
sh 'pnpm install --prefer-offline'
sh 'node node_modules/puppeteer/install.mjs'
}
}
stage('Print') {
when {
expression { FROM_GIT }
}
steps {
sh 'pnpm install --prefer-offline --prefix=print'
}
}
stage('Front') {
when {
expression { FROM_GIT }
}
steps {
sh 'pnpm install --prefer-offline --prefix=front'
}
steps {
nodejs('node-v20') {
sh 'npm install --no-audit --prefer-offline'
sh 'gulp install --ci'
}
}
}
stage('Stack') {
stage('Test') {
when { not { anyOf {
branch 'test'
branch 'master'
}}}
environment {
NODE_ENV = ""
TZ = 'Europe/Madrid'
}
parallel {
stage('Back') {
stages {
stage('Test') {
when {
expression { RUN_TESTS }
}
environment {
NODE_ENV = ''
}
steps {
sh 'node back/tests.js --junit'
}
post {
always {
junit(
testResults: 'junitresults.xml',
allowEmptyResults: true
)
}
}
}
stage('Build') {
when {
expression { RUN_BUILD }
}
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
}
sh 'docker-compose build back'
}
stage('Frontend') {
steps {
nodejs('node-v20') {
sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=2'
}
}
}
stage('Front') {
when {
expression { FROM_GIT }
}
stages {
stage('Test') {
when {
expression { RUN_TESTS }
}
environment {
NODE_ENV = ''
}
steps {
sh 'jest --ci --reporters=default --reporters=jest-junit --maxWorkers=10'
}
post {
always {
junit(
testResults: 'junit.xml',
allowEmptyResults: true
)
}
}
}
stage('Build') {
when {
expression { RUN_BUILD }
}
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
}
sh 'gulp build'
sh 'docker-compose build front'
}
stage('Backend') {
steps {
nodejs('node-v20') {
sh 'npm run test:back:ci'
}
}
}
}
}
stage('Push') {
when {
expression { RUN_BUILD }
}
stage('Build') {
when { anyOf {
branch 'test'
branch 'master'
}}
environment {
CREDENTIALS = credentials('docker-registry')
}
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
nodejs('node-v20') {
sh 'gulp build'
}
sh 'docker login --username $CREDENTIALS_USR --password $CREDENTIALS_PSW $REGISTRY'
sh 'docker-compose push'
dockerBuild()
}
}
stage('Deploy') {
when {
expression { PROTECTED_BRANCH }
when { anyOf {
branch 'test'
branch 'master'
}}
environment {
DOCKER_HOST = "${env.SWARM_HOST}"
}
parallel {
stage('Database') {
steps {
configFileProvider([
configFile(fileId: "config.${env.NODE_ENV}.ini",
variable: 'MYSQL_CONFIG')
]) {
sh 'mkdir -p db/remotes'
sh 'cp "$MYSQL_CONFIG" db/remotes/$NODE_ENV.ini'
}
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'
}
sh 'npx myt push $NODE_ENV --force --commit'
}
}
stage('Kubernetes') {
when {
expression { FROM_GIT }
}
steps {
script {
def packageJson = readJSON file: 'package.json'
env.VERSION = "${packageJson.version}-build${env.BUILD_ID}"
}
withKubeConfig([
serverUrl: "$KUBERNETES_API",
credentialsId: 'kubernetes',
namespace: 'salix'
]) {
sh 'kubectl set image deployment/salix-back-$BRANCH_NAME salix-back-$BRANCH_NAME=$REGISTRY/salix-back:$VERSION'
sh 'kubectl set image deployment/salix-front-$BRANCH_NAME salix-front-$BRANCH_NAME=$REGISTRY/salix-front:$VERSION'
}
}
}
sh 'db/import-changes.sh -f $NODE_ENV'
}
}
}
post {
success {
always {
script {
if (env.BRANCH_NAME == 'master' && FROM_GIT) {
env.GIT_COMMIT_MSG = sh(
script: 'git log -1 --pretty=%B ${GIT_COMMIT}',
returnStdout: true
).trim()
if (!['master', 'test'].contains(env.BRANCH_NAME)) {
try {
junit 'junitresults.xml'
junit 'junit.xml'
} catch (e) {
echo e.toString()
}
}
String message = env.GIT_COMMIT_MSG
int index = message.indexOf('\n')
if (index != -1)
message = message.substring(0, index)
setEnv()
rocketSend(
channel: 'vn-database',
message: "*DB version uploaded:* ${message}"
+"\n$COMMITTER_EMAIL ($BRANCH_NAME)"
+"\n$RUN_DISPLAY_URL",
rawMessage: true
if (!env.COMMITTER_EMAIL || currentBuild.currentResult == 'SUCCESS') 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()
}
}
}
unsuccessful {
setEnv()
sendEmail()
}
}
}

View File

@ -11,16 +11,11 @@ Required applications.
* Node.js
* Docker
* Git
* MYT
You will need to install globally the following items.
```
$ sudo npm install -g jest gulp-cli
```
After installing MYT you will need the following item.
```
$ apt install libkrb5-dev libssl-dev
```
## Installing dependencies and launching
@ -28,7 +23,7 @@ Pull from repository.
Run this commands on project root directory to install Node dependencies.
```
$ pnpm install
$ npm install
$ gulp install
```
@ -59,12 +54,6 @@ For end-to-end tests run from project's root.
$ npm run test:e2e
```
## Generate changeLog test → master
```
$ bash changelog.sh
```
## Visual Studio Code extensions
Open Visual Studio Code, press Ctrl+P and paste the following commands.

View File

@ -1,57 +0,0 @@
FROM debian:bookworm-slim
ENV TZ Europe/Madrid
ARG DEBIAN_FRONTEND=noninteractive
# NodeJs
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
ca-certificates \
gnupg2 \
graphicsmagick \
&& curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \
&& apt-get install -y --no-install-recommends nodejs \
&& corepack enable pnpm
# Puppeteer
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
libfontconfig lftp xvfb gconf-service libasound2 libatk1.0-0 libc6 \
libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 \
libgcc1 libgconf-2-4 libgdk-pixbuf2.0-0 libglib2.0-0 libgtk-3-0 \
libnspr4 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 \
libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 \
libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 \
fonts-liberation libappindicator1 libnss3 lsb-release xdg-utils wget
# Extra dependencies
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
samba-common-bin samba-dsdb-modules\
&& rm -rf /var/lib/apt/lists/*
# Salix
WORKDIR /salix
COPY print/package.json print/pnpm-lock.yaml print/
RUN pnpm install --prod --prefix=print
COPY package.json pnpm-lock.yaml ./
COPY loopback/package.json loopback/
RUN pnpm install --prod
COPY loopback loopback
COPY back back
COPY modules modules
COPY print print
COPY \
LICENSE \
README.md \
./
CMD ["node", "--tls-min-v1.0", "--openssl-legacy-provider", "./loopback/server/server.js"]

View File

@ -1,5 +1,3 @@
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethodCtx('sendCheckingPresence', {
description: 'Creates a message in the chat model checking the user status',
@ -39,7 +37,7 @@ module.exports = Self => {
if (!recipient)
throw new Error(`Could not send message "${message}" to worker id ${recipientId} from user ${userId}`);
if (!isProduction())
if (process.env.NODE_ENV == 'test')
message = `[Test:Environment to user ${userId}] ` + message;
const chat = await models.Chat.create({

View File

@ -1,6 +1,4 @@
const axios = require('axios');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethodCtx('sendQueued', {
description: 'Send a RocketChat message',
@ -96,7 +94,7 @@ module.exports = Self => {
* @return {Promise} - The request promise
*/
Self.sendMessage = async function sendMessage(senderFk, recipient, message) {
if (!isProduction(false)) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
statusCode: 200,
@ -151,7 +149,7 @@ module.exports = Self => {
* @return {Promise} - The request promise
*/
Self.getUserStatus = async function getUserStatus(username) {
if (!isProduction(false)) {
if (process.env.NODE_ENV !== 'production') {
return new Promise(resolve => {
return resolve({
data: {

View File

@ -3,14 +3,14 @@ const {models} = require('vn-loopback/server/server');
describe('Chat send()', () => {
it('should return true as response', async() => {
let ctx = {req: {accessToken: {userId: 1}}};
let response = await models.Chat.send(ctx, '@salesperson', 'I changed something');
let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(true);
});
it('should return false as response', async() => {
let ctx = {req: {accessToken: {userId: 18}}};
let response = await models.Chat.send(ctx, '@salesperson', 'I changed something');
let response = await models.Chat.send(ctx, '@salesPerson', 'I changed something');
expect(response).toEqual(false);
});

View File

@ -1,37 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('assign', {
description: 'Assign a collection',
accessType: 'WRITE',
http: {
path: `/assign`,
verb: 'POST'
},
returns: {
type: ['object'],
root: true
},
});
Self.assign = async(ctx, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const randStr = Math.random().toString(36).substring(3);
const result = await Self.rawSql(`
CALL vn.collection_assign(?, @vCollectionFk);
SELECT @vCollectionFk ?
`, [userId, randStr], myOptions);
// Por si entra en SELECT FOR UPDATE una o varias veces
const collectionFk = result.find(item => item[0]?.[randStr] !== undefined)?.[0]?.[randStr];
if (!collectionFk) throw new UserError('There are not picking tickets');
await Self.rawSql('CALL vn.collection_printSticker(?, NULL)', [collectionFk], myOptions);
return collectionFk;
};
};

View File

@ -1,36 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('assignCollection', {
description: 'Assign a collection',
accessType: 'WRITE',
http: {
path: `/assignCollection`,
verb: 'POST'
},
returns: {
type: ['object'],
root: true
},
});
Self.assignCollection = async(ctx, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const randStr = Math.random().toString(36).substring(3);
const result = await Self.rawSql(`
CALL vn.collection_getAssigned(?, @vCollectionFk);
SELECT @vCollectionFk ?
`, [userId, randStr], myOptions);
const collectionFk = result.find(item => item[0]?.[randStr] !== undefined)?.[0]?.[randStr];
if (!collectionFk) throw new UserError('There are not picking tickets');
await Self.rawSql('CALL vn.collection_printSticker(?, NULL)', [collectionFk], myOptions);
return collectionFk;
};
};

View File

@ -1,142 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('getSales', {
description: 'Get sales from ticket, collection or sectorCollection',
accessType: 'READ',
accepts: [
{
arg: 'collectionOrTicketFk',
type: 'number',
required: true
}, {
arg: 'print',
type: 'boolean',
required: true
}, {
arg: 'source',
type: 'string',
required: true
},
],
returns: {
type: 'Object',
root: true
},
http: {
path: `/getSales`,
verb: 'GET'
},
});
Self.getSales = async(ctx, collectionOrTicketFk, print, source, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const [{id}] = await Self.rawSql('SELECT vn.ticket_get(?) as id',
[collectionOrTicketFk],
myOptions);
const [tickets] = await Self.rawSql('CALL vn.collection_getTickets(?)', [id], myOptions);
if (source) {
await Self.rawSql(
'CALL vn.ticketStateToday_setState(?,?)', [id, source], myOptions
);
}
const [sales] = await Self.rawSql('CALL vn.sale_getFromTicketOrCollection(?)',
[id], myOptions);
const isPicker = source != 'CHECKER';
const [placements] = await Self.rawSql('CALL vn.collectionPlacement_get(?, ?)',
[id, isPicker], myOptions
);
if (print) await Self.rawSql('CALL vn.collection_printSticker(?,NULL)', [id], myOptions);
return getCollection(id, tickets, sales, placements, myOptions);
};
async function getCollection(id, tickets, sales, placements, options) {
const collection = {
collectionFk: id,
tickets: [],
};
for (let ticket of tickets) {
const {ticketFk} = ticket;
ticket.sales = [];
const barcodes = await getBarcodes(ticketFk, options);
await Self.rawSql(
'CALL util.log_add(?, ?, ?, ?, ?, ?, ?, ?)',
['vn', 'ticket', 'Ticket', ticketFk, ticketFk, 'select', null, null],
options
);
for (let sale of sales) {
if (sale.ticketFk == ticketFk) {
sale.placements = [];
for (const salePlacement of placements) {
if (salePlacement.saleFk == sale.saleFk && salePlacement.order) {
const placement = {
saleFk: salePlacement.saleFk,
itemFk: salePlacement.itemFk,
placement: salePlacement.placement,
shelving: salePlacement.shelving,
created: salePlacement.created,
visible: salePlacement.visible,
order: salePlacement.order,
grouping: salePlacement.grouping,
priority: salePlacement.priority,
saleOrder: salePlacement.saleOrder,
isPreviousPrepared: salePlacement.isPreviousPrepared,
itemShelvingSaleFk: salePlacement.itemShelvingSaleFk,
ticketFk: salePlacement.ticketFk,
id: salePlacement.id
};
sale.placements.push(placement);
}
}
sale.barcodes = [];
for (const barcode of barcodes) {
if (barcode.movementId == sale.saleFk) {
if (barcode.code) {
sale.barcodes.push(barcode.code);
sale.barcodes.push(`0 ${barcode.code}`);
}
if (barcode.id) {
sale.barcodes.push(barcode.id);
sale.barcodes.push(`0 ${barcode.id}`);
}
}
}
ticket.sales.push(sale);
}
}
collection.tickets.push(ticket);
}
return collection;
}
async function getBarcodes(ticketId, options) {
const query =
`SELECT s.id movementId,
b.code,
c.id
FROM vn.sale s
LEFT JOIN vn.itemBarcode b ON b.itemFk = s.itemFk
LEFT JOIN vn.buy c ON c.itemFk = s.itemFk
LEFT JOIN vn.entry e ON e.id = c.entryFk
LEFT JOIN vn.travel tr ON tr.id = e.travelFk
WHERE s.ticketFk = ?
AND tr.landed >= DATE_SUB(CURDATE(), INTERVAL 1 YEAR)`;
return Self.rawSql(query, [ticketId], options);
}
};

View File

@ -0,0 +1,20 @@
module.exports = Self => {
Self.remoteMethod('getSectors', {
description: 'Get all sectors',
accessType: 'READ',
returns: {
type: 'Object',
root: true
},
http: {
path: `/getSectors`,
verb: 'GET'
}
});
Self.getSectors = async() => {
const query = `CALL vn.sector_get()`;
const [result] = await Self.rawSql(query);
return result;
};
};

View File

@ -37,7 +37,6 @@ module.exports = Self => {
const promises = [];
const [tickets] = await Self.rawSql(`CALL vn.collection_getTickets(?)`, [id], myOptions);
const sales = await Self.rawSql(`
SELECT s.ticketFk,
sgd.saleGroupFk,
@ -57,84 +56,37 @@ module.exports = Self => {
ROW_NUMBER () OVER (PARTITION BY s.id ORDER BY pickingOrder) currentItemShelving,
COUNT(*) OVER (PARTITION BY s.id ORDER BY s.id) totalItemShelving,
sh.code,
p2.code parkingCodePrevia,
p2.pickingOrder pickingOrderPrevia,
p.code parkingCode,
p.pickingOrder pickingOrder,
IFNULL(p2.code, p.code) parkingCode,
IFNULL(p2.pickingOrder, p.pickingOrder) pickingOrder,
iss.id itemShelvingSaleFk,
iss.isPicked,
iss.itemShelvingFk,
st.code stateCode
iss.isPicked
FROM ticketCollection tc
LEFT JOIN collection c ON c.id = tc.collectionFk
JOIN sale s ON s.ticketFk = tc.ticketFk
JOIN ticket t ON t.id = tc.ticketFk
JOIN sale s ON s.ticketFk = t.id
LEFT JOIN saleGroupDetail sgd ON sgd.saleFk = s.id
LEFT JOIN saleGroup sg ON sg.id = sgd.saleGroupFk
LEFT JOIN parking p2 ON p2.id = sg.parkingFk
JOIN item i ON i.id = s.itemFk
JOIN itemShelvingSale iss ON iss.saleFk = s.id
LEFT JOIN itemShelvingSale iss ON iss.saleFk = s.id
LEFT JOIN itemShelving ish ON ish.id = iss.itemShelvingFk
LEFT JOIN shelving sh ON sh.id = ish.shelvingFk
LEFT JOIN shelving sh ON sh.code = ish.shelvingFk
LEFT JOIN parking p ON p.id = sh.parkingFk
LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk
LEFT JOIN origin o ON o.id = i.originFk
LEFT JOIN state st ON st.id = sg.stateFk
WHERE tc.collectionFk = ?
GROUP BY s.id, ish.id, p.code, p2.code
UNION ALL
SELECT s.ticketFk,
sgd.saleGroupFk,
s.id saleFk,
s.itemFk,
i.longName,
i.size,
ic.color,
o.code origin,
ish.packing,
ish.grouping,
s.isAdded,
s.originalQuantity,
s.quantity,
iss.quantity,
SUM(iss.quantity) OVER (PARTITION BY s.id ORDER BY ish.id),
ROW_NUMBER () OVER (PARTITION BY s.id ORDER BY p.pickingOrder),
COUNT(*) OVER (PARTITION BY s.id ORDER BY s.id) ,
sh.code,
p2.code,
p2.pickingOrder,
p.code,
p.pickingOrder,
iss.id itemShelvingSaleFk,
iss.isPicked,
iss.itemShelvingFk,
st.code stateCode
FROM sectorCollection sc
JOIN sectorCollectionSaleGroup ss ON ss.sectorCollectionFk = sc.id
JOIN saleGroup sg ON sg.id = ss.saleGroupFk
LEFT JOIN saleGroupDetail sgd ON sgd.saleGroupFk = sg.id
JOIN sale s ON s.id = sgd.saleFk
LEFT JOIN parking p2 ON p2.id = sg.parkingFk
JOIN item i ON i.id = s.itemFk
JOIN itemShelvingSale iss ON iss.saleFk = s.id
LEFT JOIN itemShelving ish ON ish.id = iss.itemShelvingFk
LEFT JOIN shelving sh ON sh.id = ish.shelvingFk
LEFT JOIN parking p ON p.id = sh.parkingFk
LEFT JOIN itemColor ic ON ic.itemFk = s.itemFk
LEFT JOIN origin o ON o.id = i.originFk
LEFT JOIN state st ON st.id = sg.stateFk
WHERE sc.id = ?
AND sgd.saleGroupFk
GROUP BY s.id, ish.id, p.code, p2.code`, [id, id], myOptions);
ORDER BY pickingOrder;`, [id], myOptions);
if (print)
await Self.rawSql(`CALL vn.collection_printSticker(?, ?)`, [id, null], myOptions);
const collection = {collectionFk: id, tickets: []};
if (tickets && tickets.length) {
for (const ticket of tickets) {
const ticketId = ticket.ticketFk;
if (ticket.observation) {
for (observation of ticket.observation?.split(' ')) {
if (ticket.observaciones != '') {
for (observation of ticket.observaciones.split(' ')) {
if (['#', '@'].includes(observation.charAt(0))) {
promises.push(Self.app.models.Chat.send(ctx, observation,
$t('The ticket is in preparation', {
@ -148,11 +100,11 @@ module.exports = Self => {
if (sales && sales.length) {
const barcodes = await Self.rawSql(`
SELECT s.id saleFk, b.code, c.id
FROM sale s
LEFT JOIN itemBarcode b ON b.itemFk = s.itemFk
LEFT JOIN buy c ON c.itemFk = s.itemFk
LEFT JOIN entry e ON e.id = c.entryFk
LEFT JOIN travel tr ON tr.id = e.travelFk
FROM vn.sale s
LEFT JOIN vn.itemBarcode b ON b.itemFk = s.itemFk
LEFT JOIN vn.buy c ON c.itemFk = s.itemFk
LEFT JOIN vn.entry e ON e.id = c.entryFk
LEFT JOIN vn.travel tr ON tr.id = e.travelFk
WHERE s.ticketFk = ?
AND tr.landed >= util.VN_CURDATE() - INTERVAL 1 YEAR`,
[ticketId], myOptions);

View File

@ -1,39 +0,0 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket assign()', () => {
let ctx;
let options;
let tx;
beforeEach(async() => {
ctx = {
req: {
accessToken: {userId: 1106},
headers: {origin: 'http://localhost'},
__: value => value
},
args: {}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: ctx.req
});
options = {transaction: tx};
tx = await models.Sale.beginTransaction({});
options.transaction = tx;
});
afterEach(async() => {
await tx.rollback();
});
it('should throw an error when there are no picking tickets', async() => {
try {
await models.Collection.assign(ctx, options);
fail('Expected an error to be thrown, but none was thrown.');
} catch (e) {
expect(e.message).toEqual('There are not picking tickets');
}
});
});

View File

@ -1,36 +0,0 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('ticket assignCollection()', () => {
let ctx;
let options;
let tx;
beforeEach(async() => {
ctx = {
req: {
accessToken: {userId: 1106},
headers: {origin: 'http://localhost'},
__: value => value
},
args: {}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({active: ctx.req});
options = {transaction: tx};
tx = await models.Sale.beginTransaction({});
options.transaction = tx;
});
afterEach(async() => {
if (tx) await tx.rollback();
});
it('should throw an error when there is not picking tickets', async() => {
try {
await models.Collection.assignCollection(ctx, options);
} catch (e) {
expect(e.message).toEqual('There are not picking tickets');
}
});
});

View File

@ -1,54 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('collection getSales()', () => {
const collectionOrTicketFk = 999999;
const print = true;
const source = 'CHECKER';
const ctx = beforeAll.getCtx();
it('should return a collection with tickets, placements and barcodes settled correctly', async() => {
const tx = await models.Collection.beginTransaction({});
const options = {transaction: tx};
try {
const collection = await models.Collection.getSales(ctx,
collectionOrTicketFk, print, source, options);
const [firstTicket] = collection.tickets;
const [firstSale] = firstTicket.sales;
const [firstPlacement] = firstSale.placements;
expect(collection.tickets.length).toBeTruthy();
expect(collection.collectionFk).toEqual(firstTicket.ticketFk);
expect(firstSale.ticketFk).toEqual(firstTicket.ticketFk);
expect(firstSale.placements.length).toBeTruthy();
expect(firstSale.barcodes.length).toBeTruthy();
expect(firstSale.saleFk).toEqual(firstPlacement.saleFk);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should print a sticker', async() => {
const tx = await models.Collection.beginTransaction({});
const options = {transaction: tx};
const query = 'SELECT * FROM printQueue pq JOIN printQueueArgs pqa ON pqa.printQueueFk = pq.id';
try {
const printQueueBefore = await models.Collection.rawSql(
query, [], options);
await models.Collection.getSales(ctx,
collectionOrTicketFk, true, source, options);
const printQueueAfter = await models.Collection.rawSql(
query, [], options);
expect(printQueueAfter.length).toEqual(printQueueBefore.length + 1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,11 @@
const {models} = require('vn-loopback/server/server');
describe('getSectors()', () => {
it('return list of sectors', async() => {
let response = await models.Collection.getSectors();
expect(response.length).toBeGreaterThan(0);
expect(response[0].id).toEqual(1);
expect(response[0].description).toEqual('First sector');
});
});

View File

@ -1,7 +1,15 @@
const models = require('vn-loopback/server/server').models;
describe('collection getTickets()', () => {
const ctx = beforeAll.getCtx();
let ctx;
beforeAll(async() => {
ctx = {
req: {
accessToken: {userId: 9},
headers: {origin: 'http://localhost'}
}
};
});
it('should get tickets, sales and barcodes from collection', async() => {
const tx = await models.Collection.beginTransaction({});
@ -18,8 +26,8 @@ describe('collection getTickets()', () => {
expect(collectionTickets.tickets[1].ticketFk).toEqual(2);
expect(collectionTickets.tickets[2].ticketFk).toEqual(23);
expect(collectionTickets.tickets[0].sales[0].ticketFk).toEqual(1);
expect(collectionTickets.tickets[1].sales.length).toEqual(0);
expect(collectionTickets.tickets[2].sales.length).toEqual(0);
expect(collectionTickets.tickets[0].sales[1].ticketFk).toEqual(1);
expect(collectionTickets.tickets[0].sales[2].ticketFk).toEqual(1);
expect(collectionTickets.tickets[0].sales[0].Barcodes.length).toBeTruthy();
await tx.rollback();

View File

@ -1,7 +1,20 @@
const models = require('vn-loopback/server/server').models;
const LoopBackContext = require('loopback-context');
describe('setSaleQuantity()', () => {
beforeAll.mockLoopBackContext();
beforeAll(async() => {
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should change quantity sale', async() => {
const tx = await models.Ticket.beginTransaction({});

View File

@ -1,7 +1,6 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const path = require('path');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethod('deleteTrashFiles', {
@ -23,7 +22,7 @@ module.exports = Self => {
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!isProduction())
if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`);
const models = Self.app.models;

View File

@ -29,8 +29,7 @@ module.exports = Self => {
http: {
path: `/:id/downloadFile`,
verb: 'GET'
},
accessScopes: ['DEFAULT', 'read:multimedia']
}
});
Self.downloadFile = async function(ctx, id) {

View File

@ -1,17 +1,84 @@
const {models} = require('vn-loopback/server/server');
const fs = require('fs');
const {request, IncomingMessage} = require('http');
describe('dms uploadFile()', () => {
fdescribe('dms uploadFile()', () => {
it(`should return an error for a user without enough privileges`, async() => {
let clientId = 1101;
let ticketDmsTypeId = 14;
let ctx = {req: {accessToken: {userId: clientId}}, args: {dmsTypeId: ticketDmsTypeId}};
let ctx = {
req: {accessToken: {userId: clientId}},
args: {dmsTypeId: ticketDmsTypeId},
};
let error;
await models.Dms.uploadFile(ctx).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
await models.Dms.uploadFile(ctx)
.catch(e => {
error = e;
})
.finally(() => {
expect(error.message).toEqual(
`You don't have enough privileges`
);
});
expect(error).toBeDefined();
});
fit(`should return an error for a user without enough privileges`, async() => {
const filename = '2001A1010001.pdf';
const path = `${__dirname}/../../../../storage/pdfs/invoice/2001/1/1/${filename}`;
const fileContent = fs.readFileSync(path);
const {size} = fs.statSync(path);
let clientId = 1101;
let ticketDmsTypeId = 14;
const headers = {
'content-type': 'application/octet-stream', // Especifica el tipo de contenido como octet-stream
'content-length': size};
const fileStream = fs.createReadStream(path); // Reemplaza 'ruta/al/archivo.txt' con la ruta a tu archivo
const options = {
'method': 'POST',
'content-type': 'application/octet-stream', // Especifica el tipo de contenido como octet-stream
'content-length': size
};
const req = new IncomingMessage();
req.headers = {
'content-type': 'multipart/form-data',
};
// Simula la carga de archivos
req.pipe(fileStream);
const formidable = require('formidable')
const form = new formidable.IncomingForm();
form.parse(req, (err, fields, files) => {
// Asegúrate de manejar el resultado de la carga de archivos según tu lógica de aplicación
});
let ctx = {
req: {headers, accessToken: {userId: clientId}},
result: fileStream,
args: {dmsTypeId: ticketDmsTypeId},
};
ctx.req = {...ctx.req,
...new IncomingMessage({headers: {
'method': 'POST',
'content-type': 'application/octet-stream', // Especifica el tipo de contenido como octet-stream
'content-length': size
},
req
})};
let error;
await models.Dms.uploadFile(ctx, {'content-length': size})
.catch(e => {
error = e;
})
.finally(() => {
expect(error.message).toEqual(
`You don't have enough privileges`
);
});
expect(error).toBeDefined();
});

View File

@ -38,7 +38,7 @@ module.exports = Self => {
{
arg: 'hasFile',
type: 'Boolean',
description: 'True if has the original in paper'
description: 'True if has an attached file'
},
{
arg: 'hasFileAttached',

View File

@ -49,6 +49,7 @@ module.exports = Self => {
Self.uploadFile = async(ctx, options) => {
const models = Self.app.models;
const TempContainer = models.TempContainer;
const DmsContainer = models.DmsContainer;
const fileOptions = {};
const args = ctx.args;
@ -65,9 +66,9 @@ module.exports = Self => {
let srcFile;
try {
const hasWriteRole = await models.DmsType.hasWriteRole(ctx, args.dmsTypeId, myOptions);
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
// 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 TempContainer.container('dms');
@ -78,21 +79,19 @@ module.exports = Self => {
const addedDms = [];
for (const uploadedFile of files) {
const newDms = await createDms(ctx, uploadedFile, myOptions);
const pathHash = DmsContainer.getHash(newDms.id);
const file = await TempContainer.getFile(tempContainer.name, uploadedFile.name);
srcFile = path.join(file.client.root, file.container, file.name);
const data = {
workerFk: ctx.req.accessToken.userId,
dmsTypeFk: args.dmsTypeId,
companyFk: args.companyId,
warehouseFk: args.warehouseId,
reference: args.reference,
description: args.description,
contentType: uploadedFile.type,
hasFile: args.hasFile
};
const extension = await models.DmsContainer.getFileExtension(uploadedFile.name);
const newDms = await Self.createFromFile(data, extension, srcFile, myOptions);
const dmsContainer = await DmsContainer.container(pathHash);
const dstFile = path.join(dmsContainer.client.root, pathHash, newDms.file);
await fs.move(srcFile, dstFile, {
overwrite: true
});
addedDms.push(newDms);
}
@ -108,4 +107,27 @@ module.exports = Self => {
throw e;
}
};
async function createDms(ctx, file, myOptions) {
const models = Self.app.models;
const myUserId = ctx.req.accessToken.userId;
const args = ctx.args;
const newDms = await Self.create({
workerFk: myUserId,
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 = models.DmsContainer.getFileExtension(fileName);
fileName = `${newDms.id}.${extension}`;
return newDms.updateAttribute('file', fileName, myOptions);
}
};

View File

@ -4,45 +4,21 @@ module.exports = Self => {
/**
* Returns basic headers
*
* @param {string} cookie - The docuware cookie
* @return {object} - The headers
*/
Self.getOptions = async() => {
const docuwareConfig = await Self.app.models.DocuwareConfig.findOne();
const now = Date.vnNow();
let {url, username, password, token, expired} = docuwareConfig;
if (process.env.NODE_ENV && (!expired || expired < now + 60)) {
const {data: {IdentityServiceUrl}} = await axios.get(`${url}/Home/IdentityServiceInfo`);
const {data: {token_endpoint}} = await axios.get(`${IdentityServiceUrl}/.well-known/openid-configuration`);
const {data} = await axios.post(token_endpoint, {
grant_type: 'password',
scope: 'docuware.platform',
client_id: 'docuware.platform.net.client',
username,
password
}, {headers: {
'Accept': 'application/json',
'Content-Type': 'application/x-www-form-urlencoded'
}});
const newToken = data.access_token;
token = data.token_type + ' ' + newToken;
await docuwareConfig.updateAttributes({
token,
expired: now + data.expires_in
});
}
const headers = {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'Authorization': token
'Cookie': docuwareConfig.cookie
}
};
return {
url,
url: docuwareConfig.url,
headers
};
};

View File

@ -42,8 +42,7 @@ module.exports = Self => {
http: {
path: `/:id/download`,
verb: 'GET'
},
accessScopes: ['DEFAULT', 'read:multimedia']
}
});
Self.download = async function(id, fileCabinet, filter) {

View File

@ -2,54 +2,87 @@ const axios = require('axios');
const models = require('vn-loopback/server/server').models;
describe('Docuware core', () => {
const fileCabinetCode = 'deliveryNote';
beforeAll(async() => {
beforeAll(() => {
process.env.NODE_ENV = 'testing';
});
const docuwareInfo = await models.Docuware.findOne({
where: {
code: fileCabinetCode
}
afterAll(() => {
delete process.env.NODE_ENV;
});
describe('getOptions()', () => {
it('should return url and headers', async() => {
const result = await models.Docuware.getOptions();
expect(result.url).toBeDefined();
expect(result.headers).toBeDefined();
});
});
spyOn(axios, 'get').and.callFake(url => {
if (url.includes('IdentityServiceInfo')) return {data: {IdentityServiceUrl: 'IdentityServiceUrl'}};
if (url.includes('IdentityServiceUrl')) return {data: {token_endpoint: 'token_endpoint'}};
if (url.includes('dialogs')) {
return {
data: {
Dialog: [
{
DisplayName: 'find',
Id: 'getDialogTest'
}
]
}
};
}
describe('getDialog()', () => {
it('should return dialogId', async() => {
const dialogs = {
data: {
Dialog: [
{
DisplayName: 'find',
Id: 'getDialogTest'
}
]
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
if (url.includes('FileCabinets')) {
return {data: {
expect(result).toEqual('getDialogTest');
});
});
describe('getFileCabinet()', () => {
it('should return fileCabinetId', async() => {
const code = 'deliveryNote';
const docuwareInfo = await models.Docuware.findOne({
where: {
code
}
});
const dialogs = {
data: {
FileCabinet: [
{
Name: docuwareInfo.fileCabinetName,
Id: 'getFileCabinetTest'
}
]
}};
}
}
};
spyOn(axios, 'get').and.returnValue(new Promise(resolve => resolve(dialogs)));
const result = await models.Docuware.getFileCabinet(code);
expect(result).toEqual('getFileCabinetTest');
});
});
describe('get()', () => {
it('should return data without parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.get('deliveryNote');
expect(result.id).toEqual(1);
});
spyOn(axios, 'post').and.callFake(url => {
if (url.includes('token_endpoint')) {
return {data: {
access_token: 'access_token',
token_type: 'bearer',
expires_in: 10000
}};
}
if (url.includes('DialogExpression')) {
return {data: {
it('should return data with parse', async() => {
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
Items: [{
Fields: [
{
@ -70,52 +103,12 @@ describe('Docuware core', () => {
]
}]
}
};
}
});
});
afterAll(() => {
delete process.env.NODE_ENV;
});
describe('getOptions()', () => {
it('should return url and headers', async() => {
const result = await models.Docuware.getOptions();
expect(result.url).toBeDefined();
expect(result.headers).toBeDefined();
});
});
describe('Dialog()', () => {
it('should return dialogId', async() => {
const result = await models.Docuware.getDialog('deliveryNote', 'find', 'randomFileCabinetId');
expect(result).toEqual('getDialogTest');
});
});
describe('getFileCabinet()', () => {
it('should return fileCabinetId', async() => {
const result = await models.Docuware.getFileCabinet(fileCabinetCode);
expect(result).toEqual('getFileCabinetTest');
});
});
describe('get()', () => {
it('should return data without parse', async() => {
const [result] = await models.Docuware.get('deliveryNote');
expect(result.firstRequiredField).toEqual(1);
});
it('should return data with parse', async() => {
};
const parse = {
'firstRequiredField': 'id',
'secondRequiredField': 'name',
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const [result] = await models.Docuware.get('deliveryNote', null, parse);
expect(result.id).toEqual(1);
@ -126,14 +119,17 @@ describe('Docuware core', () => {
describe('getById()', () => {
it('should return data', async() => {
spyOn(models.Docuware, 'get');
await models.Docuware.getById('deliveryNote', 1);
spyOn(models.Docuware, 'getFileCabinet').and.returnValue((new Promise(resolve => resolve(Math.random()))));
spyOn(models.Docuware, 'getDialog').and.returnValue((new Promise(resolve => resolve(Math.random()))));
const data = {
data: {
id: 1
}
};
spyOn(axios, 'post').and.returnValue(new Promise(resolve => resolve(data)));
const result = await models.Docuware.getById('deliveryNote', 1);
expect(models.Docuware.get).toHaveBeenCalledWith(
'deliveryNote',
{condition: [Object({DBName: 'N__ALBAR_N', Value: [1]})]},
undefined
);
expect(result.id).toEqual(1);
});
});
});

View File

@ -1,6 +1,6 @@
const models = require('vn-loopback/server/server').models;
describe('docuware upload()', () => {
fdescribe('docuware upload()', () => {
const userId = 9;
const ticketIds = [10];
const ctx = {

View File

@ -1,6 +1,5 @@
const UserError = require('vn-loopback/util/user-error');
const axios = require('axios');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethodCtx('upload', {
@ -120,20 +119,20 @@ module.exports = Self => {
]
};
if (!isProduction(false))
if (process.env.NODE_ENV != 'production')
throw new UserError('Action not allowed on the test environment');
const BASE_URL = `${options.url}/FileCabinets/${fileCabinetId}/Documents`;
// delete old
const docuwareFile = await models.Docuware.checkFile(id, fileCabinet, false);
if (docuwareFile) {
const deleteJson = {
'Field': [{'FieldName': 'ESTADO', 'Item': 'Pendiente eliminar', 'ItemElementName': 'String'}]
};
const deleteUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, docuwareOptions.headers);
const deleteUri = `${BASE_URL}/${docuwareFile.id}/Fields`;
await axios.put(deleteUri, deleteJson, options.headers);
}
const uploadUri = `${docuwareOptions.url}/FileCabinets/${fileCabinetId}/Documents?StoreDialogId=${dialogId}`;
const uploadUri = `${BASE_URL}?StoreDialogId=${dialogId}`;
const FormData = require('form-data');
const data = new FormData();
@ -143,7 +142,7 @@ module.exports = Self => {
headers: {
'Content-Type': 'multipart/form-data',
'X-File-ModifiedDate': Date.vnNew(),
'Authorization': docuwareOptions.headers.headers.Authorization,
'Cookie': docuwareOptions.headers.headers.Cookie,
...data.getHeaders()
},
};

View File

@ -24,7 +24,7 @@ module.exports = Self => {
try {
const options = {transaction: tx, userId: ctx.req.accessToken.userId};
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileMultiConfig', null, options);
const files = await Self.rawSql('SELECT name, checksum, keyValue FROM edi.fileConfig', null, options);
const updatableFiles = [];
for (const file of files) {
@ -54,7 +54,7 @@ module.exports = Self => {
const tables = await Self.rawSql(`
SELECT fileName, toTable, file
FROM edi.tableMultiConfig
FROM edi.tableConfig
WHERE file IN (?)`, [fileNames], options);
for (const table of tables) {
@ -85,9 +85,9 @@ module.exports = Self => {
for (const file of updatableFiles) {
console.log(`Updating file ${file.name} checksum...`);
await Self.rawSql(`
UPDATE edi.fileMultiConfig
SET checksum = ?
WHERE name = ?`,
UPDATE edi.fileConfig
SET checksum = ?
WHERE name = ?`,
[file.checksum, file.name], options);
}
@ -228,7 +228,7 @@ module.exports = Self => {
await Self.rawSql(sqlTemplate, [filePath], options);
await Self.rawSql(`
UPDATE edi.tableMultiConfig
UPDATE edi.tableConfig
SET updated = ?
WHERE fileName = ?
`, [Date.vnNew(), baseName], options);

View File

@ -47,8 +47,7 @@ module.exports = Self => {
http: {
path: `/:collection/:size/:id/download`,
verb: 'GET'
},
accessScopes: ['DEFAULT', 'read:multimedia']
}
});
Self.download = async function(ctx, collection, size, id) {
@ -88,6 +87,6 @@ module.exports = Self => {
await fs.access(file.path);
const stream = fs.createReadStream(file.path);
return [stream, file.contentType, `filename="${fileName}"`];
return [stream, file.contentType, `filename="${file.name}"`];
};
};

View File

@ -1,7 +1,6 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethod('scrub', {
@ -44,7 +43,8 @@ module.exports = Self => {
Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
const $ = Self.app.models;
dryRun = dryRun || !isProduction(false);
const env = process.env.NODE_ENV;
dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],

View File

@ -4,21 +4,20 @@ describe('image download()', () => {
const collection = 'user';
const size = '160x160';
const employeeId = 1;
const developerId = 9;
const jessicaJonesId = 1110;
const ctx = {req: {accessToken: {userId: employeeId}}};
it('should return the image content-type of the user', async() => {
const image = await models.Image.download(ctx, collection, size, developerId);
const userId = 9;
const image = await models.Image.download(ctx, collection, size, userId);
const contentType = image[1];
expect(contentType).toEqual('image/png');
});
it('should return the user profile picture', async() => {
const image = await models.Image.download(ctx, collection, size, jessicaJonesId);
const fileName = image[2];
it(`should return false if the user doesn't have image`, async() => {
const userId = 1110;
const image = await models.Image.download(ctx, collection, size, userId);
expect(fileName).toMatch('1110.png');
expect(image).toBeFalse();
});
});

View File

@ -95,7 +95,10 @@ describe('image upload()', () => {
spyOn(containerModel, 'upload');
const ctx = {req: {accessToken: {userId: hhrrId}},
args: {id: itemId, collection: 'user'}
args: {
id: itemId,
collection: 'user'
}
};
try {
@ -106,7 +109,7 @@ describe('image upload()', () => {
});
it('should try to upload a file for the collection "catalog" and throw a privilege error', async() => {
const ctx = {req: {accessToken: {userId: 1}},
const ctx = {req: {accessToken: {userId: hhrrId}},
args: {
id: workerId,
collection: 'catalog'

View File

@ -1,7 +1,6 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs/promises');
const path = require('path');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethodCtx('upload', {
@ -42,7 +41,7 @@ module.exports = Self => {
if (!hasWriteRole)
throw new UserError(`You don't have enough privileges`);
if (!isProduction())
if (process.env.NODE_ENV == 'test')
throw new UserError(`Action not allowed on the test environment`);
// Upload file to temporary path

View File

@ -1,50 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('getVersion', {
description: 'gets app version data',
accessType: 'READ',
accepts: [{
arg: 'app',
type: 'string',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/getVersion`,
verb: 'GET'
}
});
Self.getVersion = async(ctx, app) => {
const {models} = Self.app;
const userId = ctx.req.accessToken.userId;
const workerFk = await models.WorkerAppTester.findOne({
where: {
workerFk: userId
}
});
let fields = ['id', 'appName'];
if (workerFk)
fields = fields.concat(['isVersionBetaCritical', 'versionBeta', 'urlBeta']);
else
fields = fields.concat(['isVersionCritical', 'version', 'urlProduction']);
const filter = {
where: {
appName: app
},
fields,
};
const result = await Self.findOne(filter);
return {
isVersionCritical: result?.isVersionBetaCritical ?? result?.isVersionCritical,
version: result?.versionBeta ?? result?.version,
url: result?.urlBeta ?? result?.urlProduction
};
};
};

View File

@ -1,29 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('mobileAppVersionControl getVersion()', () => {
const appName = 'delivery';
const appNameVersion = '9.2';
const appNameVersionBeta = '9.7';
beforeAll(async() => {
ctx = {
req: {
accessToken: {},
headers: {origin: 'http://localhost'},
}
};
});
it('should get the version app', async() => {
ctx.req.accessToken.userId = 9;
const {version} = await models.MobileAppVersionControl.getVersion(ctx, appName);
expect(version).toEqual(appNameVersion);
});
it('should get the beta version app', async() => {
ctx.req.accessToken.userId = 66;
const {version} = await models.MobileAppVersionControl.getVersion(ctx, appName);
expect(version).toEqual(appNameVersionBeta);
});
});

View File

@ -1,20 +0,0 @@
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:mrw="http://www.mrw.es/">
<soap:Header>
<mrw:AuthInfo>
<mrw:CodigoFranquicia><%= mrw.franchiseCode %></mrw:CodigoFranquicia>
<mrw:CodigoAbonado><%= clientType %></mrw:CodigoAbonado>
<mrw:CodigoDepartamento/>
<mrw:UserName><%= mrw.user %></mrw:UserName>
<mrw:Password><%= mrw.password %></mrw:Password>
</mrw:AuthInfo>
</soap:Header>
<soap:Body>
<mrw:CancelarEnvio>
<mrw:request>
<mrw:CancelaEnvio>
<mrw:NumeroEnvioOriginal><%= externalId %></mrw:NumeroEnvioOriginal>
</mrw:CancelaEnvio>
</mrw:request>
</mrw:CancelarEnvio>
</soap:Body>
</soap:Envelope>

View File

@ -1,48 +0,0 @@
const axios = require('axios');
const fs = require('fs');
const ejs = require('ejs');
const {DOMParser} = require('xmldom');
module.exports = Self => {
Self.remoteMethod('cancelShipment', {
description: 'Cancel a shipment by providing the expedition ID, interacting with MRW WebService',
accessType: 'WRITE',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: 'boolean',
root: true
},
http: {
path: `/cancelShipment`,
verb: 'POST'
}
});
Self.cancelShipment = async expeditionFk => {
const models = Self.app.models;
const mrw = await models.MrwConfig.findOne();
const {externalId} = await models.Expedition.findById(expeditionFk);
const clientType = await models.MrwConfig.getClientType(expeditionFk);
const template = fs.readFileSync(__dirname + '/cancelShipment.ejs', 'utf-8');
const renderedXml = ejs.render(template, {mrw, externalId, clientType});
const response = await axios.post(mrw.url, renderedXml, {
headers: {
'Content-Type': 'application/soap+xml; charset=utf-8'
}
});
const xmlString = response.data;
const parser = new DOMParser();
const xmlDoc = parser.parseFromString(xmlString, 'text/xml');
await Self.rawSql('CALL util.debugAdd(?,?);', ['cancelShipment', xmlDoc]);
const result = xmlDoc.getElementsByTagName('Mensaje')[0].textContent;
return result.toLowerCase().includes('se ha cancelado correctamente');
};
};

View File

@ -1,51 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:mrw="http://www.mrw.es/">
<soap:Header>
<mrw:AuthInfo>
<mrw:CodigoFranquicia><%= mrw.franchiseCode %></mrw:CodigoFranquicia>
<mrw:CodigoAbonado><%= clientType %></mrw:CodigoAbonado>
<mrw:CodigoDepartamento/>
<mrw:UserName><%= mrw.user %></mrw:UserName>
<mrw:Password><%= mrw.password %></mrw:Password>
</mrw:AuthInfo>
</soap:Header>
<soap:Body>
<mrw:TransmEnvio>
<mrw:request>
<mrw:DatosEntrega>
<mrw:Direccion>
<mrw:CodigoTipoVia/>
<mrw:Via><%= expeditionData.street %></mrw:Via>
<mrw:Numero/>
<mrw:Resto/>
<mrw:CodigoPostal><%= expeditionData.postalCode %></mrw:CodigoPostal>
<mrw:Poblacion><%= expeditionData.city %></mrw:Poblacion>
<mrw:Provincia/>
<mrw:CodigoPais/>
</mrw:Direccion>
<mrw:Nif><%= expeditionData.fi %></mrw:Nif>
<mrw:Nombre><%= expeditionData.clientName %></mrw:Nombre>
<mrw:Telefono><%= expeditionData.mobile %></mrw:Telefono>
<mrw:Observaciones><%= expeditionData.deliveryObservation %></mrw:Observaciones>
</mrw:DatosEntrega>
<mrw:DatosServicio>
<mrw:Fecha><%= expeditionData.created %></mrw:Fecha>
<mrw:Referencia><%= expeditionData.reference %></mrw:Referencia>
<mrw:CodigoServicio><%= expeditionData.serviceType %></mrw:CodigoServicio>
<mrw:NumeroBultos>1</mrw:NumeroBultos>
<mrw:EntregaSabado><%= expeditionData.weekDays %></mrw:EntregaSabado>
<mrw:Reembolso/>
<mrw:ImporteReembolso/>
<mrw:Bultos>
<mrw:BultoRequest>
<mrw:Alto><%= mrw.defaultHeight %></mrw:Alto>
<mrw:Largo><%= mrw.defaultLength %></mrw:Largo>
<mrw:Ancho><%= mrw.defaultWidth %></mrw:Ancho>
<mrw:Peso><%= mrw.defaultWeight %></mrw:Peso>
</mrw:BultoRequest>
</mrw:Bultos>
</mrw:DatosServicio>
</mrw:request>
</mrw:TransmEnvio>
</soap:Body>
</soap:Envelope>

View File

@ -1,91 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('createShipment', {
description: 'Create an expedition and return a base64Binary label from de MRW WebService',
accessType: 'WRITE',
accepts: [{
arg: 'expeditionFk',
type: 'number',
required: true
}],
returns: {
type: ['object'],
root: true
},
http: {
path: `/createShipment`,
verb: 'POST'
}
});
Self.createShipment = async expeditionFk => {
const models = Self.app.models;
const mrw = await Self.getConfig();
const clientType = await models.MrwConfig.getClientType(expeditionFk);
const today = Date.vnNew();
const [hours, minutes] = mrw?.expeditionDeadLine ? mrw.expeditionDeadLine.split(':').map(Number) : [0, 0];
const deadLine = Date.vnNew();
deadLine.setHours(hours, minutes, 0);
if (today > deadLine && (!mrw.notified || mrw.notified.setHours(0, 0, 0, 0) !== today.setHours(0, 0, 0, 0))) {
await models.NotificationQueue.create({notificationFk: 'mrw-deadline'});
await mrw.updateAttributes({notified: Date.vnNow()});
}
const query =
`SELECT
CASE co.code
WHEN 'ES' THEN a.postalCode
WHEN 'PT' THEN LEFT(a.postalCode, mc.portugalPostCodeTrim)
WHEN 'AD' THEN REPLACE(a.postalCode, 'AD', '00')
END postalCode,
a.city,
a.street,
co.code countryCode,
c.fi,
c.name clientName,
IFNULL(a.mobile, c.mobile) mobile,
DATE_FORMAT(t.shipped, '%d/%m/%Y') created,
t.shipped,
CONCAT( e.ticketFk, LPAD(e.counter, mc.counterWidth, '0')) reference,
LPAD(IF(mw.serviceType IS NULL, ms.serviceType, mw.serviceType), mc.serviceTypeWidth, '0') serviceType,
IF(mw.weekdays, 'S', 'N') weekDays,
ta.description deliveryObservation
FROM expedition e
JOIN ticket t ON e.ticketFk = t.id
JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN mrwService ms ON ms.agencyModeCodeFk = am.code
LEFT JOIN mrwServiceWeekday mw ON mw.agencyModeCodeFk = am.code
AND mw.weekDays & (1 << WEEKDAY(t.landed))
JOIN client c ON t.clientFk = c.id
JOIN address a ON t.addressFk = a.id
LEFT JOIN ticketObservation ta ON ta.ticketFk = t.id
AND ta.observationTypeFk IN (SELECT id FROM observationType ot WHERE ot.code = 'agency')
JOIN province p ON a.provinceFk = p.id
JOIN country co ON co.id = p.countryFk
JOIN mrwConfig mc
WHERE e.id = ?
LIMIT 1`;
const [expeditionData] = await Self.rawSql(query, [expeditionFk]);
if (expeditionData?.shipped.setHours(0, 0, 0, 0) < today.setHours(0, 0, 0, 0))
throw new UserError(`This ticket has a shipped date earlier than today`);
const shipmentResponse = await Self.sendXmlDoc(
__dirname + `/createShipment.ejs`,
{mrw, expeditionData, clientType},
'application/soap+xml'
);
const shipmentId = Self.getTextByTag(shipmentResponse, 'NumeroEnvio');
if (!shipmentId) throw new UserError(Self.getTextByTag(shipmentResponse, 'Mensaje'));
const file = await models.MrwConfig.getLabel(shipmentId, clientType);
return {shipmentId, file};
};
};

View File

@ -1,25 +0,0 @@
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:mrw="http://www.mrw.es/">
<soapenv:Header>
<mrw:AuthInfo>
<mrw:CodigoFranquicia><%= mrw.franchiseCode %></mrw:CodigoFranquicia>
<mrw:CodigoAbonado><%= clientType %></mrw:CodigoAbonado>
<mrw:CodigoDepartamento/>
<mrw:UserName><%= mrw.user %></mrw:UserName>
<mrw:Password><%= mrw.password %></mrw:Password>
</mrw:AuthInfo>
</soapenv:Header>
<soapenv:Body>
<mrw:GetEtiquetaEnvio>
<mrw:request>
<mrw:NumeroEnvio><%= shipmentId %></mrw:NumeroEnvio>
<mrw:NumerosEtiqueta>1</mrw:NumerosEtiqueta>
<mrw:SeparadorNumerosEnvio></mrw:SeparadorNumerosEnvio>
<mrw:FechaInicioEnvio></mrw:FechaInicioEnvio>
<mrw:FechaFinEnvio></mrw:FechaFinEnvio>
<mrw:TipoEtiquetaEnvio>0</mrw:TipoEtiquetaEnvio>
<mrw:ReportTopMargin>0</mrw:ReportTopMargin>
<mrw:ReportLeftMargin>0</mrw:ReportLeftMargin>
</mrw:request>
</mrw:GetEtiquetaEnvio>
</soapenv:Body>
</soapenv:Envelope>

View File

@ -1,37 +0,0 @@
module.exports = Self => {
Self.remoteMethod('getLabel', {
description: 'Return a base64Binary label from de MRW WebService',
accessType: 'READ',
accepts: [{
arg: 'shipmentId',
type: 'string',
required: true
},
{
arg: 'clientType',
type: 'string',
required: true
},
],
returns: {
type: 'string',
root: true
},
http: {
path: `/getLabel`,
verb: 'GET'
}
});
Self.getLabel = async(shipmentId, clientType) => {
const mrw = await Self.getConfig();
const getLabelResponse = await Self.sendXmlDoc(
__dirname + `/getLabel.ejs`,
{mrw, shipmentId, clientType},
'text/xml'
);
return Self.getTextByTag(getLabelResponse, 'EtiquetaFile');
};
};

View File

@ -1,159 +0,0 @@
const models = require('vn-loopback/server/server').models;
const axios = require('axios');
const fs = require('fs');
const filter = {notificationFk: 'mrw-deadline'};
const mockBase64Binary = 'base64BinaryString';
const ticket1 = {
'id': '44',
'clientFk': 1101,
'shipped': Date.vnNew(),
'nickname': 'MRW',
'addressFk': 1,
'agencyModeFk': 999
};
let expedition;
const expedition1 = {
'agencyModeFk': 999,
'ticketFk': 44,
'freightItemFk': 71,
'created': '2001-01-01',
'counter': 1,
'workerFk': 18,
'packagingFk': '94',
'hostFk': '',
'stateTypeFk': 3,
'hasNewRoute': 0,
'isBox': 71,
'editorFk': 100
};
describe('MRWConfig createShipment()', () => {
beforeAll(async() => {
await models.Agency.create(
{'id': 999, 'name': 'mrw'}
);
await models.AgencyMode.create(
{'id': 999, 'name': 'mrw', 'agencyFk': 999, 'code': 'mrw'}
);
await models.MrwService.create(
{'agencyModeCodeFk': 'mrw', 'clientType': '000001', 'serviceType': 105, 'kg': 10}
);
await createMrwConfig();
await models.Ticket.create(ticket1);
expedition = await models.Expedition.create(expedition1);
});
afterAll(async() => {
await cleanFixtures();
await models.Ticket.destroyAll(ticket1);
await models.Expedition.destroyAll(ticket1);
});
beforeEach(async() => {
const mockPostResponses = [
{data: fs.readFileSync(__dirname + '/mockGetLabel.xml', 'utf-8')},
{data: fs.readFileSync(__dirname + '/mockCreateShipment.xml', 'utf-8')}
];
spyOn(axios, 'post').and.callFake(() => Promise.resolve(mockPostResponses.pop()));
await cleanFixtures();
});
async function cleanFixtures() {
await models.NotificationQueue.destroyAll(filter);
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: null, notified: null});
}
async function createMrwConfig() {
await models.MrwConfig.create(
{
'id': 1,
'url': 'https://url.com',
'user': 'user',
'password': 'password',
'franchiseCode': 'franchiseCode',
'subscriberCode': 'subscriberCode',
'clientTypeWidth': 6
}
);
}
async function getLastNotification() {
return models.NotificationQueue.findOne({
order: 'id DESC',
where: filter
});
}
it('should create a shipment and return a base64Binary label', async() => {
const {file} = await models.MrwConfig.createShipment(expedition.id);
expect(file).toEqual(mockBase64Binary);
});
it('should fail if mrwConfig has no data', async() => {
let error;
await models.MrwConfig.destroyAll();
await models.MrwConfig.createShipment(expedition.id).catch(e => {
error = e;
}).finally(async() => {
expect(error.message).toEqual(`MRW service is not configured`);
});
await createMrwConfig();
expect(error).toBeDefined();
});
it('should fail if expeditionFk is not a MrwExpedition', async() => {
let error;
await models.MrwConfig.createShipment(15).catch(e => {
error = e;
}).finally(async() => {
expect(error.message).toEqual(`ClientType not available`);
});
});
it('should fail if the creation date of this ticket is before the current date', async() => {
let error;
const yesterday = Date.vnNew();
yesterday.setDate(yesterday.getDate() - 1);
await models.Ticket.updateAll({id: ticket1.id}, {shipped: yesterday});
await models.MrwConfig.createShipment(expedition.id).catch(e => {
error = e;
}).finally(async() => {
expect(error.message).toEqual(`This ticket has a shipped date earlier than today`);
});
await models.Ticket.updateAll({id: ticket1.id}, {shipped: Date.vnNew()});
});
it('should send mail if you are past the dead line and is not notified today', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: null});
await models.MrwConfig.createShipment(expedition.id);
const notification = await getLastNotification();
expect(notification.notificationFk).toEqual(filter.notificationFk);
});
it('should send mail if you are past the dead line and it is notified from another day', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: new Date()});
await models.MrwConfig.createShipment(expedition.id);
const notification = await getLastNotification();
expect(notification.notificationFk).toEqual(filter.notificationFk);
});
it('should not send mail if you are past the dead line and it is notified', async() => {
await models.MrwConfig.updateAll({id: 1}, {expeditionDeadLine: '10:00:00', notified: Date.vnNew()});
await models.MrwConfig.createShipment(expedition.id);
const notification = await getLastNotification();
expect(notification).toEqual(null);
});
});

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://www.w3.org/2003/05/soap-envelope"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<TransmEnvioResponse xmlns="http://www.mrw.es/">
<TransmEnvioResult>
<Estado>1</Estado>
<Mensaje />
<NumeroSolicitud>1</NumeroSolicitud>
<NumeroEnvio>1</NumeroEnvio>
<Url>http://url.com</Url>
</TransmEnvioResult>
</TransmEnvioResponse>
</soap:Body>
</soap:Envelope>

View File

@ -1,14 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Body>
<GetEtiquetaEnvioResponse xmlns="http://www.mrw.es/">
<GetEtiquetaEnvioResult>
<Estado>1</Estado>
<Mensaje />
<EtiquetaFile>base64BinaryString</EtiquetaFile>
</GetEtiquetaEnvioResult>
</GetEtiquetaEnvioResponse>
</soap:Body>
</soap:Envelope>

View File

@ -45,6 +45,7 @@ module.exports = Self => {
});
availableNotificationsMap.delete(active.notificationFk);
}
return {
active: [...activeNotificationsMap.entries()],
available: [...availableNotificationsMap.entries()]

View File

@ -1,5 +1,4 @@
const {Email} = require('vn-print');
const isProduction = require('vn-loopback/server/boot/isProduction');
module.exports = Self => {
Self.remoteMethod('send', {
@ -71,7 +70,7 @@ module.exports = Self => {
const newParams = Object.assign({}, queueParams, sendParams);
const email = new Email(queueName, newParams);
if (isProduction())
if (process.env.NODE_ENV != 'test')
await email.send();
await queue.updateAttribute('status', statusSent);

View File

@ -4,10 +4,10 @@ describe('NotificationSubscription getList()', () => {
it('should return a list of available and active notifications of a user', async() => {
const userId = 9;
const {active, available} = await models.NotificationSubscription.getList(userId);
const notifications = await models.NotificationSubscription.getAvailable(userId);
const totalAvailable = notifications.size - active.length;
const notifications = await models.Notification.find({});
const totalAvailable = notifications.length - active.length;
expect(active.length).toEqual(3);
expect(active.length).toEqual(2);
expect(available.length).toEqual(totalAvailable);
});
});

View File

@ -35,18 +35,11 @@ module.exports = Self => {
let html = `<strong>Motivo</strong>:<br/>${reason}<br/>`;
html += `<strong>Usuario</strong>:<br/>${ctx.req.accessToken.userId} ${emailUser.email}<br/>`;
delete additionalData.backError.config.headers.Authorization;
const httpRequest = JSON.parse(additionalData?.httpRequest);
if (httpRequest)
delete httpRequest.config.headers.Authorization;
additionalData.httpRequest = httpRequest;
for (const data in additionalData)
html += `<strong>${data}</strong>:<br/>${tryParse(additionalData[data])}<br/>`;
const subjectReason = httpRequest?.data?.error;
await smtp.send({
const subjectReason = JSON.parse(additionalData?.httpRequest)?.data?.error;
smtp.send({
to: `${config.app.reportEmail}, ${emailUser.email}`,
subject:
'[Support-Salix] ' +

View File

@ -1,73 +0,0 @@
const {ParameterizedSQL} = require('loopback-connector');
const {buildFilter} = require('vn-loopback/util/filter');
module.exports = Self => {
Self.remoteMethod('filter', {
description:
'Find all postcodes of the model matched by postcode, town, province or country.',
accessType: 'READ',
accepts: [
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
},
],
returns: {
type: ['object'],
root: true,
},
http: {
path: `/filter`,
verb: 'GET',
},
});
Self.filter = async(filter = {}, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const conn = Self.dataSource.connector;
const where = buildFilter(filter?.where, (param, value) => {
switch (param) {
case 'search':
return {
or: [
{'pc.code': {like: `%${value}%`}},
{'t.name': {like: `%${value}%`}},
{'p.name': {like: `%${value}%`}},
{'c.name': {like: `%${value}%`}}
]
};
}
}) ?? {};
delete filter.where;
const stmts = [];
let stmt;
stmt = new ParameterizedSQL(`
SELECT
pc.townFk,
t.provinceFk,
p.countryFk,
pc.code,
t.name as town,
p.name as province,
c.name country
FROM
postCode pc
JOIN town t on t.id = pc.townFk
JOIN province p on p.id = t.provinceFk
JOIN country c on c.id = p.countryFk
`);
stmt.merge(conn.makeSuffix({where}));
stmt.merge(conn.makeLimit(filter));
const itemsIndex = stmts.push(stmt) - 1;
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return itemsIndex === 0 ? result : result[itemsIndex];
};
};

View File

@ -1,92 +0,0 @@
const {models} = require('vn-loopback/server/server');
describe('Postcode filter()', () => {
it('should retrieve with no filter', async() => {
const tx = await models.Postcode.beginTransaction({});
const options = {transaction: tx};
try {
const results = await models.Postcode.filter({
limit: 1
}, options);
expect(results.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should retrieve with filter as postcode', async() => {
const tx = await models.Postcode.beginTransaction({});
const options = {transaction: tx};
try {
const results = await models.Postcode.filter({
where: {
search: 46,
}
}, options);
expect(results.length).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should retrieve with filter as city', async() => {
const tx = await models.Postcode.beginTransaction({});
const options = {transaction: tx};
try {
const results = await models.Postcode.filter({where: {
search: 'Alz',
}}, options);
expect(results.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should retrieve with filter as province', async() => {
const tx = await models.Postcode.beginTransaction({});
const options = {transaction: tx};
try {
const results = await models.Postcode.filter({where: {
search: 'one',
}}, options);
expect(results.length).toEqual(5);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should retrieve with filter as country', async() => {
const tx = await models.Postcode.beginTransaction({});
const options = {transaction: tx};
try {
const results = await models.Postcode.filter({
where: {
search: 'Ec',
}
}, options);
expect(results.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -1,88 +0,0 @@
const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
const moment = require('moment');
module.exports = Self => {
Self.remoteMethod('sendOrders', {
description: 'Sends a set of orders',
accessType: 'WRITE',
accepts: [{
arg: 'tickets',
type: ['number'],
required: true
}
],
returns: {
type: 'string',
root: true
},
http: {
path: `/sendOrders`,
verb: 'POST'
}
});
Self.sendOrders = async tickets => {
const config = await Self.app.models.QuadmindsApiConfig.findOne();
if (!config) throw new UserError('Config params not set');
if (tickets.length > config.maxObjects)
throw new UserError(`Quadminds does not support more than ${config.maxObjects} tickets`);
let poisData = [];
let isOk;
for (let offset = 0; !isOk; offset = offset + config.limit) {
const pois = await axios.get(`${config.url}pois/search?limit=${config.limit}&offset=${offset}`, {
headers: {
'Accept': 'application/json',
'X-Saas-Apikey': config.key
}
});
pois.data.data.length ? poisData.push(...pois.data.data) : isOk = true;
}
const poiMap = new Map(poisData.map(poi => [poi.code, poi._id]));
let orders = await Self.rawSql(`
SELECT a.id poiCode,
t.id code,
t.shipped date,
'PEDIDO' operation,
t.totalWithVat totalAmount,
t.totalWithoutVat totalAmountWithoutTaxes,
SUM(sv.volume) volume
FROM ticket t
JOIN address a ON a.id = t.addressFk
JOIN saleVolume sv ON sv.ticketFk = t.id
WHERE t.id IN (?)
GROUP BY t.id
`, [tickets]);
// Transformo code en string ya que lo obtenermos como integer
orders = orders.map(order => {
return {
...order,
poiId: poiMap.get(order.poiCode.toString()) || undefined,
code: order.code.toString(),
date: moment(order.date).format('YYYY-MM-DD'),
totalAmount: order.totalAmount || undefined,
totalAmountWithoutTaxes: order.totalAmountWithoutTaxes || undefined,
timeWindow: [{
from: config.orderTimeFrom,
to: config.orderTimeTo
}],
orderMeasures: [{
constraintId: 3, // Volumen
value: order.volume
}]
};
});
await axios.post(`${config.url}orders`, orders, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Saas-Apikey': config.key
}
});
};
};

View File

@ -1,87 +0,0 @@
const axios = require('axios');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('sendPois', {
description: 'Sends a set of pois',
accessType: 'WRITE',
accepts: [{
arg: 'tickets',
type: ['number'],
required: true
}
],
returns: {
type: 'string',
root: true
},
http: {
path: `/sendPois`,
verb: 'POST'
}
});
Self.sendPois = async tickets => {
const config = await Self.app.models.QuadmindsApiConfig.findOne();
if (!config) throw new UserError('Config params not set');
if (tickets.length > config.maxObjects)
throw new UserError(`Quadminds does not support more than ${config.maxObjects} tickets`);
let pois = await Self.rawSql(`
WITH deliveryNotes AS (
SELECT t.id, t.routeFk, tn.description
FROM ticket t
JOIN ticketObservation tn ON tn.ticketFk = t.id
JOIN observationType ot ON ot.id = tn.observationTypeFk
WHERE ot.code = 'delivery'
)
SELECT a.id code,
c.socialName name,
IF(ABS(a.latitude - ROUND(a.latitude)) < 0.000001, NULL, a.latitude) latitude,
IF(ABS(a.longitude - ROUND(a.longitude)) < 0.000001, NULL, a.longitude) longitude,
a.street,
a.city locality,
p.name state,
co.name country,
CONCAT_WS(', ', IFNULL(a.street, ''), IFNULL(a.city, ''), IFNULL(p.name, '')) longAddress,
CONCAT(IFNULL(a.mobile, c.mobile)) phoneNumber,
dn.description poiDeliveryComments,
c.email email
FROM ticket t
JOIN address a ON a.id = t.addressFk
JOIN province p ON p.id = a.provinceFk
JOIN country co ON co.id = p.countryFk
JOIN client c ON c.id = t.clientFk
LEFT JOIN deliveryNotes dn ON dn.id = t.id
WHERE t.id IN (?)
GROUP BY t.id
`, [tickets]);
// Transformo code en string ya que lo obtenermos como integer
pois = pois.map(poi => {
return {
...poi,
code: poi.code.toString(),
latitude: poi.latitude || undefined,
longitude: poi.longitude || undefined,
address: {
street: poi.street || undefined,
locality: poi.locality || undefined,
state: poi.state || undefined,
country: poi.country || undefined
},
poiDeliveryComments: poi.poiDeliveryComments || undefined,
phoneNumber: poi.phoneNumber || undefined,
email: poi.email || undefined
};
});
await axios.post(`${config.url}pois`, pois, {
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-Saas-Apikey': config.key
}
});
};
};

View File

@ -1,7 +1,22 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('getStarredModules()', () => {
const ctx = beforeAll.getCtx();
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {req: activeCtx};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it(`should return the starred modules for a given user`, async() => {
const newStarred = await models.StarredModule.create({workerFk: 9, moduleFk: 'customer', position: 1});

View File

@ -1,8 +1,24 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('setPosition()', () => {
const ctx = beforeAll.getCtx();
beforeAll.mockLoopBackContext();
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {
req: activeCtx
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should increase the orders module position by replacing it with clients and vice versa', async() => {
const tx = await models.StarredModule.beginTransaction({});

View File

@ -1,7 +1,24 @@
const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('toggleStarredModule()', () => {
const ctx = beforeAll.getCtx();
const activeCtx = {
accessToken: {userId: 9},
http: {
req: {
headers: {origin: 'http://localhost'}
}
}
};
const ctx = {
req: activeCtx
};
beforeEach(() => {
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
active: activeCtx
});
});
it('should create a new starred module and then remove it by calling the method again with same args', async() => {
const starredModule = await models.StarredModule.toggleStarredModule(ctx, 'order');
@ -9,7 +26,7 @@ describe('toggleStarredModule()', () => {
expect(starredModules.length).toEqual(1);
expect(starredModule.moduleFk).toEqual('order');
expect(starredModule.workerFk).toEqual(ctx.req.accessToken.userId);
expect(starredModule.workerFk).toEqual(activeCtx.accessToken.userId);
expect(starredModule.position).toEqual(starredModules.length);
await models.StarredModule.toggleStarredModule(ctx, 'order');

View File

@ -19,12 +19,12 @@ module.exports = Self => {
}
});
Self.getUrl = async(appName = 'salix') => {
const url = await Self.app.models.Url.findOne({
const {url} = await Self.app.models.Url.findOne({
where: {
appName,
environment: process.env.NODE_ENV || 'development'
enviroment: process.env.NODE_ENV || 'development'
}
});
return url?.url;
return url;
};
};

View File

@ -1,12 +1,12 @@
const models = require('vn-loopback/server/server').models;
describe('userConfig getUserConfig()', () => {
const ctx = beforeAll.getCtx();
it(`should return the configuration data of a given user`, async() => {
const tx = await models.Item.beginTransaction({});
const options = {transaction: tx};
try {
const ctx = {req: {accessToken: {userId: 9}}};
const result = await models.UserConfig.getUserConfig(ctx, options);
expect(result.warehouseFk).toEqual(1);

View File

@ -20,7 +20,7 @@ module.exports = Self => {
}
});
Self.internationalExpedition = async (expeditionFk) => {
Self.internationalExpedition = async expeditionFk => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({

View File

@ -24,7 +24,7 @@ module.exports = Self => {
const models = Self.app.models;
const viaexpressConfig = await models.ViaexpressConfig.findOne({
fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType', 'agencyModeFk']
fields: ['client', 'user', 'password', 'defaultWeight', 'deliveryType']
});
const expedition = await models.Expedition.findOne({
@ -34,7 +34,7 @@ module.exports = Self => {
{
relation: 'ticket',
scope: {
fields: ['shipped', 'addressFk', 'clientFk', 'companyFk', 'agencyModeFk'],
fields: ['shipped', 'addressFk', 'clientFk', 'companyFk'],
include: [
{
relation: 'client',
@ -102,6 +102,7 @@ module.exports = Self => {
}
]
}
}
]
});
@ -109,15 +110,13 @@ module.exports = Self => {
const ticket = expedition.ticket();
const sender = ticket.company().client();
const shipped = ticket.shipped.toISOString();
const isInterdia = (ticket.agencyModeFk === viaexpressConfig.agencyModeFk);
const data = {
viaexpressConfig,
sender,
senderAddress: sender.defaultAddress(),
client: ticket.client(),
address: ticket.address(),
shipped,
isInterdia
shipped
};
const template = fs.readFileSync(__dirname + '/template.ejs', 'utf-8');

View File

@ -13,7 +13,7 @@
<Asegurado>0</Asegurado>
<Imprimir>0</Imprimir>
<ConDevolucionAlbaran>0</ConDevolucionAlbaran>
<Intradia><%= isInterdia %></Intradia>
<Intradia>0</Intradia>
<Observaciones></Observaciones>
<AlbaranRemitente></AlbaranRemitente>
<Modo>0</Modo>

View File

@ -1,29 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('killSession', {
description: 'Kill session',
accepts: [{
arg: 'userId',
type: 'integer',
description: 'The user id',
required: true,
}, {
arg: 'created',
type: 'date',
description: 'The created time',
required: true,
}],
accessType: 'WRITE',
http: {
path: `/killSession`,
verb: 'POST'
}
});
Self.killSession = async function(ctx, userId, created) {
await Self.app.models.VnUser.userSecurity(ctx, ctx.req.accessToken.userId);
const tokens = await Self.app.models.AccessToken.find({where: {userId, created}});
if (!tokens?.length) return;
for (const token of tokens)
await Self.app.models.AccessToken.deleteById(token.id);
};
};

View File

@ -1,72 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('acls', {
description: 'Get all of the current user acls',
returns: {
type: 'Object',
root: true
},
http: {
path: '/acls',
verb: 'GET'
}
});
const staticAcls = new Map();
const app = require('vn-loopback/server/server');
app.on('started', function() {
for (const model of app.models()) {
for (const acl of model.settings.acls) {
if (acl.principalType == 'ROLE' && acl.permission == 'ALLOW') {
const staticAcl = {
model: model.name,
property: '*',
accessType: acl.accessType,
permission: acl.permission,
principalType: acl.principalType,
principalId: acl.principalId,
};
if (staticAcls.has(acl.principalId))
staticAcls.get(acl.principalId).push(staticAcl);
else
staticAcls.set(acl.principalId, [staticAcl]);
}
}
}
});
Self.acls = async function(ctx) {
const models = Self.app.models;
const acls = [];
const userId = ctx.req.accessToken.userId;
if (userId) {
const roleMapping = await models.RoleMapping.find({
where: {
principalId: userId
},
include: [
{
relation: 'role',
scope: {
fields: [
'name'
]
}
}
]
});
const dynamicAcls = await models.ACL.find({
where: {
principalId: {
inq: roleMapping.map(rm => rm.role().name)
}
}
});
dynamicAcls.forEach(acl => acls.push(acl));
staticAcls.get('$authenticated').forEach(acl => acls.push(acl));
} else
staticAcls.get('$unauthenticated').forEach(acl => acls.push(acl));
staticAcls.get('$everyone').forEach(acl => acls.push(acl));
return acls;
};
};

View File

@ -12,55 +12,38 @@ module.exports = Self => {
http: {
path: `/renewToken`,
verb: 'POST'
},
accessScopes: ['DEFAULT', 'read:multimedia']});
}
});
Self.renewToken = async function(ctx) {
let createTokenOptions = {};
let token; let isNotExceeded;
try {
token = ctx.req.accessToken;
const {accessToken: token} = ctx.req;
const {courtesyTime} = await models.AccessTokenConfig.findOne({
fields: ['courtesyTime']
});
isNotExceeded = await Self.validateToken(ctx);
if (isNotExceeded)
return token;
// Check if current token is valid
// Schedule to remove current token
setTimeout(async() => {
try {
await Self.logout(token.id);
} catch (error) {
// FIXME: Crash if do throw new Error(error)
}
}, courtesyTime * 1000);
const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({
fields: ['renewPeriod', 'courtesyTime']
});
const now = Date.now();
const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime;
if (isNotExceeded)
return token;
// Get scopes
const {scopes} = token;
if (scopes)
createTokenOptions = {scopes: [scopes[0]]};
// Create new accessToken
const user = await Self.findById(token.userId);
const accessToken = await user.accessTokens.create(createTokenOptions);
// Schedule to remove current token
setTimeout(async() => {
try {
await Self.logout(token.id);
} catch (err) {
// eslint-disable-next-line no-console
console.error(err);
}
}, courtesyTime * 1000);
return {id: accessToken.id, ttl: accessToken.ttl};
} catch (error) {
const body = {
error: error.message,
userId: token?.userId ?? null,
token: token?.id,
scopes: token?.scopes,
createTokenOptions,
isNotExceeded
};
await handleError(JSON.stringify(body));
throw new Error(error);
}
// Create new accessToken
const user = await Self.findById(token.userId);
const accessToken = await user.createAccessToken();
return {id: accessToken.id, ttl: accessToken.ttl};
};
};
async function handleError(body) {
await models.Application.rawSql('CALL util.debugAdd(?,?);', ['renewToken', body]);
}

View File

@ -1,27 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('shareToken', {
description: 'Returns token to view files or images and share it',
accessType: 'WRITE',
accepts: [],
returns: {
type: 'Object',
root: true
},
http: {
path: `/shareToken`,
verb: 'GET'
}
});
Self.shareToken = async function(ctx) {
const {accessToken: token} = ctx.req;
const user = await Self.findById(token.userId);
const multimediaToken = await user.accessTokens.create({
scopes: ['read:multimedia']
});
return {multimediaToken};
};
};

View File

@ -67,9 +67,7 @@ module.exports = Self => {
if (vnUser.twoFactor === 'email') {
const $ = Self.app.models;
const min = 100000;
const max = 999999;
const code = String(Math.floor(Math.random() * (max - min + 1)) + min);
const code = String(Math.floor(Math.random() * 999999));
const maxTTL = ((60 * 1000) * 5); // 5 min
await $.AuthCode.upsertWithWhere({userFk: vnUser.id}, {
userFk: vnUser.id,

View File

@ -1,27 +0,0 @@
const {models} = require('vn-loopback/server/server');
const id = {administrative: 5, employee: 1, productionBoss: 50};
describe('VnUser acls()', () => {
it('should get its owns acls', async() => {
expect(await hasAcl('administrative', id.administrative)).toBeTruthy();
expect(await hasAcl('productionBoss', id.productionBoss)).toBeTruthy();
});
it('should not get administrative acls', async() => {
expect(await hasAcl('administrative', id.employee)).toBeFalsy();
});
it('should get the $authenticated acls', async() => {
expect(await hasAcl('$authenticated', id.employee)).toBeTruthy();
});
it('should get the $everyone acls', async() => {
expect(await hasAcl('$everyone', id.employee)).toBeTruthy();
});
});
const hasAcl = async(role, userId) => {
const ctx = {req: {accessToken: {userId}, headers: {origin: 'http://localhost'}}};
const acls = await models.VnUser.acls(ctx);
return Object.values(acls).some(acl => acl.principalId === role);
};

View File

@ -28,25 +28,11 @@ describe('Renew Token', () => {
});
it('should renew token', async() => {
const {courtesyTime} = await models.AccessTokenConfig.findOne({
fields: ['courtesyTime']
});
const mockDate = new Date(startingTime + 26600000);
jasmine.clock().mockDate(mockDate);
const {id} = await models.VnUser.renewToken(ctx);
expect(id).not.toEqual(ctx.req.accessToken.id);
await models.VnUser.logout(ctx.req.accessToken.id);
jasmine.clock().tick((courtesyTime + 10) * 1000);
let tokenNotExists;
try {
tokenNotExists = await models.AccessToken.findById(ctx.req.accessToken.id);
} catch (e) {
error = e;
}
expect(tokenNotExists).toBeNull();
});
it('NOT should renew', async() => {
@ -61,21 +47,4 @@ describe('Renew Token', () => {
expect(error).toBeUndefined();
expect(response.id).toEqual(ctx.req.accessToken.id);
});
it('throw error', async() => {
let error;
try {
await models.VnUser.renewToken({req: {token: null}});
} catch (e) {
error = e;
}
expect(error).toBeDefined();
const query = 'SELECT * FROM util.debug WHERE variable = "renewToken"';
const debugLog = await models.Application.rawSql(query);
expect(debugLog.length).toEqual(1);
});
});

View File

@ -1,64 +0,0 @@
const {models} = require('vn-loopback/server/server');
const TOKEN_MULTIMEDIA = 'read:multimedia';
describe('Share Token', () => {
let ctx = null;
const startingTime = Date.now();
let multimediaToken = null;
beforeAll(async() => {
const unAuthCtx = {
req: {
headers: {},
connection: {
remoteAddress: '127.0.0.1'
},
getLocale: () => 'en'
},
args: {}
};
let login = await models.VnUser.signIn(unAuthCtx, 'salesAssistant', 'nightmare');
let accessToken = await models.AccessToken.findById(login.token);
ctx = {req: {accessToken: accessToken}};
});
beforeEach(async() => {
multimediaToken = await models.VnUser.shareToken(ctx);
jasmine.clock().install();
jasmine.clock().mockDate(new Date(startingTime));
});
afterEach(() => {
jasmine.clock().uninstall();
});
it('should generate token', async() => {
expect(Object.keys(multimediaToken).length).toEqual(1);
expect(multimediaToken.multimediaToken.userId).toEqual(ctx.req.accessToken.userId);
expect(multimediaToken.multimediaToken.scopes[0]).toEqual(TOKEN_MULTIMEDIA);
});
it('NOT should renew', async() => {
let error;
let response;
try {
response = await models.VnUser.renewToken(ctx);
} catch (e) {
error = e;
}
expect(error).toBeUndefined();
expect(response.id).toEqual(ctx.req.accessToken.id);
});
it('should renew token', async() => {
const mockDate = new Date(startingTime + 26600000);
jasmine.clock().mockDate(mockDate);
const newShareToken = await models.VnUser.renewToken({req: {accessToken: multimediaToken.multimediaToken}});
const {id} = newShareToken;
expect(id).not.toEqual(ctx.req.accessToken.id);
const newMultimediaToken = await models.AccessToken.findById(id);
expect(newMultimediaToken.scopes[0]).toEqual(TOKEN_MULTIMEDIA);
});
});

View File

@ -24,10 +24,6 @@ module.exports = Self => {
arg: 'lang',
type: 'string',
description: 'The user lang'
}, {
arg: 'twoFactor',
type: 'any',
description: 'The user twoFactor'
}
],
http: {
@ -36,8 +32,8 @@ module.exports = Self => {
}
});
Self.updateUser = async(ctx, id, name, nickname, email, lang, twoFactor) => {
Self.updateUser = async(ctx, id, name, nickname, email, lang) => {
await Self.userSecurity(ctx, id);
await Self.upsertWithWhere({id}, {name, nickname, email, lang, twoFactor});
await Self.upsertWithWhere({id}, {name, nickname, email, lang});
};
};

View File

@ -58,7 +58,7 @@ module.exports = Self => {
fields: ['name', 'twoFactor']
}, myOptions);
if (user.name.toLowerCase() !== username.toLowerCase())
if (user.name !== username)
throw new UserError('Authentication failed');
await authCode.destroy(myOptions);

View File

@ -1,30 +0,0 @@
const {models} = require('vn-loopback/server/server');
module.exports = Self => {
Self.remoteMethodCtx('validateToken', {
description: 'Validates the current logged user token',
accepts: [],
accessType: 'READ',
returns: {
type: 'Boolean',
root: true
},
http: {
path: `/validateToken`,
verb: 'GET'
}
});
Self.validateToken = async function(ctx) {
const {accessToken: token} = ctx.req;
// Check if current token is valid
const {renewPeriod, courtesyTime} = await models.AccessTokenConfig.findOne({
fields: ['renewPeriod', 'courtesyTime']
});
const now = Date.now();
const differenceMilliseconds = now - token.created;
const differenceSeconds = Math.floor(differenceMilliseconds / 1000);
const isNotExceeded = differenceSeconds < renewPeriod - courtesyTime;
return isNotExceeded;
};
};

View File

@ -1,50 +0,0 @@
module.exports = Self => {
Self.remoteMethodCtx('add', {
description: 'Add activity if the activity is different or is the same but have exceed time for break',
accessType: 'WRITE',
accepts: [
{
arg: 'code',
type: 'string',
description: 'Code for activity'
},
{
arg: 'model',
type: 'string',
description: 'Origin model from insert'
},
],
http: {
path: `/add`,
verb: 'POST'
}
});
Self.add = async(ctx, code, model, options) => {
const userId = ctx.req.accessToken.userId;
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
return await Self.rawSql(`
INSERT INTO workerActivity (workerFk, workerActivityTypeFk, model)
SELECT ?, ?, ?
FROM workerTimeControlConfig wtcc
LEFT JOIN (
SELECT wa.workerFk,
wa.created,
wat.code
FROM workerActivity wa
LEFT JOIN workerActivityType wat ON wat.code = wa.workerActivityTypeFk
WHERE wa.workerFk = ?
ORDER BY wa.created DESC
LIMIT 1
) sub ON TRUE
WHERE sub.workerFk IS NULL
OR sub.code <> ?
OR TIMESTAMPDIFF(SECOND, sub.created, util.VN_NOW()) > wtcc.dayBreak;`
, [userId, code, model, userId, code], myOptions);
};
};

View File

@ -1,30 +0,0 @@
const {models} = require('vn-loopback');
describe('workerActivity insert()', () => {
const ctx = beforeAll.getCtx(1106);
it('should insert in workerActivity', async() => {
const tx = await models.WorkerActivity.beginTransaction({});
let count = 0;
const options = {transaction: tx};
try {
await models.WorkerActivityType.create(
{'code': 'STOP', 'description': 'STOP'}, options
);
await models.WorkerActivity.add(ctx, 'STOP', 'APP', options);
count = await models.WorkerActivity.count(
{'workerFK': 1106}, options
);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
expect(count).toEqual(1);
});
});

View File

@ -13,10 +13,10 @@
"AuthCode": {
"dataSource": "vn"
},
"Accounting": {
"Bank": {
"dataSource": "vn"
},
"Buyer": {
"Buyer": {
"dataSource": "vn"
},
"Campaign": {
@ -28,9 +28,6 @@
"Company": {
"dataSource": "vn"
},
"Config": {
"dataSource": "vn"
},
"Continent": {
"dataSource": "vn"
},
@ -67,9 +64,6 @@
"EmailUser": {
"dataSource": "vn"
},
"Expedition_PrintOut": {
"dataSource": "vn"
},
"Image": {
"dataSource": "vn"
},
@ -79,16 +73,13 @@
"ImageCollectionSize": {
"dataSource": "vn"
},
"ImageConfig": {
"dataSource": "vn"
},
"ImageContainer": {
"dataSource": "imageStorage"
},
"Language": {
"dataSource": "vn"
},
"Machine": {
"MachineWorker": {
"dataSource": "vn"
},
"MobileAppVersionControl": {
@ -97,9 +88,6 @@
"Module": {
"dataSource": "vn"
},
"MrwConfig": {
"dataSource": "vn"
},
"Notification": {
"dataSource": "vn"
},
@ -115,15 +103,9 @@
"NotificationSubscription": {
"dataSource": "vn"
},
"OrmConfig": {
"dataSource": "vn"
},
"Province": {
"dataSource": "vn"
},
"QuadmindsApiConfig": {
"dataSource": "vn"
},
"Autonomy": {
"dataSource": "vn"
},
@ -133,21 +115,12 @@
"Postcode": {
"dataSource": "vn"
},
"ReferenceRate": {
"dataSource": "vn"
},
"SageWithholding": {
"dataSource": "vn"
},
"StarredModule": {
"dataSource": "vn"
},
"SaySimpleCountry": {
"dataSource": "vn"
},
"SaySimpleConfig": {
"dataSource": "vn"
},
"TempContainer": {
"dataSource": "tempStorage"
},
@ -178,40 +151,16 @@
"PrintConfig": {
"dataSource": "vn"
},
"QueueMember": {
"dataSource": "vn"
},
"ViaexpressConfig": {
"dataSource": "vn"
},
"VnToken": {
"dataSource": "vn"
},
"VnUser": {
"dataSource": "vn"
},
"VnRole": {
"dataSource": "vn"
},
"WorkerActivity": {
"dataSource": "vn"
},
"WorkerActivityType": {
"dataSource": "vn"
},
"ProductionConfig": {
"dataSource": "vn"
},
"AgencyLog": {
"dataSource": "vn"
},
"AgencyWorkCenter": {
"dataSource": "vn"
},
"RouteConfig": {
"dataSource": "vn"
},
"MrwService": {
"dataSource": "vn"
}
}
}

View File

@ -1,47 +0,0 @@
{
"name": "Accounting",
"base": "VnModel",
"options": {
"mysql": {
"table": "accounting"
}
},
"properties": {
"id": {
"type": "number",
"id": true,
"description": "Identifier"
},
"bank": {
"type": "string",
"required": true
},
"account": {
"type": "string",
"required": true
},
"accountingTypeFk": {
"type": "number",
"required": true
},
"entityFk": {
"type": "number",
"required": true
},
"isActive": {
"type": "boolean",
"required": true
},
"currencyFk": {
"type": "number",
"required": true
}
},
"relations": {
"accountingType": {
"type": "belongsTo",
"model": "AccountingType",
"foreignKey": "accountingTypeFk"
}
}
}

View File

@ -1,9 +0,0 @@
{
"name": "AgencyLog",
"base": "Log",
"options": {
"mysql": {
"table": "agencyLog"
}
}
}

View File

@ -1,8 +0,0 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.rewriteDbError(function(err) {
if (err.code === 'ER_DUP_ENTRY')
return new UserError(`This workCenter is already assigned to this agency`);
return err;
});
};

View File

@ -1,41 +0,0 @@
{
"name": "AgencyWorkCenter",
"base": "VnModel",
"options": {
"mysql": {
"table": "agencyWorkCenter"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"forceId": false
},
"agencyFk": {
"type": "number",
"required": false
},
"workCenterFk": {
"type": "number",
"required": false
}
},
"relations": {
"agency": {
"type": "belongsTo",
"model": "WorkCenter",
"foreignKey": "agencyFk"
},
"workCenter": {
"type": "belongsTo",
"model": "WorkCenter",
"foreignKey": "workCenterFk"
}
},
"scope": {
"include":{
"relation": "workCenter"
}
}
}

View File

@ -16,10 +16,6 @@
"name": {
"type": "string",
"required": true
},
"hasDailyInvoice": {
"type": "boolean",
"description": "Indicates if the autonomy has daily invoice enabled"
}
},
"relations": {
@ -44,4 +40,4 @@
"permission": "ALLOW"
}
]
}
}

View File

@ -8,26 +8,6 @@ module.exports = Self => {
});
Self.validatesUniquenessOf('bic', {
message: 'This BIC already exist'
message: 'This BIC already exist.'
});
Self.validatesPresenceOf('countryFk', {
message: 'CountryFK cannot be empty'
});
Self.validateAsync('bic', checkBic, {
message: 'Bank entity id must be specified'
});
async function checkBic(err, done) {
const filter = {
fields: ['code'],
where: {id: this.countryFk}
};
const country = await Self.app.models.Country.findOne(filter);
const code = country ? country.code.toLowerCase() : null;
if (code == 'es' && !this.id)
err();
done();
}
};

50
back/models/bank.json Normal file
View File

@ -0,0 +1,50 @@
{
"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
},
"accountingTypeFk": {
"type": "number",
"required": true,
"mysql": {
"columnName": "cash"
}
},
"entityFk": {
"type": "number",
"required": true
},
"isActive": {
"type": "boolean",
"required": true
},
"currencyFk": {
"type": "number",
"required": true
}
},
"relations": {
"accountingType": {
"type": "belongsTo",
"model": "AccountingType",
"foreignKey": "accountingTypeFk"
}
}
}

View File

@ -1,9 +1,7 @@
module.exports = Self => {
require('../methods/collection/getCollection')(Self);
require('../methods/collection/getSectors')(Self);
require('../methods/collection/setSaleQuantity')(Self);
require('../methods/collection/previousLabel')(Self);
require('../methods/collection/getTickets')(Self);
require('../methods/collection/assignCollection')(Self);
require('../methods/collection/assign')(Self);
require('../methods/collection/getSales')(Self);
};

View File

@ -1,21 +1,6 @@
{
"name": "Collection",
"base": "VnModel",
"properties": {
"id": {
"id": true,
"type": "number",
"required": true
},
"workerFk": {
"type": "number"
}
},
"options": {
"mysql": {
"table": "collection"
}
},
"acls": [{
"property": "validations",
"accessType": "EXECUTE",
@ -24,3 +9,4 @@
"permission": "ALLOW"
}]
}

View File

@ -1,22 +0,0 @@
{
"name": "Config",
"base": "VnModel",
"options": {
"mysql": {
"table": "config"
}
},
"properties": {
"inventoried": {
"type": "date"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}

View File

@ -13,7 +13,7 @@
"id": true,
"description": "Identifier"
},
"name": {
"country": {
"type": "string",
"required": true
},
@ -25,13 +25,6 @@
},
"isSocialNameUnique": {
"type": "boolean"
},
"continentFk": {
"type": "number"
},
"hasDailyInvoice": {
"type": "boolean",
"description": "Indicates if the autonomy has daily invoice enabled"
}
},
"relations": {
@ -39,16 +32,6 @@
"type": "belongsTo",
"model": "Currency",
"foreignKey": "currencyFk"
},
"continent": {
"type": "belongsTo",
"model": "Continent",
"foreignKey": "continentFk"
},
"saySimpleCountry": {
"type": "hasOne",
"model": "SaySimpleCountry",
"foreignKey": "countryFk"
}
},
"acls": [
@ -59,4 +42,4 @@
"permission": "ALLOW"
}
]
}
}

View File

@ -3,7 +3,7 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "salix.defaultViewMultiConfig"
"table": "salix.defaultViewConfig"
}
},
"properties": {

View File

@ -17,6 +17,10 @@
"type": "string",
"required": true
},
"path": {
"type": "string",
"required": true
},
"code": {
"type": "string",
"required": true

View File

@ -1,6 +1,4 @@
const UserError = require('vn-loopback/util/user-error');
const fs = require('fs-extra');
const path = require('path');
module.exports = Self => {
require('../methods/dms/downloadFile')(Self);
@ -37,32 +35,4 @@ module.exports = Self => {
return [stream, dms.contentType, `filename="${dms.file}"`];
};
Self.getPath = async function(dms) {
const models = Self.app.models;
const pathHash = await models.DmsContainer.getHash(dms.id);
const dmsContainer = await models.DmsContainer.container(pathHash);
const dstFile = path.join(dmsContainer.client.root, pathHash, dms.file);
return dstFile;
};
Self.createWithExtension = async function(data, extension, options) {
const newDms = await Self.create(data, options);
return newDms.updateAttribute('file', `${newDms.id}.${extension}`, options);
};
Self.createFromFile = async function(data, extension, srcFile, options) {
const dms = await Self.createWithExtension(data, extension, options);
const dstFile = await Self.getPath(dms);
await fs.move(srcFile, dstFile, {overwrite: true});
return dms;
};
Self.createFromStream = async function(data, extension, stream, options) {
const dms = await Self.createWithExtension(data, extension, options);
const dstFile = await Self.getPath(dms);
const writeStream = await fs.createWriteStream(dstFile);
await stream.pipe(writeStream);
return dms;
};
};

View File

@ -16,17 +16,17 @@
"url": {
"type": "string"
},
"token": {
"cookie": {
"type": "string"
},
"username": {
"type": "string"
},
"password": {
"type": "string"
},
"expired":{
"type": "number"
}
}
},
"acls": [
{
"property": "*",
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
]
}

View File

@ -1,19 +0,0 @@
{
"name": "Expedition_PrintOut",
"base": "VnModel",
"options": {
"mysql": {
"table": "dipole.expedition_PrintOut"
}
},
"properties": {
"expeditionFk": {
"type": "number",
"id": true,
"description": "id expeditionFk"
},
"itemFk": {
"type": "number"
}
}
}

View File

@ -1,22 +0,0 @@
{
"name": "ImageConfig",
"base": "VnModel",
"options": {
"mysql": {
"table": "hedera.imageConfig"
}
},
"properties": {
"url": {
"type": "string"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "$authenticated",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,33 @@
{
"name": "MachineWorker",
"base": "VnModel",
"options": {
"mysql": {
"table": "vn.machineWorker"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"workerFk": {
"type": "number"
},
"machineFk": {
"type": "number"
},
"inTime": {
"type": "date",
"mysql": {
"columnName": "inTimed"
}
},
"outTime": {
"type": "date",
"mysql": {
"columnName": "outTimed"
}
}
}
}

View File

@ -1,18 +0,0 @@
{
"name": "Machine",
"base": "VnModel",
"options": {
"mysql": {
"table": "vn.machine"
}
},
"properties": {
"id": {
"type": "number",
"id": true
},
"plate": {
"type": "string"
}
}
}

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