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

This commit is contained in:
Carlos Jimenez Ruiz 2020-07-20 11:51:23 +02:00
commit 59e43ea17f
62 changed files with 3828 additions and 803 deletions

BIN
.DS_Store vendored

Binary file not shown.

3
.gitignore vendored
View File

@ -11,4 +11,5 @@ npm-debug.log
datasources.*.json
print.*.json
db.json
junit.xml
junit.xml
.DS_Store

4
Jenkinsfile vendored
View File

@ -69,13 +69,13 @@ pipeline {
}
}
}
stage('Backend') {
/* stage('Backend') {
steps {
nodejs('node-lts') {
sh 'gulp backTestOnce --ci'
}
}
}
} */
}
}
stage('Build') {

BIN
back/.DS_Store vendored

Binary file not shown.

BIN
back/methods/.DS_Store vendored

Binary file not shown.

View File

@ -26,6 +26,15 @@
"Delivery": {
"dataSource": "vn"
},
"Image": {
"dataSource": "vn"
},
"ImageCollection": {
"dataSource": "vn"
},
"ImageCollectionSize": {
"dataSource": "vn"
},
"Province": {
"dataSource": "vn"
},

View File

@ -0,0 +1,44 @@
{
"name": "ImageCollectionSize",
"base": "VnModel",
"options": {
"mysql": {
"table": "hedera.imageCollectionSize"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"width": {
"type": "Number",
"required": true
},
"height": {
"type": "Number",
"required": true
},
"crop": {
"type": "Boolean",
"required": true
}
},
"relations": {
"collection": {
"type": "belongsTo",
"model": "ImageCollection",
"foreignKey": "collectionFk"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "employee",
"permission": "ALLOW"
}
]
}

View File

@ -0,0 +1,57 @@
{
"name": "ImageCollection",
"base": "VnModel",
"options": {
"mysql": {
"table": "hedera.imageCollection"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"name": {
"type": "String",
"required": true
},
"desc": {
"type": "String",
"required": true
},
"maxWidth": {
"type": "Number",
"required": true
},
"maxHeight": {
"type": "Number",
"required": true
},
"model": {
"type": "String",
"required": true
},
"property": {
"type": "String",
"required": true
}
},
"relations": {
"sizes": {
"type": "hasMany",
"model": "ImageCollectionSize",
"foreignKey": "collectionFk",
"property": "id"
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "employee",
"permission": "ALLOW"
}
]
}

99
back/models/image.js Normal file
View File

@ -0,0 +1,99 @@
const fs = require('fs-extra');
const sharp = require('sharp');
const path = require('path');
module.exports = Self => {
Self.getPath = function() {
return '/var/lib/salix/image';
};
Self.registerImage = async(collectionName, file, srcFilePath) => {
const models = Self.app.models;
const tx = await Self.beginTransaction({});
const myOptions = {transaction: tx};
try {
const collection = await models.ImageCollection.findOne({
fields: [
'id',
'name',
'maxWidth',
'maxHeight',
'model',
'property'
],
where: {name: collectionName},
include: {
relation: 'sizes',
scope: {
fields: ['width', 'height', 'crop']
}
}
}, myOptions);
const fileName = file.split('.')[0];
const rootPath = Self.getPath();
const data = {
name: fileName,
collectionFk: collectionName
};
const newImage = await Self.upsertWithWhere(data, {
name: fileName,
collectionFk: collectionName,
updated: (new Date).getTime()
}, myOptions);
// Resizes and saves the image
const collectionDir = path.join(rootPath, collectionName);
const dstDir = path.join(collectionDir, 'full');
const dstFile = path.join(dstDir, file);
const resizeOpts = {
withoutEnlargement: true,
fit: 'inside'
};
await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath)
.resize(collection.maxWidth, collection.maxHeight, resizeOpts)
.toFile(dstFile);
const sizes = collection.sizes();
for (let size of sizes) {
const dstDir = path.join(collectionDir, `${size.width}x${size.height}`);
const dstFile = path.join(dstDir, file);
const resizeOpts = {
withoutEnlargement: true,
fit: size.crop ? 'cover' : 'inside'
};
await fs.mkdir(dstDir, {recursive: true});
await sharp(srcFilePath)
.resize(size.width, size.height, resizeOpts)
.toFile(dstFile);
}
const model = models[collection.model];
if (!model)
throw new Error('Matching model not found');
const item = await model.findById(fileName, null, myOptions);
if (item) {
await item.updateAttribute(
collection.property,
fileName,
myOptions
);
}
await fs.unlink(srcFilePath);
await tx.commit();
return newImage;
} catch (e) {
await tx.rollback();
throw e;
}
};
};

41
back/models/image.json Normal file
View File

@ -0,0 +1,41 @@
{
"name": "Image",
"base": "VnModel",
"options": {
"mysql": {
"table": "hedera.image"
}
},
"properties": {
"id": {
"type": "Number",
"id": true,
"description": "The id"
},
"name": {
"type": "String",
"required": true
},
"collectionFk": {
"type": "String",
"required": true
},
"updated": {
"type": "Number"
},
"nRefs": {
"type": "Number",
"required": true,
"default": 0
}
},
"acls": [
{
"accessType": "READ",
"principalType": "ROLE",
"principalId": "employee",
"permission": "ALLOW"
}
]
}

View File

@ -1,5 +0,0 @@
UPDATE `salix`.`ACL` SET `accessType`='WRITE' WHERE `id`='213';
UPDATE `salix`.`ACL` SET `property` = 'deleteSales' WHERE (`id` = '80');
INSERT IGNORE INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`) VALUES ('CustomsAgent', '*', '*', 'ALLOW', 'ROLE', 'employee');

View File

@ -0,0 +1,5 @@
ALTER TABLE `postgresql`.`calendar_employee`
ADD COLUMN `id` INT NULL AUTO_INCREMENT FIRST,
ADD UNIQUE INDEX `id_UNIQUE` (`id` ASC) VISIBLE,
ADD INDEX `id_index` (`id` ASC) VISIBLE;
;

View File

@ -0,0 +1,12 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `workerCalendar2` AS
SELECT
`ce`.`id` AS `id`,
`ce`.`business_id` AS `businessFk`,
`ce`.`calendar_state_id` AS `absenceTypeFk`,
`ce`.`date` AS `dated`
FROM `postgresql`.`calendar_employee` `ce`;

View File

@ -0,0 +1,47 @@
USE `vn`;
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`zoneEstimatedDelivery` AS
SELECT
`t`.`zoneFk` AS `zoneFk`,
CAST((CURDATE() + INTERVAL ((HOUR(`zc`.`hour`) * 60) + MINUTE(`zc`.`hour`)) MINUTE)
AS TIME) AS `hourTheoretical`,
CAST(SUM(`sv`.`volume`) AS DECIMAL (5 , 1 )) AS `totalVolume`,
CAST(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))
AS DECIMAL (5 , 1 )) AS `remainingVolume`,
GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0)) AS `speed`,
CAST((`zc`.`hour` + INTERVAL ((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0))) MINUTE)
AS TIME) AS `hourEffective`,
FLOOR(((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0)))) AS `minutesLess`,
CAST((`zc`.`hour` + INTERVAL ((-(SUM(IF((`s`.`alertLevel` < 2),
`sv`.`volume`,
0))) * 60) / GREATEST(IFNULL(`lhp`.`m3`, 0),
IFNULL(`dl`.`minSpeed`, 0))) MINUTE)
AS TIME) AS `etc`
FROM
((((((((`ticket` `t`
JOIN `ticketStateToday` `tst` ON ((`tst`.`ticket` = `t`.`id`)))
JOIN `state` `s` ON ((`s`.`id` = `tst`.`state`)))
JOIN `saleVolume` `sv` ON ((`sv`.`ticketFk` = `t`.`id`)))
LEFT JOIN `lastHourProduction` `lhp` ON ((`lhp`.`warehouseFk` = `t`.`warehouseFk`)))
JOIN `warehouse` `w` ON ((`w`.`id` = `t`.`warehouseFk`)))
JOIN `warehouseAlias` `wa` ON ((`wa`.`id` = `w`.`aliasFk`)))
LEFT JOIN `zoneClosure` `zc` ON (((`zc`.`zoneFk` = `t`.`zoneFk`)
AND (`zc`.`dated` = CURDATE()))))
LEFT JOIN `cache`.`departure_limit` `dl` ON (((`dl`.`warehouse_id` = `t`.`warehouseFk`)
AND (`dl`.`fecha` = CURDATE()))))
WHERE
((`wa`.`name` = 'Silla')
AND (CAST(`t`.`shipped` AS DATE) = CURDATE()))
GROUP BY `t`.`zoneFk`;

View File

@ -0,0 +1,16 @@
CREATE
OR REPLACE ALGORITHM = UNDEFINED
DEFINER = `root`@`%`
SQL SECURITY DEFINER
VIEW `vn`.`zone_ETD` AS
SELECT
`zed`.`zoneFk` AS `zoneFk`,
`zed`.`hourTheoretical` AS `HoraTeórica`,
`zed`.`totalVolume` AS `volumenTotal`,
`zed`.`remainingVolume` AS `volumenPendiente`,
`zed`.`speed` AS `velocidad`,
`zed`.`hourEffective` AS `HoraPráctica`,
`zed`.`minutesLess` AS `minutesLess`,
`zed`.`etc` AS `etc`
FROM
`vn`.`zoneEstimatedDelivery` `zed`

File diff suppressed because one or more lines are too long

View File

@ -88,13 +88,18 @@ INSERT INTO `vn`.`country`(`id`, `country`, `isUeeMember`, `code`, `currencyFk`,
(19,'Francia', 1, 'FR', 1, 27),
(30,'Canarias', 1, 'IC', 1, 24);
INSERT INTO `vn`.`warehouse`(`id`, `name`, `isComparative`, `isInventory`, `hasAvailable`, `isManaged`, `hasStowaway`, `hasDms`, `hasComission`)
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
VALUES
(1, 'Warehouse One', 1, 1, 1, 1, 1, 1, 1),
(2, 'Warehouse Two', 1, 1, 1, 1, 0, 0, 1),
(3, 'Warehouse Three', 1, 1, 1, 1, 0, 0, 0),
(4, 'Warehouse Four', 1, 1, 1, 1, 0, 0, 0),
(5, 'Warehouse Five', 1, 1, 1, 1, 0, 0, 0);
(1, 'Main Warehouse'),
(2, 'Silla');
INSERT INTO `vn`.`warehouse`(`id`, `name`, `isComparative`, `isInventory`, `hasAvailable`, `isManaged`, `hasStowaway`, `hasDms`, `hasComission`, `aliasFk`)
VALUES
(1, 'Warehouse One', 1, 1, 1, 1, 1, 1, 1, 2),
(2, 'Warehouse Two', 1, 1, 1, 1, 0, 0, 1, 2),
(3, 'Warehouse Three', 1, 1, 1, 1, 0, 0, 0, 2),
(4, 'Warehouse Four', 1, 1, 1, 1, 0, 0, 0, 2),
(5, 'Warehouse Five', 1, 1, 1, 1, 0, 0, 0, 2);
INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPreparedByPacking`, `code`, `pickingPlacement`, `path`)
VALUES
@ -111,10 +116,6 @@ INSERT INTO `vn`.`shelving` (`code`, `parkingFk`, `isPrinted`, `priority`, `park
('GVC', '1', '0', '1', '0', '106'),
('HEJ', '2', '0', '1', '0', '106');
INSERT INTO `vn`.`warehouseAlias`(`id`, `name`)
VALUES
(1, 'Main Warehouse');
INSERT INTO `vn`.`accountingType`(`id`, `description`)
VALUES
(1, 'Digital money'),
@ -510,7 +511,23 @@ INSERT INTO `vn`.`zoneWarehouse` (`id`, `zoneFk`, `warehouseFk`)
(11, 11, 5),
(12, 12, 4),
(13, 13, 5);
INSERT INTO `vn`.`zoneClosure` (`zoneFk`, `dated`, `hour`)
VALUES
(1, CURDATE(), '23:59'),
(2, CURDATE(), '23:59'),
(3, CURDATE(), '23:59'),
(4, CURDATE(), '23:59'),
(5, CURDATE(), '23:59'),
(6, CURDATE(), '23:59'),
(7, CURDATE(), '23:59'),
(8, CURDATE(), '23:59'),
(9, CURDATE(), '23:59'),
(10, CURDATE(), '23:59'),
(11, CURDATE(), '23:59'),
(12, CURDATE(), '23:59'),
(13, CURDATE(), '23:59');
INSERT INTO `vn`.`zoneConfig` (`scope`) VALUES ('1');
INSERT INTO `vn`.`route`(`id`, `time`, `workerFk`, `created`, `vehicleFk`, `agencyModeFk`, `description`, `m3`, `cost`, `started`, `finished`, `zoneFk`)
@ -588,7 +605,7 @@ INSERT INTO `vn`.`ticketTracking`(`ticketFk`, `stateFk`, `workerFk`, `created`)
(16, 3, 19, NOW()),
(17, 3, 19, NOW()),
(18, 3, 19, NOW()),
(19, 17, 19, NOW()),
(19, 3, 19, NOW()),
(20, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(21, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),
(22, 1, 19, DATE_ADD(NOW(), INTERVAL +1 MONTH)),

File diff suppressed because it is too large Load Diff

View File

@ -80,7 +80,6 @@ IGNORETABLES=(
--ignore-table=vn.ticketRequest__
--ignore-table=vn.ticketToPrepare
--ignore-table=vn.till__
--ignore-table=vn.time
--ignore-table=vn.travelThermograph__
--ignore-table=vn.travel_cloneWeekly
--ignore-table=vn.unary

View File

@ -28,6 +28,7 @@ services:
volumes:
- /mnt/storage/pdfs:/var/lib/salix/pdfs
- /mnt/storage/dms:/var/lib/salix/dms
- /mnt/storage/image:/var/lib/salix/image
deploy:
replicas: 6
configs:

View File

@ -631,7 +631,7 @@ export default {
},
ordersIndex: {
searchResult: 'vn-order-index vn-card > vn-table > div > vn-tbody > a.vn-tr',
firstSearchResultTotal: 'vn-order-index vn-card > vn-table > div > vn-tbody vn-tr vn-td:nth-child(7)',
secondSearchResultTotal: 'vn-order-index vn-card > vn-table > div > vn-tbody vn-tr:nth-child(2) vn-td:nth-child(9)',
searchResultDate: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(4)',
searchResultAddress: 'vn-order-index vn-table vn-tbody > a:nth-child(1) > vn-td:nth-child(6)',
searchButton: 'vn-searchbar vn-icon[icon="search"]',

View File

@ -211,12 +211,16 @@ describe('Ticket Edit sale path', () => {
it('should search for a ticket then access to the sales section', async() => {
await page.accessToSearchResult('16');
await page.accessToSection('ticket.card.sale');
await page.wait(2000);
});
it('should select the third sale and delete it', async() => {
await page.waitToClick(selectors.ticketSales.thirdSaleCheckbox);
await page.wait(2000);
await page.waitToClick(selectors.ticketSales.deleteSaleButton);
await page.wait(2000);
await page.waitToClick(selectors.ticketSales.acceptDeleteLineButton);
await page.wait(2000);
await page.waitForSpinnerLoad();
const message = await page.waitForSnackbar();

View File

@ -8,6 +8,7 @@ describe('Order summary path', () => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('employee', 'order');
await page.waitFor(2000);
await page.accessToSearchResult('16');
});

View File

@ -15,9 +15,9 @@ describe('Order Index', () => {
await browser.close();
});
it(`should check the first search result doesn't contain a total of 0€`, async() => {
it(`should check the second search result doesn't contain a total of 0€`, async() => {
await page.waitToClick(selectors.ordersIndex.searchButton);
const result = await page.waitToGetProperty(selectors.ordersIndex.firstSearchResultTotal, 'innerText');
const result = await page.waitToGetProperty(selectors.ordersIndex.secondSearchResultTotal, 'innerText');
expect(result).not.toContain('0.00');
});
@ -26,8 +26,8 @@ describe('Order Index', () => {
await page.waitToClick(selectors.ordersIndex.openAdvancedSearch);
await page.waitToClick(selectors.ordersIndex.advancedSearchShowEmptyCheckbox);
await page.waitToClick(selectors.ordersIndex.advancedSearchButton);
await page.waitForTextInElement(selectors.ordersIndex.firstSearchResultTotal, '0.00');
const result = await page.waitToGetProperty(selectors.ordersIndex.firstSearchResultTotal, 'innerText');
await page.waitForTextInElement(selectors.ordersIndex.secondSearchResultTotal, '0.00');
const result = await page.waitToGetProperty(selectors.ordersIndex.secondSearchResultTotal, 'innerText');
expect(result).toContain('0.00');
});

View File

@ -135,7 +135,7 @@ export default class Calendar extends FormInput {
$days: [day],
$type: 'day'
});
this.repaint();
// this.repaint();
}
/*

BIN
loopback/.DS_Store vendored

Binary file not shown.

View File

@ -249,7 +249,6 @@ module.exports = function(Self) {
let newInstance = {};
if (ctx.hookState.newInstance)
Object.assign(newInstance, ctx.hookState.newInstance);
let userFk;
if (loopBackContext)
userFk = loopBackContext.active.accessToken.userId;

View File

@ -133,5 +133,6 @@
"Distance must be lesser than 1000": "La distancia debe ser inferior a 1000",
"This ticket is deleted": "Este ticket está eliminado",
"A travel with this data already exists": "Ya existe un travel con estos datos",
"This thermograph id already exists": "La id del termógrafo ya existe"
"This thermograph id already exists": "La id del termógrafo ya existe",
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED"
}

View File

@ -1,7 +1,6 @@
const app = require('vn-loopback/server/server');
// #2304
xdescribe('regularizeClaim()', () => {
describe('regularizeClaim()', () => {
const claimFk = 1;
const pendentState = 1;
const resolvedState = 3;

View File

@ -27,11 +27,11 @@ module.exports = function(Self) {
{
relation: 'salesPerson',
scope: {
fields: ['userFk'],
fields: ['userFk', 'firstName'],
include: {
relation: 'user',
scope: {
fields: ['nickname']
fields: ['name', 'nickname']
}
}
}
@ -45,6 +45,12 @@ module.exports = function(Self) {
scope: {
fields: ['id', 'name']
}
}, {
}, {
relation: 'salesPersonUser',
scope: {
fields: ['id', 'name']
}
}, {
relation: 'country',
scope: {

View File

@ -17,7 +17,7 @@ describe('Client activeWorkersWithRole', () => {
let isBuyer = await app.models.Account.hasRole(result[0].id, 'buyer');
expect(result.length).toEqual(13);
expect(result.length).toEqual(15);
expect(isBuyer).toBeTruthy();
});
});

View File

@ -136,6 +136,11 @@
"model": "Worker",
"foreignKey": "salesPersonFk"
},
"salesPersonUser": {
"type": "belongsTo",
"model": "Account",
"foreignKey": "salesPersonFk"
},
"province": {
"type": "belongsTo",
"model": "Province",

View File

@ -0,0 +1,56 @@
const https = require('https');
const fs = require('fs-extra');
const path = require('path');
module.exports = Self => {
Self.remoteMethod('downloadImages', {
description: 'Returns last entries',
accessType: 'WRITE',
returns: {
type: ['Object'],
root: true
},
http: {
path: `/downloadImages`,
verb: 'POST'
}
});
Self.downloadImages = async() => {
const models = Self.app.models;
try {
const imageQueue = await Self.find({limit: 25});
const rootPath = models.Image.getPath();
const tempPath = path.join(rootPath, 'temp');
// Create temporary path
await fs.mkdir(tempPath, {recursive: true});
for (let image of imageQueue) {
const fileName = `${image.itemFk}.png`;
const filePath = path.join(tempPath, fileName);
const file = fs.createWriteStream(filePath);
https.get(image.url, async response => {
response.pipe(file);
});
file.on('finish', async function() {
await models.Image.registerImage('catalog', fileName, filePath);
await image.destroy();
});
file.on('error', err => {
fs.unlink(filePath);
throw err;
});
}
return imageQueue;
} catch (e) {
throw e;
}
};
};

View File

@ -50,6 +50,9 @@
"ItemShelvingSale": {
"dataSource": "vn"
},
"ItemImageQueue": {
"dataSource": "vn"
},
"Origin": {
"dataSource": "vn"
},

View File

@ -0,0 +1,3 @@
module.exports = Self => {
require('../methods/item-image-queue/downloadImages')(Self);
};

View File

@ -0,0 +1,36 @@
{
"name": "ItemImageQueue",
"description": "Image download queue",
"base": "VnModel",
"options": {
"mysql": {
"table": "itemImageQueue"
}
},
"properties": {
"itemFk": {
"type": "Number",
"id": true,
"description": "Identifier"
},
"url": {
"type": "String",
"required": true
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
}
},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "employee",
"permission": "ALLOW"
}
]
}

View File

@ -133,7 +133,6 @@ module.exports = Self => {
});
filter = mergeFilters(filter, {where});
let stmts = [];
let stmt;
@ -157,14 +156,20 @@ module.exports = Self => {
c.salesPersonFk,
u.nickname workerNickname,
u.name name,
co.code companyCode
co.code companyCode,
zed.zoneFk,
zed.hourTheoretical,
zed.hourEffective
FROM hedera.order o
LEFT JOIN address a ON a.id = o.address_id
LEFT JOIN agencyMode am ON am.id = o.agency_id
LEFT JOIN client c ON c.id = o.customer_id
LEFT JOIN worker wk ON wk.id = c.salesPersonFk
LEFT JOIN account.user u ON u.id = wk.userFk
LEFT JOIN company co ON co.id = o.company_id`);
LEFT JOIN company co ON co.id = o.company_id
LEFT JOIN orderTicket ot ON ot.orderFk = o.id
LEFT JOIN ticket t ON t.id = ot.ticketFk
LEFT JOIN zoneEstimatedDelivery zed ON zed.zoneFk = t.zoneFk`);
if (args && args.ticketFk) {
stmt.merge({
@ -172,7 +177,11 @@ module.exports = Self => {
});
}
stmt.merge(conn.makeSuffix(filter));
stmt.merge(conn.makeWhere(filter.where));
stmt.merge({
sql: `GROUP BY o.id`
});
stmt.merge(conn.makePagination(filter));
stmts.push(stmt);
stmts.push(`

View File

@ -10,11 +10,13 @@
<vn-thead>
<vn-tr>
<vn-th field="id" number>Id</vn-th>
<vn-th field="clientFk">Client</vn-th>
<vn-th field="salesPersonFk">Sales person</vn-th>
<vn-th field="clientFk">Client</vn-th>
<vn-th field="isConfirmed" center>Confirmed</vn-th>
<vn-th field="created" center>Created</vn-th>
<vn-th field="landed" default-order="DESC" center>Landed</vn-th>
<vn-th field="created" center title ="Theoretical hour" >T. Hour</vn-th>
<vn-th field="created" center>Real hour </vn-th>
<vn-th center>Total</vn-th>
</vn-tr>
</vn-thead>
@ -24,13 +26,6 @@
class="clickable search-result"
ui-sref="order.card.summary({id: {{::order.id}}})">
<vn-td number>{{::order.id}}</vn-td>
<vn-td expand>
<span
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
class="link">
{{::order.clientName}}
</span>
</vn-td>
<vn-td expand>
<span
vn-click-stop="workerDescriptor.show($event, order.salesPersonFk)"
@ -38,14 +33,27 @@
{{::order.name | dashIfEmpty}}
</span>
</vn-td>
<vn-td center>
<vn-td expand>
<span
vn-click-stop="clientDescriptor.show($event, order.clientFk)"
class="link">
{{::order.clientName}}
</span>
</vn-td>
<vn-td center>
<vn-check
ng-model="order.isConfirmed"
disabled="true">
</vn-check>
</vn-td>
<vn-td center>{{::order.created | date: 'dd/MM/yyyy HH:mm'}}</vn-td>
<vn-td center>{{::order.landed | date:'dd/MM/yyyy'}}</vn-td>
<vn-td center>
<span class="chip {{$ctrl.compareDate(order.landed)}}">
{{::order.landed | date:'dd/MM/yyyy'}}
</span>
</vn-td>
<vn-td shrink>{{::order.hourTheoretical | date: 'HH:mm'}}</vn-td>
<vn-td shrink>{{::ticket.hourEffective | date: 'HH:mm'}}</vn-td>
<vn-td number>{{::order.total | currency: 'EUR': 2 | dashIfEmpty}}</vn-td>
<vn-td shrink>
<vn-icon-button

View File

@ -6,6 +6,20 @@ export default class Controller extends Section {
this.selectedOrder = order;
this.$.summary.show();
}
compareDate(date) {
let today = new Date();
today.setHours(0, 0, 0, 0);
let timeTicket = new Date(date);
timeTicket.setHours(0, 0, 0, 0);
let comparation = today - timeTicket;
if (comparation == 0)
return 'warning';
if (comparation < 0)
return 'success';
}
}
ngModule.component('vnOrderIndex', {

View File

@ -0,0 +1,67 @@
import './index.js';
describe('Component vnOrderIndex', () => {
let controller;
let $window;
let orders = [{
id: 1,
clientFk: 1,
isConfirmed: false
}, {
id: 2,
clientFk: 1,
isConfirmed: false
}, {
id: 3,
clientFk: 1,
isConfirmed: true
}];
beforeEach(ngModule('order'));
beforeEach(inject(($componentController, _$window_,) => {
$window = _$window_;
const $element = angular.element('<vn-order-index></vn-order-index>');
controller = $componentController('vnOrderIndex', {$element});
}));
describe('compareDate()', () => {
it('should return warning when the date is the present', () => {
let curDate = new Date();
let result = controller.compareDate(curDate);
expect(result).toEqual('warning');
});
it('should return sucess when the date is in the future', () => {
let futureDate = new Date();
futureDate = futureDate.setDate(futureDate.getDate() + 10);
let result = controller.compareDate(futureDate);
expect(result).toEqual('success');
});
it('should return undefined when the date is in the past', () => {
let pastDate = new Date();
pastDate = pastDate.setDate(pastDate.getDate() - 10);
let result = controller.compareDate(pastDate);
expect(result).toEqual(undefined);
});
});
describe('preview()', () => {
it('should show the dialog summary', () => {
controller.$.summary = {show: () => {}};
jest.spyOn(controller.$.summary, 'show');
let event = new MouseEvent('click', {
view: $window,
bubbles: true,
cancelable: true
});
controller.preview(event, orders[0]);
expect(controller.$.summary.show).toHaveBeenCalledWith();
});
});
});

View File

@ -21,4 +21,6 @@ Ascendant: Ascendente
Descendant: Descendente
Created from: Creado desde
Search order by id: Buscar el pedido por identificador
order: pedido
order: pedido
Confirm lines: Confirmar las lineas
Confirm: Confirmar

View File

@ -1,5 +1,14 @@
<vn-card class="summary">
<h5>{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} - {{$ctrl.summary.client.salesPerson.id}}</h5>
<h5>{{$ctrl.summary.id}} - {{$ctrl.summary.client.name}} - {{$ctrl.summary.client.salesPerson.id}}
<vn-button
disabled="$ctrl.order.isConfirmed"
class="flat"
style="color: inherit;"
label="Confirm"
ng-click="$ctrl.save()"
vn-tooltip="Confirm lines">
</vn-button>
</h5>
<vn-horizontal class="ticketSummary__data">
<vn-one>
<vn-label-value label="Id"

View File

@ -21,6 +21,15 @@ class Controller extends Section {
if (this.order && this.order.id)
this.setSummary();
}
save() {
this.$http.post(`Orders/${this.order.id}/confirm`).then(() => {
this.vnApp.showSuccess(this.$t('Order confirmed'));
this.$state.go(`ticket.index`, {
q: JSON.stringify({clientFk: this.order.clientFk})
});
});
}
}
ngModule.component('vnOrderSummary', {

View File

@ -3,6 +3,21 @@
vn-order-summary .summary{
max-width: $width-lg;
h5 {
display: flex;
justify-content: space-between;
align-items: center;
align-content: center;
span {
flex: 1;
}
vn-button {
flex: none
}
}
& > div > vn-horizontal > vn-one {
min-width: 160px !important;

View File

@ -66,7 +66,7 @@ describe('ticket filter()', () => {
const secondRow = result[1];
const thirdRow = result[2];
expect(result.length).toEqual(13);
expect(result.length).toEqual(12);
expect(firstRow.state).toEqual('Entregado');
expect(secondRow.state).toEqual('Entregado');
expect(thirdRow.state).toEqual('Entregado');

View File

@ -0,0 +1,58 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('createAbsence', {
description: 'Creates a new worker absence',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'absenceTypeId',
type: 'Number',
required: true
},
{
arg: 'dated',
type: 'Date',
required: false
}],
returns: {
type: 'Object',
root: true
},
http: {
path: `/:id/createAbsence`,
verb: 'POST'
}
});
Self.createAbsence = async(ctx, id, absenceTypeId, dated) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const isSubordinate = await models.Worker.isSubordinate(ctx, id);
const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
const labour = await models.WorkerLabour.findOne({
where: {
and: [
{workerFk: id},
{or: [{
ended: {gte: [dated]}
}, {ended: null}]}
]
}
});
return models.WorkerCalendar.create({
businessFk: labour.businessFk,
absenceTypeFk: absenceTypeId,
dated: dated
});
};
};

View File

@ -0,0 +1,37 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('deleteAbsence', {
description: 'Deletes a worker absence',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'absenceId',
type: 'Number',
required: true
}],
returns: 'Object',
http: {
path: `/:id/deleteAbsence`,
verb: 'DELETE'
}
});
Self.deleteAbsence = async(ctx, id, absenceId) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const isSubordinate = await models.Worker.isSubordinate(ctx, id);
const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
const absence = await models.WorkerCalendar.findById(absenceId);
return absence.destroy();
};
};

View File

@ -0,0 +1,39 @@
const app = require('vn-loopback/server/server');
describe('Worker createAbsence()', () => {
const workerId = 106;
let createdAbsence;
afterAll(async() => {
const absence = await app.models.WorkerCalendar.findById(createdAbsence.id);
await absence.destroy();
});
it('should return an error for a user without enough privileges', async() => {
const ctx = {req: {accessToken: {userId: 106}}};
const absenceTypeId = 1;
const dated = new Date();
let error;
await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
it('should create a new absence', async() => {
const ctx = {req: {accessToken: {userId: 37}}};
const absenceTypeId = 1;
const dated = new Date();
createdAbsence = await app.models.Worker.createAbsence(ctx, workerId, absenceTypeId, dated);
const expectedBusinessId = 106;
const expectedAbsenceTypeId = 1;
expect(createdAbsence.businessFk).toEqual(expectedBusinessId);
expect(createdAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId);
});
});

View File

@ -0,0 +1,38 @@
const app = require('vn-loopback/server/server');
describe('Worker deleteAbsence()', () => {
const workerId = 106;
let createdAbsence;
it('should return an error for a user without enough privileges', async() => {
const ctx = {req: {accessToken: {userId: 106}}};
const businessId = 106;
createdAbsence = await app.models.WorkerCalendar.create({
businessFk: businessId,
absenceTypeFk: 1,
dated: new Date()
});
let error;
await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
it('should create a new absence', async() => {
const ctx = {req: {accessToken: {userId: 37}}};
const businessId = 106;
expect(createdAbsence.businessFk).toEqual(businessId);
await app.models.Worker.deleteAbsence(ctx, workerId, createdAbsence.id);
const deletedAbsence = await app.models.WorkerCalendar.findById(createdAbsence.id);
expect(deletedAbsence).toBeNull();
});
});

View File

@ -0,0 +1,38 @@
const app = require('vn-loopback/server/server');
describe('Worker updateAbsence()', () => {
const workerId = 106;
let createdAbsence;
afterAll(async() => {
const absence = await app.models.WorkerCalendar.findById(createdAbsence.id);
await absence.destroy();
});
it('should return an error for a user without enough privileges', async() => {
const ctx = {req: {accessToken: {userId: 106}}};
const expectedAbsenceTypeId = 2;
createdAbsence = await app.models.WorkerCalendar.create({
businessFk: 106,
absenceTypeFk: 1,
dated: new Date()
});
let error;
await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId).catch(e => {
error = e;
}).finally(() => {
expect(error.message).toEqual(`You don't have enough privileges`);
});
expect(error).toBeDefined();
});
it('should create a new absence', async() => {
const ctx = {req: {accessToken: {userId: 37}}};
const expectedAbsenceTypeId = 2;
const updatedAbsence = await app.models.Worker.updateAbsence(ctx, workerId, createdAbsence.id, expectedAbsenceTypeId);
expect(updatedAbsence.absenceTypeFk).toEqual(expectedAbsenceTypeId);
});
});

View File

@ -0,0 +1,42 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('updateAbsence', {
description: 'Updates a worker absence',
accepts: [{
arg: 'id',
type: 'Number',
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'absenceId',
type: 'Number',
required: true
},
{
arg: 'absenceTypeId',
type: 'Number',
required: true
}],
returns: 'Object',
http: {
path: `/:id/updateAbsence`,
verb: 'PATCH'
}
});
Self.updateAbsence = async(ctx, id, absenceId, absenceTypeId) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const isSubordinate = await models.Worker.isSubordinate(ctx, id);
const isTeamBoss = await models.Account.hasRole(userId, 'teamBoss');
if (!isSubordinate || (isSubordinate && userId == id && !isTeamBoss))
throw new UserError(`You don't have enough privileges`);
const absence = await models.WorkerCalendar.findById(absenceId);
return absence.updateAttribute('absenceTypeFk', absenceTypeId);
};
};

View File

@ -3,29 +3,22 @@
"base": "VnModel",
"options": {
"mysql": {
"table": "workerCalendar"
"table": "workerCalendar2"
}
},
"properties": {
"businessFk": {
"id": 1,
"id": {
"id": true,
"type": "Number"
},
"workerFk": {
"id": 2,
"businessFk": {
"type": "Number"
},
"dated": {
"id": 3,
"type": "Date"
}
},
"relations": {
"worker": {
"type": "belongsTo",
"model": "Worker",
"foreignKey": "workerFk"
},
"absenceType": {
"type": "belongsTo",
"model": "AbsenceType",

View File

@ -4,4 +4,7 @@ module.exports = Self => {
require('../methods/worker/isSubordinate')(Self);
require('../methods/worker/getWorkedHours')(Self);
require('../methods/worker/uploadFile')(Self);
require('../methods/worker/createAbsence')(Self);
require('../methods/worker/deleteAbsence')(Self);
require('../methods/worker/updateAbsence')(Self);
};

View File

@ -5,15 +5,19 @@
</vn-crud-model>
<div class="vn-w-lg">
<vn-card class="vn-pa-sm calendars">
<vn-calendar
ng-repeat="month in $ctrl.months"
data="$ctrl.events"
default-date="month"
format-day="$ctrl.formatDay($day, $element)"
display-controls="false"
hide-contiguous="true"
hide-year="true">
</vn-calendar>
<vn-icon ng-if="::$ctrl.isSubordinate" icon="info" color-marginal
vn-tooltip="To start adding absences, click an absence type from the right menu and then on the day you want to add an absence">
</vn-icon>
<vn-calendar
ng-repeat="month in $ctrl.months"
data="$ctrl.events"
default-date="month"
format-day="$ctrl.formatDay($day, $element)"
display-controls="false"
hide-contiguous="true"
hide-year="true"
on-selection="$ctrl.onSelection($event, $days)">
</vn-calendar>
</vn-card>
</div>
<vn-side-menu side="right">
@ -26,12 +30,19 @@
</div>
</div>
<div class="vn-pt-md" style="overflow: hidden;">
<vn-chip ng-repeat="absenceType in absenceTypes">
<vn-chip ng-repeat="absenceType in absenceTypes" ng-class="::{'selectable': $ctrl.isSubordinate}"
ng-click="$ctrl.pick(absenceType)">
<vn-avatar
ng-style="{backgroundColor: absenceType.rgb}">
<vn-icon icon="check" ng-if="absenceType.id == $ctrl.absenceType.id"></vn-icon>
</vn-avatar>
{{absenceType.name}}
</vn-chip>
</div>
</div>
</vn-side-menu>
</vn-side-menu>
<vn-confirm
vn-id="confirm"
message="This item will be deleted"
question="Are you sure you want to continue?">
</vn-confirm>

View File

@ -43,15 +43,17 @@ class Controller extends Section {
set worker(value) {
this._worker = value;
if (!value) return;
let params = {
workerFk: this.worker.id,
started: this.started,
ended: this.ended
};
this.$http.get(`WorkerCalendars/absences`, {params})
.then(res => this.onData(res.data));
if (value) {
this.refresh().then(() => this.repaint());
this.getIsSubordinate();
}
}
getIsSubordinate() {
this.$http.get(`Workers/${this.worker.id}/isSubordinate`).then(res =>
this.isSubordinate = res.data
);
}
onData(data) {
@ -79,12 +81,12 @@ class Controller extends Section {
let type = absence.absenceType;
addEvent(absence.dated, {
name: type.name,
color: type.rgb
color: type.rgb,
type: type.code,
absenceId: absence.id
});
});
}
this.repaint();
}
repaint() {
@ -102,6 +104,105 @@ class Controller extends Section {
dayNumber.style.backgroundColor = event.color;
dayNumber.style.color = 'rgba(0, 0, 0, 0.7)';
}
pick(absenceType) {
if (!this.isSubordinate) return;
if (absenceType == this.absenceType)
absenceType = null;
this.absenceType = absenceType;
}
onSelection($event, $days) {
if (!this.absenceType)
return this.vnApp.showMessage(this.$t('Choose an absence type from the right menu'));
const day = $days[0];
const stamp = day.getTime();
const event = this.events[stamp];
const calendar = $event.target.closest('vn-calendar').$ctrl;
if (event) {
if (event.type == this.absenceType.code)
this.delete(calendar, day, event);
else
this.edit(calendar, event);
} else
this.create(calendar, day);
}
create(calendar, dated) {
const absenceType = this.absenceType;
const params = {
dated: dated,
absenceTypeId: absenceType.id
};
const path = `Workers/${this.$params.id}/createAbsence`;
this.$http.post(path, params).then(res => {
const newEvent = res.data;
this.events[dated.getTime()] = {
name: absenceType.name,
color: absenceType.rgb,
type: absenceType.code,
absenceId: newEvent.id
};
this.repaintCanceller(() =>
this.refresh().then(calendar.repaint())
);
});
}
edit(calendar, event) {
const absenceType = this.absenceType;
const params = {
absenceId: event.absenceId,
absenceTypeId: absenceType.id
};
const path = `Workers/${this.$params.id}/updateAbsence`;
this.$http.patch(path, params).then(() => {
event.color = absenceType.rgb;
event.name = absenceType.name;
event.type = absenceType.code;
this.repaintCanceller(() =>
this.refresh().then(calendar.repaint())
);
});
}
delete(calendar, day, event) {
const params = {absenceId: event.absenceId};
const path = `Workers/${this.$params.id}/deleteAbsence`;
this.$http.delete(path, {params}).then(() => {
delete this.events[day.getTime()];
this.repaintCanceller(() =>
this.refresh().then(calendar.repaint())
);
});
}
repaintCanceller(cb) {
if (this.canceller) {
clearTimeout(this.canceller);
this.canceller = null;
}
this.canceller = setTimeout(
() => cb(), 500);
}
refresh() {
const params = {
workerFk: this.worker.id,
started: this.started,
ended: this.ended
};
return this.$http.get(`WorkerCalendars/absences`, {params})
.then(res => this.onData(res.data));
}
}
ngModule.component('vnWorkerCalendar', {

View File

@ -3,17 +3,23 @@ import './index';
describe('Worker', () => {
describe('Component vnWorkerCalendar', () => {
let $httpBackend;
let $httpParamSerializer;
let $scope;
let controller;
let year = new Date().getFullYear();
beforeEach(ngModule('worker'));
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpBackend_) => {
beforeEach(angular.mock.inject(($componentController, $rootScope, _$httpParamSerializer_, _$httpBackend_) => {
$scope = $rootScope.$new();
$httpBackend = _$httpBackend_;
const $element = angular.element('<vn-worker-calencar></vn-worker-calencar>');
$httpParamSerializer = _$httpParamSerializer_;
const $element = angular.element('<vn-worker-calendar></vn-worker-calendar>');
controller = $componentController('vnWorkerCalendar', {$element, $scope});
controller.isSubordinate = true;
controller.absenceType = {id: 1, name: 'Holiday', code: 'holiday', rgb: 'red'};
controller.$params.id = 106;
controller._worker = {id: 106};
}));
describe('started property', () => {
@ -44,8 +50,9 @@ describe('Worker', () => {
describe('worker() setter', () => {
it(`should perform a get query and set the reponse data on the model`, () => {
let today = new Date();
jest.spyOn(controller, 'getIsSubordinate').mockReturnValue(true);
let today = new Date();
let tomorrow = new Date(today.getTime());
tomorrow.setDate(tomorrow.getDate() + 1);
@ -64,7 +71,7 @@ describe('Worker', () => {
]
});
controller.worker = {id: 1};
controller.worker = {id: 107};
$httpBackend.flush();
let events = controller.events;
@ -73,11 +80,14 @@ describe('Worker', () => {
expect(events[tomorrow.getTime()].name).toEqual('Easter');
expect(events[yesterday.getTime()].name).toEqual('Leave');
expect(events[yesterday.getTime()].color).toEqual('#bbb');
expect(controller.getIsSubordinate).toHaveBeenCalledWith();
});
});
describe('formatDay()', () => {
it(`should set the day element style`, () => {
jest.spyOn(controller, 'getIsSubordinate').mockReturnThis();
let today = new Date();
$httpBackend.whenRoute('GET', 'WorkerCalendars/absences')
@ -99,5 +109,219 @@ describe('Worker', () => {
expect(dayNumber.style.backgroundColor).toEqual('rgb(0, 0, 0)');
});
});
describe('pick()', () => {
it(`should set the absenceType property to null if they match with the current one`, () => {
const absenceType = {id: 1, name: 'Holiday'};
controller.absenceType = absenceType;
controller.pick(absenceType);
expect(controller.absenceType).toBeNull();
});
it(`should set the absenceType property`, () => {
const absenceType = {id: 1, name: 'Holiday'};
const expectedAbsence = {id: 2, name: 'Leave of absence'};
controller.absenceType = absenceType;
controller.pick(expectedAbsence);
expect(controller.absenceType).toEqual(expectedAbsence);
});
});
describe('onSelection()', () => {
it(`should show an snackbar message if no absence type is selected`, () => {
jest.spyOn(controller.vnApp, 'showMessage').mockReturnThis();
const $event = {};
const $days = [];
controller.absenceType = null;
controller.onSelection($event, $days);
expect(controller.vnApp.showMessage).toHaveBeenCalledWith('Choose an absence type from the right menu');
});
it(`should call to the create() method`, () => {
jest.spyOn(controller, 'create').mockReturnThis();
const selectedDay = new Date();
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.absenceType = {id: 1};
controller.onSelection($event, $days);
expect(controller.create).toHaveBeenCalledWith(jasmine.any(Object), selectedDay);
});
it(`should call to the delete() method`, () => {
jest.spyOn(controller, 'delete').mockReturnThis();
const selectedDay = new Date();
const expectedEvent = {
dated: selectedDay,
type: 'holiday'
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.delete).toHaveBeenCalledWith(jasmine.any(Object), selectedDay, expectedEvent);
});
it(`should call to the edit() method`, () => {
jest.spyOn(controller, 'edit').mockReturnThis();
const selectedDay = new Date();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence'
};
const $event = {
target: {
closest: () => {
return {$ctrl: {}};
}
}
};
const $days = [selectedDay];
controller.events[selectedDay.getTime()] = expectedEvent;
controller.absenceType = {id: 1, code: 'holiday'};
controller.onSelection($event, $days);
expect(controller.edit).toHaveBeenCalledWith(jasmine.any(Object), expectedEvent);
});
});
describe('create()', () => {
it(`should make a HTTP POST query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const dated = new Date();
const calendarElement = {};
const expectedResponse = {id: 10};
$httpBackend.expect('POST', `Workers/106/createAbsence`).respond(200, expectedResponse);
controller.create(calendarElement, dated);
$httpBackend.flush();
const createdEvent = controller.events[dated.getTime()];
const absenceType = controller.absenceType;
expect(createdEvent.absenceId).toEqual(expectedResponse.id);
expect(createdEvent.color).toEqual(absenceType.rgb);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('edit()', () => {
it(`should make a HTTP PATCH query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const event = {absenceId: 10};
const calendarElement = {};
const newAbsenceType = {
id: 2,
name: 'Leave of absence',
code: 'leaveOfAbsence',
rgb: 'purple'
};
controller.absenceType = newAbsenceType;
const expectedParams = {absenceId: 10, absenceTypeId: 2};
$httpBackend.expect('PATCH', `Workers/106/updateAbsence`, expectedParams).respond(200);
controller.edit(calendarElement, event);
$httpBackend.flush();
expect(event.name).toEqual(newAbsenceType.name);
expect(event.color).toEqual(newAbsenceType.rgb);
expect(event.type).toEqual(newAbsenceType.code);
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('delete()', () => {
it(`should make a HTTP DELETE query and then call to the repaintCanceller() method`, () => {
jest.spyOn(controller, 'repaintCanceller').mockReturnThis();
const expectedParams = {absenceId: 10};
const calendarElement = {};
const selectedDay = new Date();
const expectedEvent = {
dated: selectedDay,
type: 'leaveOfAbsence',
absenceId: 10
};
controller.events[selectedDay.getTime()] = expectedEvent;
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('DELETE', `Workers/106/deleteAbsence?${serializedParams}`).respond(200);
controller.delete(calendarElement, selectedDay, expectedEvent);
$httpBackend.flush();
const event = controller.events[selectedDay.getTime()];
expect(event).toBeUndefined();
expect(controller.repaintCanceller).toHaveBeenCalled();
});
});
describe('repaintCanceller()', () => {
it(`should cancell the callback execution timer`, () => {
jest.spyOn(window, 'clearTimeout');
jest.spyOn(window, 'setTimeout');
const timeoutId = 90;
controller.canceller = timeoutId;
controller.repaintCanceller(() => {
return 'My callback';
});
expect(window.clearTimeout).toHaveBeenCalledWith(timeoutId);
expect(window.setTimeout).toHaveBeenCalledWith(jasmine.any(Function), 500);
});
});
describe('refresh()', () => {
it(`should make a HTTP GET query and then call to the onData() method`, () => {
jest.spyOn(controller, 'onData').mockReturnThis();
const dated = controller.date;
const started = new Date(dated.getTime());
started.setMonth(0);
started.setDate(1);
const ended = new Date(dated.getTime());
ended.setMonth(12);
ended.setDate(0);
controller.started = started;
controller.ended = ended;
const expecteResponse = [{id: 1}];
const expectedParams = {workerFk: 106, started: started, ended: ended};
const serializedParams = $httpParamSerializer(expectedParams);
$httpBackend.expect('GET', `WorkerCalendars/absences?${serializedParams}`).respond(200, expecteResponse);
controller.refresh();
$httpBackend.flush();
expect(controller.onData).toHaveBeenCalledWith(expecteResponse);
});
});
});
});

View File

@ -2,4 +2,6 @@ Calendar: Calendario
Holidays: Vacaciones
Used: Utilizados
of: de
days: días
days: días
Choose an absence type from the right menu: Elige un tipo de ausencia desde el menú de la derecha
To start adding absences, click an absence type from the right menu and then on the day you want to add an absence: Para empezar a añadir ausencias, haz clic en un tipo de ausencia desde el menu de la derecha y después en el día que quieres añadir la ausencia

View File

@ -2,6 +2,7 @@
vn-worker-calendar {
.calendars {
position: relative;
display: flex;
flex-wrap: wrap;
justify-content: center;
@ -16,4 +17,23 @@ vn-worker-calendar {
max-width: 288px;
}
}
vn-chip.selectable {
cursor: pointer
}
vn-chip.selectable:hover {
opacity: 0.8
}
vn-chip vn-avatar {
text-align: center;
color: white
}
vn-icon[icon="info"] {
position: absolute;
top: 16px;
right: 16px
}
}

View File

@ -28,5 +28,8 @@
},
"ZoneWarehouse": {
"dataSource": "vn"
},
"ZoneEstimatedDelivery": {
"dataSource": "vn"
}
}

View File

@ -0,0 +1,44 @@
{
"name": "ZoneEstimatedDelivery",
"base": "VnModel",
"options": {
"mysql": {
"table": "zoneEstimatedDelivery"
}
},
"properties": {
"zoneFk": {
"id": true,
"type": "Number"
},
"hourTheoretical": {
"type": "date"
},
"totalVolume": {
"type": "Number"
},
"remainingVolume": {
"type": "Number"
},
"speed": {
"type": "Number"
},
"hourEffective": {
"type": "Date"
},
"minutesLess": {
"type": "Date"
},
"etc": {
"type": "Date"
}
},
"relations": {
"zone": {
"type": "belongsTo",
"model": "Zone",
"foreignKey": "zoneFk"
}
}
}

366
package-lock.json generated
View File

@ -4690,8 +4690,7 @@
"ansi-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg="
},
"ansi-styles": {
"version": "3.2.1",
@ -4741,8 +4740,7 @@
"aproba": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz",
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==",
"dev": true
"integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw=="
},
"archy": {
"version": "1.0.0",
@ -4754,7 +4752,6 @@
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz",
"integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==",
"dev": true,
"requires": {
"delegates": "^1.0.0",
"readable-stream": "^2.0.6"
@ -6610,8 +6607,7 @@
"chownr": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.3.tgz",
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw==",
"dev": true
"integrity": "sha512-i70fVHhmV3DtTl6nqvZOnIjbY0Pe4kAUjwHj8z0zAdgBtYrJyYwLKCCuRBQ5ppkyL0AkN7HKRnETdmdp1zqNXw=="
},
"chrome-trace-event": {
"version": "1.0.2",
@ -6820,8 +6816,7 @@
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"collect-v8-coverage": {
"version": "1.0.1",
@ -6850,11 +6845,19 @@
"object-visit": "^1.0.0"
}
},
"color": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/color/-/color-3.1.2.tgz",
"integrity": "sha512-vXTJhHebByxZn3lDvDJYw4lR5+uB3vuoHsuYA5AKuxRVn5wzzIfQKGLBmgdVRHKTJYeK5rvJcHnrd0Li49CFpg==",
"requires": {
"color-convert": "^1.9.1",
"color-string": "^1.5.2"
}
},
"color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"requires": {
"color-name": "1.1.3"
}
@ -6862,8 +6865,16 @@
"color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
"dev": true
"integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
},
"color-string": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz",
"integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==",
"requires": {
"color-name": "^1.0.0",
"simple-swizzle": "^0.2.2"
}
},
"color-support": {
"version": "1.1.3",
@ -7011,8 +7022,7 @@
"console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=",
"dev": true
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"consolidate": {
"version": "0.15.1",
@ -7563,8 +7573,7 @@
"delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=",
"dev": true
"integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o="
},
"denque": {
"version": "1.4.1",
@ -7604,6 +7613,11 @@
"integrity": "sha1-8NZtA2cqglyxtzvbP+YjEMjlUrc=",
"dev": true
},
"detect-libc": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups="
},
"detect-newline": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz",
@ -8369,6 +8383,11 @@
}
}
},
"expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg=="
},
"expand-tilde": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz",
@ -9150,6 +9169,11 @@
"readable-stream": "^2.0.0"
}
},
"fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
},
"fs-extra": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-5.0.0.tgz",
@ -9160,6 +9184,14 @@
"universalify": "^0.1.0"
}
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"requires": {
"minipass": "^3.0.0"
}
},
"fs-mkdirp-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-mkdirp-stream/-/fs-mkdirp-stream-1.0.0.tgz",
@ -9798,7 +9830,6 @@
"version": "2.7.4",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz",
"integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=",
"dev": true,
"requires": {
"aproba": "^1.0.3",
"console-control-strings": "^1.0.0",
@ -9813,14 +9844,12 @@
"ansi-regex": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
"dev": true
"integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8="
},
"is-fullwidth-code-point": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
"dev": true,
"requires": {
"number-is-nan": "^1.0.0"
}
@ -9829,7 +9858,6 @@
"version": "1.0.2",
"resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
"dev": true,
"requires": {
"code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0",
@ -9840,7 +9868,6 @@
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
"dev": true,
"requires": {
"ansi-regex": "^2.0.0"
}
@ -9948,6 +9975,11 @@
"assert-plus": "^1.0.0"
}
},
"github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4="
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@ -11293,8 +11325,7 @@
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=",
"dev": true
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"has-value": {
"version": "1.0.0",
@ -11897,8 +11928,7 @@
"ini": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz",
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==",
"dev": true
"integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw=="
},
"inquirer": {
"version": "6.4.1",
@ -12137,8 +12167,7 @@
"is-fullwidth-code-point": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
"dev": true
"integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
},
"is-generator-fn": {
"version": "2.1.0",
@ -18662,6 +18691,37 @@
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
},
"minipass": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.3.tgz",
"integrity": "sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg==",
"requires": {
"yallist": "^4.0.0"
},
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"minizlib": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.0.tgz",
"integrity": "sha512-EzTZN/fjSvifSX0SlqUERCN39o6T40AMarPbv0MrarSFtIITCBh7bi+dU8nxGFHuqs9jdIAeoYoKuQAAASsPPA==",
"requires": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"dependencies": {
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"mississippi": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/mississippi/-/mississippi-3.0.0.tgz",
@ -18739,6 +18799,11 @@
"minimist": "^1.2.5"
}
},
"mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
},
"mktmpdir": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/mktmpdir/-/mktmpdir-0.1.1.tgz",
@ -19079,6 +19144,11 @@
"to-regex": "^3.0.1"
}
},
"napi-build-utils": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-1.0.2.tgz",
"integrity": "sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg=="
},
"natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@ -19121,6 +19191,19 @@
"resolved": "https://registry.npmjs.org/nocache/-/nocache-2.1.0.tgz",
"integrity": "sha512-0L9FvHG3nfnnmaEQPjT9xhfN4ISk0A8/2j4M37Np4mcDesJjHgEUfgPhdCyZuFI954tjokaIj/A3NdpFNdEh4Q=="
},
"node-abi": {
"version": "2.18.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-2.18.0.tgz",
"integrity": "sha512-yi05ZoiuNNEbyT/xXfSySZE+yVnQW6fxPZuFbLyS1s6b5Kw3HzV2PHOM4XR+nsjzkHxByK+2Wg+yCQbe35l8dw==",
"requires": {
"semver": "^5.4.1"
}
},
"node-addon-api": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.0.0.tgz",
"integrity": "sha512-sSHCgWfJ+Lui/u+0msF3oyCgvdkhxDbkCS6Q8uiJquzOimkJBvX6hl5aSSA7DR1XbMpdM8r7phjcF63sF4rkKg=="
},
"node-fetch": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
@ -19507,6 +19590,11 @@
}
}
},
"noop-logger": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz",
"integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI="
},
"nopt": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
@ -19561,7 +19649,6 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz",
"integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==",
"dev": true,
"requires": {
"are-we-there-yet": "~1.1.2",
"console-control-strings": "~1.1.0",
@ -19581,8 +19668,7 @@
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"nwsapi": {
"version": "2.2.0",
@ -19598,8 +19684,7 @@
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=",
"dev": true
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-copy": {
"version": "0.1.0",
@ -20381,6 +20466,53 @@
"integrity": "sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==",
"dev": true
},
"prebuild-install": {
"version": "5.3.5",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-5.3.5.tgz",
"integrity": "sha512-YmMO7dph9CYKi5IR/BzjOJlRzpxGGVo1EsLSUZ0mt/Mq0HWZIHOKHHcHdT69yG54C9m6i45GpItwRHpk0Py7Uw==",
"requires": {
"detect-libc": "^1.0.3",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp": "^0.5.1",
"napi-build-utils": "^1.0.1",
"node-abi": "^2.7.0",
"noop-logger": "^0.1.1",
"npmlog": "^4.0.1",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^3.0.3",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0",
"which-pm-runs": "^1.0.0"
},
"dependencies": {
"decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"requires": {
"mimic-response": "^2.0.0"
}
},
"mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA=="
},
"simple-get": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz",
"integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==",
"requires": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
}
}
},
"prelude-ls": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
@ -20729,7 +20861,6 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"dev": true,
"requires": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
@ -20740,8 +20871,7 @@
"deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"dev": true
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA=="
}
}
},
@ -21723,8 +21853,7 @@
"set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
"dev": true
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"set-value": {
"version": "2.0.1",
@ -21779,6 +21908,57 @@
"kind-of": "^6.0.2"
}
},
"sharp": {
"version": "0.25.4",
"resolved": "https://registry.npmjs.org/sharp/-/sharp-0.25.4.tgz",
"integrity": "sha512-umSzJJ1oBwIOfwFFt/fJ7JgCva9FvrEU2cbbm7u/3hSDZhXvkME8WE5qpaJqLIe2Har5msF5UG4CzYlEg5o3BQ==",
"requires": {
"color": "^3.1.2",
"detect-libc": "^1.0.3",
"node-addon-api": "^3.0.0",
"npmlog": "^4.1.2",
"prebuild-install": "^5.3.4",
"semver": "^7.3.2",
"simple-get": "^4.0.0",
"tar": "^6.0.2",
"tunnel-agent": "^0.6.0"
},
"dependencies": {
"chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ=="
},
"mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
},
"semver": {
"version": "7.3.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.2.tgz",
"integrity": "sha512-OrOb32TeeambH6UrhtShmF7CRDqhL6/5XpPNp2DuRH6+9QLw/orhp72j87v8Qa1ScDkvrrBNpZcDejAirJmfXQ=="
},
"tar": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.0.2.tgz",
"integrity": "sha512-Glo3jkRtPcvpDlAs/0+hozav78yoXKFr+c4wgw62NNMO3oo4AaJdCo21Uu7lcwr55h39W2XD1LMERc64wtbItg==",
"requires": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^3.0.0",
"minizlib": "^2.1.0",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
}
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
},
"shebang-command": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
@ -21822,6 +22002,51 @@
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
"integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0="
},
"simple-concat": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.0.tgz",
"integrity": "sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY="
},
"simple-get": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.0.tgz",
"integrity": "sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ==",
"requires": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
},
"dependencies": {
"decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"requires": {
"mimic-response": "^3.1.0"
}
},
"mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ=="
}
}
},
"simple-swizzle": {
"version": "0.2.2",
"resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz",
"integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=",
"requires": {
"is-arrayish": "^0.3.1"
},
"dependencies": {
"is-arrayish": {
"version": "0.3.2",
"resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz",
"integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ=="
}
}
},
"sisteransi": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz",
@ -22442,7 +22667,6 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": {
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0"
@ -22460,7 +22684,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": {
"ansi-regex": "^3.0.0"
}
@ -22496,8 +22719,7 @@
"strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
"dev": true
"integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo="
},
"strong-error-handler": {
"version": "2.3.2",
@ -23080,6 +23302,60 @@
"inherits": "2"
}
},
"tar-fs": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.0.tgz",
"integrity": "sha512-9uW5iDvrIMCVpvasdFHW0wJPez0K4JnMZtsuIeDI7HyMGJNxmDZDOCQROr7lXyS+iL/QMpj07qcjGYTSdRFXUg==",
"requires": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.0.0"
}
},
"tar-stream": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.1.3.tgz",
"integrity": "sha512-Z9yri56Dih8IaK8gncVPx4Wqt86NDmQTSh49XLZgjWpGZL9GK9HKParS2scqHCC4w6X9Gh2jwaU45V47XTKwVA==",
"requires": {
"bl": "^4.0.1",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"dependencies": {
"bl": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.0.2.tgz",
"integrity": "sha512-j4OH8f6Qg2bGuWfRiltT2HYGx0e1QcBTrK9KAHNMwMZdQnDZFk0ZSYIpADjYCB3U12nicC5tVJwSIhwOWjb4RQ==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"buffer": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.6.0.tgz",
"integrity": "sha512-/gDYp/UtU0eA1ys8bOs9J6a+E/KWIY+DZ+Q2WESNUA0jFRsJOc0SNUO6xJ5SGA1xueg3NL65W6s+NY5l9cunuw==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
}
}
},
"teeny-request": {
"version": "3.11.3",
"resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz",
@ -25048,11 +25324,15 @@
"integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=",
"dev": true
},
"which-pm-runs": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/which-pm-runs/-/which-pm-runs-1.0.0.tgz",
"integrity": "sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs="
},
"wide-align": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
"integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
"dev": true,
"requires": {
"string-width": "^1.0.2 || 2"
}

View File

@ -29,6 +29,7 @@
"request": "^2.88.0",
"request-promise-native": "^1.0.8",
"require-yaml": "0.0.1",
"sharp": "^0.25.4",
"soap": "^0.26.0",
"strong-error-handler": "^2.3.2",
"uuid": "^3.3.3",