Merge branch 'dev' into 6156-advanceTicketsNewField
gitea/salix/pipeline/head This commit looks good Details
gitea/salix/pipeline/pr-dev This commit looks good Details

This commit is contained in:
Pablo Natek 2024-09-11 08:22:24 +00:00
commit 842c98edfc
20 changed files with 253 additions and 62 deletions

View File

@ -9,7 +9,7 @@
},
"vn": {
"view": {
"expeditionPallet_Print": "ced2b84a114fcb99fce05f0c34f4fc03f3fa387bef92621be1bc306608a84345"
"expeditionPallet_Print": "99f75145ac2e7b612a6d71e74b6e55f194a465780fd9875a15eb01e6596b447e"
}
}
}

View File

@ -21,6 +21,7 @@ BEGIN
t.clientFk,
t.warehouseFk,
ts.alertLevel,
sub2.alertLevel futureAlertLevel,
t.shipped,
t.totalWithVat,
sub2.shipped futureShipped,
@ -47,6 +48,7 @@ BEGIN
t.addressFk,
t.id,
t.shipped,
ts.alertLevel,
st.name state,
st.code,
st.classColor,

View File

@ -85,7 +85,7 @@ BEGIN
IF(vHasDailyInvoice) AND vHasToInvoice THEN
SELECT invoiceSerial(vClientFk, vCompanyFk, 'quick') INTO vSerial;
IF NOT vSerial THEN
IF vSerial IS NULL THEN
CALL util.throw('Cannot booking without a serial');
END IF;

View File

@ -1,4 +1,5 @@
-- Calculamos todos los volumenes de todos los tickets una sola vez
/* Se ejecutará el dia de test - master manualmente para no hacer lenta la subida
CREATE OR REPLACE TEMPORARY TABLE tmp.tTicketVolume
(PRIMARY KEY (id))
ENGINE = MEMORY
@ -14,3 +15,4 @@ UPDATE vn.ticket t
SET t.volume = tv.volume;
DROP TEMPORARY TABLE tmp.tTicketVolume;
*/

View File

@ -0,0 +1,9 @@
-- Place your SQL code here
INSERT INTO salix.ACL
SET model = 'Ticket',
property = 'setWeight',
accessType = 'WRITE',
permission = 'ALLOW',
principalType = 'ROLE',
principalId = 'salesPerson';

View File

@ -0,0 +1,12 @@
-- Place your SQL code here
DELETE FROM salix.ACL WHERE model = 'Province' LIMIT 1;
DELETE FROM salix.ACL WHERE model = 'Town' LIMIT 1;
UPDATE salix.ACL SET accessType = 'READ' WHERE model = 'BankEntity';
INSERT INTO salix.ACL
SET model = 'BankEntity',
property = '*',
accessType = 'WRITE',
permission = 'ALLOW',
principalType = 'ROLE',
principalId = 'financial';

View File

