Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2820-add_item_scopeDays
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
jorgebl 2021-02-25 11:02:51 +01:00
commit 4cedd53bce
20 changed files with 444 additions and 32586 deletions

View File

@ -1,11 +1,51 @@
const fs = require('fs-extra'); const fs = require('fs-extra');
const sharp = require('sharp'); const sharp = require('sharp');
const path = require('path'); const path = require('path');
const readChunk = require('read-chunk');
const imageType = require('image-type');
const bmp = require('bmp-js');
module.exports = Self => { module.exports = Self => {
require('../methods/image/download')(Self); require('../methods/image/download')(Self);
require('../methods/image/upload')(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) => { Self.registerImage = async(collectionName, srcFilePath, fileName, entityId) => {
const models = Self.app.models; const models = Self.app.models;
const tx = await Self.beginTransaction({}); const tx = await Self.beginTransaction({});
@ -48,13 +88,31 @@ module.exports = Self => {
const dstDir = path.join(collectionDir, 'full'); const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file); 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 = { const resizeOpts = {
withoutEnlargement: true, withoutEnlargement: true,
fit: 'inside' fit: 'inside'
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath, {failOnError: false}) await sharp(imgSrc, sharpOptions)
.resize(collection.maxWidth, collection.maxHeight, resizeOpts) .resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);
@ -69,7 +127,7 @@ module.exports = Self => {
}; };
await fs.mkdir(dstDir, {recursive: true}); await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath, {failOnError: false}) await sharp(imgSrc, sharpOptions)
.resize(size.width, size.height, resizeOpts) .resize(size.width, size.height, resizeOpts)
.png() .png()
.toFile(dstFile); .toFile(dstFile);

View File

@ -78,6 +78,8 @@ module.exports = Self => {
return {'ic.id': value}; return {'ic.id': value};
case 'salesPersonFk': case 'salesPersonFk':
return {'it.workerFk': value}; return {'it.workerFk': value};
case 'code':
return {'it.code': value};
case 'typeFk': case 'typeFk':
return {'i.typeFk': value}; return {'i.typeFk': value};
case 'active': case 'active':
@ -103,6 +105,7 @@ module.exports = Self => {
i.id AS itemFk, i.id AS itemFk,
i.size, i.size,
i.density, i.density,
it.code,
i.typeFk, i.typeFk,
i.family, i.family,
i.isActive, i.isActive,

View File

@ -37,8 +37,8 @@
<vn-th field="quantity">Quantity</vn-th> <vn-th field="quantity">Quantity</vn-th>
<vn-th field="description" style="text-align: center">Description</vn-th> <vn-th field="description" style="text-align: center">Description</vn-th>
<vn-th field="size">Size</vn-th> <vn-th field="size">Size</vn-th>
<vn-th field="tags" style="text-align: center">Tags</vn-th> <vn-th field="name" style="text-align: center">Tags</vn-th>
<vn-th field="type">Type</vn-th> <vn-th field="code">Type</vn-th>
<vn-th field="intrastat">Intrastat</vn-th> <vn-th field="intrastat">Intrastat</vn-th>
<vn-th field="origin">Origin</vn-th> <vn-th field="origin">Origin</vn-th>
<vn-th field="density">Density</vn-th> <vn-th field="density">Density</vn-th>
@ -109,7 +109,7 @@
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td shrink title="{{::buy.type}}"> <vn-td shrink title="{{::buy.type}}">
{{::buy.type}} {{::buy.code}}
</vn-td> </vn-td>
<vn-td shrink title="{{::item.intrastat}}"> <vn-td shrink title="{{::item.intrastat}}">
{{::buy.intrastat}} {{::buy.intrastat}}

View File

@ -46,13 +46,8 @@ module.exports = Self => {
if (!image) return; if (!image) return;
const srcFile = image.url.split('/').pop(); const srcFile = image.url;
const dotIndex = srcFile.lastIndexOf('.'); const fileName = srcFile.replace(/\.|\/|:|\?|\\|=|%/g, '');
let fileName = srcFile.substring(0, dotIndex);
if (dotIndex == -1)
fileName = srcFile;
const file = `${fileName}.png`; const file = `${fileName}.png`;
const filePath = path.join(tempPath, file); const filePath = path.join(tempPath, file);

View File

@ -44,6 +44,10 @@ module.exports = Self => {
arg: 'description', arg: 'description',
type: 'String', type: 'String',
description: 'The item description', description: 'The item description',
}, {
arg: 'stemMultiplier',
type: 'Integer',
description: 'The item multiplier',
} }
], ],
returns: { returns: {
@ -80,16 +84,22 @@ module.exports = Self => {
: {or: [{'i.name': {like: `%${value}%`}}, codeWhere]}; : {or: [{'i.name': {like: `%${value}%`}}, codeWhere]};
case 'id': case 'id':
return {'i.id': value}; 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': case 'isActive':
return {'i.isActive': value}; 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}); filter = mergeFilters(filter, {where});
@ -98,7 +108,8 @@ module.exports = Self => {
let stmt; let stmt;
stmt = new ParameterizedSQL( stmt = new ParameterizedSQL(
`SELECT i.id, `SELECT
i.id,
i.image, i.image,
i.name, i.name,
i.description, i.description,
@ -111,29 +122,30 @@ module.exports = Self => {
i.tag10, i.value10, i.tag10, i.value10,
i.subName, i.subName,
i.isActive, i.isActive,
t.name type,
t.workerFk buyerFk,
u.name userName,
intr.description AS intrastat,
i.stems, i.stems,
ori.code AS origin,
ic.name AS category,
i.density, i.density,
i.stemMultiplier, 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.grouping,
b.packing, b.packing,
itn.code AS niche, @visibleCalc ip.code AS niche, @visibleCalc
FROM item i FROM item i
LEFT JOIN itemType t ON t.id = i.typeFk LEFT JOIN itemType it ON it.id = i.typeFk
LEFT JOIN itemCategory ic ON ic.id = t.categoryFk LEFT JOIN itemCategory ic ON ic.id = it.categoryFk
LEFT JOIN worker w ON w.id = t.workerFk LEFT JOIN worker w ON w.id = it.workerFk
LEFT JOIN account.user u ON u.id = w.userFk LEFT JOIN account.user u ON u.id = w.userFk
LEFT JOIN intrastat intr ON intr.id = i.intrastatFk LEFT JOIN intrastat intr ON intr.id = i.intrastatFk
LEFT JOIN producer pr ON pr.id = i.producerFk LEFT JOIN producer pr ON pr.id = i.producerFk
LEFT JOIN origin ori ON ori.id = i.originFk 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 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) { if (ctx.args.tags) {

View File

@ -113,6 +113,12 @@
ng-model="$ctrl.item.stems" ng-model="$ctrl.item.stems"
rule> rule>
</vn-input-number> </vn-input-number>
<vn-input-number
vn-one
min="0"
label="Multiplier"
ng-model="$ctrl.item.stemMultiplier">
</vn-input-number>
</vn-horizontal> </vn-horizontal>
<vn-horizontal> <vn-horizontal>
<vn-input-number <vn-input-number

View File

@ -9,4 +9,5 @@ Price in kg: Precio en kg
New intrastat: Nuevo intrastat New intrastat: Nuevo intrastat
Identifier: Identificador Identifier: Identificador
Fragile: Frágil Fragile: Frágil
Is shown at website, app that this item cannot travel (wreath, palms, ...): Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...) Is shown at website, app that this item cannot travel (wreath, palms, ...): Se muestra en la web, app que este artículo no puede viajar (coronas, palmas, ...)
Multiplier: Multiplicador

View File

@ -5,107 +5,107 @@
model="model" model="model"
class="vn-w-xl vn-mb-xl"> class="vn-w-xl vn-mb-xl">
<vn-card> <vn-card>
<vn-table <vn-table
model="model" model="model"
show-fields="$ctrl.showFields" show-fields="$ctrl.showFields"
vn-smart-table="itemIndex"> vn-smart-table="itemIndex">
<vn-thead> <vn-thead>
<vn-tr> <vn-tr>
<vn-th shrink></vn-th> <vn-th shrink></vn-th>
<vn-th field="id" shrink>Id</vn-th> <vn-th field="id" shrink>Id</vn-th>
<vn-th field="grouping" shrink>Grouping</vn-th> <vn-th field="grouping" shrink>Grouping</vn-th>
<vn-th field="packing" shrink>Packing</vn-th> <vn-th field="packing" shrink>Packing</vn-th>
<vn-th field="description">Description</vn-th> <vn-th field="name">Description</vn-th>
<vn-th field="stems" shrink>Stems</vn-th> <vn-th field="stems" shrink>Stems</vn-th>
<vn-th field="size" shrink>Size</vn-th> <vn-th field="size" shrink>Size</vn-th>
<vn-th field="niche" shrink>Niche</vn-th> <vn-th field="niche" shrink>Niche</vn-th>
<vn-th field="type" shrink>Type</vn-th> <vn-th field="typeFk" shrink>Type</vn-th>
<vn-th field="category" shrink>Category</vn-th> <vn-th field="category" shrink>Category</vn-th>
<vn-th field="intrastat" shrink>Intrastat</vn-th> <vn-th field="intrastat" shrink>Intrastat</vn-th>
<vn-th field="origin" shrink>Origin</vn-th> <vn-th field="origin" shrink>Origin</vn-th>
<vn-th field="salesperson" shrink>Buyer</vn-th> <vn-th field="salesperson" shrink>Buyer</vn-th>
<vn-th field="density" shrink>Density</vn-th> <vn-th field="density" shrink>Density</vn-th>
<vn-th field="stemMultiplier" shrink>Multiplier</vn-th> <vn-th field="stemMultiplier" shrink>Multiplier</vn-th>
<vn-th field="active" shrink>Active</vn-th> <vn-th field="active" shrink>Active</vn-th>
<vn-th></vn-th> <vn-th></vn-th>
</vn-tr> </vn-tr>
</vn-thead> </vn-thead>
<vn-tbody> <vn-tbody>
<a ng-repeat="item in model.data" <a ng-repeat="item in model.data"
class="clickable vn-tr search-result" class="clickable vn-tr search-result"
ui-sref="item.card.summary({id: item.id})"> ui-sref="item.card.summary({id: item.id})">
<vn-td shrink> <vn-td shrink>
<img <img
ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}" ng-src="{{::$root.imagePath('catalog', '50x50', item.id)}}"
zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}" zoom-image="{{::$root.imagePath('catalog', '1600x900', item.id)}}"
vn-click-stop vn-click-stop
on-error-src/> on-error-src/>
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<span <span
vn-click-stop="itemDescriptor.show($event, item.id)" vn-click-stop="itemDescriptor.show($event, item.id)"
class="link"> class="link">
{{::item.id}} {{::item.id}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td> <vn-td shrink>{{::item.grouping | dashIfEmpty}}</vn-td>
<vn-td shrink>{{::item.packing | dashIfEmpty}}</vn-td> <vn-td shrink>{{::item.packing | dashIfEmpty}}</vn-td>
<vn-td vn-fetched-tags> <vn-td vn-fetched-tags>
<vn-one title="{{::item.name}}">{{::item.name}}</vn-one> <vn-one title="{{::item.name}}">{{::item.name}}</vn-one>
<vn-one ng-if="::item.subName"> <vn-one ng-if="::item.subName">
<h3 title="{{::item.subName}}">{{::item.subName}}</h3> <h3 title="{{::item.subName}}">{{::item.subName}}</h3>
</vn-one> </vn-one>
<vn-fetched-tags <vn-fetched-tags
max-length="6" max-length="6"
item="item" item="item"
tabindex="-1"> tabindex="-1">
</vn-fetched-tags> </vn-fetched-tags>
</vn-td> </vn-td>
<vn-td shrink>{{::item.stems}}</vn-td> <vn-td shrink>{{::item.stems}}</vn-td>
<vn-td shrink>{{::item.size}}</vn-td> <vn-td shrink>{{::item.size}}</vn-td>
<vn-td shrink>{{::item.niche}}</vn-td> <vn-td shrink>{{::item.niche}}</vn-td>
<vn-td shrink title="{{::item.type}}"> <vn-td shrink title="{{::item.typeName}}">
{{::item.type}} {{::item.typeName}}
</vn-td> </vn-td>
<vn-td shrink title="{{::item.category}}"> <vn-td shrink title="{{::item.category}}">
{{::item.category}} {{::item.category}}
</vn-td> </vn-td>
<vn-td shrink title="{{::item.intrastat}}"> <vn-td shrink title="{{::item.intrastat}}">
{{::item.intrastat}} {{::item.intrastat}}
</vn-td> </vn-td>
<vn-td shrink>{{::item.origin}}</vn-td> <vn-td shrink>{{::item.origin}}</vn-td>
<vn-td shrink title="{{::item.userName}}"> <vn-td shrink title="{{::item.userName}}">
<span <span
class="link" class="link"
vn-click-stop="workerDescriptor.show($event, item.buyerFk)"> vn-click-stop="workerDescriptor.show($event, item.buyerFk)">
{{::item.userName}} {{::item.userName}}
</span> </span>
</vn-td> </vn-td>
<vn-td shrink>{{::item.density}}</vn-td> <vn-td shrink>{{::item.density}}</vn-td>
<vn-td shrink >{{::item.stemMultiplier}}</vn-td> <vn-td shrink >{{::item.stemMultiplier}}</vn-td>
<vn-td shrink> <vn-td shrink>
<vn-check <vn-check
disabled="true" disabled="true"
ng-model="::item.isActive"> ng-model="::item.isActive">
</vn-check> </vn-check>
</vn-td> </vn-td>
<vn-td shrink> <vn-td shrink>
<vn-horizontal class="buttons"> <vn-horizontal class="buttons">
<vn-icon-button <vn-icon-button
vn-click-stop="clone.show(item.id)" vn-click-stop="clone.show(item.id)"
vn-tooltip="Clone" vn-tooltip="Clone"
icon="icon-clone"> icon="icon-clone">
</vn-icon-button> </vn-icon-button>
<vn-icon-button <vn-icon-button
vn-click-stop="$ctrl.preview(item)" vn-click-stop="$ctrl.preview(item)"
vn-tooltip="Preview" vn-tooltip="Preview"
icon="preview"> icon="preview">
</vn-icon-button> </vn-icon-button>
</vn-horizontal> </vn-horizontal>
</vn-td> </vn-td>
</a> </a>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>
<a ui-sref="item.create" vn-tooltip="New item" vn-bind="+" fixed-bottom-right> <a ui-sref="item.create" vn-tooltip="New item" vn-bind="+" fixed-bottom-right>
@ -127,4 +127,31 @@
<vn-item-summary <vn-item-summary
item="$ctrl.itemSelected"> item="$ctrl.itemSelected">
</vn-item-summary> </vn-item-summary>
</vn-popup> </vn-popup>
<vn-contextmenu
vn-id="contextmenu"
targets="['vn-data-viewer']"
model="model"
expr-builder="$ctrl.exprBuilder(param, value)">
<slot-menu>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.filterBySelection()">
Filter by selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.excludeSelection()">
Exclude selection
</vn-item>
<vn-item translate
ng-if="contextmenu.isFilterAllowed()"
ng-click="contextmenu.removeFilter()">
Remove filter
</vn-item>
<vn-item translate
ng-click="contextmenu.removeAllFilters()">
Remove all filters
</vn-item>
</slot-menu>
</vn-contextmenu>

View File

@ -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) { onCloneAccept(itemFk) {
return this.$http.post(`Items/${itemFk}/clone`) return this.$http.post(`Items/${itemFk}/clone`)
.then(res => { .then(res => {

View File

@ -55,6 +55,9 @@
<vn-label-value label="stems" <vn-label-value label="stems"
value="{{$ctrl.summary.item.stems}}"> value="{{$ctrl.summary.item.stems}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Multiplier"
value="{{$ctrl.summary.item.stemMultiplier}}">
</vn-label-value>
<vn-label-value label="Buyer"> <vn-label-value label="Buyer">
<span <span
ng-click="workerDescriptor.show($event, $ctrl.summary.item.itemType.worker.userFk)" ng-click="workerDescriptor.show($event, $ctrl.summary.item.itemType.worker.userFk)"

View File

@ -1,3 +1,4 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
module.exports = Self => { module.exports = Self => {
Self.remoteMethod('getAverageDays', { Self.remoteMethod('getAverageDays', {
description: 'Returns the average days duration and the two warehouses of the travel.', description: 'Returns the average days duration and the two warehouses of the travel.',
@ -8,7 +9,7 @@ module.exports = Self => {
required: true required: true
}], }],
returns: { returns: {
type: 'number', type: 'object',
root: true root: true
}, },
http: { http: {
@ -18,15 +19,48 @@ module.exports = Self => {
}); });
Self.getAverageDays = async agencyModeFk => { Self.getAverageDays = async agencyModeFk => {
const query = ` const conn = Self.dataSource.connector;
SELECT t.id, t.warehouseInFk, t.warehouseOutFk, let stmts = [];
(SELECT ROUND(AVG(DATEDIFF(t.landed, t.shipped )))
FROM travel t stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.travel');
WHERE t.agencyFk = ? LIMIT 50) AS dayDuration
FROM travel t stmt = new ParameterizedSQL(`
WHERE t.agencyFk = ? ORDER BY t.id DESC LIMIT 1;`; 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; return avgDays;
}; };
}; };

View File

@ -22,6 +22,9 @@ class Controller extends Section {
agencyModeFk: this.travel.agencyModeFk agencyModeFk: this.travel.agencyModeFk
}; };
this.$http.get(query, {params}).then(res => { this.$http.get(query, {params}).then(res => {
if (!res.data)
return;
const landed = new Date(value); const landed = new Date(value);
const futureDate = landed.getDate() + res.data.dayDuration; const futureDate = landed.getDate() + res.data.dayDuration;
landed.setDate(futureDate); landed.setDate(futureDate);

View File

@ -51,14 +51,29 @@ describe('Travel Component vnTravelCreate', () => {
expect(controller.travel.warehouseOutFk).toBeUndefined(); 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.`, () => { it(`should fill the fields when it's selected a date and agency.`, () => {
controller.travel = {agencyModeFk: 1}; controller.travel = {agencyModeFk: 1};
const tomorrow = new Date(); const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1); tomorrow.setDate(tomorrow.getDate() + 9);
const expectedResponse = { const expectedResponse = {
dayDuration: 2, id: 8,
warehouseInFk: 1, dayDuration: 9,
warehouseOutFk: 2 warehouseInFk: 5,
warehouseOutFk: 1
}; };
const query = `travels/getAverageDays?agencyModeFk=${controller.travel.agencyModeFk}`; const query = `travels/getAverageDays?agencyModeFk=${controller.travel.agencyModeFk}`;

View File

@ -42,6 +42,11 @@
"model": "Account", "model": "Account",
"foreignKey": "userFk" "foreignKey": "userFk"
}, },
"boss": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "bossFk"
},
"client": { "client": {
"type": "belongsTo", "type": "belongsTo",
"model": "Client", "model": "Client",

View File

@ -29,6 +29,15 @@
ng-model="$ctrl.worker.phone" ng-model="$ctrl.worker.phone"
rule> rule>
</vn-textfield> </vn-textfield>
<vn-autocomplete
disabled="false"
ng-model="$ctrl.worker.bossFk"
url="Clients/activeWorkersWithRole"
show-field="nickname"
search-function="{firstName: $search}"
where="{role: 'employee'}"
label="Boss">
</vn-autocomplete>
</vn-horizontal> </vn-horizontal>
</vn-vertical> </vn-vertical>
</vn-card> </vn-card>

View File

@ -7,6 +7,7 @@ Extension: Extensión
Fiscal identifier: NIF Fiscal identifier: NIF
Go to client: Ir al cliente Go to client: Ir al cliente
Last name: Apellidos Last name: Apellidos
Boss: Jefe
Log: Historial Log: Historial
Private Branch Exchange: Centralita Private Branch Exchange: Centralita
Role: Rol Role: Rol

View File

@ -30,6 +30,14 @@
<vn-label-value label="Department" <vn-label-value label="Department"
value="{{worker.department.department.name}}"> value="{{worker.department.department.name}}">
</vn-label-value> </vn-label-value>
<vn-label-value
label="Boss">
<span
ng-click="workerDescriptor.show($event, worker.boss.id)"
class="link">
{{::worker.boss.nickname}}
</span>
</vn-label-value>
<vn-label-value label="Phone" <vn-label-value label="Phone"
value="{{worker.phone}}"> value="{{worker.phone}}">
</vn-label-value> </vn-label-value>
@ -50,4 +58,7 @@
</vn-label-value> </vn-label-value>
</vn-one> </vn-one>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-worker-descriptor-popover
vn-id="workerDescriptor">
</vn-worker-descriptor-popover>

View File

@ -11,8 +11,8 @@ class Controller extends Summary {
this.$.worker = null; this.$.worker = null;
if (!value) return; if (!value) return;
let query = `Workers/${value.id}`; const query = `Workers/${value.id}`;
let filter = { const filter = {
include: [ include: [
{ {
relation: 'user', relation: 'user',
@ -31,13 +31,20 @@ class Controller extends Summary {
} }
}] }]
} }
}, { },
{
relation: 'client', relation: 'client',
scope: {fields: ['fi']} scope: {fields: ['fi']}
}, { },
{
relation: 'boss',
scope: {fields: ['id', 'nickname']}
},
{
relation: 'sip', relation: 'sip',
scope: {fields: ['extension']} scope: {fields: ['extension']}
}, { },
{
relation: 'department', relation: 'department',
scope: { scope: {
include: { include: {

32497
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,10 +12,12 @@
"node": ">=12" "node": ">=12"
}, },
"dependencies": { "dependencies": {
"bmp-js": "^0.1.0",
"compression": "^1.7.3", "compression": "^1.7.3",
"fs-extra": "^5.0.0", "fs-extra": "^5.0.0",
"helmet": "^3.21.2", "helmet": "^3.21.2",
"i18n": "^0.8.4", "i18n": "^0.8.4",
"image-type": "^4.1.0",
"imap": "^0.8.19", "imap": "^0.8.19",
"ldapjs": "^2.2.0", "ldapjs": "^2.2.0",
"loopback": "^3.26.0", "loopback": "^3.26.0",
@ -30,6 +32,7 @@
"node-ssh": "^11.0.0", "node-ssh": "^11.0.0",
"object-diff": "0.0.4", "object-diff": "0.0.4",
"object.pick": "^1.3.0", "object.pick": "^1.3.0",
"read-chunk": "^3.2.0",
"request": "^2.88.0", "request": "^2.88.0",
"request-promise-native": "^1.0.8", "request-promise-native": "^1.0.8",
"require-yaml": "0.0.1", "require-yaml": "0.0.1",