Merge branch 'dev' into 5128-client.credit-management
gitea/salix/pipeline/head This commit looks good Details

This commit is contained in:
Vicent Llopis 2023-04-21 11:30:58 +00:00
commit ea1035311e
22 changed files with 2237 additions and 5910 deletions

130
back/methods/image/scrub.js Normal file
View File

@ -0,0 +1,130 @@
const fs = require('fs-extra');
const path = require('path');
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethod('scrub', {
description: 'Deletes images without database reference',
accessType: 'WRITE',
accepts: [
{
arg: 'collection',
type: 'string',
description: 'The collection name',
required: true
}, {
arg: 'remove',
type: 'boolean',
description: 'Delete instead of move images to trash'
}, {
arg: 'limit',
type: 'integer',
description: 'Maximum number of images to clean'
}, {
arg: 'dryRun',
type: 'boolean',
description: 'Simulate actions'
}, {
arg: 'skipLock',
type: 'boolean',
description: 'Wether to skip exclusive lock'
}
],
returns: {
type: 'integer',
root: true
},
http: {
path: `/scrub`,
verb: 'POST'
}
});
Self.scrub = async function(collection, remove, limit, dryRun, skipLock) {
const $ = Self.app.models;
const env = process.env.NODE_ENV;
dryRun = dryRun || (env && env !== 'production');
const instance = await $.ImageCollection.findOne({
fields: ['id'],
where: {name: collection}
});
if (!instance)
throw new UserError('Collection does not exist');
const container = await $.ImageContainer.container(collection);
const rootPath = container.client.root;
let tx;
let opts;
const lockName = 'salix.Image.scrub';
if (!skipLock) {
tx = await Self.beginTransaction({timeout: null});
opts = {transaction: tx};
const [row] = await Self.rawSql(
`SELECT GET_LOCK(?, 10) hasLock`, [lockName], opts);
if (!row.hasLock)
throw new UserError('Cannot obtain exclusive lock');
}
try {
const now = Date.vnNew().toJSON();
const scrubDir = path.join(rootPath, '.scrub', now);
const collectionDir = path.join(rootPath, collection);
const sizes = await fs.readdir(collectionDir);
let cleanCount = 0;
mainLoop: for (const size of sizes) {
const sizeDir = path.join(collectionDir, size);
const scrubSizeDir = path.join(scrubDir, collection, size);
const images = await fs.readdir(sizeDir);
for (const image of images) {
const imageName = path.parse(image).name;
const count = await Self.count({
collectionFk: collection,
name: imageName
}, opts);
const exists = count > 0;
let scrubDirCreated = false;
if (!exists) {
const srcFile = path.join(sizeDir, image);
if (remove !== true) {
if (!scrubDirCreated) {
if (!dryRun)
await fs.mkdir(scrubSizeDir, {recursive: true});
scrubDirCreated = true;
}
const dstFile = path.join(scrubSizeDir, image);
if (!dryRun) await fs.rename(srcFile, dstFile);
} else {
try {
if (!dryRun) await fs.unlink(srcFile);
} catch (err) {
console.error(err.message);
}
}
cleanCount++;
if (limit && cleanCount == limit)
break mainLoop;
}
}
}
return cleanCount;
} finally {
if (!skipLock) {
try {
await Self.rawSql(`DO RELEASE_LOCK(?)`, [lockName], opts);
await tx.rollback();
} catch (err) {
console.error(err.message);
}
}
}
};
};

View File