@ -687,8 +687,8 @@ export default {
ticketFuture: {
searchResult: 'vn-ticket-future tbody tr',
openAdvancedSearchButton: 'vn-searchbar .append vn-icon[icon="arrow_drop_down"]',
originDated: 'vn-date-picker[label="Origin date"]',
futureDated: 'vn-date-picker[label="Destination date"]',
originScopeDays: 'vn-date-picker[label="Origin date"]',
futureScopeDays: 'vn-date-picker[label="Destination date"]',
linesMax: 'vn-textfield[label="Max Lines"]',
litersMax: 'vn-textfield[label="Max Liters"]',
ipt: 'vn-autocomplete[label="Origin IPT"]',

View File

@ -35,6 +35,14 @@ describe('Client Edit billing data path', () => {
it(`should attempt to edit the billing data without an IBAN but fail`, async() => {
await page.autocompleteSearch($.payMethod, 'PayMethod with IBAN');
await page.waitToClick($.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('That payment method requires an IBAN');
});
it(`should edit the billing data and save the form`, async() => {
await page.autocompleteSearch($.payMethod, 'PayMethod five');
await page.autocompleteSearch($.swiftBic, 'BBKKESMMMMM');
await page.clearInput($.dueDay);
await page.write($.dueDay, '60');
@ -45,10 +53,13 @@ describe('Client Edit billing data path', () => {
await page.waitToClick($.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('That payment method requires an IBAN');
expect(message.text).toContain('Notification sent!');
});
it(`should create a new BIC code`, async() => {
await page.loginAndModule('financial', 'client');
await page.accessToSearchResult('Bruce Banner');
await page.accessToSection('client.card.billingData');
await page.waitToClick($.newBankEntityButton);
await page.write($.newBankEntityName, 'Gotham City Bank');
await page.write($.newBankEntityBIC, 'GTHMCT');
@ -66,7 +77,7 @@ describe('Client Edit billing data path', () => {
it(`should confirm the IBAN pay method was sucessfully saved`, async() => {
const payMethod = await page.waitToGetProperty($.payMethod, 'value');
expect(payMethod).toEqual('PayMethod with IBAN');
expect(payMethod).toEqual('PayMethod five');
});
it(`should clear the BIC code field, update the IBAN to see how he BIC code autocompletes`, async() => {
@ -79,14 +90,6 @@ describe('Client Edit billing data path', () => {
expect(automaticCode).toEqual('CAIXESBB');
});
it(`should save the form with all its new data`, async() => {
await page.waitForWatcherData($.watcher);
await page.waitToClick($.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Notification sent!');
});
it('should confirm the billing data have been edited', async() => {
const dueDate = await page.waitToGetProperty($.dueDay, 'value');
const IBAN = await page.waitToGetProperty($.IBAN, 'value');
@ -94,7 +97,9 @@ describe('Client Edit billing data path', () => {
const receivedCoreLCR = await page.checkboxState($.receivedCoreLCRCheckbox);
const receivedCoreVNL = await page.checkboxState($.receivedCoreVNLCheckbox);
const receivedB2BVNL = await page.checkboxState($.receivedB2BVNLCheckbox);
const payMethod = await page.waitToGetProperty($.payMethod, 'value');
expect(payMethod).toEqual('PayMethod five');
expect(dueDate).toEqual('60');
expect(IBAN).toEqual('ES9121000418450200051332');
expect(swiftBic).toEqual('CAIXESBB');

View File

@ -30,18 +30,18 @@ describe('Ticket Future path', () => {
expect(message.text).toContain('warehouseFk is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.futureDated);
await page.clearInput(selectors.ticketFuture.futureScopeDays);
await page.waitToClick(selectors.ticketFuture.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('futureDated is a required argument');
expect(message.text).toContain('futureScopeDays is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.originDated);
await page.clearInput(selectors.ticketFuture.originScopeDays);
await page.waitToClick(selectors.ticketFuture.submit);
message = await page.waitForSnackbar();
expect(message.text).toContain('originDated is a required argument');
expect(message.text).toContain('originScopeDays is a required argument');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.waitToClick(selectors.ticketFuture.submit);
@ -71,7 +71,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.state, 'Free');
await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('state=FREE');
expect(httpRequest).toContain('state=0');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
@ -80,7 +80,7 @@ describe('Ticket Future path', () => {
await page.autocompleteSearch(selectors.ticketFuture.futureState, 'Free');
await page.waitToClick(selectors.ticketFuture.submit);
expect(httpRequest).toContain('futureState=FREE');
expect(httpRequest).toContain('futureState=0');
await page.waitToClick(selectors.ticketFuture.openAdvancedSearchButton);
await page.clearInput(selectors.ticketFuture.state);

View File

@ -372,5 +372,7 @@
"The entry not have stickers": "La entrada no tiene etiquetas",
"Too many records": "Demasiados registros",
"Original invoice not found": "Factura original no encontrada",
"The entry has no lines or does not exist": "La entrada no tiene lineas o no existe"
"The entry has no lines or does not exist": "La entrada no tiene lineas o no existe",
"Weight already set": "El peso ya está establecido",
"This ticket is not allocated to your department": "Este ticket no está asignado a tu departamento"
}

View File

@ -320,7 +320,8 @@ module.exports = Self => {
// Credit management changes
if (changes?.rating >= 0 || changes?.recommendedCredit >= 0)
if ((changes?.rating != null && changes.rating >= 0)
|| (changes?.recommendedCredit != null && changes.recommendedCredit >= 0))
await Self.changeCreditManagement(ctx, finalState, changes);
const oldInstance = {};

View File

@ -9,13 +9,13 @@ module.exports = Self => {
accessType: 'READ',
accepts: [
{
arg: 'originDated',
arg: 'originScopeDays',
type: 'date',
description: 'The date in question',
required: true
},
{
arg: 'futureDated',
arg: 'futureScopeDays',
type: 'date',
description: 'The date to probe',
required: true
@ -129,9 +129,9 @@ module.exports = Self => {
]
};
case 'state':
return {'f.stateCode': {like: `%${value}%`}};
return {'f.alertLevel': value};
case 'futureState':
return {'f.futureStateCode': {like: `%${value}%`}};
return {'f.futureAlertLevel': value};
}
});
@ -141,7 +141,7 @@ module.exports = Self => {
stmt = new ParameterizedSQL(
`CALL vn.ticket_canbePostponed(?,?,?)`,
[args.originDated, args.futureDated, args.warehouseFk]);
[args.originScopeDays, args.futureScopeDays, args.warehouseFk]);
stmts.push(stmt);
@ -170,7 +170,7 @@ module.exports = Self => {
LEFT JOIN tmp.ticket_problems tp ON tp.ticketFk = f.id
`);
if (args.problems != undefined && (!args.originDated && !args.futureDated))
if (args.problems != undefined && (!args.originScopeDays && !args.futureScopeDays))
throw new UserError('Choose a date range or days forward');
let condition;

View File

@ -0,0 +1,86 @@
const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('setWeight', {
description: 'Sets weight of a ticket',
accessType: 'WRITE',
accepts: [{
arg: 'id',
type: 'number',
required: true,
description: 'The ticket id',
http: {source: 'path'}
}, {
arg: 'weight',
type: 'number',
required: true,
description: 'The weight value',
}],
returns: {
type: 'Array',
root: true
},
http: {
path: `/:id/setWeight`,
verb: 'POST'
}
});
Self.setWeight = async(ctx, ticketId, weight, options) => {
const models = Self.app.models;
const userId = ctx.req.accessToken.userId;
const myOptions = {userId};
let tx;
if (typeof options == 'object') Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const ticket = await Self.findById(ticketId, null, myOptions);
if (ticket.weight) throw new UserError('Weight already set');
const canEdit = await models.ACL.checkAccessAcl(ctx, 'Ticket', 'updateAttributes');
const client = await models.Client.findById(ticket.clientFk, {
include: {relation: 'salesPersonUser'}},
myOptions);
if (!canEdit) {
const salesPersonUser = client.salesPersonUser();
const workerDepartments = await models.WorkerDepartment.find({
include: {relation: 'department'},
where: {workerFk: {inq: [userId, salesPersonUser.id]}}
}, myOptions);
if (
workerDepartments.length == 2 &&
workerDepartments[0].departmentFk != workerDepartments[1].departmentFk
)
throw new UserError('This ticket is not allocated to your department');
}
await ticket.updateAttribute('weight', weight, myOptions);
const packedState = await models.State.findOne({where: {code: 'PACKED'}}, myOptions);
const ticketState = await models.TicketState.findOne({where: {ticketFk: ticketId}}, myOptions);
const [{taxArea}] = await Self.rawSql('SELECT clientTaxArea(?,?) taxArea',
[ticket.clientFk, ticket.warehouseFk], myOptions);
const isInvoiceable = ticketState.alertLevel >= packedState.alertLevel &&
taxArea == 'WORLD' && client.hasDailyInvoice;
if (tx) await tx.commit();
let invoiceIds = [];
if (isInvoiceable) invoiceIds = [...await Self.invoiceTicketsAndPdf(ctx, [ticketId])];
return invoiceIds;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -12,8 +12,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
};
@ -35,8 +35,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
problems: true
};
@ -60,8 +60,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
problems: false
};
@ -85,8 +85,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
problems: null
};
@ -110,8 +110,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
state: 'OK'
};
@ -135,8 +135,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
futureState: 'OK'
};
@ -160,8 +160,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
ipt: null
};
@ -185,8 +185,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
ipt: 'H'
};
@ -210,8 +210,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
futureIpt: null
};
@ -235,8 +235,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
futureIpt: 'H'
};
@ -260,8 +260,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
id: 13
};
@ -285,8 +285,8 @@ describe('ticket getTicketsFuture()', () => {
const options = {transaction: tx};
const args = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: 1,
futureId: 12
};

View File

@ -0,0 +1,70 @@
const {models} = require('vn-loopback/server/server');
describe('ticket setWeight()', () => {
const ctx = beforeAll.getCtx();
beforeAll.mockLoopBackContext();
let opts;
let tx;
const employeeId = 1;
const administrativeId = 5;
beforeEach(async() => {
opts = {transaction: tx};
tx = await models.Ticket.beginTransaction({});
opts.transaction = tx;
ctx.req.accessToken.userId = administrativeId;
});
afterEach(async() => await tx.rollback());
it('should throw an error if the weight is already set', async() => {
try {
const ticketId = 1;
const weight = 10;
await models.Ticket.setWeight(ctx, ticketId, weight, opts);
} catch (e) {
expect(e.message).toEqual('Weight already set');
}
});
it('should set the weight of a ticket', async() => {
const ticketId = 31;
const weight = 15;
await models.Ticket.setWeight(ctx, ticketId, weight, opts);
const ticket = await models.Ticket.findById(ticketId, null, opts);
expect(ticket.weight).toEqual(weight);
});
it('should throw an error if the user is not allocated to the same department', async() => {
ctx.req.accessToken.userId = employeeId;
try {
const ticketId = 10;
const weight = 20;
await models.Ticket.setWeight(ctx, ticketId, weight, opts);
} catch (e) {
expect(e.message).toEqual('This ticket is not allocated to your department');
}
});
it('should call invoiceTicketsAndPdf if the ticket is invoiceable', async() => {
const ticketId = 10;
const weight = 25;
spyOn(models.Client, 'findById').and.returnValue({
hasDailyInvoice: true,
salesPersonUser: () => ({id: 1})
});
spyOn(models.TicketState, 'findOne').and.returnValue({alertLevel: 3});
spyOn(models.Ticket, 'rawSql').and.returnValue([{taxArea: 'WORLD'}]);
spyOn(models.Ticket, 'invoiceTicketsAndPdf').and.returnValue([10]);
const invoiceIds = await models.Ticket.setWeight(ctx, ticketId, weight, opts);
expect(invoiceIds.length).toBeGreaterThan(0);
});
});

View File

@ -46,4 +46,5 @@ module.exports = function(Self) {
require('../methods/ticket/invoiceTicketsAndPdf')(Self);
require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/setWeight')(Self);
};

View File

@ -1,7 +1,7 @@
<div class="search-panel">
<vn-crud-model
auto-load="true"
url="Warehouses"
auto-load="true"
url="Warehouses"
data="warehouses">
</vn-crud-model>
<form id="manifold-form" ng-submit="$ctrl.onSearch()">
@ -9,13 +9,13 @@
<vn-date-picker
vn-one
label="Origin date"
ng-model="filter.originDated"
ng-model="filter.originScopeDays"
required="true">
</vn-date-picker>
<vn-date-picker
vn-one
label="Destination date"
ng-model="filter.futureDated"
ng-model="filter.futureScopeDays"
required="true">
</vn-date-picker>
</vn-horizontal>
@ -59,7 +59,7 @@
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Origin Grouped State"
value-field="code"
value-field="id"
show-field="name"
ng-model="filter.state">
<tpl-item>
@ -69,7 +69,7 @@
<vn-autocomplete vn-one
data="$ctrl.groupedStates"
label="Destination Grouped State"
value-field="code"
value-field="id"
show-field="name"
ng-model="filter.futureState">
<tpl-item>

View File

@ -65,8 +65,8 @@ export default class Controller extends Section {
this.$http.get(`UserConfigs/getUserConfig`)
.then(res => {
this.filterParams = {
originDated: today,
futureDated: today,
originScopeDays: today,
futureScopeDays: today,
warehouseFk: res.data.warehouseFk
};
this.$.model.applyFilter(null, this.filterParams);

View File

@ -3,4 +3,5 @@ Address mobile: Móv. consignatario
Client phone: Tel. cliente
Client mobile: Móv. cliente
Go to the ticket: Ir al ticket
Change state: Cambiar estado
Change state: Cambiar estado
Weight: Peso

View File

@ -1,6 +1,6 @@
{
"name": "salix-back",
"version": "24.36.0",
"version": "24.40.0",
"author": "Verdnatura Levante SL",
"description": "Salix backend",
"license": "GPL-3.0",