Merge branch 'dev' into 4409-binlog
gitea/salix/pipeline/pr-dev Build queued... Details

This commit is contained in:
Juan Ferrer 2025-02-26 09:32:16 +01:00
commit a310b3e642
8 changed files with 367 additions and 29 deletions

View File

@ -104,6 +104,24 @@ BEGIN
LEFT JOIN agencyModeItemType ait LEFT JOIN agencyModeItemType ait
ON ait.agencyModeFk = vAgencyModeFk ON ait.agencyModeFk = vAgencyModeFk
AND ait.itemTypeFk = itt.id AND ait.itemTypeFk = itt.id
LEFT JOIN (
SELECT i.id
FROM item i
JOIN priceDelta pd
ON pd.itemTypeFk = i.typeFk
AND (pd.minSize IS NULL OR pd.minSize <= i.`size`)
AND (pd.maxSize IS NULL OR pd.maxSize >= i.`size`)
AND (pd.inkFk IS NULL OR pd.inkFk = i.inkFk)
AND (pd.originFk IS NULL OR pd.originFk = i.originFk)
AND (pd.producerFk IS NULL OR pd.producerFk = i.producerFk)
AND (pd.warehouseFk IS NULL OR pd.warehouseFk = vWarehouseFk)
LEFT JOIN zoneGeo zg ON zg.id = pd.zoneGeoFk
LEFT JOIN zoneGeo zg2 ON zg2.id = address_getGeo(vAddressFk)
WHERE (pd.fromDated IS NULL OR pd.fromDated <= vShipped)
AND (pd.toDated IS NULL OR pd.toDated >= vShipped)
AND (pd.zoneGeoFk IS NULL OR zg2.lft BETWEEN zg.lft AND zg.rgt)
AND pd.isHidden
GROUP BY i.id) pd ON pd.id = i.itemFk
WHERE a.calc_id = vAvailableCalc WHERE a.calc_id = vAvailableCalc
AND a.available > 0 AND a.available > 0
AND (ag.isAnyVolumeAllowed OR NOT itt.isUnconventionalSize) AND (ag.isAnyVolumeAllowed OR NOT itt.isUnconventionalSize)
@ -113,7 +131,9 @@ BEGIN
it.size <= z.itemMaxSize OR z.itemMaxSize IS NULL)) it.size <= z.itemMaxSize OR z.itemMaxSize IS NULL))
AND cit.id IS NULL AND cit.id IS NULL
AND zit.id IS NULL AND zit.id IS NULL
AND ait.id IS NULL; AND ait.id IS NULL
AND pd.id IS NULL
;
DROP TEMPORARY TABLE tmp.buyUltimate; DROP TEMPORARY TABLE tmp.buyUltimate;

View File

@ -0,0 +1,3 @@
-- Place your SQL code here
INSERT INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES ('Worker','getWorkerBusiness','READ','ALLOW','ROLE','hr');

View File

@ -0,0 +1,3 @@
ALTER TABLE vn.priceDelta ADD IF NOT EXISTS isHidden BOOL
DEFAULT FALSE NOT NULL
COMMENT 'Hides the itemType when building de catalog recordset';

View File