@ -12,13 +12,13 @@ module.exports = Self => {
type: 'Number', type: 'Number',
description: 'The entity id', description: 'The entity id',
required: true required: true
}, }, {
{
arg: 'collection', arg: 'collection',
type: 'string', type: 'string',
description: 'The collection name', description: 'The collection name',
required: true required: true
}], }
],
returns: { returns: {
type: 'Object', type: 'Object',
root: true root: true

View File

@ -5,6 +5,7 @@ const gm = require('gm');
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);
require('../methods/image/scrub')(Self);
Self.resize = async function({collectionName, srcFile, fileName, entityId}) { Self.resize = async function({collectionName, srcFile, fileName, entityId}) {
const models = Self.app.models; const models = Self.app.models;
@ -29,13 +30,14 @@ module.exports = Self => {
); );
// Insert image row // Insert image row
const imageName = path.parse(fileName).name;
await models.Image.upsertWithWhere( await models.Image.upsertWithWhere(
{ {
name: fileName, name: imageName,
collectionFk: collectionName collectionFk: collectionName
}, },
{ {
name: fileName, name: imageName,
collectionFk: collectionName, collectionFk: collectionName,
updated: Date.vnNow() / 1000, updated: Date.vnNow() / 1000,
} }
@ -49,7 +51,7 @@ module.exports = Self => {
if (entity) { if (entity) {
await entity.updateAttribute( await entity.updateAttribute(
collection.property, collection.property,
fileName imageName
); );
} }

View File

@ -53,7 +53,7 @@ async function test() {
const JunitReporter = require('jasmine-reporters'); const JunitReporter = require('jasmine-reporters');
jasmine.addReporter(new JunitReporter.JUnitXmlReporter()); jasmine.addReporter(new JunitReporter.JUnitXmlReporter());
jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 30000; jasmine.jasmine.DEFAULT_TIMEOUT_INTERVAL = 90000;
jasmine.exitOnCompletion = true; jasmine.exitOnCompletion = true;
} }

View File

@ -0,0 +1 @@
ALTER TABLE `vn`.`printQueueArgs` MODIFY COLUMN value varchar(255) CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NULL;

View File

@ -1,12 +1,5 @@
DROP TABLE IF EXISTS `vn`.`dmsRecover`; DROP TABLE IF EXISTS `vn`.`dmsRecover`;
DROP PROCEDURE IF EXISTS `vn`.`route_getTickets`;
ALTER TABLE `vn`.`delivery` DROP COLUMN addressFk;
ALTER TABLE `vn`.`delivery` DROP CONSTRAINT delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);
DROP PROCEDURE IF EXISTS vn.route_getTickets;
DELIMITER $$ DELIMITER $$
$$ $$
@ -17,54 +10,68 @@ BEGIN
* de sus tickets. * de sus tickets.
* *
* @param vRouteFk * @param vRouteFk
*
* @select Información de los tickets * @select Información de los tickets
*/ */
SELECT *
SELECT FROM (
t.id Id, SELECT t.id Id,
t.clientFk Client, t.clientFk Client,
a.id Address, a.id Address,
t.packages Packages, a.nickname ClientName,
a.street AddressName, t.packages Packages,
a.postalCode PostalCode, a.street AddressName,
a.city City, a.postalCode PostalCode,
sub2.itemPackingTypeFk PackingType, a.city City,
c.phone ClientPhone, sub2.itemPackingTypeFk PackingType,
c.mobile ClientMobile, c.phone ClientPhone,
a.phone AddressPhone, c.mobile ClientMobile,
a.mobile AddressMobile, a.phone AddressPhone,
d.longitude Longitude, a.mobile AddressMobile,
d.latitude Latitude, d.longitude Longitude,
wm.mediaValue SalePersonPhone, d.latitude Latitude,
tob.Note Note, wm.mediaValue SalePersonPhone,
t.isSigned Signed tob.description Note,
FROM ticket t t.isSigned Signed,
JOIN client c ON t.clientFk = c.id t.priority
JOIN address a ON t.addressFk = a.id FROM ticket t
LEFT JOIN delivery d ON t.id = d.ticketFk JOIN client c ON t.clientFk = c.id
LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk JOIN address a ON t.addressFk = a.id
LEFT JOIN LEFT JOIN delivery d ON d.ticketFk = t.id
(SELECT tob.description Note, t.id LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk
FROM ticketObservation tob LEFT JOIN(
JOIN ticket t ON tob.ticketFk = t.id SELECT tob.description, t.id
JOIN observationType ot ON ot.id = tob.observationTypeFk FROM ticketObservation tob
WHERE t.routeFk = vRouteFk JOIN ticket t ON tob.ticketFk = t.id
AND ot.code = 'delivery' JOIN observationType ot ON ot.id = tob.observationTypeFk
)tob ON tob.id = t.id WHERE t.routeFk = vRouteFk
LEFT JOIN AND ot.code = 'delivery'
(SELECT sub.ticketFk, )tob ON tob.id = t.id
CONCAT('(', GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk ORDER BY sub.items DESC SEPARATOR ','), ') ') itemPackingTypeFk LEFT JOIN(
FROM (SELECT s.ticketFk , i.itemPackingTypeFk, COUNT(*) items SELECT sub.ticketFk,
FROM ticket t CONCAT('(',
JOIN sale s ON s.ticketFk = t.id GROUP_CONCAT(DISTINCT sub.itemPackingTypeFk
JOIN item i ON i.id = s.itemFk ORDER BY sub.items DESC SEPARATOR ','),
WHERE t.routeFk = vRouteFk ') ') itemPackingTypeFk
GROUP BY t.id,i.itemPackingTypeFk)sub FROM (
GROUP BY sub.ticketFk SELECT s.ticketFk, i.itemPackingTypeFk, COUNT(*) items
) sub2 ON sub2.ticketFk = t.id FROM ticket t
WHERE t.routeFk = vRouteFk JOIN sale s ON s.ticketFk = t.id
GROUP BY t.id JOIN item i ON i.id = s.itemFk
ORDER BY t.priority; WHERE t.routeFk = vRouteFk
GROUP BY t.id, i.itemPackingTypeFk
)sub
GROUP BY sub.ticketFk
)sub2 ON sub2.ticketFk = t.id
WHERE t.routeFk = vRouteFk
ORDER BY d.id DESC
LIMIT 10000000000000000000
)sub3
GROUP BY sub3.id
ORDER BY sub3.priority;
END$$ END$$
DELIMITER ; DELIMITER ;
ALTER TABLE `vn`.`delivery` DROP FOREIGN KEY delivery_ticketFk_FK;
ALTER TABLE `vn`.`delivery` DROP COLUMN ticketFk;
ALTER TABLE `vn`.`delivery` ADD ticketFk INT DEFAULT NULL;
ALTER TABLE `vn`.`delivery` ADD CONSTRAINT delivery_ticketFk_FK FOREIGN KEY (`ticketFk`) REFERENCES `vn`.`ticket`(`id`);

View File

@ -173,10 +173,6 @@ INSERT INTO `vn`.`sector`(`id`, `description`, `warehouseFk`, `isPreviousPrepare
(1, 'First sector', 1, 1, 'FIRST'), (1, 'First sector', 1, 1, 'FIRST'),
(2, 'Second sector', 2, 0, 'SECOND'); (2, 'Second sector', 2, 0, 'SECOND');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES ('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '2', '1');
INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`) INSERT INTO `vn`.`printer` (`id`, `name`, `path`, `isLabeler`, `sectorFk`, `ipAddress`)
VALUES VALUES
(1, 'printer1', 'path1', 0, 1 , NULL), (1, 'printer1', 'path1', 0, 1 , NULL),
@ -1200,6 +1196,11 @@ INSERT INTO `vn`.`train`(`id`, `name`)
(1, 'Train1'), (1, 'Train1'),
(2, 'Train2'); (2, 'Train2');
INSERT INTO `vn`.`operator` (`workerFk`, `numberOfWagons`, `trainFk`, `itemPackingTypeFk`, `warehouseFk`, `sectorFk`, `labelerFk`)
VALUES
('1106', '1', '1', 'H', '1', '1', '1'),
('1107', '1', '1', 'V', '1', '1', '1');
INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`) INSERT INTO `vn`.`collection`(`id`, `workerFk`, `stateFk`, `created`, `trainFk`)
VALUES VALUES
(1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1), (1, 1106, 5, DATE_ADD(util.VN_CURDATE(),INTERVAL +1 DAY), 1),

View File

@ -66,7 +66,6 @@
</vn-tr> </vn-tr>
</vn-tbody> </vn-tbody>
</vn-table> </vn-table>
<vn-pagination model="model"></vn-pagination>
</vn-card> </vn-card>
</vn-data-viewer> </vn-data-viewer>
<vn-worker-descriptor-popover vn-id="workerDescriptor"> <vn-worker-descriptor-popover vn-id="workerDescriptor">

View File

@ -21,8 +21,8 @@ module.exports = function(Self) {
let orgBeginTransaction = this.beginTransaction; let orgBeginTransaction = this.beginTransaction;
this.beginTransaction = function(options, cb) { this.beginTransaction = function(options, cb) {
options = options || {}; options = options || {};
if (!options.timeout) if (options.timeout === undefined)
options.timeout = 120000; options.timeout = 120 * 1000;
return orgBeginTransaction.call(this, options, cb); return orgBeginTransaction.call(this, options, cb);
}; };
}); });

View File

@ -158,4 +158,4 @@
"Description cannot be blank": "Description cannot be blank", "Description cannot be blank": "Description cannot be blank",
"Added observation": "Added observation", "Added observation": "Added observation",
"Comment added to client": "Comment added to client" "Comment added to client": "Comment added to client"
} }

View File

@ -272,6 +272,8 @@
"Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}", "Tickets with associated refunds": "No se pueden borrar tickets con abonos asociados. Este ticket está asociado al abono Nº {{id}}",
"Not exist this branch": "La rama no existe", "Not exist this branch": "La rama no existe",
"This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado", "This ticket cannot be signed because it has not been boxed": "Este ticket no puede firmarse porque no ha sido encajado",
"Collection does not exist": "La colección no existe",
"Cannot obtain exclusive lock": "No se puede obtener un bloqueo exclusivo",
"Insert a date range": "Inserte un rango de fechas", "Insert a date range": "Inserte un rango de fechas",
"Added observation": "{{user}} añadió esta observacion: {{text}}", "Added observation": "{{user}} añadió esta observacion: {{text}}",
"Comment added to client": "Observación añadida al cliente {{clientFk}}", "Comment added to client": "Observación añadida al cliente {{clientFk}}",

View File

@ -15,7 +15,7 @@
"legacyUtcDateProcessing": false, "legacyUtcDateProcessing": false,
"timezone": "local", "timezone": "local",
"connectTimeout": 40000, "connectTimeout": 40000,
"acquireTimeout": 60000, "acquireTimeout": 90000,
"waitForConnections": true "waitForConnections": true
}, },
"osticket": { "osticket": {

View File

@ -21,7 +21,7 @@ module.exports = Self => {
id, id,
params params
FROM clientConsumptionQueue FROM clientConsumptionQueue
WHERE status = ''`); WHERE status = '' OR status IS NULL`);
for (const queue of queues) { for (const queue of queues) {
try { try {
@ -44,16 +44,23 @@ module.exports = Self => {
GROUP BY c.id`, [params.clients, params.from, params.to]); GROUP BY c.id`, [params.clients, params.from, params.to]);
for (const client of clients) { for (const client of clients) {
const args = { try {
id: client.clientFk, const args = {
recipient: client.clientEmail, id: client.clientFk,
replyTo: client.salesPersonEmail, recipient: client.clientEmail,
from: params.from, replyTo: client.salesPersonEmail,
to: params.to from: params.from,
}; to: params.to
};
const email = new Email('campaign-metrics', args); const email = new Email('campaign-metrics', args);
await email.send(); await email.send();
} catch (error) {
if (error.code === 'EENVELOPE')
continue;
throw error;
}
} }
await Self.rawSql(` await Self.rawSql(`

View File

@ -98,6 +98,7 @@ module.exports = Self => {
stmt.merge(conn.makeWhere(args.filter.where)); stmt.merge(conn.makeWhere(args.filter.where));
stmt.merge(conn.makeOrderBy(args.filter.order)); stmt.merge(conn.makeOrderBy(args.filter.order));
stmt.merge(conn.makeLimit(args.filter));
const negativeBasesIndex = stmts.push(stmt) - 1; const negativeBasesIndex = stmts.push(stmt) - 1;

View File

@ -2,7 +2,8 @@
vn-id="model" vn-id="model"
url="InvoiceIns/negativeBases" url="InvoiceIns/negativeBases"
auto-load="true" auto-load="true"
params="$ctrl.params"> params="$ctrl.params"
limit="20">
</vn-crud-model> </vn-crud-model>
<vn-portal slot="topbar"> <vn-portal slot="topbar">
</vn-portal> </vn-portal>

View File

@ -37,7 +37,7 @@ describe('Item editFixedPrice()', () => {
const options = {transaction: tx}; const options = {transaction: tx};
try { try {
const filter = {'it.categoryFk': 1}; const filter = {where: {'it.categoryFk': 1}};
const ctx = { const ctx = {
args: { args: {
filter: filter filter: filter
@ -48,7 +48,7 @@ describe('Item editFixedPrice()', () => {
const field = 'rate2'; const field = 'rate2';
const newValue = 88; const newValue = 88;
await models.FixedPrice.editFixedPrice(ctx, field, newValue, null, filter, options); await models.FixedPrice.editFixedPrice(ctx, field, newValue, null, filter.where, options);
const [result] = await models.FixedPrice.filter(ctx, filter, options); const [result] = await models.FixedPrice.filter(ctx, filter, options);

View File

@ -68,7 +68,9 @@
<th field="stateFk"> <th field="stateFk">
<span translate>State</span> <span translate>State</span>
</th> </th>
<th field="isFragile"></th> <th field="isFragile" number>
<span translate>Fragile</span>
</th>
<th field="zoneFk"> <th field="zoneFk">
<span translate>Zone</span> <span translate>Zone</span>
</th> </th>

View File

@ -30,35 +30,47 @@ module.exports = Self => {
const ticketLogs = await models.TicketLog.find( const ticketLogs = await models.TicketLog.find(
{ {
where: { where: {
and: [ or: [
{originFk: id}, {
{action: 'update'}, and: [
{changedModel: 'Sale'} {originFk: id},
{action: 'update'},
{changedModel: 'Sale'}
]
},
{
and: [
{originFk: id},
{action: 'delete'},
{changedModel: 'Sale'}
]
}
] ]
}, },
fields: [ fields: [
'oldInstance', 'oldInstance',
'newInstance', 'newInstance',
'changedModelId' 'changedModelId',
'changedModelValue'
], ],
}, myOptions); }, myOptions);
const changes = []; const changes = [];
for (const ticketLog of ticketLogs) {
const oldQuantity = ticketLog.oldInstance.quantity; for (const log of ticketLogs) {
const newQuantity = ticketLog.newInstance.quantity; const oldQuantity = log.oldInstance.quantity;
const newQuantity = log.newInstance?.quantity || 0;
if (oldQuantity || newQuantity) { if (oldQuantity || newQuantity) {
const sale = await models.Sale.findById(ticketLog.changedModelId, null, myOptions); const changeMessage = $t('Change quantity', {
const message = $t('Change quantity', { concept: log.changedModelValue,
concept: sale.concept, oldQuantity: oldQuantity || 0,
oldQuantity: oldQuantity || 0, newQuantity: newQuantity || 0,
newQuantity: newQuantity || 0, });
}); changes.push(changeMessage);
changes.push(message);
} }
} }
return changes.join('\n'); return changes.join('\n');
}; };
}; };

View File

@ -11,8 +11,7 @@ module.exports = Self => {
http: {source: 'path'} http: {source: 'path'}
}, { }, {
arg: 'userFk', arg: 'userFk',
type: 'number', type: 'any',
required: true,
description: 'The user id' description: 'The user id'
} }
], ],

7692
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +1,46 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<body>
<table class="mainTable"> <body>
<tbody> <table class="mainTable">
<tr> <tbody>
<td id="truck" class="ellipsize">{{labelData.truck || '---'}}</td> <tr>
</tr> <td id="truck" class="ellipsize">{{labelData.truck || '---'}}</td>
<tr> </tr>
<td> <tr>
<div v-html="getBarcode(labelData.palletFk)" id="barcode"></div> <td>
<table v-for="labelData in labelsData" class="zoneTable"> <div v-html="getBarcode(labelData.palletFk)" id="barcode"></div>
<thead> <table v-for="labelData in labelsData" class="zoneTable">
<tr v-if="!labelData.isMatch" id="black"> <thead>
<td id="routeFk" class="ellipsize">{{labelData.routeFk}}</td> <tr v-if="!labelData.isMatch" id="black">
<td id="zone" class="ellipsize">{{labelData.zone || '---'}}</td> <td id="routeFk" class="ellipsize">{{labelData.routeFk}}</td>
<td id="labels" class="ellipsize">{{labelData.labels}}</td> <td id="zone" class="ellipsize">{{labelData.zone || '---'}}</td>
</tr> <td id="labels" class="ellipsize">{{labelData.labels}}</td>
<tr v-else> </tr>
<td id="routeFk" class="ellipsize">{{labelData.routeFk}}</td> <tr v-else>
<td id="zone" class="ellipsize">{{labelData.zone || '---'}}</td> <td id="routeFk" class="ellipsize">{{labelData.routeFk}}</td>
<td id="labels" class="ellipsize">{{labelData.labels || '--'}}</td> <td id="zone" class="ellipsize">{{labelData.zone || '---'}}</td>
</tr> <td id="labels" class="ellipsize">{{labelData.labels || '--'}}</td>
</thead> </tr>
</table> </thead>
</td> </table>
</tr> </td>
<tr> </tr>
<td> <tr>
<img :src="QR" id="QR"/> <td>
<div id="right"> <img :src="QR" id="QR" />
<div id="additionalInfo" class="ellipsize"><b>Pallet: </b>{{id}}</div> <div id="right">
<div id="additionalInfo" class="ellipsize"><b>User: </b> {{username.name || '---'}}</div> <div id="additionalInfo" class="ellipsize"><b>Pallet: </b>{{id}}</div>
<div id="additionalInfo" class="ellipsize"><b>Day: </b>{{labelData.dayName.toUpperCase() || '---'}}</div> <div id="additionalInfo" class="ellipsize"><b>User: </b>
{{ (username ? username.name : '---')}}
</div> </div>
</td> <div id="additionalInfo" class="ellipsize"><b>Day: </b>{{labelData.dayName.toUpperCase() ||
</tr> '---'}}</div>
</tbody> </div>
</table> </td>
</body> </tr>
</tbody>
</table>
</body>
</html> </html>

View File

@ -14,13 +14,13 @@ module.exports = {
}, },
userFk: { userFk: {
type: Number, type: Number,
required: true,
description: 'The user id' description: 'The user id'
} }
}, },
async serverPrefetch() { async serverPrefetch() {
this.username = null;
this.labelsData = await this.rawSqlFromDef('labelData', this.id); this.labelsData = await this.rawSqlFromDef('labelData', this.id);
this.username = await this.findOneFromDef('username', this.userFk); if (this.userFk) this.username = await this.findOneFromDef('username', this.userFk);
this.labelData = this.labelsData[0]; this.labelData = this.labelsData[0];
this.checkMainEntity(this.labelData); this.checkMainEntity(this.labelData);