diff --git a/modules/entry/front/buy/index/index.js b/modules/entry/front/buy/index/index.js
index c991b745b..9131c31f6 100644
--- a/modules/entry/front/buy/index/index.js
+++ b/modules/entry/front/buy/index/index.js
@@ -53,12 +53,8 @@ export default class Controller extends Section {
}
toggleGroupingMode(buy, mode) {
- const grouping = 1;
- const packing = 2;
- const groupingMode = mode === 'grouping' ? grouping : packing;
-
- const newGroupingMode = buy.groupingMode === groupingMode ? 0 : groupingMode;
-
+ const groupingMode = mode === 'grouping' ? mode : 'packing';
+ const newGroupingMode = buy.groupingMode === groupingMode ? null : groupingMode;
const params = {
groupingMode: newGroupingMode
};
diff --git a/modules/entry/front/buy/index/index.spec.js b/modules/entry/front/buy/index/index.spec.js
index b9d5fab51..f5c6d1bdb 100644
--- a/modules/entry/front/buy/index/index.spec.js
+++ b/modules/entry/front/buy/index/index.spec.js
@@ -57,45 +57,34 @@ describe('Entry buy', () => {
describe('toggleGroupingMode()', () => {
it(`should toggle grouping mode from grouping to packing`, () => {
- const grouping = 1;
- const packing = 2;
- const buy = {id: 999, groupingMode: grouping};
+ const buy = {id: 999, groupingMode: 'grouping'};
const query = `Buys/${buy.id}`;
- $httpBackend.expectPATCH(query, {groupingMode: packing}).respond(200);
+ $httpBackend.expectPATCH(query, {groupingMode: 'packing'}).respond(200);
controller.toggleGroupingMode(buy, 'packing');
$httpBackend.flush();
});
it(`should toggle grouping mode from packing to grouping`, () => {
- const grouping = 1;
- const packing = 2;
- const buy = {id: 999, groupingMode: packing};
-
+ const buy = {id: 999, groupingMode: 'packing'};
const query = `Buys/${buy.id}`;
- $httpBackend.expectPATCH(query, {groupingMode: grouping}).respond(200);
+ $httpBackend.expectPATCH(query, {groupingMode: 'grouping'}).respond(200);
controller.toggleGroupingMode(buy, 'grouping');
$httpBackend.flush();
});
it(`should toggle off the grouping mode if it was packing to packing`, () => {
- const noGrouping = 0;
- const packing = 2;
- const buy = {id: 999, groupingMode: packing};
-
+ const buy = {id: 999, groupingMode: 'packing'};
const query = `Buys/${buy.id}`;
- $httpBackend.expectPATCH(query, {groupingMode: noGrouping}).respond(200);
+ $httpBackend.expectPATCH(query, {groupingMode: null}).respond(200);
controller.toggleGroupingMode(buy, 'packing');
$httpBackend.flush();
});
it(`should toggle off the grouping mode if it was grouping to grouping`, () => {
- const noGrouping = 0;
- const grouping = 1;
- const buy = {id: 999, groupingMode: grouping};
-
+ const buy = {id: 999, groupingMode: 'grouping'};
const query = `Buys/${buy.id}`;
- $httpBackend.expectPATCH(query, {groupingMode: noGrouping}).respond(200);
+ $httpBackend.expectPATCH(query, {groupingMode: null}).respond(200);
controller.toggleGroupingMode(buy, 'grouping');
$httpBackend.flush();
});
diff --git a/modules/entry/front/latest-buys/index.html b/modules/entry/front/latest-buys/index.html
index 16e039cf0..2e6de83b9 100644
--- a/modules/entry/front/latest-buys/index.html
+++ b/modules/entry/front/latest-buys/index.html
@@ -143,12 +143,12 @@
-
+
{{::buy.packing | dashIfEmpty}}
|
-
+
{{::buy.grouping | dashIfEmpty}}
|
diff --git a/modules/entry/front/summary/index.html b/modules/entry/front/summary/index.html
index 655dcd66f..baa310bb6 100644
--- a/modules/entry/front/summary/index.html
+++ b/modules/entry/front/summary/index.html
@@ -121,12 +121,12 @@
{{::line.packagingFk | dashIfEmpty}} |
{{::line.weight}} |
-
+
{{::line.packing | dashIfEmpty}}
|
-
+
{{::line.grouping | dashIfEmpty}}
diff --git a/modules/invoiceIn/back/methods/invoice-in-due-day/specs/new.spec.js b/modules/invoiceIn/back/methods/invoice-in-due-day/specs/new.spec.js
index c188a511d..f21dad9f2 100644
--- a/modules/invoiceIn/back/methods/invoice-in-due-day/specs/new.spec.js
+++ b/modules/invoiceIn/back/methods/invoice-in-due-day/specs/new.spec.js
@@ -13,11 +13,10 @@ describe('invoiceInDueDay new()', () => {
it('should correctly create a new due day', async() => {
const userId = 9;
- const invoiceInFk = 6;
+ const invoiceInFk = 3;
const ctx = {
req: {
-
accessToken: {userId: userId},
}
};
diff --git a/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js b/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js
new file mode 100644
index 000000000..3ad06b242
--- /dev/null
+++ b/modules/invoiceIn/back/methods/invoice-in/exchangeRateUpdate.js
@@ -0,0 +1,64 @@
+const axios = require('axios');
+const {DOMParser} = require('xmldom');
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethod('exchangeRateUpdate', {
+ description: 'Updates the exchange rates from an XML feed',
+ accessType: 'WRITE',
+ accepts: [],
+ http: {
+ path: '/exchangeRateUpdate',
+ verb: 'post'
+ }
+ });
+
+ Self.exchangeRateUpdate = async() => {
+ const response = await axios.get('http://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml');
+ const xmlData = response.data;
+
+ const doc = new DOMParser({errorHandler: {warning: () => {}}})?.parseFromString(xmlData, 'text/xml');
+ const cubes = doc?.getElementsByTagName('Cube');
+ if (!cubes || cubes.length === 0)
+ throw new UserError('No cubes found. Exiting the method.');
+
+ const models = Self.app.models;
+
+ const maxDateRecord = await models.ReferenceRate.findOne({order: 'dated DESC'});
+
+ const maxDate = maxDateRecord?.dated ? new Date(maxDateRecord.dated) : null;
+
+ for (const cube of Array.from(cubes)) {
+ if (cube.nodeType === doc.ELEMENT_NODE && cube.attributes.getNamedItem('time')) {
+ const xmlDate = new Date(cube.getAttribute('time'));
+ const xmlDateWithoutTime = new Date(xmlDate.getFullYear(), xmlDate.getMonth(), xmlDate.getDate());
+ if (!maxDate || maxDate < xmlDateWithoutTime) {
+ for (const rateCube of Array.from(cube.childNodes)) {
+ if (rateCube.nodeType === doc.ELEMENT_NODE) {
+ const currencyCode = rateCube.getAttribute('currency');
+ const rate = rateCube.getAttribute('rate');
+ if (['USD', 'CNY', 'GBP'].includes(currencyCode)) {
+ const currency = await models.Currency.findOne({where: {code: currencyCode}});
+ if (!currency) throw new UserError(`Currency not found for code: ${currencyCode}`);
+ const existingRate = await models.ReferenceRate.findOne({
+ where: {currencyFk: currency.id, dated: xmlDate}
+ });
+
+ if (existingRate) {
+ if (existingRate.value !== rate)
+ await existingRate.updateAttributes({value: rate});
+ } else {
+ await models.ReferenceRate.create({
+ currencyFk: currency.id,
+ dated: xmlDate,
+ value: rate
+ });
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ };
+};
diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js
new file mode 100644
index 000000000..0fd7ea165
--- /dev/null
+++ b/modules/invoiceIn/back/methods/invoice-in/specs/exchangeRateUpdate.spec.js
@@ -0,0 +1,52 @@
+describe('exchangeRateUpdate functionality', function() {
+ const axios = require('axios');
+ const models = require('vn-loopback/server/server').models;
+
+ beforeEach(function() {
+ spyOn(axios, 'get').and.returnValue(Promise.resolve({
+ data: `
+
+
+
+
+ `
+ }));
+ });
+
+ it('should process XML data and update or create rates in the database', async function() {
+ spyOn(models.ReferenceRate, 'findOne').and.returnValue(Promise.resolve(null));
+ spyOn(models.ReferenceRate, 'create').and.returnValue(Promise.resolve());
+
+ await models.InvoiceIn.exchangeRateUpdate();
+
+ expect(models.ReferenceRate.create).toHaveBeenCalledTimes(2);
+ });
+
+ it('should not create or update rates when no XML data is available', async function() {
+ axios.get.and.returnValue(Promise.resolve({}));
+ spyOn(models.ReferenceRate, 'create');
+
+ let thrownError = null;
+ try {
+ await models.InvoiceIn.exchangeRateUpdate();
+ } catch (error) {
+ thrownError = error;
+ }
+
+ expect(thrownError.message).toBe('No cubes found. Exiting the method.');
+ });
+
+ it('should handle errors gracefully', async function() {
+ axios.get.and.returnValue(Promise.reject(new Error('Network error')));
+ let error;
+
+ try {
+ await models.InvoiceIn.exchangeRateUpdate();
+ } catch (e) {
+ error = e;
+ }
+
+ expect(error).toBeDefined();
+ expect(error.message).toBe('Network error');
+ });
+});
diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/filter.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/filter.spec.js
index 9834989fc..ff2164783 100644
--- a/modules/invoiceIn/back/methods/invoice-in/specs/filter.spec.js
+++ b/modules/invoiceIn/back/methods/invoice-in/specs/filter.spec.js
@@ -158,7 +158,7 @@ describe('InvoiceIn filter()', () => {
const result = await models.InvoiceIn.filter(ctx, {}, options);
- expect(result.length).toEqual(6);
+ expect(result.length).toEqual(5);
await tx.rollback();
} catch (e) {
@@ -180,7 +180,7 @@ describe('InvoiceIn filter()', () => {
const result = await models.InvoiceIn.filter(ctx, {}, options);
- expect(result.length).toEqual(6);
+ expect(result.length).toEqual(5);
expect(result[0].isBooked).toBeTruthy();
await tx.rollback();
diff --git a/modules/invoiceIn/back/models/invoice-in-due-day.json b/modules/invoiceIn/back/models/invoice-in-due-day.json
index d2cffc81b..ca3616ef8 100644
--- a/modules/invoiceIn/back/models/invoice-in-due-day.json
+++ b/modules/invoiceIn/back/models/invoice-in-due-day.json
@@ -34,7 +34,7 @@
"relations": {
"bank": {
"type": "belongsTo",
- "model": "Bank",
+ "model": "Accounting",
"foreignKey": "bankFk"
}
}
diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js
index af5efbcdf..31cdc1abe 100644
--- a/modules/invoiceIn/back/models/invoice-in.js
+++ b/modules/invoiceIn/back/models/invoice-in.js
@@ -10,6 +10,7 @@ module.exports = Self => {
require('../methods/invoice-in/invoiceInEmail')(Self);
require('../methods/invoice-in/getSerial')(Self);
require('../methods/invoice-in/corrective')(Self);
+ require('../methods/invoice-in/exchangeRateUpdate')(Self);
Self.rewriteDbError(function(err) {
if (err.code === 'ER_ROW_IS_REFERENCED_2' && err.sqlMessage.includes('vehicleInvoiceIn'))
diff --git a/modules/invoiceIn/front/dueDay/index.html b/modules/invoiceIn/front/dueDay/index.html
index 1a1935e72..abc91312d 100644
--- a/modules/invoiceIn/front/dueDay/index.html
+++ b/modules/invoiceIn/front/dueDay/index.html
@@ -23,7 +23,7 @@
{
}
});
- Self.createManualInvoice = async(ctx, options) => {
+ Self.createManualInvoice = async(ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options) => {
+ if (!clientFk && !ticketFk) throw new UserError(`Select ticket or client`);
const models = Self.app.models;
- const args = ctx.args;
-
- let tx;
const myOptions = {userId: ctx.req.accessToken.userId};
+ let tx;
if (typeof options == 'object')
Object.assign(myOptions, options);
@@ -61,18 +60,15 @@ module.exports = Self => {
myOptions.transaction = tx;
}
- const ticketId = args.ticketFk;
- let clientId = args.clientFk;
- let maxShipped = args.maxShipped;
let companyId;
let newInvoice;
let query;
try {
- if (ticketId) {
- const ticket = await models.Ticket.findById(ticketId, null, myOptions);
+ if (ticketFk) {
+ const ticket = await models.Ticket.findById(ticketFk, null, myOptions);
const company = await models.Company.findById(ticket.companyFk, null, myOptions);
- clientId = ticket.clientFk;
+ clientFk = ticket.clientFk;
maxShipped = ticket.shipped;
companyId = ticket.companyFk;
@@ -85,7 +81,7 @@ module.exports = Self => {
throw new UserError(`A ticket with an amount of zero can't be invoiced`);
// Validates ticket nagative base
- const hasNegativeBase = await getNegativeBase(maxShipped, clientId, companyId, myOptions);
+ const hasNegativeBase = await getNegativeBase(maxShipped, clientFk, companyId, myOptions);
if (hasNegativeBase && company.code == 'VNL')
throw new UserError(`A ticket with a negative base can't be invoiced`);
} else {
@@ -95,7 +91,7 @@ module.exports = Self => {
const company = await models.Ticket.findOne({
fields: ['companyFk'],
where: {
- clientFk: clientId,
+ clientFk: clientFk,
shipped: {lte: maxShipped}
}
}, myOptions);
@@ -103,7 +99,7 @@ module.exports = Self => {
}
// Validate invoiceable client
- const isClientInvoiceable = await isInvoiceable(clientId, myOptions);
+ const isClientInvoiceable = await isInvoiceable(clientFk, myOptions);
if (!isClientInvoiceable)
throw new UserError(`This client is not invoiceable`);
@@ -114,27 +110,27 @@ module.exports = Self => {
if (maxShipped >= tomorrow)
throw new UserError(`Can't invoice to future`);
- const maxInvoiceDate = await getMaxIssued(args.serial, companyId, myOptions);
+ const maxInvoiceDate = await getMaxIssued(serial, companyId, myOptions);
if (Date.vnNew() < maxInvoiceDate)
throw new UserError(`Can't invoice to past`);
- if (ticketId) {
+ if (ticketFk) {
query = `CALL invoiceOut_newFromTicket(?, ?, ?, ?, @newInvoiceId)`;
await Self.rawSql(query, [
- ticketId,
- args.serial,
- args.taxArea,
- args.reference
+ ticketFk,
+ serial,
+ taxArea,
+ reference
], myOptions);
} else {
query = `CALL invoiceOut_newFromClient(?, ?, ?, ?, ?, ?, @newInvoiceId)`;
await Self.rawSql(query, [
- clientId,
- args.serial,
+ clientFk,
+ serial,
maxShipped,
companyId,
- args.taxArea,
- args.reference
+ taxArea,
+ reference
], myOptions);
}
@@ -146,26 +142,27 @@ module.exports = Self => {
throw e;
}
- if (newInvoice.id)
- await Self.createPdf(ctx, newInvoice.id);
+ if (!newInvoice.id) throw new UserError('It was not able to create the invoice');
+
+ await Self.createPdf(ctx, newInvoice.id);
return newInvoice;
};
- async function isInvoiceable(clientId, options) {
+ async function isInvoiceable(clientFk, options) {
const models = Self.app.models;
const query = `SELECT (hasToInvoice AND isTaxDataChecked) AS invoiceable
FROM client
WHERE id = ?`;
- const [result] = await models.InvoiceOut.rawSql(query, [clientId], options);
+ const [result] = await models.InvoiceOut.rawSql(query, [clientFk], options);
return result.invoiceable;
}
- async function getNegativeBase(maxShipped, clientId, companyId, options) {
+ async function getNegativeBase(maxShipped, clientFk, companyId, options) {
const models = Self.app.models;
await models.InvoiceOut.rawSql('CALL invoiceOut_exportationFromClient(?,?,?)',
- [maxShipped, clientId, companyId], options
+ [maxShipped, clientFk, companyId], options
);
const query = 'SELECT vn.hasAnyNegativeBase() AS base';
const [result] = await models.InvoiceOut.rawSql(query, [], options);
diff --git a/modules/invoiceOut/back/methods/invoiceOut/download.js b/modules/invoiceOut/back/methods/invoiceOut/download.js
index 4c76f7c07..748e2df17 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/download.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/download.js
@@ -31,7 +31,8 @@ module.exports = Self => {
http: {
path: '/:id/download',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.download = async function(ctx, id, options) {
diff --git a/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js
index fe005f1ab..8d6e7c6d9 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/downloadZip.js
@@ -31,7 +31,8 @@ module.exports = Self => {
http: {
path: '/downloadZip',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.downloadZip = async function(ctx, ids, options) {
diff --git a/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js b/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js
index 7a2526b35..6c4845c11 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/exportationPdf.js
@@ -34,7 +34,8 @@ module.exports = Self => {
http: {
path: '/:reference/exportation-pdf',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.exportationPdf = (ctx, reference) => Self.printReport(ctx, reference, 'exportation');
diff --git a/modules/invoiceOut/back/methods/invoiceOut/invoiceCsv.js b/modules/invoiceOut/back/methods/invoiceOut/invoiceCsv.js
index d33df74a2..fd754d51b 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/invoiceCsv.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/invoiceCsv.js
@@ -37,23 +37,24 @@ module.exports = Self => {
http: {
path: '/:reference/invoice-csv',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.invoiceCsv = async reference => {
const sales = await Self.rawSql(`
SELECT io.ref Invoice,
io.issued InvoiceDate,
- s.ticketFk Ticket,
- s.itemFk Item,
- s.concept Description,
- i.size,
+ s.ticketFk Ticket,
+ s.itemFk Item,
+ s.concept Description,
+ i.size,
i.subName Producer,
- s.quantity Quantity,
- s.price Price,
- s.discount Discount,
- s.created Created,
- tc.code Taxcode,
+ s.quantity Quantity,
+ s.price Price,
+ s.discount Discount,
+ s.created Created,
+ tc.code Taxcode,
tc.description TaxDescription,
i.tag5,
i.value5,
@@ -67,19 +68,19 @@ module.exports = Self => {
i.value9,
i.tag10,
i.value10
- FROM sale s
- JOIN ticket t ON t.id = s.ticketFk
- JOIN item i ON i.id = s.itemFk
- JOIN supplier s2 ON s2.id = t.companyFk
- JOIN itemTaxCountry itc ON itc.itemFk = i.id
- AND itc.countryFk = s2.countryFk
- JOIN taxClass tc ON tc.id = itc.taxClassFk
- JOIN invoiceOut io ON io.ref = t.refFk
+ FROM sale s
+ JOIN ticket t ON t.id = s.ticketFk
+ JOIN item i ON i.id = s.itemFk
+ JOIN supplier s2 ON s2.id = t.companyFk
+ JOIN itemTaxCountry itc ON itc.itemFk = i.id
+ AND itc.countryFk = s2.countryFk
+ JOIN taxClass tc ON tc.id = itc.taxClassFk
+ JOIN invoiceOut io ON io.ref = t.refFk
WHERE t.refFk = ?
ORDER BY s.ticketFk, s.created`, [reference]);
const content = toCSV(sales);
- return [content, 'text/csv', `inline; filename="doc-${reference}.pdf"`];
+ return [content, 'text/csv', `inline; filename="doc-${reference}.csv"`];
};
};
diff --git a/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js b/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js
index 96c789316..fc8830885 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/negativeBases.js
@@ -46,26 +46,23 @@ module.exports = Self => {
const stmts = [];
let stmt;
- stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.ticket`);
-
stmts.push(new ParameterizedSQL(
- `CREATE TEMPORARY TABLE tmp.ticket
+ `CREATE OR REPLACE TEMPORARY TABLE tmp.ticket
(KEY (ticketFk))
ENGINE = MEMORY
SELECT id ticketFk
FROM ticket t
- WHERE shipped BETWEEN ? AND ?
+ WHERE shipped BETWEEN ? AND util.dayEnd(?)
AND refFk IS NULL`, [args.from, args.to]));
stmts.push(`CALL vn.ticket_getTax(NULL)`);
- stmts.push(`DROP TEMPORARY TABLE IF EXISTS tmp.filter`);
stmts.push(new ParameterizedSQL(
- `CREATE TEMPORARY TABLE tmp.filter
+ `CREATE OR REPLACE TEMPORARY TABLE tmp.filter
ENGINE = MEMORY
- SELECT
- co.code company,
+ SELECT co.code company,
cou.country,
c.id clientId,
c.socialName clientSocialName,
+ u.nickname workerSocialName,
SUM(s.quantity * s.price * ( 100 - s.discount ) / 100) amount,
negativeBase.taxableBase,
negativeBase.ticketFk,
@@ -73,28 +70,27 @@ module.exports = Self => {
c.hasToInvoice,
c.isTaxDataChecked,
w.id comercialId,
- CONCAT(w.firstName, ' ', w.lastName) comercialName
- FROM vn.ticket t
- JOIN vn.company co ON co.id = t.companyFk
- JOIN vn.sale s ON s.ticketFk = t.id
- JOIN vn.client c ON c.id = t.clientFk
- JOIN vn.country cou ON cou.id = c.countryFk
- LEFT JOIN vn.worker w ON w.id = c.salesPersonFk
- LEFT JOIN (
- SELECT ticketFk, taxableBase
- FROM tmp.ticketAmount
- GROUP BY ticketFk
- HAVING taxableBase < 0
- ) negativeBase ON negativeBase.ticketFk = t.id
- WHERE t.shipped BETWEEN ? AND ?
- AND t.refFk IS NULL
- AND c.typeFk IN ('normal','trust')
- GROUP BY t.clientFk, negativeBase.taxableBase
- HAVING amount < 0`, [args.from, args.to]));
+ u.name workerName
+ FROM vn.ticket t
+ JOIN vn.company co ON co.id = t.companyFk
+ JOIN vn.sale s ON s.ticketFk = t.id
+ JOIN vn.client c ON c.id = t.clientFk
+ JOIN vn.country cou ON cou.id = c.countryFk
+ LEFT JOIN vn.worker w ON w.id = c.salesPersonFk
+ JOIN account.user u ON u.id = w.id
+ LEFT JOIN (
+ SELECT ticketFk, taxableBase
+ FROM tmp.ticketAmount
+ GROUP BY ticketFk
+ HAVING taxableBase < 0
+ ) negativeBase ON negativeBase.ticketFk = t.id
+ WHERE t.shipped BETWEEN ? AND util.dayEnd(?)
+ AND t.refFk IS NULL
+ AND c.typeFk IN ('normal','trust')
+ GROUP BY t.clientFk, negativeBase.taxableBase
+ HAVING amount < 0`, [args.from, args.to]));
- stmt = new ParameterizedSQL(`
- SELECT f.*
- FROM tmp.filter f`);
+ stmt = new ParameterizedSQL(`SELECT * FROM tmp.filter`);
if (args.filter) {
stmt.merge(conn.makeWhere(args.filter.where));
diff --git a/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js b/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js
index 87e9a67ea..3e466d1f4 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/negativeBasesCsv.js
@@ -39,7 +39,8 @@ module.exports = Self => {
http: {
path: '/negativeBasesCsv',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.negativeBasesCsv = async(ctx, options) => {
diff --git a/modules/invoiceOut/back/methods/invoiceOut/specs/createManualInvoice.spec.js b/modules/invoiceOut/back/methods/invoiceOut/specs/createManualInvoice.spec.js
index b166caf78..55739e570 100644
--- a/modules/invoiceOut/back/methods/invoiceOut/specs/createManualInvoice.spec.js
+++ b/modules/invoiceOut/back/methods/invoiceOut/specs/createManualInvoice.spec.js
@@ -1,13 +1,10 @@
-const models = require('vn-loopback/server/server').models;
+const {models} = require('vn-loopback/server/server');
const LoopBackContext = require('loopback-context');
describe('InvoiceOut createManualInvoice()', () => {
- const userId = 1;
const ticketId = 16;
const clientId = 1106;
- const activeCtx = {
- accessToken: {userId: userId},
- };
+ const activeCtx = {accessToken: {userId: 1}};
const ctx = {req: activeCtx};
it('should throw an error trying to invoice again', async() => {
@@ -18,13 +15,8 @@ describe('InvoiceOut createManualInvoice()', () => {
let error;
try {
- ctx.args = {
- ticketFk: ticketId,
- serial: 'T',
- taxArea: 'CEE'
- };
- await models.InvoiceOut.createManualInvoice(ctx, options);
- await models.InvoiceOut.createManualInvoice(ctx, options);
+ await createInvoice(ctx, options, undefined, ticketId);
+ await createInvoice(ctx, options, undefined, ticketId);
await tx.rollback();
} catch (e) {
@@ -47,17 +39,9 @@ describe('InvoiceOut createManualInvoice()', () => {
let error;
try {
const ticket = await models.Ticket.findById(ticketId, null, options);
- await ticket.updateAttributes({
- totalWithVat: 0
- }, options);
-
- ctx.args = {
- ticketFk: ticketId,
- serial: 'T',
- taxArea: 'CEE'
- };
- await models.InvoiceOut.createManualInvoice(ctx, options);
+ await ticket.updateAttributes({totalWithVat: 0}, options);
+ await createInvoice(ctx, options, undefined, ticketId);
await tx.rollback();
} catch (e) {
error = e;
@@ -75,13 +59,7 @@ describe('InvoiceOut createManualInvoice()', () => {
let error;
try {
- ctx.args = {
- clientFk: clientId,
- serial: 'T',
- taxArea: 'CEE'
- };
- await models.InvoiceOut.createManualInvoice(ctx, options);
-
+ await createInvoice(ctx, options, clientId);
await tx.rollback();
} catch (e) {
error = e;
@@ -103,16 +81,9 @@ describe('InvoiceOut createManualInvoice()', () => {
let error;
try {
const client = await models.Client.findById(clientId, null, options);
- await client.updateAttributes({
- isTaxDataChecked: false
- }, options);
+ await client.updateAttributes({isTaxDataChecked: false}, options);
- ctx.args = {
- ticketFk: ticketId,
- serial: 'T',
- taxArea: 'CEE'
- };
- await models.InvoiceOut.createManualInvoice(ctx, options);
+ await createInvoice(ctx, options, undefined, ticketId);
await tx.rollback();
} catch (e) {
@@ -130,12 +101,7 @@ describe('InvoiceOut createManualInvoice()', () => {
const options = {transaction: tx};
try {
- ctx.args = {
- ticketFk: ticketId,
- serial: 'T',
- taxArea: 'CEE'
- };
- const result = await models.InvoiceOut.createManualInvoice(ctx, options);
+ const result = await createInvoice(ctx, options, undefined, ticketId);
expect(result.id).toEqual(jasmine.any(Number));
@@ -146,3 +112,18 @@ describe('InvoiceOut createManualInvoice()', () => {
}
});
});
+
+function createInvoice(
+ ctx,
+ options,
+ clientFk = undefined,
+ ticketFk = undefined,
+ maxShipped = undefined,
+ serial = 'T',
+ taxArea = 'CEE',
+ reference = undefined
+) {
+ return models.InvoiceOut.createManualInvoice(
+ ctx, clientFk, ticketFk, maxShipped, serial, taxArea, reference, options
+ );
+}
diff --git a/modules/invoiceOut/back/models/cplus-rectification-type.json b/modules/invoiceOut/back/models/cplus-rectification-type.json
index e7bfb957f..06a57ea67 100644
--- a/modules/invoiceOut/back/models/cplus-rectification-type.json
+++ b/modules/invoiceOut/back/models/cplus-rectification-type.json
@@ -15,5 +15,13 @@
"description": {
"type": "string"
}
- }
+ },
+ "acls": [
+ {
+ "accessType": "READ",
+ "principalType": "ROLE",
+ "principalId": "$everyone",
+ "permission": "ALLOW"
+ }
+ ]
}
\ No newline at end of file
diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js
index 91f4883ad..e4fcc1a69 100644
--- a/modules/invoiceOut/back/models/invoice-out.js
+++ b/modules/invoiceOut/back/models/invoice-out.js
@@ -1,5 +1,6 @@
const print = require('vn-print');
const path = require('path');
+const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
require('../methods/invoiceOut/filter')(Self);
@@ -66,4 +67,24 @@ module.exports = Self => {
});
}
};
+
+ Self.getSerial = async function(clientId, companyId, addressId, type, myOptions) {
+ const [{serial}] = await Self.rawSql(
+ `SELECT vn.invoiceSerial(?, ?, ?) AS serial`,
+ [
+ clientId,
+ companyId,
+ type
+ ],
+ myOptions);
+
+ const invoiceOutSerial = await Self.app.models.InvoiceOutSerial.findById(serial);
+ if (invoiceOutSerial?.taxAreaFk == 'WORLD') {
+ const address = await Self.app.models.Address.findById(addressId);
+ if (!address || !address.customsAgentFk || !address.incotermsFk)
+ throw new UserError('The address of the customer must have information about Incoterms and Customs Agent');
+ }
+
+ return serial;
+ };
};
diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html
index 435db3612..1bf34831e 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.html
+++ b/modules/invoiceOut/front/descriptor-menu/index.html
@@ -37,7 +37,7 @@
@@ -102,7 +102,7 @@
diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js
index 2c28599e7..5184c137e 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.js
+++ b/modules/invoiceOut/front/descriptor-menu/index.js
@@ -118,11 +118,14 @@ class Controller extends Section {
const query = 'InvoiceOuts/refund';
const params = {ref: this.invoiceOut.ref, withWarehouse: withWarehouse};
this.$http.post(query, params).then(res => {
- const refundTicket = res.data;
- this.vnApp.showSuccess(this.$t('The following refund ticket have been created', {
- ticketId: refundTicket.id
+ const tickets = res.data;
+ const refundTickets = tickets.map(ticket => ticket.id);
+
+ this.vnApp.showSuccess(this.$t('The following refund tickets have been created', {
+ ticketId: refundTickets.join(',')
}));
- this.$state.go('ticket.card.sale', {id: refundTicket.id});
+ if (refundTickets.length == 1)
+ this.$state.go('ticket.card.sale', {id: refundTickets[0]});
});
}
diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml
index aaeefd9cc..9285fafa7 100644
--- a/modules/invoiceOut/front/descriptor-menu/locale/es.yml
+++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml
@@ -15,7 +15,7 @@ Are you sure you want to clone this invoice?: Estas seguro de clonar esta factur
InvoiceOut booked: Factura asentada
Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?
Are you sure you want to refund this invoice?: Estas seguro de querer abonar esta factura?
-Create a single ticket with all the content of the current invoice: Crear un ticket unico con todo el contenido de la factura actual
+Create a refund ticket for each ticket on the current invoice: Crear un ticket abono por cada ticket de la factura actual
Regenerate PDF invoice: Regenerar PDF factura
The invoice PDF document has been regenerated: El documento PDF de la factura ha sido regenerado
The email can't be empty: El correo no puede estar vacío
diff --git a/modules/invoiceOut/front/index/index.js b/modules/invoiceOut/front/index/index.js
index 2cde3c940..f109cd5b0 100644
--- a/modules/invoiceOut/front/index/index.js
+++ b/modules/invoiceOut/front/index/index.js
@@ -23,15 +23,16 @@ export default class Controller extends Section {
}
openPdf() {
+ const access_token = this.vnToken.tokenMultimedia;
if (this.checked.length <= 1) {
const [invoiceOutId] = this.checked;
- const url = `api/InvoiceOuts/${invoiceOutId}/download?access_token=${this.vnToken.token}`;
+ const url = `api/InvoiceOuts/${invoiceOutId}/download?access_token=${access_token}`;
window.open(url, '_blank');
} else {
const invoiceOutIds = this.checked;
const invoicesIds = invoiceOutIds.join(',');
const serializedParams = this.$httpParamSerializer({
- access_token: this.vnToken.token,
+ access_token,
ids: invoicesIds
});
const url = `api/InvoiceOuts/downloadZip?${serializedParams}`;
diff --git a/modules/invoiceOut/front/negative-bases/index.html b/modules/invoiceOut/front/negative-bases/index.html
index 26f67c7d4..499b6bfe0 100644
--- a/modules/invoiceOut/front/negative-bases/index.html
+++ b/modules/invoiceOut/front/negative-bases/index.html
@@ -114,7 +114,7 @@
- {{::client.comercialName | dashIfEmpty}}
+ {{::client.workerName | dashIfEmpty}}
|
diff --git a/modules/item/back/methods/item-barcode/delete.js b/modules/item/back/methods/item-barcode/delete.js
new file mode 100644
index 000000000..0eea651d3
--- /dev/null
+++ b/modules/item/back/methods/item-barcode/delete.js
@@ -0,0 +1,34 @@
+module.exports = Self => {
+ Self.remoteMethod('delete', {
+ description: 'Delete an ItemBarcode by itemFk and code',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'barcode',
+ type: 'string',
+ required: true,
+ },
+ {
+ arg: 'itemFk',
+ type: 'number',
+ required: true,
+ }
+ ],
+ http: {
+ path: `/delete`,
+ verb: 'DELETE'
+ }
+ });
+
+ Self.delete = async(barcode, itemFk, options) => {
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ await Self.destroyAll({
+ code: barcode,
+ itemFk
+ }, myOptions);
+ };
+};
diff --git a/modules/item/back/methods/item-barcode/specs/delete.spec.js b/modules/item/back/methods/item-barcode/specs/delete.spec.js
new file mode 100644
index 000000000..094a351a3
--- /dev/null
+++ b/modules/item/back/methods/item-barcode/specs/delete.spec.js
@@ -0,0 +1,22 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('itemBarcode delete()', () => {
+ it('should delete a record by itemFk and code', async() => {
+ const tx = await models.ItemBarcode.beginTransaction({});
+ const options = {transaction: tx};
+ const itemFk = 1;
+ const code = 1111111111;
+
+ try {
+ const itemsBarcodeBefore = await models.ItemBarcode.find({}, options);
+ await models.ItemBarcode.delete(code, itemFk, options);
+ const itemsBarcodeAfter = await models.ItemBarcode.find({}, options);
+
+ expect(itemsBarcodeBefore.length).toBeGreaterThan(itemsBarcodeAfter.length);
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+});
diff --git a/modules/item/back/methods/item-image-queue/download.js b/modules/item/back/methods/item-image-queue/download.js
index eb952daa4..001a2b950 100644
--- a/modules/item/back/methods/item-image-queue/download.js
+++ b/modules/item/back/methods/item-image-queue/download.js
@@ -11,6 +11,7 @@ module.exports = Self => {
path: `/download`,
verb: 'POST',
},
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.download = async() => {
diff --git a/modules/item/back/methods/item-shelving/getAlternative.js b/modules/item/back/methods/item-shelving/getAlternative.js
new file mode 100644
index 000000000..8108bfa6e
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/getAlternative.js
@@ -0,0 +1,64 @@
+module.exports = Self => {
+ Self.remoteMethod('getAlternative', {
+ description: 'Returns a list of items and possible alternative locations',
+ accessType: 'READ',
+ accepts: [{
+ arg: 'shelvingFk',
+ type: 'string',
+ required: true,
+ }],
+ returns: {
+ type: ['object'],
+ root: true
+ },
+ http: {
+ path: `/getAlternative`,
+ verb: 'GET'
+ }
+ });
+
+ Self.getAlternative = async(shelvingFk, options) => {
+ const models = Self.app.models;
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const filterItemShelvings = {
+ fields: ['id', 'visible', 'itemFk', 'shelvingFk'],
+ where: {shelvingFk},
+ include: [
+ {
+ relation: 'item',
+ scope: {
+ fields: ['longName', 'name', 'size']
+ }
+ },
+
+ ]
+ };
+
+ let itemShelvings = await models.ItemShelving.find(filterItemShelvings, myOptions);
+
+ if (itemShelvings) {
+ const [alternatives] = await models.ItemShelving.rawSql('CALL vn.itemShelving_getAlternatives(?)',
+ [shelvingFk], myOptions
+ );
+ return itemShelvings.map(itemShelving => {
+ const item = itemShelving.item();
+
+ const shelvings = alternatives.filter(alternative => alternative.itemFk == itemShelving.itemFk);
+
+ return {
+ id: itemShelving.id,
+ itemFk: itemShelving.itemFk,
+ name: item.name,
+ size: item.size,
+ longName: item.longName,
+ quantity: itemShelving.visible,
+ shelvings
+ };
+ });
+ }
+ };
+};
diff --git a/modules/item/back/methods/item-shelving/hasItemOlder.js b/modules/item/back/methods/item-shelving/hasItemOlder.js
new file mode 100644
index 000000000..ee4cdc829
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/hasItemOlder.js
@@ -0,0 +1,63 @@
+const UserError = require('vn-loopback/util/user-error');
+module.exports = Self => {
+ Self.remoteMethod('hasItemOlder', {
+ description:
+ 'Get boolean if any or specific item of the shelving has older created in another shelving or parking',
+ accessType: 'READ',
+ accepts: [{
+ arg: 'shelvingFkIn',
+ type: 'string',
+ required: true,
+ description: 'Shelving code'
+ },
+ {
+ arg: 'parking',
+ type: 'string',
+ description: 'Parking code'
+ },
+ {
+ arg: 'shelvingFkOut',
+ type: 'string',
+ description: 'Shelving code'
+ },
+ {
+ arg: 'itemFk',
+ type: 'integer',
+ description: 'Item id'
+ }],
+ returns: {
+ type: 'boolean',
+ root: true
+ },
+ http: {
+ path: `/hasItemOlder`,
+ verb: 'GET'
+ }
+ });
+
+ Self.hasItemOlder = async(shelvingFkIn, parking, shelvingFkOut, itemFk, options) => {
+ if (!parking && !shelvingFkOut) throw new UserError('Missing data: parking or shelving');
+
+ const myOptions = {};
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const result = await Self.rawSql(`
+ SELECT COUNT(ish.id) countItemOlder
+ FROM vn.itemShelving ish
+ JOIN (
+ SELECT ish.itemFk, created,shelvingFk
+ FROM vn.itemShelving ish
+ JOIN vn.shelving s ON ish.shelvingFk = s.code
+ WHERE ish.shelvingFk = ?
+ )sub ON sub.itemFK = ish.itemFk
+ JOIN vn.shelving s ON s.code = ish.shelvingFk
+ JOIN vn.parking p ON p.id = s.parkingFk
+ WHERE sub.created > ish.created
+ AND (p.code <> ? OR ? IS NULL)
+ AND (ish.shelvingFk <> ? OR ? IS NULL)
+ AND (ish.itemFk <> ? OR ? IS NULL)`,
+ [shelvingFkIn, parking, parking, shelvingFkOut, shelvingFkOut, itemFk, itemFk], myOptions);
+ return result[0]['countItemOlder'] > 0;
+ };
+};
diff --git a/modules/item/back/methods/item-shelving/specs/getAlternative.spec.js b/modules/item/back/methods/item-shelving/specs/getAlternative.spec.js
new file mode 100644
index 000000000..3f4917477
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/specs/getAlternative.spec.js
@@ -0,0 +1,25 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('itemShelving getAlternative()', () => {
+ beforeAll(async() => {
+ ctx = {
+ req: {
+ headers: {origin: 'http://localhost'},
+ }
+ };
+ });
+
+ it('should return a list of items without alternatives', async() => {
+ const shelvingFk = 'HEJ';
+ const itemShelvings = await models.ItemShelving.getAlternative(shelvingFk);
+
+ expect(itemShelvings[0].shelvings.length).toEqual(0);
+ });
+
+ it('should return an empty list', async() => {
+ const shelvingFk = 'ZZP';
+ const itemShelvings = await models.ItemShelving.getAlternative(shelvingFk);
+
+ expect(itemShelvings.length).toEqual(0);
+ });
+});
diff --git a/modules/item/back/methods/item-shelving/specs/hasItemOlder.spec.js b/modules/item/back/methods/item-shelving/specs/hasItemOlder.spec.js
new file mode 100644
index 000000000..abffead53
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/specs/hasItemOlder.spec.js
@@ -0,0 +1,45 @@
+
+const {models} = require('vn-loopback/server/server');
+
+describe('itemShelving hasOlder()', () => {
+ it('should return false because there are not older items', async() => {
+ const shelvingFkIn = 'GVC';
+ const shelvingFkOut = 'HEJ';
+ const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, null, shelvingFkOut);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return false because there are not older items in parking', async() => {
+ const shelvingFkIn = 'HEJ';
+ const parking = '700-01';
+ const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, parking);
+
+ expect(result).toBe(false);
+ });
+
+ it('should return true because there is an older item', async() => {
+ const shelvingFkIn = 'UXN';
+ const shelvingFkOut = 'PCC';
+ const parking = 'A-01-1';
+ const itemFk = 1;
+
+ const tx = await models.ItemShelving.beginTransaction({});
+ const myOptions = {transaction: tx};
+ const filter = {where: {shelvingFk: shelvingFkOut}
+ };
+ try {
+ const itemShelvingBefore = await models.ItemShelving.findOne(filter, myOptions);
+ await itemShelvingBefore.updateAttributes({
+ itemFk: itemFk
+ }, myOptions);
+ const result = await models.ItemShelving.hasItemOlder(shelvingFkIn, parking, null, null, myOptions);
+
+ expect(result).toBe(true);
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+});
diff --git a/modules/item/back/methods/item-shelving/specs/updateFromSale.spec.js b/modules/item/back/methods/item-shelving/specs/updateFromSale.spec.js
new file mode 100644
index 000000000..dfa294000
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/specs/updateFromSale.spec.js
@@ -0,0 +1,22 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('itemShelving updateFromSale()', () => {
+ it('should update the quantity', async() => {
+ const tx = await models.ItemBarcode.beginTransaction({});
+ const options = {transaction: tx};
+ const saleFk = 2;
+ const filter = {where: {itemFk: 4, shelvingFk: 'HEJ'}
+ };
+ try {
+ const {visible: visibleBefore} = await models.ItemShelving.findOne(filter, options);
+ await models.ItemShelving.updateFromSale(saleFk, options);
+ const {visible: visibleAfter} = await models.ItemShelving.findOne(filter, options);
+
+ expect(visibleAfter).toEqual(visibleBefore + 5);
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+});
diff --git a/modules/item/back/methods/item-shelving/specs/upsertItem.spec.js b/modules/item/back/methods/item-shelving/specs/upsertItem.spec.js
new file mode 100644
index 000000000..8615b7b86
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/specs/upsertItem.spec.js
@@ -0,0 +1,57 @@
+const { models } = require('vn-loopback/server/server');
+const LoopBackContext = require('loopback-context');
+
+// #6276
+describe('ItemShelving upsertItem()', () => {
+ const warehouseFk = 1;
+ let ctx;
+ let options;
+ let tx;
+
+ beforeEach(async () => {
+ ctx = {
+ req: {
+ accessToken: { userId: 9 },
+ headers: { origin: 'http://localhost' }
+ },
+ args: {}
+ };
+
+ spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
+ active: ctx.req
+ });
+
+ options = { transaction: tx };
+ tx = await models.ItemShelving.beginTransaction({});
+ options.transaction = tx;
+ });
+
+ afterEach(async () => {
+ await tx.rollback();
+ });
+
+ it('should add two new records', async () => {
+ const shelvingFk = 'ZPP';
+ const items = [1, 1, 1, 2];
+
+ await models.ItemShelving.upsertItem(ctx, shelvingFk, items, warehouseFk, options);
+ const itemShelvings = await models.ItemShelving.find({ where: { shelvingFk } }, options);
+
+ expect(itemShelvings.length).toEqual(2);
+ });
+
+ it('should update the visible items', async () => {
+ const shelvingFk = 'GVC';
+ const items = [2, 2];
+ const { visible: visibleItemsBefore } = await models.ItemShelving.findOne({
+ where: { shelvingFk, itemFk: items[0] }
+ }, options);
+ await models.ItemShelving.upsertItem(ctx, shelvingFk, items, warehouseFk, options);
+
+ const { visible: visibleItemsAfter } = await models.ItemShelving.findOne({
+ where: { shelvingFk, itemFk: items[0] }
+ }, options);
+
+ expect(visibleItemsAfter).toEqual(visibleItemsBefore + 2);
+ });
+});
diff --git a/modules/item/back/methods/item-shelving/updateFromSale.js b/modules/item/back/methods/item-shelving/updateFromSale.js
new file mode 100644
index 000000000..2b9f49cae
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/updateFromSale.js
@@ -0,0 +1,48 @@
+module.exports = Self => {
+ Self.remoteMethod('updateFromSale', {
+ description: 'Update the visible items',
+ accessType: 'WRITE',
+ accepts: [{
+ arg: 'saleFk',
+ type: 'number',
+ required: true,
+ }],
+ http: {
+ path: `/updateFromSale`,
+ verb: 'POST'
+ }
+ });
+
+ Self.updateFromSale = async(saleFk, options) => {
+ const models = Self.app.models;
+ const myOptions = {};
+ let tx;
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ const itemShelvingSale = await models.ItemShelvingSale.findOne({
+ where: {saleFk},
+ include: {relation: 'itemShelving'}
+ }, myOptions);
+
+ const itemShelving = itemShelvingSale.itemShelving();
+ const quantity = itemShelving.visible + itemShelvingSale.quantity;
+
+ await itemShelving.updateAttributes(
+ {visible: quantity},
+ myOptions
+ );
+ if (tx) await tx.commit();
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/item/back/methods/item-shelving/upsertItem.js b/modules/item/back/methods/item-shelving/upsertItem.js
new file mode 100644
index 000000000..49c2f1b0d
--- /dev/null
+++ b/modules/item/back/methods/item-shelving/upsertItem.js
@@ -0,0 +1,64 @@
+module.exports = Self => {
+ Self.remoteMethodCtx('upsertItem', {
+ description: 'Add a record or update it if it already exists.',
+ accessType: 'WRITE',
+ accepts: [{
+ arg: 'shelvingFk',
+ type: 'string',
+ required: true,
+ },
+ {
+ arg: 'items',
+ type: ['number'],
+ required: true,
+ description: 'array of item foreign keys'
+ },
+ {
+ arg: 'warehouseFk',
+ type: 'number',
+ required: true
+ }],
+
+ http: {
+ path: `/upsertItem`,
+ verb: 'POST'
+ }
+ });
+
+ Self.upsertItem = async(ctx, shelvingFk, items, warehouseFk, options) => {
+ const myOptions = {userId: ctx.req.accessToken.userId};
+ let tx;
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ const discardItems = new Set();
+ const itemCounts = items.reduce((acc, item) => {
+ acc[item] = (acc[item] || 0) + 1;
+ return acc;
+ }, {});
+
+ try {
+ for (let item of items) {
+ if (!discardItems.has(item)) {
+ let quantity = itemCounts[item];
+ discardItems.add(item);
+
+ await Self.rawSql('CALL vn.itemShelving_add(?, ?, ?, NULL, NULL, NULL, ?)',
+ [shelvingFk, item, quantity, warehouseFk], myOptions
+ );
+ }
+ }
+
+ if (tx) await tx.commit();
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/item/back/methods/item/get.js b/modules/item/back/methods/item/get.js
new file mode 100644
index 000000000..38b37a90c
--- /dev/null
+++ b/modules/item/back/methods/item/get.js
@@ -0,0 +1,48 @@
+module.exports = Self => {
+ Self.remoteMethod('get', {
+ description: 'Get the data from an item',
+ accessType: 'READ',
+ http: {
+ path: `/get`,
+ verb: 'GET'
+ },
+ accepts: [
+ {
+ arg: 'barcode',
+ type: 'number',
+ required: true,
+ },
+ {
+ arg: 'warehouseFk',
+ type: 'number',
+ required: true,
+ }
+ ],
+ returns: {
+ type: ['object'],
+ root: true
+ },
+ });
+
+ Self.get = async(barcode, warehouseFk, options) => {
+ const myOptions = {};
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ const models = Self.app.models;
+
+ const [[itemInfo]] = await Self.rawSql('CALL vn.item_getInfo(?, ?)', [barcode, warehouseFk], myOptions);
+
+ if (itemInfo) {
+ itemInfo.barcodes = await models.ItemBarcode.find({
+ fields: ['code'],
+ where: {
+ itemFk: itemInfo.id
+ }
+ });
+ }
+
+ return itemInfo;
+ };
+};
diff --git a/modules/item/back/methods/item/specs/get.spec.js b/modules/item/back/methods/item/specs/get.spec.js
new file mode 100644
index 000000000..55262004a
--- /dev/null
+++ b/modules/item/back/methods/item/specs/get.spec.js
@@ -0,0 +1,12 @@
+const {models} = require('vn-loopback/server/server');
+
+describe('item get()', () => {
+ const barcode = 1;
+ const warehouseFk = 1;
+ it('should get an item with several barcodes', async() => {
+ const card = await models.Item.get(barcode, warehouseFk);
+
+ expect(card).toBeDefined();
+ expect(card.barcodes.length).toBeTruthy();
+ });
+});
diff --git a/modules/item/back/models/item-barcode.js b/modules/item/back/models/item-barcode.js
index b608a7fe9..616d973e1 100644
--- a/modules/item/back/models/item-barcode.js
+++ b/modules/item/back/models/item-barcode.js
@@ -1,5 +1,6 @@
module.exports = Self => {
require('../methods/item-barcode/toItem')(Self);
+ require('../methods/item-barcode/delete')(Self);
Self.validatesUniquenessOf('code', {
message: `Barcode must be unique`
diff --git a/modules/item/back/models/item-shelving.js b/modules/item/back/models/item-shelving.js
index 98ff18931..d48ee10d5 100644
--- a/modules/item/back/models/item-shelving.js
+++ b/modules/item/back/models/item-shelving.js
@@ -1,4 +1,8 @@
module.exports = Self => {
require('../methods/item-shelving/deleteItemShelvings')(Self);
+ require('../methods/item-shelving/upsertItem')(Self);
require('../methods/item-shelving/getInventory')(Self);
+ require('../methods/item-shelving/getAlternative')(Self);
+ require('../methods/item-shelving/updateFromSale')(Self);
+ require('../methods/item-shelving/hasItemOlder')(Self);
};
diff --git a/modules/item/back/models/item-shelving.json b/modules/item/back/models/item-shelving.json
index f3be98fc4..893a1f81d 100644
--- a/modules/item/back/models/item-shelving.json
+++ b/modules/item/back/models/item-shelving.json
@@ -54,7 +54,8 @@
"shelving": {
"type": "belongsTo",
"model": "Shelving",
- "foreignKey": "shelvingFk"
- }
+ "foreignKey": "shelvingFk",
+ "primaryKey": "code"
+ }
}
}
diff --git a/modules/item/back/models/item.js b/modules/item/back/models/item.js
index eac1ecb7d..e715ab431 100644
--- a/modules/item/back/models/item.js
+++ b/modules/item/back/models/item.js
@@ -17,6 +17,7 @@ module.exports = Self => {
require('../methods/item/buyerWasteEmail')(Self);
require('../methods/item/labelPdf')(Self);
require('../methods/item/setVisibleDiscard')(Self);
+ require('../methods/item/get')(Self);
Self.validatesPresenceOf('originFk', {message: 'Cannot be blank'});
diff --git a/modules/item/front/index.js b/modules/item/front/index.js
index d2ffcc8fb..354477d4d 100644
--- a/modules/item/front/index.js
+++ b/modules/item/front/index.js
@@ -20,7 +20,6 @@ import './botanical';
import './barcode';
import './summary';
import './waste/index/';
-import './waste/detail';
import './fixed-price';
import './fixed-price-search-panel';
import './item-type';
diff --git a/modules/item/front/last-entries/index.html b/modules/item/front/last-entries/index.html
index 429955ef5..e3b84655c 100644
--- a/modules/item/front/last-entries/index.html
+++ b/modules/item/front/last-entries/index.html
@@ -71,12 +71,12 @@
{{entry.stickers | dashIfEmpty}}
-
+
{{::entry.packing | dashIfEmpty}}
-
+
{{::entry.grouping | dashIfEmpty}}
diff --git a/modules/item/front/last-entries/index.js b/modules/item/front/last-entries/index.js
index 31616f8a7..a5f1f4d9d 100644
--- a/modules/item/front/last-entries/index.js
+++ b/modules/item/front/last-entries/index.js
@@ -16,7 +16,7 @@ class Controller extends Section {
this.filter = {
where: {
itemFk: this.$params.id,
- shipped: {
+ landed: {
between: [from, to]
}
}
@@ -36,7 +36,7 @@ class Controller extends Section {
const to = new Date(this._dateTo);
to.setHours(23, 59, 59, 59);
- this.filter.where.shipped = {
+ this.filter.where.landed = {
between: [from, to]
};
this.$.model.refresh();
@@ -53,7 +53,7 @@ class Controller extends Section {
const to = new Date(value);
to.setHours(23, 59, 59, 59);
- this.filter.where.shipped = {
+ this.filter.where.landed = {
between: [from, to]
};
this.$.model.refresh();
diff --git a/modules/item/front/waste/detail/index.html b/modules/item/front/waste/detail/index.html
deleted file mode 100644
index 1b44088bf..000000000
--- a/modules/item/front/waste/detail/index.html
+++ /dev/null
@@ -1,43 +0,0 @@
-
-
-
-
-
-
-
-
-
- Item
- Percentage
- Dwindle
- Total
-
-
-
-
-
-
- {{::waste.itemFk}}
-
-
- {{::(waste.percentage / 100) | percentage: 2}}
- {{::waste.dwindle | currency: 'EUR'}}
- {{::waste.total | currency: 'EUR'}}
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/modules/item/front/waste/detail/index.js b/modules/item/front/waste/detail/index.js
deleted file mode 100644
index 2949a493b..000000000
--- a/modules/item/front/waste/detail/index.js
+++ /dev/null
@@ -1,7 +0,0 @@
-import ngModule from '../../module';
-import Section from 'salix/components/section';
-
-ngModule.vnComponent('vnItemWasteDetail', {
- template: require('./index.html'),
- controller: Section
-});
diff --git a/modules/item/front/waste/detail/style.scss b/modules/item/front/waste/detail/style.scss
deleted file mode 100644
index 55a6eb2ef..000000000
--- a/modules/item/front/waste/detail/style.scss
+++ /dev/null
@@ -1,25 +0,0 @@
-@import "variables";
-
-vn-item-waste {
- .header {
- margin-bottom: 16px;
- text-transform: uppercase;
- font-size: 1.25rem;
- line-height: 1;
- padding: 7px;
- padding-bottom: 7px;
- padding-bottom: 4px;
- font-weight: lighter;
- background-color: #fde6ca;
- border-bottom: 1px solid #f7931e;
- white-space: nowrap;
- overflow: hidden;
- text-overflow: ellipsis;
- }
-
- vn-table vn-th.waste-family,
- vn-table vn-td.waste-family {
- max-width: 64px;
- width: 64px
- }
-}
\ No newline at end of file
diff --git a/modules/item/front/waste/index/index.html b/modules/item/front/waste/index/index.html
index f1475c1b3..7fb3b870e 100644
--- a/modules/item/front/waste/index/index.html
+++ b/modules/item/front/waste/index/index.html
@@ -1,49 +1,2 @@
-
-
-
-
-
-
-
-
-
-
-
-
- {{::waste.family}}
- {{::(waste.percentage / 100) | percentage: 2}}
- {{::waste.dwindle | currency: 'EUR'}}
- {{::waste.total | currency: 'EUR'}}
-
-
-
-
-
-
+
+
diff --git a/modules/item/front/waste/index/index.js b/modules/item/front/waste/index/index.js
index b11f54b08..86d9d3778 100644
--- a/modules/item/front/waste/index/index.js
+++ b/modules/item/front/waste/index/index.js
@@ -5,27 +5,11 @@ import './style.scss';
export default class Controller extends Section {
constructor($element, $) {
super($element, $);
-
- this.getWasteConfig();
}
- getWasteConfig() {
- return this.wasteConfig = JSON.parse(localStorage.getItem('wasteConfig')) || {};
- }
-
- setWasteConfig() {
- localStorage.setItem('wasteConfig', JSON.stringify(this.wasteConfig));
- }
-
- toggleHidePanel(detail) {
- if (!this.wasteConfig[detail.buyer]) {
- this.wasteConfig[detail.buyer] = {
- hidden: true
- };
- } else
- this.wasteConfig[detail.buyer].hidden = !this.wasteConfig[detail.buyer].hidden;
-
- this.setWasteConfig();
+ async $onInit() {
+ this.$state.go('item.index');
+ window.location.href = 'https://grafana.verdnatura.es/d/TTNXQAxVk';
}
}
diff --git a/modules/item/front/waste/index/index.spec.js b/modules/item/front/waste/index/index.spec.js
deleted file mode 100644
index fd7332f68..000000000
--- a/modules/item/front/waste/index/index.spec.js
+++ /dev/null
@@ -1,53 +0,0 @@
-import './index.js';
-import crudModel from 'core/mocks/crud-model';
-
-describe('Item', () => {
- describe('Component vnItemWasteIndex', () => {
- let $scope;
- let controller;
-
- beforeEach(ngModule('item'));
-
- beforeEach(inject(($componentController, $rootScope) => {
- $scope = $rootScope.$new();
- $scope.model = crudModel;
- const $element = angular.element('');
- controller = $componentController('vnItemWasteIndex', {$element, $scope});
- }));
-
- describe('getWasteConfig / setWasteConfig', () => {
- it('should return the local storage wasteConfig', () => {
- const result = controller.getWasteConfig();
-
- expect(result).toEqual({});
- });
-
- it('should set and return the local storage wasteConfig', () => {
- controller.wasteConfig = {salesPerson: {hidden: true}};
- controller.setWasteConfig();
-
- const result = controller.getWasteConfig();
-
- expect(result).toEqual(controller.wasteConfig);
- });
- });
-
- describe('toggleHidePanel()', () => {
- it('should make details hidden by default', () => {
- controller.wasteConfig = {};
-
- controller.toggleHidePanel({buyer: 'salesPerson'});
-
- expect(controller.wasteConfig.salesPerson.hidden).toEqual(true);
- });
-
- it('should toggle hidden false', () => {
- controller.wasteConfig = {salesPerson: {hidden: true}};
-
- controller.toggleHidePanel({buyer: 'salesPerson'});
-
- expect(controller.wasteConfig.salesPerson.hidden).toEqual(false);
- });
- });
- });
-});
diff --git a/modules/order/back/methods/order/catalogFilter.js b/modules/order/back/methods/order/catalogFilter.js
index dc0e2f60b..ab1d7784e 100644
--- a/modules/order/back/methods/order/catalogFilter.js
+++ b/modules/order/back/methods/order/catalogFilter.js
@@ -92,10 +92,11 @@ module.exports = Self => {
// Calculate items
const order = await Self.findById(orderFk, null, myOptions);
stmts.push(new ParameterizedSQL(
- 'CALL vn.catalog_calculate(?, ?, ?)', [
+ 'CALL vn.catalog_calculate(?, ?, ?, ?)', [
order.landed,
order.address_id,
order.agency_id,
+ false
]
));
diff --git a/modules/order/back/methods/order/getItemTypeAvailable.js b/modules/order/back/methods/order/getItemTypeAvailable.js
index b62adebb5..b84863953 100644
--- a/modules/order/back/methods/order/getItemTypeAvailable.js
+++ b/modules/order/back/methods/order/getItemTypeAvailable.js
@@ -64,10 +64,11 @@ module.exports = Self => {
stmts.push(stmt);
stmt = new ParameterizedSQL(
- 'CALL vn.catalog_calculate(?, ?, ?)', [
+ 'CALL vn.catalog_calculate(?, ?, ?,?)', [
order.landed,
order.addressFk,
order.agencyModeFk,
+ false
]
);
stmts.push(stmt);
diff --git a/modules/order/back/methods/order/isEditable.js b/modules/order/back/methods/order/isEditable.js
index 4ef76c11f..3fd2f993c 100644
--- a/modules/order/back/methods/order/isEditable.js
+++ b/modules/order/back/methods/order/isEditable.js
@@ -29,17 +29,11 @@ module.exports = Self => {
where: {id: orderId},
fields: ['isConfirmed', 'clientFk'],
include: [
- {relation: 'client',
- scope: {
- include: {
- relation: 'type'
- }
- }
- }
+ {relation: 'client'}
]
}, myOptions);
- if (exists && exists.client().type().code !== 'normal')
+ if (exists && exists.client().typeFk !== 'normal')
return true;
if (!exists || exists.isConfirmed === 1)
diff --git a/modules/order/back/methods/order/new.js b/modules/order/back/methods/order/new.js
index d65b18e12..2aad27c9a 100644
--- a/modules/order/back/methods/order/new.js
+++ b/modules/order/back/methods/order/new.js
@@ -49,17 +49,12 @@ module.exports = Self => {
where: {id: addressId},
fields: ['clientFk'],
include: [
- {relation: 'client',
- scope: {
- include: {
- relation: 'type'
- }
- }
+ {relation: 'client'
}
]
}, myOptions);
- if (address.client().type().code === 'normal') {
+ if (address.client().typeFk === 'normal') {
if (!address.client().isActive)
throw new UserError(`You can't create an order for an inactive client`);
}
diff --git a/modules/parking/back/model-config.json b/modules/parking/back/model-config.json
new file mode 100644
index 000000000..5c0d3d916
--- /dev/null
+++ b/modules/parking/back/model-config.json
@@ -0,0 +1,5 @@
+{
+ "ParkingLog": {
+ "dataSource": "vn"
+ }
+}
diff --git a/modules/parking/back/models/parking-log.json b/modules/parking/back/models/parking-log.json
new file mode 100644
index 000000000..1bbb031d8
--- /dev/null
+++ b/modules/parking/back/models/parking-log.json
@@ -0,0 +1,9 @@
+{
+ "name": "ParkingLog",
+ "base": "Log",
+ "options": {
+ "mysql": {
+ "table": "parkingLog"
+ }
+ }
+}
diff --git a/modules/route/back/methods/route/cmr.js b/modules/route/back/methods/route/cmr.js
index cd7ef57ce..5033dee2f 100644
--- a/modules/route/back/methods/route/cmr.js
+++ b/modules/route/back/methods/route/cmr.js
@@ -29,7 +29,8 @@ module.exports = Self => {
http: {
path: '/:id/cmr',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.cmr = (ctx, id) => Self.printReport(ctx, id, 'cmr');
diff --git a/modules/route/back/methods/route/cmrEmail.js b/modules/route/back/methods/route/cmrEmail.js
new file mode 100644
index 000000000..0c4cc5061
--- /dev/null
+++ b/modules/route/back/methods/route/cmrEmail.js
@@ -0,0 +1,84 @@
+const {Email} = require('vn-print');
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('cmrEmail', {
+ description: 'Sends the email with an cmr attached PDF',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'tickets',
+ type: ['number'],
+ required: true,
+ description: 'The ticket id',
+ }
+ ],
+ http: {
+ path: '/cmrEmail',
+ verb: 'POST'
+ }
+ });
+
+ Self.cmrEmail = async function(ctx, tickets, options) {
+ const models = Self.app.models;
+ const myOptions = {};
+ let tx;
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ for (const ticketId of tickets) {
+ const ticket = await models.Ticket.findOne({
+ where: {
+ id: ticketId
+ },
+ include: [{
+ relation: 'client',
+ fields: ['email']
+ }]
+ }, myOptions);
+
+ const recipient = ticket.client().email;
+ if (!recipient)
+ throw new UserError('There is no assigned email for this client');
+
+ const dms = await Self.rawSql(`
+ SELECT d.id
+ FROM ticketDms td
+ JOIN dms d ON d.id = td.dmsFk
+ JOIN dmsType dt ON dt.id = d.dmsTypeFk
+ WHERE td.ticketFk = ?
+ AND dt.code = 'cmr'
+ `, [ticketId]);
+
+ if (!dms.length) throw new UserError('Cmr file does not exist');
+
+ const response = await models.Dms.downloadFile(ctx, dms[0].id);
+
+ const email = new Email('cmr', {
+ ticketId,
+ lang: ctx.req.getLocale(),
+ recipient
+ });
+
+ await email.send({
+ overrideAttachments: true,
+ attachments: [{
+ filename: `${ticket.cmrFk}.pdf`,
+ content: response[0]
+ }]
+ });
+ }
+ if (tx) await tx.commit();
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ };
+};
diff --git a/modules/route/back/methods/route/downloadCmrsZip.js b/modules/route/back/methods/route/downloadCmrsZip.js
index 532e019b6..c6934edca 100644
--- a/modules/route/back/methods/route/downloadCmrsZip.js
+++ b/modules/route/back/methods/route/downloadCmrsZip.js
@@ -1,6 +1,4 @@
const JSZip = require('jszip');
-const axios = require('axios');
-const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('downloadCmrsZip', {
@@ -31,41 +29,27 @@ module.exports = Self => {
http: {
path: '/downloadCmrsZip',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.downloadCmrsZip = async function(ctx, ids, options) {
const models = Self.app.models;
const myOptions = {};
- const token = ctx.req.accessToken;
const zip = new JSZip();
if (typeof options == 'object')
Object.assign(myOptions, options);
- const zipConfig = await models.ZipConfig.findOne(null, myOptions);
- let totalSize = 0;
ids = ids.split(',');
- try {
- for (let id of ids) {
- if (zipConfig && totalSize > zipConfig.maxSize) throw new UserError('Files are too large');
- const response = await axios.get(
- `${ctx.req.headers.referer}api/Routes/${id}/cmr?access_token=${token.id}`, {
- ...myOptions,
- responseType: 'arraybuffer',
- });
-
- if (response.headers['content-type'] !== 'application/pdf')
- throw new UserError(`The response is not a PDF`);
-
- zip.file(`${id}.pdf`, response.data, { binary: true });
- }
-
- const zipStream = zip.generateNodeStream({ streamFiles: true });
-
- return [zipStream, 'application/zip', `filename="cmrs.zip"`];
- } catch (e) {
- throw e;
+
+ for (const id of ids) {
+ ctx.args = ctx.args || {};
+ ctx.args.id = Number(id);
+ const [data] = await models.Route.cmr(ctx, myOptions);
+ zip.file(`${id}.pdf`, data, {binary: true});
}
+ const zipStream = zip.generateNodeStream({streamFiles: true});
+ return [zipStream, 'application/zip', `filename="cmrs.zip"`];
};
};
diff --git a/modules/route/back/methods/route/downloadZip.js b/modules/route/back/methods/route/downloadZip.js
index 597f1d1f6..8eecf62e4 100644
--- a/modules/route/back/methods/route/downloadZip.js
+++ b/modules/route/back/methods/route/downloadZip.js
@@ -29,7 +29,8 @@ module.exports = Self => {
http: {
path: '/downloadZip',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
});
Self.downloadZip = async function(ctx, id, options) {
diff --git a/modules/route/back/methods/route/driverRouteEmail.js b/modules/route/back/methods/route/driverRouteEmail.js
index 82b005e44..bbac2b0e8 100644
--- a/modules/route/back/methods/route/driverRouteEmail.js
+++ b/modules/route/back/methods/route/driverRouteEmail.js
@@ -1,3 +1,4 @@
+const UserError = require('vn-loopback/util/user-error');
module.exports = Self => {
Self.remoteMethodCtx('driverRouteEmail', {
description: 'Sends the driver route email with an attached PDF',
@@ -9,24 +10,14 @@ module.exports = Self => {
required: true,
description: 'The client id',
http: {source: 'path'}
- },
- {
- arg: 'recipient',
- type: 'string',
- description: 'The recipient email',
- required: true,
- },
- {
+ }, {
arg: 'replyTo',
type: 'string',
description: 'The sender email to reply to',
- required: false
- },
- {
+ }, {
arg: 'recipientId',
type: 'number',
description: 'The recipient id to send to the recipient preferred language',
- required: false
}
],
returns: {
@@ -39,5 +30,28 @@ module.exports = Self => {
}
});
- Self.driverRouteEmail = ctx => Self.sendTemplate(ctx, 'driver-route');
+ Self.driverRouteEmail = async(ctx, id) => {
+ const models = Self.app.models;
+ const {workerFk, agencyMode} = await Self.findById(id, {
+ fields: ['workerFk', 'agencyModeFk'],
+ include: {relation: 'agencyMode'}
+ });
+ const {reportMail} = agencyMode();
+ let user;
+ let account;
+
+ if (workerFk) {
+ user = await models.VnUser.findById(workerFk, {
+ fields: ['active', 'id'],
+ include: {relation: 'emailUser'}
+ });
+ account = await models.Account.findById(workerFk);
+ }
+
+ if (user?.active && account) ctx.args.recipient = user.emailUser().email;
+ else ctx.args.recipient = reportMail;
+
+ if (!ctx.args.recipient) throw new UserError('An email is necessary');
+ return Self.sendTemplate(ctx, 'driver-route');
+ };
};
diff --git a/modules/route/back/methods/route/driverRoutePdf.js b/modules/route/back/methods/route/driverRoutePdf.js
index f0cd75f0e..69b26d846 100644
--- a/modules/route/back/methods/route/driverRoutePdf.js
+++ b/modules/route/back/methods/route/driverRoutePdf.js
@@ -34,7 +34,9 @@ module.exports = Self => {
http: {
path: '/:id/driver-route-pdf',
verb: 'GET'
- }
+ },
+ accessScopes: ['DEFAULT', 'read:multimedia']
+
});
Self.driverRoutePdf = (ctx, id) => Self.printReport(ctx, id, 'driver-route');
diff --git a/modules/route/back/methods/route/filter.js b/modules/route/back/methods/route/filter.js
index 7c2225dc9..5b13a9004 100644
--- a/modules/route/back/methods/route/filter.js
+++ b/modules/route/back/methods/route/filter.js
@@ -67,6 +67,12 @@ module.exports = Self => {
type: 'string',
description: 'The description filter',
http: {source: 'query'}
+ },
+ {
+ arg: 'isOk',
+ type: 'boolean',
+ description: 'The isOk filter',
+ http: {source: 'query'}
}
],
returns: {
@@ -102,6 +108,8 @@ module.exports = Self => {
case 'agencyModeFk':
param = `r.${param}`;
return {[param]: value};
+ case 'isOk':
+ return {'r.isOk': value};
}
});
diff --git a/modules/route/back/methods/route/getExpeditionSummary.js b/modules/route/back/methods/route/getExpeditionSummary.js
index ee89401a8..2bd2ca43a 100644
--- a/modules/route/back/methods/route/getExpeditionSummary.js
+++ b/modules/route/back/methods/route/getExpeditionSummary.js
@@ -49,8 +49,7 @@ module.exports = Self => {
JOIN vn.agencyMode am ON am.id = r.agencyModeFk
JOIN vn.agency ag ON ag.id = am.agencyFk
LEFT JOIN vn.userConfig uc ON uc.userFk = account.myUser_getId()
- WHERE (r.created = util.VN_CURDATE() OR r.created = util.yesterday())
- AND t.routeFk = ?
+ WHERE t.routeFk = ?
GROUP BY t.addressFk, e.itemPackingTypeFk
) sub
GROUP BY addressFk
diff --git a/modules/route/back/methods/route/getExternalCmrs.js b/modules/route/back/methods/route/getExternalCmrs.js
index 3fc9798b0..b8cd1041a 100644
--- a/modules/route/back/methods/route/getExternalCmrs.js
+++ b/modules/route/back/methods/route/getExternalCmrs.js
@@ -98,40 +98,38 @@ module.exports = Self => {
let stmts = [];
const stmt = new ParameterizedSQL(`
- SELECT *
- FROM (
- SELECT t.cmrFk,
- t.id ticketFk,
- t.routeFk,
- co.country,
- t.clientFk,
- IF(sub.id, TRUE, FALSE) hasCmrDms,
- DATE(t.shipped) shipped
- FROM ticket t
- JOIN ticketState ts ON ts.ticketFk = t.id
- JOIN state s ON s.id = ts.stateFk
- JOIN alertLevel al ON al.id = s.alertLevel
- JOIN client c ON c.id = t.clientFk
- JOIN address a ON a.id = t.addressFk
- JOIN province p ON p.id = a.provinceFk
- JOIN country co ON co.id = p.countryFk
- JOIN agencyMode am ON am.id = t.agencyModeFk
- JOIN deliveryMethod dm ON dm.id = am.deliveryMethodFk
- JOIN warehouse w ON w.id = t.warehouseFk
- LEFT JOIN (
- SELECT td.ticketFk, d.id
- FROM ticketDms td
- JOIN dms d ON d.id = td.dmsFk
- JOIN dmsType dt ON dt.id = d.dmsTypeFk
- WHERE dt.name = 'cmr'
- ) sub ON sub.ticketFk = t.id
- WHERE co.code <> 'ES'
- AND am.name <> 'ABONO'
- AND w.code = 'ALG'
- AND dm.code = 'DELIVERY'
- AND t.cmrFk
- ) sub
- `);
+ SELECT *
+ FROM (
+ SELECT t.cmrFk,
+ t.id ticketFk,
+ t.routeFk,
+ co.country,
+ t.clientFk,
+ IF(sub.id, TRUE, FALSE) hasCmrDms,
+ DATE(t.shipped) shipped
+ FROM ticket t
+ JOIN ticketState ts ON ts.ticketFk = t.id
+ JOIN state s ON s.id = ts.stateFk
+ JOIN alertLevel al ON al.id = s.alertLevel
+ JOIN client c ON c.id = t.clientFk
+ JOIN address a ON a.id = t.addressFk
+ JOIN province p ON p.id = a.provinceFk
+ JOIN country co ON co.id = p.countryFk
+ JOIN agencyMode am ON am.id = t.agencyModeFk
+ JOIN warehouse w ON w.id = t.warehouseFk
+ LEFT JOIN (
+ SELECT td.ticketFk, d.id
+ FROM ticketDms td
+ JOIN dms d ON d.id = td.dmsFk
+ JOIN dmsType dt ON dt.id = d.dmsTypeFk
+ WHERE dt.name = 'cmr'
+ ) sub ON sub.ticketFk = t.id
+ WHERE co.code <> 'ES'
+ AND am.name <> 'ABONO'
+ AND w.code = 'ALG'
+ AND t.cmrFk
+ ) sub
+ `);
stmt.merge(conn.makeSuffix(filter));
const itemsIndex = stmts.push(stmt) - 1;
diff --git a/modules/route/back/methods/route/getTickets.js b/modules/route/back/methods/route/getTickets.js
index d1ebf9ee7..0e7c9fe20 100644
--- a/modules/route/back/methods/route/getTickets.js
+++ b/modules/route/back/methods/route/getTickets.js
@@ -1,5 +1,5 @@
-const ParameterizedSQL = require('loopback-connector').ParameterizedSQL;
+const {ParameterizedSQL} = require('loopback-connector');
module.exports = Self => {
Self.remoteMethod('getTickets', {
@@ -33,63 +33,69 @@ module.exports = Self => {
const stmt = new ParameterizedSQL(
`SELECT
- t.id,
- t.packages,
- t.warehouseFk,
- t.nickname,
- t.clientFk,
- t.priority,
- t.addressFk,
- st.code ticketStateCode,
- st.name ticketStateName,
- wh.name warehouseName,
- tob.description ticketObservation,
- a.street,
- a.postalCode,
- a.city,
- am.name agencyModeName,
- u.nickname userNickname,
- vn.ticketTotalVolume(t.id) volume,
- tob.description,
- GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt,
- c.phone clientPhone,
- c.mobile clientMobile,
- a.phone addressPhone,
- a.mobile addressMobile,
- a.longitude,
- a.latitude,
- wm.mediaValue salePersonPhone,
- t.cmrFk,
- t.isSigned signed
- FROM vn.route r
- JOIN ticket t ON t.routeFk = r.id
- JOIN client c ON t.clientFk = c.id
- LEFT JOIN vn.sale s ON s.ticketFk = t.id
- LEFT JOIN vn.item i ON i.id = s.itemFk
- LEFT JOIN ticketState ts ON ts.ticketFk = t.id
- LEFT JOIN state st ON st.id = ts.stateFk
- LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
- LEFT JOIN observationType ot ON ot.code = 'delivery'
- LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
- AND tob.observationTypeFk = ot.id
- LEFT JOIN address a ON a.id = t.addressFk
- LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
- LEFT JOIN account.user u ON u.id = r.workerFk
- LEFT JOIN vehicle v ON v.id = r.vehicleFk
- LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk`
+ t.id,
+ t.packages,
+ t.warehouseFk,
+ t.nickname,
+ t.clientFk,
+ t.priority,
+ t.addressFk,
+ st.code ticketStateCode,
+ st.name ticketStateName,
+ wh.name warehouseName,
+ tob.description observationDelivery,
+ tob2.description observationDropOff,
+ tob2.id observationId,
+ a.street,
+ a.postalCode,
+ a.city,
+ am.name agencyModeName,
+ u.nickname userNickname,
+ vn.ticketTotalVolume(t.id) volume,
+ GROUP_CONCAT(DISTINCT i.itemPackingTypeFk ORDER BY i.itemPackingTypeFk) ipt,
+ c.phone clientPhone,
+ c.mobile clientMobile,
+ a.phone addressPhone,
+ a.mobile addressMobile,
+ a.longitude,
+ a.latitude,
+ wm.mediaValue salePersonPhone,
+ t.cmrFk,
+ t.isSigned signed
+ FROM vn.route r
+ JOIN ticket t ON t.routeFk = r.id
+ JOIN client c ON t.clientFk = c.id
+ LEFT JOIN vn.sale s ON s.ticketFk = t.id
+ LEFT JOIN vn.item i ON i.id = s.itemFk
+ LEFT JOIN ticketState ts ON ts.ticketFk = t.id
+ LEFT JOIN state st ON st.id = ts.stateFk
+ LEFT JOIN warehouse wh ON wh.id = t.warehouseFk
+ LEFT JOIN observationType ot ON ot.code = 'delivery'
+ LEFT JOIN ticketObservation tob ON tob.ticketFk = t.id
+ AND tob.observationTypeFk = ot.id
+ LEFT JOIN observationType ot2 ON ot2.code = 'dropOff'
+ LEFT JOIN ticketObservation tob2 ON tob2.ticketFk = t.id
+ AND tob2.observationTypeFk = ot2.id
+ LEFT JOIN address a ON a.id = t.addressFk
+ LEFT JOIN agencyMode am ON am.id = t.agencyModeFk
+ LEFT JOIN account.user u ON u.id = r.workerFk
+ LEFT JOIN vehicle v ON v.id = r.vehicleFk
+ LEFT JOIN workerMedia wm ON wm.workerFk = c.salesPersonFk`
);
if (!filter.where) filter.where = {};
const where = filter.where;
where['r.id'] = filter.id;
+ where.and = [{or: [
+ {'t.packages': {gt: 0}},
+ {and: [{'ot.code': 'delivery'}, {'tob.observationTypeFk': {neq: null}}]}
+ ]}];
stmt.merge(conn.makeWhere(filter.where));
stmt.merge(conn.makeGroupBy('t.id'));
stmt.merge(conn.makeOrderBy(filter.order));
- const tickets = await conn.executeStmt(stmt, myOptions);
-
- return tickets;
+ return conn.executeStmt(stmt, myOptions);
};
};
diff --git a/modules/route/back/methods/route/specs/downloadCmrsZip.spec.js b/modules/route/back/methods/route/specs/downloadCmrsZip.spec.js
new file mode 100644
index 000000000..7312a5d44
--- /dev/null
+++ b/modules/route/back/methods/route/specs/downloadCmrsZip.spec.js
@@ -0,0 +1,25 @@
+const models = require('vn-loopback/server/server').models;
+
+describe('route downloadCmrsZip()', () => {
+ it('should create a zip file with the given cmr ids', async() => {
+ const tx = await models.Route.beginTransaction({});
+ const ctx = {
+ req: {
+ getLocale: () => {
+ return 'en';
+ },
+ accessToken: {userId: 9}
+ }
+ };
+ let cmrs = '1,2';
+ try {
+ const stream = await models.Route.downloadCmrsZip(ctx, cmrs);
+
+ expect(stream[0]).toBeDefined();
+ await tx.rollback();
+ } catch (e) {
+ await tx.rollback();
+ throw e;
+ }
+ });
+});
diff --git a/modules/route/back/models/cmr.json b/modules/route/back/models/cmr.json
new file mode 100644
index 000000000..0e2168bed
--- /dev/null
+++ b/modules/route/back/models/cmr.json
@@ -0,0 +1,58 @@
+{
+ "name": "Cmr",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "cmr"
+ }
+ },
+ "properties": {
+ "id": {
+ "type": "number",
+ "id": true,
+ "description": "Identifier"
+ },
+ "truckPlate": {
+ "type": "number"
+ },
+ "observations": {
+ "type": "string"
+ },
+ "senderInstrucctions": {
+ "type": "string"
+ },
+ "paymentInstruccions": {
+ "type": "string"
+ },
+ "specialAgreements": {
+ "type": "string"
+ },
+ "created": {
+ "type": "date"
+ },
+ "companyFk": {
+ "type": "number"
+ },
+ "addressToFk": {
+ "type": "number"
+ },
+ "addressFromFk": {
+ "type": "number"
+ },
+ "supplierFk": {
+ "type": "number"
+ },
+ "packagesList": {
+ "type": "string"
+ },
+ "merchandiseDetail": {
+ "type": "string"
+ },
+ "landed": {
+ "type": "date"
+ },
+ "ead": {
+ "type": "date"
+ }
+ }
+}
diff --git a/modules/route/back/models/route.js b/modules/route/back/models/route.js
index 9b5f3564f..bf1f26e74 100644
--- a/modules/route/back/models/route.js
+++ b/modules/route/back/models/route.js
@@ -17,21 +17,24 @@ module.exports = Self => {
require('../methods/route/cmr')(Self);
require('../methods/route/getExternalCmrs')(Self);
require('../methods/route/downloadCmrsZip')(Self);
+ require('../methods/route/cmrEmail')(Self);
require('../methods/route/getExpeditionSummary')(Self);
require('../methods/route/getByWorker')(Self);
Self.validate('kmStart', validateDistance, {
- message: 'Distance must be lesser than 1000'
+ message: 'Distance must be lesser than 4000'
});
Self.validate('kmEnd', validateDistance, {
- message: 'Distance must be lesser than 1000'
+ message: 'Distance must be lesser than 4000'
});
function validateDistance(err) {
- const routeTotalKm = this.kmEnd - this.kmStart;
- const routeMaxKm = 1000;
- if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
- err();
+ if (this.kmEnd) {
+ const routeTotalKm = this.kmEnd - this.kmStart;
+ const routeMaxKm = 4000;
+ if (routeTotalKm > routeMaxKm || this.kmStart > this.kmEnd)
+ err();
+ }
}
};
diff --git a/modules/route/front/create/index.html b/modules/route/front/create/index.html
index b76878f2e..de341220e 100644
--- a/modules/route/front/create/index.html
+++ b/modules/route/front/create/index.html
@@ -22,7 +22,6 @@
label="Vehicle"
ng-model="$ctrl.route.vehicleFk"
url="Vehicles"
- where="{warehouseFk: $ctrl.vnConfig.warehouseFk}"
show-field="numberPlate">