@ -1,5 +1,6 @@
const smtp = require('vn-print/core/smtp'); const smtp = require('vn-print/core/smtp');
const config = require('vn-print/core/config'); const config = require('vn-print/core/config');
const Email = require('vn-print/core/email');
module.exports = Self => { module.exports = Self => {
Self.remoteMethodCtx('closeAll', { Self.remoteMethodCtx('closeAll', {
@ -44,8 +45,7 @@ module.exports = Self => {
LIMIT 1`, [toDate, toDate], myOptions); LIMIT 1`, [toDate, toDate], myOptions);
await Self.rawSql(` await Self.rawSql(`
DROP TEMPORARY TABLE IF EXISTS tmp.ticket_close; CREATE OR REPLACE TEMPORARY TABLE tmp.ticket_close
CREATE TEMPORARY TABLE tmp.ticket_close
ENGINE = MEMORY ENGINE = MEMORY
WITH wTickets AS( WITH wTickets AS(
SELECT t.id ticketFk SELECT t.id ticketFk
@ -63,11 +63,12 @@ module.exports = Self => {
FROM wTicketsTracking wt FROM wTicketsTracking wt
JOIN ticketTracking tt ON tt.id = wt.maxTracking JOIN ticketTracking tt ON tt.id = wt.maxTracking
) SELECT tls.ticketFk, ) SELECT tls.ticketFk,
t.clientFk, t.clientFk clientId,
c.name clientName, c.name clientName,
c.email recipient, c.email recipient,
c.isToBeMailed,
eu.email salesPersonEmail, eu.email salesPersonEmail,
t.addressFk, t.addressFk addressId,
c.hasDailyInvoice, c.hasDailyInvoice,
c.hasToInvoiceByAddress, c.hasToInvoiceByAddress,
t.totalWithVat, t.totalWithVat,
@ -79,7 +80,7 @@ module.exports = Self => {
JOIN agencyMode am ON am.id = t.agencyModeFk JOIN agencyMode am ON am.id = t.agencyModeFk
JOIN client c ON c.id = t.clientFk JOIN client c ON c.id = t.clientFk
LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk LEFT JOIN account.emailUser eu ON eu.userFk = c.salesPersonFk
WHERE (al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered')); WHERE al.code = 'PACKED' OR (am.code = 'refund' AND al.code <> 'delivered');
CALL ticket_close(); CALL ticket_close();
`, [dateFrom, dateTo], myOptions); `, [dateFrom, dateTo], myOptions);
@ -98,21 +99,27 @@ module.exports = Self => {
AND tob.id IS NULL AND tob.id IS NULL
AND t.routeFk`, [dateFrom, dateTo], myOptions); AND t.routeFk`, [dateFrom, dateTo], myOptions);
const [clients] = await Self.rawSql(` const clients = await Self.rawSql(`
SELECT clientFk clientId, SELECT *,
clientName,
recipient,
salesPersonEmail,
addressFk addressId,
companyFk,
SUM(totalWithVat) total, SUM(totalWithVat) total,
'quick' serialType 'quick' serialType
FROM tmp.ticket_close FROM tmp.ticket_close
WHERE hasDailyInvoice WHERE hasDailyInvoice
GROUP BY IF (hasToInvoiceByAddress, addressFk, clientFk), companyFk GROUP BY IF (hasToInvoiceByAddress, addressId, clientId), companyFk
HAVING total > 0; HAVING total > 0
`, [], myOptions);
const [ticketsToMail] = await Self.rawSql(`
SELECT *
FROM tmp.ticket_close
WHERE NOT hasDailyInvoice
AND isToBeMailed
AND salesPersonEmail IS NOT NULL
AND salesPersonEmail <> ''
AND recipient IS NOT NULL
AND recipient <> '';
DROP TEMPORARY TABLE tmp.ticket_close; DROP TEMPORARY TABLE tmp.ticket_close;
`, [], myOptions); `, [], myOptions);
if (tx) if (tx)
await tx.commit(); await tx.commit();
@ -130,21 +137,23 @@ module.exports = Self => {
if (id) if (id)
await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id, null, nestedTransaction); await Self.app.models.InvoiceOut.makePdfAndNotify(ctx, id, null, nestedTransaction);
} catch (error) { } catch (error) {
await Self.rawSql(` await handleInvoicingError(error, client, failedClients);
INSERT INTO util.debug (variable, value) }
VALUES ('invoicingTicketError', ?) }
`, [client.clientId + ' - ' + error]);
if (error.responseCode == 450) { for (const ticket of ticketsToMail) {
await invalidEmail(client); const args = {
continue; id: ticket.ticketFk,
} recipientId: ticket.clientId,
recipient: ticket.recipient,
replyTo: ticket.salesPersonEmail,
};
failedClients.push({ try {
id: client.clientId, const email = new Email('delivery-note-link', args);
address: client.addressId, await email.send();
error } catch (error) {
}); await handleInvoicingError(error, ticket, failedClients);
} }
} }
@ -188,4 +197,22 @@ module.exports = Self => {
html: body, html: body,
}).catch(err => console.error(err)); }).catch(err => console.error(err));
} }
async function handleInvoicingError(error, entity, failedClients) {
await Self.rawSql(`
INSERT INTO util.debug (variable, value)
VALUES ('invoicingTicketError', ?)
`, [entity.clientId + ' - ' + error]);
if (error.responseCode == 450) {
await invalidEmail(entity);
return;
}
failedClients.push({
id: entity.clientId,
address: entity.addressId,
error
});
}
}; };

View File

