diff --git a/back/models/image.js b/back/models/image.js
index 78d159940..a35018814 100644
--- a/back/models/image.js
+++ b/back/models/image.js
@@ -1,11 +1,51 @@
const fs = require('fs-extra');
const sharp = require('sharp');
const path = require('path');
+const readChunk = require('read-chunk');
+const imageType = require('image-type');
+const bmp = require('bmp-js');
module.exports = Self => {
require('../methods/image/download')(Self);
require('../methods/image/upload')(Self);
+ // Function extracted from jimp package (utils)
+ function scan(image, x, y, w, h, f) {
+ // round input
+ x = Math.round(x);
+ y = Math.round(y);
+ w = Math.round(w);
+ h = Math.round(h);
+
+ for (let _y = y; _y < y + h; _y++) {
+ for (let _x = x; _x < x + w; _x++) {
+ const idx = (image.bitmap.width * _y + _x) << 2;
+ f.call(image, _x, _y, idx);
+ }
+ }
+
+ return image;
+ }
+
+ // Function extracted from jimp package (type-bmp)
+ function fromAGBR(bitmap) {
+ return scan({bitmap}, 0, 0, bitmap.width, bitmap.height, function(
+ x,
+ y,
+ index
+ ) {
+ const alpha = this.bitmap.data[index + 0];
+ const blue = this.bitmap.data[index + 1];
+ const green = this.bitmap.data[index + 2];
+ const red = this.bitmap.data[index + 3];
+
+ this.bitmap.data[index + 0] = red;
+ this.bitmap.data[index + 1] = green;
+ this.bitmap.data[index + 2] = blue;
+ this.bitmap.data[index + 3] = bitmap.is_with_alpha ? alpha : 0xff;
+ }).bitmap;
+ }
+
Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
@@ -48,13 +88,31 @@ module.exports = Self => {
const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file);
+ const buffer = readChunk.sync(srcFilePath, 0, 12);
+ const type = imageType(buffer);
+
+ let sharpOptions;
+ let imgSrc = srcFilePath;
+ if (type.mime == 'image/bmp') {
+ const bmpBuffer = fs.readFileSync(srcFilePath);
+ const bmpData = fromAGBR(bmp.decode(bmpBuffer));
+ imgSrc = bmpData.data;
+ sharpOptions = {
+ raw: {
+ width: bmpData.width,
+ height: bmpData.height,
+ channels: 4
+ }
+ };
+ }
+
const resizeOpts = {
withoutEnlargement: true,
fit: 'inside'
};
await fs.mkdir(dstDir, {recursive: true});
- await sharp(srcFilePath, {failOnError: false})
+ await sharp(imgSrc, sharpOptions)
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png()
.toFile(dstFile);
@@ -69,7 +127,7 @@ module.exports = Self => {
};
await fs.mkdir(dstDir, {recursive: true});
- await sharp(srcFilePath, {failOnError: false})
+ await sharp(imgSrc, sharpOptions)
.resize(size.width, size.height, resizeOpts)
.png()
.toFile(dstFile);
diff --git a/modules/item/back/methods/item-image-queue/downloadImages.js b/modules/item/back/methods/item-image-queue/downloadImages.js
index e3412d6a8..2d2e8af91 100644
--- a/modules/item/back/methods/item-image-queue/downloadImages.js
+++ b/modules/item/back/methods/item-image-queue/downloadImages.js
@@ -46,13 +46,8 @@ module.exports = Self => {
if (!image) return;
- const srcFile = image.url.split('/').pop();
- const dotIndex = srcFile.lastIndexOf('.');
-
- let fileName = srcFile.substring(0, dotIndex);
- if (dotIndex == -1)
- fileName = srcFile;
-
+ const srcFile = image.url;
+ const fileName = srcFile.replace(/\.|\/|:|\?|\\|=|%/g, '');
const file = `${fileName}.png`;
const filePath = path.join(tempPath, file);
diff --git a/modules/item/back/methods/item/filter.js b/modules/item/back/methods/item/filter.js
index f392dba87..eba0b0f91 100644
--- a/modules/item/back/methods/item/filter.js
+++ b/modules/item/back/methods/item/filter.js
@@ -44,6 +44,10 @@ module.exports = Self => {
arg: 'description',
type: 'String',
description: 'The item description',
+ }, {
+ arg: 'stemMultiplier',
+ type: 'Integer',
+ description: 'The item multiplier',
}
],
returns: {
@@ -80,16 +84,22 @@ module.exports = Self => {
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]};
case 'id':
return {'i.id': value};
- case 'description':
- return {'i.description': {like: `%${value}%`}};
- case 'categoryFk':
- return {'ic.id': value};
- case 'salesPersonFk':
- return {'t.workerFk': value};
- case 'typeFk':
- return {'i.typeFk': value};
case 'isActive':
return {'i.isActive': value};
+ case 'multiplier':
+ return {'i.stemMultiplier': value};
+ case 'typeFk':
+ return {'i.typeFk': value};
+ case 'category':
+ return {'ic.name': value};
+ case 'salesPersonFk':
+ return {'it.workerFk': value};
+ case 'origin':
+ return {'ori.code': value};
+ case 'niche':
+ return {'ip.code': value};
+ case 'intrastat':
+ return {'intr.description': value};
}
});
filter = mergeFilters(filter, {where});
@@ -98,7 +108,8 @@ module.exports = Self => {
let stmt;
stmt = new ParameterizedSQL(
- `SELECT i.id,
+ `SELECT
+ i.id,
i.image,
i.name,
i.description,
@@ -111,29 +122,30 @@ module.exports = Self => {
i.tag10, i.value10,
i.subName,
i.isActive,
- t.name type,
- t.workerFk buyerFk,
- u.name userName,
- intr.description AS intrastat,
i.stems,
- ori.code AS origin,
- ic.name AS category,
i.density,
i.stemMultiplier,
+ i.typeFk,
+ it.name AS typeName,
+ it.workerFk AS buyerFk,
+ u.name AS userName,
+ ori.code AS origin,
+ ic.name AS category,
+ intr.description AS intrastat,
b.grouping,
b.packing,
- itn.code AS niche, @visibleCalc
+ ip.code AS niche, @visibleCalc
FROM item i
- LEFT JOIN itemType t ON t.id = i.typeFk
- LEFT JOIN itemCategory ic ON ic.id = t.categoryFk
- LEFT JOIN worker w ON w.id = t.workerFk
+ LEFT JOIN itemType it ON it.id = i.typeFk
+ LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
+ LEFT JOIN worker w ON w.id = it.workerFk
LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN producer pr ON pr.id = i.producerFk
LEFT JOIN origin ori ON ori.id = i.originFk
- LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = t.warehouseFk
+ LEFT JOIN cache.last_buy lb ON lb.item_id = i.id AND lb.warehouse_id = it.warehouseFk
LEFT JOIN vn.buy b ON b.id = lb.buy_id
- LEFT JOIN itemPlacement itn ON itn.itemFk = i.id AND itn.warehouseFk = t.warehouseFk`
+ LEFT JOIN itemPlacement ip ON ip.itemFk = i.id AND ip.warehouseFk = it.warehouseFk`
);
if (ctx.args.tags) {
diff --git a/modules/item/front/basic-data/index.html b/modules/item/front/basic-data/index.html
index 3f049675c..cec7a063f 100644
--- a/modules/item/front/basic-data/index.html
+++ b/modules/item/front/basic-data/index.html
@@ -113,6 +113,12 @@
ng-model="$ctrl.item.stems"
rule>
+
+
-
-
-
-
- Id
- Grouping
- Packing
- Description
- Stems
- Size
- Niche
- Type
- Category
- Intrastat
- Origin
- Buyer
- Density
- Multiplier
- Active
-
-
-
-
-
-
-
-
-
-
- {{::item.id}}
-
-
- {{::item.grouping | dashIfEmpty}}
- {{::item.packing | dashIfEmpty}}
-
- {{::item.name}}
-
- {{::item.subName}}
-
-
-
-
- {{::item.stems}}
- {{::item.size}}
- {{::item.niche}}
-
- {{::item.type}}
-
-
- {{::item.category}}
-
-
- {{::item.intrastat}}
-
- {{::item.origin}}
-
-
- {{::item.userName}}
-
-
- {{::item.density}}
- {{::item.stemMultiplier}}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ Id
+ Grouping
+ Packing
+ Description
+ Stems
+ Size
+ Niche
+ Type
+ Category
+ Intrastat
+ Origin
+ Buyer
+ Density
+ Multiplier
+ Active
+
+
+
+
+
+
+
+
+
+
+ {{::item.id}}
+
+
+ {{::item.grouping | dashIfEmpty}}
+ {{::item.packing | dashIfEmpty}}
+
+ {{::item.name}}
+
+ {{::item.subName}}
+
+
+
+
+ {{::item.stems}}
+ {{::item.size}}
+ {{::item.niche}}
+
+ {{::item.typeName}}
+
+
+ {{::item.category}}
+
+
+ {{::item.intrastat}}
+
+ {{::item.origin}}
+
+
+ {{::item.userName}}
+
+
+ {{::item.density}}
+ {{::item.stemMultiplier}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -127,4 +127,31 @@
-
\ No newline at end of file
+
+
+
+
+ Filter by selection
+
+
+ Exclude selection
+
+
+ Remove filter
+
+
+ Remove all filters
+
+
+
\ No newline at end of file
diff --git a/modules/item/front/index/index.js b/modules/item/front/index/index.js
index cafa3e475..175beb88a 100644
--- a/modules/item/front/index/index.js
+++ b/modules/item/front/index/index.js
@@ -11,6 +11,36 @@ class Controller extends Section {
};
}
+ exprBuilder(param, value) {
+ switch (param) {
+ case 'category':
+ return {'ic.name': value};
+ case 'salesPersonFk':
+ return {'it.workerFk': value};
+ case 'grouping':
+ return {'b.grouping': value};
+ case 'packing':
+ return {'b.packing': value};
+ case 'origin':
+ return {'ori.code': value};
+ case 'niche':
+ return {'ip.code': value};
+ case 'typeFk':
+ return {'i.typeFk': value};
+ case 'intrastat':
+ return {'intr.description': value};
+ case 'id':
+ case 'size':
+ case 'name':
+ case 'subname':
+ case 'isActive':
+ case 'density':
+ case 'stemMultiplier':
+ case 'stems':
+ return {[`i.${param}`]: value};
+ }
+ }
+
onCloneAccept(itemFk) {
return this.$http.post(`Items/${itemFk}/clone`)
.then(res => {
diff --git a/modules/item/front/summary/index.html b/modules/item/front/summary/index.html
index 59ceaebbe..67d3bf3c6 100644
--- a/modules/item/front/summary/index.html
+++ b/modules/item/front/summary/index.html
@@ -55,6 +55,9 @@
+
+
{
Self.remoteMethod('getAverageDays', {
description: 'Returns the average days duration and the two warehouses of the travel.',
@@ -8,7 +9,7 @@ module.exports = Self => {
required: true
}],
returns: {
- type: 'number',
+ type: 'object',
root: true
},
http: {
@@ -18,15 +19,48 @@ module.exports = Self => {
});
Self.getAverageDays = async agencyModeFk => {
- const query = `
- SELECT t.id, t.warehouseInFk, t.warehouseOutFk,
- (SELECT ROUND(AVG(DATEDIFF(t.landed, t.shipped )))
- FROM travel t
- WHERE t.agencyFk = ? LIMIT 50) AS dayDuration
- FROM travel t
- WHERE t.agencyFk = ? ORDER BY t.id DESC LIMIT 1;`;
+ const conn = Self.dataSource.connector;
+ let stmts = [];
+
+ stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.travel');
+
+ stmt = new ParameterizedSQL(`
+ CREATE TEMPORARY TABLE tmp.travel (
+ SELECT
+ t.id,
+ t.warehouseInFk,
+ t.warehouseOutFk,
+ t.landed,
+ t.shipped,
+ t.agencyFk
+ FROM travel t
+ WHERE t.agencyFk = ? LIMIT 50)`, [agencyModeFk]);
+ stmts.push(stmt);
+
+ stmt = new ParameterizedSQL(`
+ SELECT
+ t.id,
+ t.warehouseInFk,
+ t.warehouseOutFk,
+ (SELECT ROUND(AVG(DATEDIFF(t.landed, t.shipped )))
+ FROM tmp.travel t
+ WHERE t.agencyFk
+ ORDER BY id DESC LIMIT 50) AS dayDuration
+ FROM tmp.travel t
+ WHERE t.agencyFk
+ ORDER BY t.id DESC LIMIT 1`);
+
+ const avgDaysIndex = stmts.push(stmt) - 1;
+
+ stmts.push(
+ `DROP TEMPORARY TABLE
+ tmp.travel`);
+
+ const sql = ParameterizedSQL.join(stmts, ';');
+ const result = await conn.executeStmt(sql);
+
+ const [avgDays] = result[avgDaysIndex];
- const [avgDays] = await Self.rawSql(query, [agencyModeFk, agencyModeFk]);
return avgDays;
};
};
diff --git a/modules/travel/front/create/index.js b/modules/travel/front/create/index.js
index cf0b2f382..a85917ca8 100644
--- a/modules/travel/front/create/index.js
+++ b/modules/travel/front/create/index.js
@@ -22,6 +22,9 @@ class Controller extends Section {
agencyModeFk: this.travel.agencyModeFk
};
this.$http.get(query, {params}).then(res => {
+ if (!res.data)
+ return;
+
const landed = new Date(value);
const futureDate = landed.getDate() + res.data.dayDuration;
landed.setDate(futureDate);
diff --git a/modules/travel/front/create/index.spec.js b/modules/travel/front/create/index.spec.js
index b59530604..e3f85d9ae 100644
--- a/modules/travel/front/create/index.spec.js
+++ b/modules/travel/front/create/index.spec.js
@@ -51,14 +51,29 @@ describe('Travel Component vnTravelCreate', () => {
expect(controller.travel.warehouseOutFk).toBeUndefined();
});
+ it(`should do nothing if there's no response data.`, () => {
+ controller.travel = {agencyModeFk: 4};
+ const tomorrow = new Date();
+
+ const query = `travels/getAverageDays?agencyModeFk=${controller.travel.agencyModeFk}`;
+ $httpBackend.expectGET(query).respond(undefined);
+ controller.onShippedChange(tomorrow);
+ $httpBackend.flush();
+
+ expect(controller.travel.warehouseInFk).toBeUndefined();
+ expect(controller.travel.warehouseOutFk).toBeUndefined();
+ expect(controller.travel.dayDuration).toBeUndefined();
+ });
+
it(`should fill the fields when it's selected a date and agency.`, () => {
controller.travel = {agencyModeFk: 1};
const tomorrow = new Date();
- tomorrow.setDate(tomorrow.getDate() + 1);
+ tomorrow.setDate(tomorrow.getDate() + 9);
const expectedResponse = {
- dayDuration: 2,
- warehouseInFk: 1,
- warehouseOutFk: 2
+ id: 8,
+ dayDuration: 9,
+ warehouseInFk: 5,
+ warehouseOutFk: 1
};
const query = `travels/getAverageDays?agencyModeFk=${controller.travel.agencyModeFk}`;
diff --git a/modules/worker/back/models/worker.json b/modules/worker/back/models/worker.json
index 45eee23bf..cad38ac3b 100644
--- a/modules/worker/back/models/worker.json
+++ b/modules/worker/back/models/worker.json
@@ -42,6 +42,11 @@
"model": "Account",
"foreignKey": "userFk"
},
+ "boss": {
+ "type": "belongsTo",
+ "model": "Account",
+ "foreignKey": "bossFk"
+ },
"client": {
"type": "belongsTo",
"model": "Client",
diff --git a/modules/worker/front/basic-data/index.html b/modules/worker/front/basic-data/index.html
index a2cbbc637..a767eccc4 100644
--- a/modules/worker/front/basic-data/index.html
+++ b/modules/worker/front/basic-data/index.html
@@ -29,6 +29,15 @@
ng-model="$ctrl.worker.phone"
rule>
+
+
diff --git a/modules/worker/front/locale/es.yml b/modules/worker/front/locale/es.yml
index 63570ddf0..1414d089b 100644
--- a/modules/worker/front/locale/es.yml
+++ b/modules/worker/front/locale/es.yml
@@ -7,6 +7,7 @@ Extension: Extensión
Fiscal identifier: NIF
Go to client: Ir al cliente
Last name: Apellidos
+Boss: Jefe
Log: Historial
Private Branch Exchange: Centralita
Role: Rol
diff --git a/modules/worker/front/summary/index.html b/modules/worker/front/summary/index.html
index 0a99959e4..d2a7e750b 100644
--- a/modules/worker/front/summary/index.html
+++ b/modules/worker/front/summary/index.html
@@ -30,6 +30,14 @@
+
+
+ {{::worker.boss.nickname}}
+
+
@@ -50,4 +58,7 @@
-
\ No newline at end of file
+
+
+
\ No newline at end of file
diff --git a/modules/worker/front/summary/index.js b/modules/worker/front/summary/index.js
index 6a4d87007..3cdb2c36f 100644
--- a/modules/worker/front/summary/index.js
+++ b/modules/worker/front/summary/index.js
@@ -11,8 +11,8 @@ class Controller extends Summary {
this.$.worker = null;
if (!value) return;
- let query = `Workers/${value.id}`;
- let filter = {
+ const query = `Workers/${value.id}`;
+ const filter = {
include: [
{
relation: 'user',
@@ -31,13 +31,20 @@ class Controller extends Summary {
}
}]
}
- }, {
+ },
+ {
relation: 'client',
scope: {fields: ['fi']}
- }, {
+ },
+ {
+ relation: 'boss',
+ scope: {fields: ['id', 'nickname']}
+ },
+ {
relation: 'sip',
scope: {fields: ['extension']}
- }, {
+ },
+ {
relation: 'department',
scope: {
include: {
diff --git a/package-lock.json b/package-lock.json
index 3ed1b37c9..40780aaf6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6996,6 +6996,11 @@
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
"integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
},
+ "bmp-js": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz",
+ "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM="
+ },
"bn.js": {
"version": "4.11.8",
"resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
@@ -10041,6 +10046,11 @@
}
}
},
+ "file-type": {
+ "version": "10.11.0",
+ "resolved": "https://registry.npmjs.org/file-type/-/file-type-10.11.0.tgz",
+ "integrity": "sha512-uzk64HRpUZyTGZtVuvrjP0FYxzQrBf4rojot6J65YMEbwBLB0CWm0CLojVpwpmFmxcE/lkvYICgfcGozbBq6rw=="
+ },
"filed-mimefix": {
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/filed-mimefix/-/filed-mimefix-0.1.3.tgz",
@@ -12977,6 +12987,14 @@
"integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=",
"dev": true
},
+ "image-type": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/image-type/-/image-type-4.1.0.tgz",
+ "integrity": "sha512-CFJMJ8QK8lJvRlTCEgarL4ro6hfDQKif2HjSvYCdQZESaIPV4v9imrf7BQHK+sQeTeNeMpWciR9hyC/g8ybXEg==",
+ "requires": {
+ "file-type": "^10.10.0"
+ }
+ },
"imap": {
"version": "0.8.19",
"resolved": "https://registry.npmjs.org/imap/-/imap-0.8.19.tgz",
@@ -21180,8 +21198,7 @@
"p-try": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
- "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
- "dev": true
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
},
"package-json": {
"version": "4.0.1",
@@ -22102,6 +22119,15 @@
"integrity": "sha512-tJBzzzIgnnRfEm046qRcURvwQnZVXmuCbscxUO5RWrGTXpon2d4c8mI0D8WE6ydVIm29JiLB6+RslkIvym9Rjw==",
"dev": true
},
+ "read-chunk": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz",
+ "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==",
+ "requires": {
+ "pify": "^4.0.1",
+ "with-open-file": "^0.1.6"
+ }
+ },
"read-pkg": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz",
@@ -26605,6 +26631,16 @@
"string-width": "^2.1.1"
}
},
+ "with-open-file": {
+ "version": "0.1.7",
+ "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz",
+ "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==",
+ "requires": {
+ "p-finally": "^1.0.0",
+ "p-try": "^2.1.0",
+ "pify": "^4.0.1"
+ }
+ },
"word-count": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/word-count/-/word-count-0.2.2.tgz",
diff --git a/package.json b/package.json
index 6e40883db..1fe209cf2 100644
--- a/package.json
+++ b/package.json
@@ -12,10 +12,12 @@
"node": ">=12"
},
"dependencies": {
+ "bmp-js": "^0.1.0",
"compression": "^1.7.3",
"fs-extra": "^5.0.0",
"helmet": "^3.21.2",
"i18n": "^0.8.4",
+ "image-type": "^4.1.0",
"imap": "^0.8.19",
"ldapjs": "^2.2.0",
"loopback": "^3.26.0",
@@ -30,6 +32,7 @@
"node-ssh": "^11.0.0",
"object-diff": "0.0.4",
"object.pick": "^1.3.0",
+ "read-chunk": "^3.2.0",
"request": "^2.88.0",
"request-promise-native": "^1.0.8",
"require-yaml": "0.0.1",