@ -0,0 +1,255 @@
const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
const buildFilter = require('vn-loopback/util/filter').buildFilter;
const mergeFilters = require('vn-loopback/util/filter').mergeFilters;
module.exports = Self => {
Self.remoteMethodCtx('getWorkerBusiness', {
description: 'Returns an array of business from an specified worker',
accessType: 'READ',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The worker id',
http: {source: 'path'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string'
},
{
arg: 'started',
type: 'date',
description: 'started',
http: {source: 'query'}
},
{
arg: 'ended',
type: 'date',
description: 'ended',
http: {source: 'query'}
},
{
arg: 'companyCodeFk',
type: 'string',
description: 'companyCodeFk',
http: {source: 'query'}
},
{
arg: 'reasonEndFk',
type: 'number',
description: 'reasonEndFk',
http: {source: 'query'}
},
{
arg: 'reasondEnd',
type: 'string',
description: 'reasondEnd',
http: {source: 'query'}
},
{
arg: 'departmentFk',
type: 'number',
description: 'departmentFk',
http: {source: 'query'}
},
{
arg: 'department',
type: 'string',
description: 'department',
http: {source: 'query'}
},
{
arg: 'workerBusinessProfessionalCategoryFk',
type: 'number',
description: 'workerBusinessProfessionalCategoryFk',
http: {source: 'query'}
},
{
arg: 'workerBusinessProfessionalCategory',
type: 'string',
description: 'workerBusinessProfessionalCategory',
http: {source: 'query'}
},
{
arg: 'calendarTypeFk',
type: 'number',
description: 'calendarTypeFk',
http: {source: 'query'}
},
{
arg: 'calendarType',
type: 'string',
description: 'calendarType',
http: {source: 'query'}
},
{
arg: 'workcenterFk',
type: 'number',
description: 'workcenterFk',
http: {source: 'query'}
},
{
arg: 'workCenter',
type: 'string',
description: 'workCenter',
http: {source: 'query'}
},
{
arg: 'workerBusinessCategoryFk',
type: 'number',
description: 'WorkerBusinessCategoryFk',
http: {source: 'query'}
},
{
arg: 'workerBusinessCategory',
type: 'string',
description: 'workerBusinessCategory',
http: {source: 'query'}
},
{
arg: 'occupationCodeFk',
type: 'number',
description: 'occupationCodeFk',
http: {source: 'query'}
},
{
arg: 'occupationCode',
type: 'string',
description: 'occupationCode',
http: {source: 'query'}
},
{
arg: 'rate',
type: 'number',
description: 'rate',
http: {source: 'query'}
},
{
arg: 'workerBusinessTypeFk',
type: 'number',
description: 'workerBusinessTypeFk',
http: {source: 'query'}
},
{
arg: 'workerBusinessType',
type: 'string',
description: 'workerBusinessType',
http: {source: 'query'}
},
{
arg: 'amount',
type: 'number',
description: 'amount',
http: {source: 'query'}
},
{
arg: 'basicSalary',
type: 'number',
description: 'amount',
http: {source: 'query'}
},
{
arg: 'notes',
type: 'string',
description: 'notes',
http: {source: 'query'}
}],
returns: {
type: ['object'],
root: true
},
http: {
path: '/:id/getWorkerBusiness',
verb: 'GET'
}
});
Self.getWorkerBusiness = async(ctx, id, filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
let conn = Self.dataSource.connector;
let where = buildFilter(ctx.args, (param, value) => {
switch (param) {
case 'started':
case 'ended':
case 'companyCodeFk':
case 'reasonEndFk':
case 'reasondEnd':
case 'departmentFk':
case 'department':
case 'workerBusinessProfessionalCategoryFk':
case 'workerBusinessProfessionalCategory':
case 'calendarTypeFk':
case 'calendarType':
case 'workcenterFk':
case 'workCenter':
case 'workerBusinessCategoryFk':
case 'workerBusinessCategory':
case 'occupationCodeFk':
case 'occupationCode':
case 'rate':
case 'workerBusinessTypeFk':
case 'workerBusinessType':
case 'amount':
case 'basicSalary':
case 'notes':
}
});
where = {...where, ...{workerFk: id}};
filter = mergeFilters(filter, {where});
let stmts = [];
let stmt;
stmt = new ParameterizedSQL(
`SELECT * FROM(
SELECT b.id,
b.workerFk,
b.started,
b.ended,
b.companyCodeFk,
b.reasonEndFk,
bre.reason,
b.departmentFk,
d.name departmentName,
b.occupationCodeFk,
oc.name occupationName,
b.workerBusinessProfessionalCategoryFk,
pf.description professionalDescription,
b.calendarTypeFk,
ct.description calendarTypeDescription,
b.workcenterFk,
wc.name workCenterName,
b.workerBusinessCategoryFk,
py.description payrollDescription,
b.workerBusinessTypeFk,
wt.name workerBusinessTypeName,
b.amount,
b.basicSalary,
b.notes
FROM business b
LEFT JOIN businessReasonEnd bre ON bre.id = b.reasonEndFk
LEFT JOIN occupationCode oc ON oc.code = b.occupationCodeFk
LEFT JOIN department d ON d.id = b.departmentFk
LEFT JOIN professionalCategory pf ON pf.id = b.workerBusinessProfessionalCategoryFk
JOIN calendarType ct ON ct.id = b.calendarTypeFk
LEFT JOIN workCenter wc ON wc.id = b.workcenterFk
LEFT JOIN payrollCategories py ON py.id = b.workerBusinessCategoryFk
LEFT JOIN workerBusinessType wt ON wt.id = b.workerBusinessTypeFk
) sub
`
);
stmt.merge(conn.makeSuffix(filter));
stmts.push(stmt);
let sql = ParameterizedSQL.join(stmts, ';');
let result = await conn.executeStmt(sql, myOptions);
return result;
};
};

View File

@ -21,6 +21,7 @@ module.exports = Self => {
require('../methods/worker/setPassword')(Self); require('../methods/worker/setPassword')(Self);
require('../methods/worker/getAvailablePda')(Self); require('../methods/worker/getAvailablePda')(Self);
require('../methods/worker/myTeam')(Self); require('../methods/worker/myTeam')(Self);
require('../methods/worker/getWorkerBusiness')(Self);
Self.canModifyAbsenceInPast = async(ctx, time) => { Self.canModifyAbsenceInPast = async(ctx, time) => {
const hasPrivs = await Self.app.models.ACL.checkAccessAcl(ctx, 'Worker', 'canModifyAbsenceInPast', 'WRITE'); const hasPrivs = await Self.app.models.ACL.checkAccessAcl(ctx, 'Worker', 'canModifyAbsenceInPast', 'WRITE');

View File

@ -0,0 +1,18 @@
buttons:
webAcccess: Visita la nostra Web
info: Ajuda'ns a millorar
fiscalAddress: VERDNATURA LEVANTE SL, B97367486 C/ Fenollar, 2. 46680 ALGEMESÍ
· verdnatura.es · clientes@verdnatura.es
disclaimer: '- AVÍS - Aquest missatge és privat i confidencial, i ha de ser utilitzat
exclusivament per la persona destinatària del mateix. Si has rebut aquest missatge
per error, et preguem que ho comuniquis al remitent i esborres aquest missatge
i qualsevol document adjunt que pugui contenir. Verdnatura Levante SL no renuncia
a la confidencialitat ni a cap privilegi per causa de transmissió errònia o mal
funcionament. Igualment, no es fa responsable dels canvis, alteracions, errors
o omissions que es puguin fer al missatge un cop enviat.'
privacy: En compliment del que disposa la Llei Orgànica 15/1999, de Protecció de
Dades de Caràcter Personal, et comuniquem que les dades personals que facilitis
s'inclouran en fitxers automatitzats de VERDNATURA LEVANTE S.L., podent en tot
moment exercir els drets d'accés, rectificació, cancel·lació i oposició,
comunicant-ho per escrit al domicili social de l'entitat. La finalitat del
fitxer és la gestió administrativa, comptabilitat i facturació.

View File

@ -0,0 +1,11 @@
subject: El teu albarà
title: El teu albarà
dear: Estimat client
description: Ja està disponible l'albarà corresponent a la comanda <strong>{0}</strong>. <br/>
Pots veure'l fent clic <a href="https://shop.verdnatura.es/#!form=ecomerce/ticket&ticket={0}">en aquest enllaç</a>.
copyLink: 'Com a alternativa, pots copiar el següent enllaç al teu navegador:'
poll: Si ho desitges, pots respondre a la nostra enquesta de satisfacció per
ajudar-nos a oferir un millor servei. La teva opinió és molt important per a nosaltres!
help: Qualsevol dubte que tinguis, no dubtis a consultar-nos, <strong>estem aquí
per atendre't!</strong>
conclusion: Gràcies per la teva atenció!