#6321 - Negative tickets #1945

Open
jsegarra wants to merge 93 commits from 6321_negative_tickets into dev
28 changed files with 972 additions and 18 deletions

View File

@ -111,7 +111,7 @@ async function test() {
const JunitReporter = require('jasmine-reporters'); const JunitReporter = require('jasmine-reporters');
runner.addReporter(new JunitReporter.JUnitXmlReporter()); runner.addReporter(new JunitReporter.JUnitXmlReporter());
} }
if (opts.ci) if (opts.ci || opts.debug)
Review

Quitar supongo

Quitar supongo
runner.jasmine.DEFAULT_TIMEOUT_INTERVAL = SPEC_TIMEOUT; runner.jasmine.DEFAULT_TIMEOUT_INTERVAL = SPEC_TIMEOUT;
runner.loadConfig(config); runner.loadConfig(config);

View File

@ -3973,3 +3973,12 @@ INSERT INTO vn.accountDetailType (id, description, code)
INSERT IGNORE INTO ormConfig INSERT IGNORE INTO ormConfig
SET id =1, SET id =1,
selectLimit = 1000; selectLimit = 1000;
INSERT INTO `vn`.`item` (id,name,`size`,stems,minPrice,isToPrint,family,box,originFk,doPhoto,image,inkFk,intrastatFk,hasMinPrice,created,typeFk,generic,density,relevancy,expenseFk,isActive,longName,subName,tag5,value5,tag6,value6,tag7,value7,minimum,upToDown,hasKgPrice,isFloramondo,isFragile,stemMultiplier,isLaid,lastUsed,editorFk,isBoxPickingMode)
VALUES
(88,'Lack negative',200,1,10.0,0,'VT',0,2,0,'','WHT',6021010,1,'2024-07-19 11:27:32.000',1,0,167,0,'4751000000',1,'Lack negative origin','Stark Industries','Color','White','Categoria','supply','Tallos','1',3,0,0,0,0,1.0,0,'2024-07-19 11:27:32.000',100,0);
INSERT INTO `vn`.`ticket` (id, clientFk,warehouseFk,shipped,nickname,refFk,addressFk,workerFk,observations,isSigned,isLabeled,isPrinted,packages,location,`hour`,created,isBlocked,solution,routeFk,priority,hasPriority,companyFk,agencyModeFk,landed,isBoxed,isDeleted,zoneFk,zonePrice,zoneBonus,totalWithVat,totalWithoutVat,weight,clonedFrom,cmrFk,editorFk,problem,risk) VALUES
(1000000, 1,1,'2001-01-01 00:00:00.000','employee',NULL,131,NULL,NULL,0,0,0,0,NULL,0,'2024-07-19 23:32:48.000',1,NULL,NULL,NULL,1,442,1,'2001-01-01',0,0,1,1.00,0.00,0.00,NULL,NULL,NULL,NULL,9,'',NULL);
INSERT INTO `vn`.`sale` (id, itemFk,ticketFk,concept,quantity,originalQuantity,price,discount,priceFixed,reserved,isPicked,isPriceFixed,created,isAdded,total,editorFk,problem) VALUES
(43, 88,1000000,'Chest medical box 2',15.00,155.0,10.00,0,0.00,0,0,0,'2024-07-19 23:33:08.000',0,1550.00,100,'');

View File

@ -1,5 +1,16 @@
DELIMITER $$ DELIMITER $$
CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`item_getLack`(IN vForce BOOLEAN, IN vDays INT) CREATE OR REPLACE DEFINER=`vn`@`localhost` PROCEDURE `vn`.`item_getLack`(
vForce BOOLEAN,
vDays INT,
vId INT,
vLongname VARCHAR(255),
vSupplierFk VARCHAR(255),
vColor VARCHAR(255),
vSize INT,
vOrigen INT,
vLack INT,
vWarehouseFk INT
)
BEGIN BEGIN
/** /**
* Calcula una tabla con el máximo negativo visible para cada producto y almacen * Calcula una tabla con el máximo negativo visible para cada producto y almacen
@ -47,6 +58,14 @@ BEGIN
WHERE w.isForTicket WHERE w.isForTicket
AND ic.display AND ic.display
AND it.code != 'GEN' AND it.code != 'GEN'
AND (vId IS NULL OR i.id = vId)
AND (vLongname IS NULL OR i.name = vLongname)
AND (vSupplierFk IS NULL OR p.`name` LIKE CONCAT('%', vSupplierFk, '%'))
AND (vColor IS NULL OR vColor = i.inkFk)
AND (vSize IS NULL OR vSize = i.`size`)
AND (vOrigen IS NULL OR vOrigen = w.id)
AND (vLack IS NULL OR vLack = sub.amount)
AND (vWarehouseFk IS NULL OR vWarehouseFk = w.id)
GROUP BY i.id, w.id GROUP BY i.id, w.id
HAVING lack < 0; HAVING lack < 0;

View File

@ -95,6 +95,9 @@ BEGIN
AND it.priority = vPriority AND it.priority = vPriority
LEFT JOIN vn.tag t ON t.id = it.tagFk LEFT JOIN vn.tag t ON t.id = it.tagFk
LEFT JOIN vn.buy b ON b.id = lb.buy_id LEFT JOIN vn.buy b ON b.id = lb.buy_id
LEFT JOIN vn.itemShelvingStock iss ON iss.itemFk = i.id
AND iss.warehouseFk = vWarehouseFk
LEFT JOIN vn.ink ink ON ink.id = i.tag5
JOIN itemTags its JOIN itemTags its
WHERE (a.available > 0 OR sd.quantity < sk.stock) WHERE (a.available > 0 OR sd.quantity < sk.stock)
AND (i.typeFk = its.typeFk OR NOT vShowType) AND (i.typeFk = its.typeFk OR NOT vShowType)
@ -104,6 +107,7 @@ BEGIN
(t.name = its.name) DESC, (t.name = its.name) DESC,
(it.value = its.value) DESC, (it.value = its.value) DESC,
(i.tag5 = its.tag5) DESC, (i.tag5 = its.tag5) DESC,
(ink.`showOrder`) DESC,
match5 DESC, match5 DESC,
(i.tag6 = its.tag6) DESC, (i.tag6 = its.tag6) DESC,
match6 DESC, match6 DESC,

View File

@ -0,0 +1,8 @@
INSERT IGNORE INTO salix.ACL (model,property,accessType,permission,principalType,principalId)
VALUES
('Ticket','itemLack','READ','ALLOW','ROLE','employee'),
('Ticket','itemLackDetail','READ','ALLOW','ROLE','employee'),
('Ticket','itemLackOrigin','WRITE','ALLOW','ROLE','employee'),
('Ticket','split','WRITE','ALLOW','ROLE','employee'),
('Ticket','negativeOrigin','READ','ALLOW','ROLE','employee'),
('Sale','replaceItem','READ','ALLOW','ROLE','employee');

View File

@ -0,0 +1 @@
ALTER TABLE vn.negativeOrigin MODIFY COLUMN `type` enum('FALTAS','CONTENEDOR','ENTRADAS','OVERBOOKING', 'SUSTITUCION') CHARACTER SET utf8mb3 COLLATE utf8mb3_unicode_ci NOT NULL;

View File

@ -234,6 +234,7 @@
"It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated", "It has been invoiced but the PDF of refund not be generated": "It has been invoiced but the PDF of refund not be generated",
"Cannot add holidays on this day": "Cannot add holidays on this day", "Cannot add holidays on this day": "Cannot add holidays on this day",
"Cannot send mail": "Cannot send mail", "Cannot send mail": "Cannot send mail",
"This worker already exists": "This worker already exists",
"CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`", "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`": "CONSTRAINT `chkParkingCodeFormat` failed for `vn`.`parking`",
"Original invoice not found": "Original invoice not found", "Original invoice not found": "Original invoice not found",
"There is already a tray with the same height": "There is already a tray with the same height", "There is already a tray with the same height": "There is already a tray with the same height",

View File

@ -378,5 +378,6 @@
"The maximum height of the wagon is 200cm": "La altura máxima es 200cm", "The maximum height of the wagon is 200cm": "La altura máxima es 200cm",
"The entry does not have stickers": "La entrada no tiene etiquetas", "The entry does not have stickers": "La entrada no tiene etiquetas",
"Too many records": "Demasiados registros", "Too many records": "Demasiados registros",
"This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha" "This buyer has already made a reservation for this date": "Este comprador ya ha hecho una reserva para esta fecha",
"price retrieval failed": "price retrieval failed"
} }

View File

@ -0,0 +1,115 @@
module.exports = Self => {
Self.remoteMethodCtx('getSimilar', {
description: 'Returns the ',
accessType: 'READ',
accepts: [{
arg: 'filter',
type: 'Object',
required: true,
description: 'Filter defining where and paginated data',
http: {source: 'query'}
}],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/getSimilar`,
verb: 'GET'
}
});
Self.getSimilar = async(ctx, filter, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
if (typeof options == 'object')
Object.assign(myOptions, options);
const where = filter.where;
const today =
new Date().toLocaleDateString('es-ES', {year: 'numeric',
month: '2-digit',
day: '2-digit'});
const query = [
where.itemFk,
where.warehouseFk,
where.date ?? today,
where.showType ?? true,
where.scopeDays ?? 2
];
const [results] = await Self.rawSql('CALL vn.item_getSimilar(?, ?, ?, ?, ?)', query, myOptions);
return [
{
'id': 1,
'longName': 'Ranged weapon longbow 50cm',
'subName': 'Stark Industries',
'tag5': 'Color',
'value5': 'Brown',
'match5': 0,
'match6': 0,
'match7': 0,
'match8': 1,
'tag6': 'Categoria',
'value6': '+1 precission',
'tag7': 'Tallos',
'value7': '1',
'tag8': null,
'value8': null,
'available': 20,
'calc_id': 6,
'counter': 0,
'minQuantity': 1,
'visible': null,
'price2': 1
},
{
'id': 2,
'longName': 'Ranged weapon longbow 100cm',
'subName': 'Stark Industries',
'tag5': 'Color',
'value5': 'Brown',
'match5': 0,
'match6': 1,
'match7': 0,
'match8': 1,
'tag6': 'Categoria',
'value6': '+1 precission',
'tag7': 'Tallos',
'value7': '1',
'tag8': null,
'value8': null,
'available': 50,
'calc_id': 6,
'counter': 1,
'minQuantity': 5,
'visible': null,
'price2': 10
},
{
'id': 3,
'longName': 'Ranged weapon longbow 200cm',
'subName': 'Stark Industries',
'tag5': 'Color',
'value5': 'Brown',
'match5': 1,
'match6': 1,
'match7': 1,
'match8': 1,
'tag6': 'Categoria',
'value6': '+1 precission',
'tag7': 'Tallos',
'value7': '1',
'tag8': null,
'value8': null,
'available': 185,
'calc_id': 6,
'counter': 10,
'minQuantity': 10,
'visible': null,
'price2': 100
}
];
};
};

View File

@ -5,6 +5,7 @@ module.exports = Self => {
require('../methods/item/clone')(Self); require('../methods/item/clone')(Self);
require('../methods/item/updateTaxes')(Self); require('../methods/item/updateTaxes')(Self);
require('../methods/item/getBalance')(Self); require('../methods/item/getBalance')(Self);
require('../methods/item/getSimilar')(Self);
require('../methods/item/lastEntriesFilter')(Self); require('../methods/item/lastEntriesFilter')(Self);
require('../methods/item/getSummary')(Self); require('../methods/item/getSummary')(Self);
require('../methods/item/getCard')(Self); require('../methods/item/getCard')(Self);

View File

@ -385,6 +385,6 @@ module.exports = Self => {
const sql = ParameterizedSQL.join(stmts, ';'); const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions); const result = await conn.executeStmt(sql, myOptions);
return result[ticketsIndex]; return Array(19).fill().flatMap(() => result[ticketsIndex]);
}; };
}; };

View File

@ -31,7 +31,7 @@ describe('route getSuggestedTickets()', () => {
const length = result.length; const length = result.length;
const anyResult = result[Math.floor(Math.random() * Math.floor(length))]; const anyResult = result[Math.floor(Math.random() * Math.floor(length))];
expect(result.length).toEqual(4); expect(result.length).toEqual(5);
expect(anyResult.zoneFk).toEqual(1); expect(anyResult.zoneFk).toEqual(1);
expect(anyResult.agencyModeFk).toEqual(8); expect(anyResult.agencyModeFk).toEqual(8);

View File

@ -14,7 +14,7 @@ describe('route unlink()', () => {
let tickets = await models.Route.getSuggestedTickets(routeId, options); let tickets = await models.Route.getSuggestedTickets(routeId, options);
expect(zoneAgencyModes.length).toEqual(4); expect(zoneAgencyModes.length).toEqual(4);
expect(tickets.length).toEqual(3); expect(tickets.length).toEqual(4);
await models.Route.unlink(agencyModeId, zoneId, options); await models.Route.unlink(agencyModeId, zoneId, options);

View File

@ -0,0 +1,53 @@
module.exports = Self => {
Self.remoteMethodCtx('replaceItem', {
description: 'Replace item from sale',
accessType: 'WRITE',
accepts: [{
arg: 'saleFk',
type: 'number',
required: true,
},
{
arg: 'substitutionFk',
type: 'number',
required: true
},
{
arg: 'quantity',
type: 'number',
required: true
}
],
returns: {
type: 'object',
root: true
},
http: {
path: `/replaceItem`,
verb: 'POST'
}
});
Self.replaceItem = async(ctx, saleFk, substitutionFk, quantity, options) => {
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
const {_saleFk, _substitutionFk, _quantity} = ctx.args;
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const result = await Self.rawSql('CALL sale_replaceItem(?,?,?)', [saleFk, substitutionFk, quantity], myOptions);
return result;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -0,0 +1,103 @@
module.exports = Self => {
Self.remoteMethod('itemLack', {
description: 'Get tickets as negative status',
accessType: 'READ',
accepts: [
{
arg: 'ctx',
type: 'object',
http: {source: 'context'}
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
},
{
arg: 'itemFk',
type: 'number',
description: 'The item id',
},
{
arg: 'longname',
type: 'string',
description: 'Article name',
},
{
arg: 'supplier',
type: 'string',
description: 'Supplier id',
},
{
arg: 'colour',
type: 'string',
description: 'Colour\'s item',
},
{
arg: 'size',
type: 'string',
description: 'Size\'s item',
},
{
arg: 'origen',
type: 'string',
description: 'origen id',
},
{
arg: 'warehouseFk',
type: 'number',
description: 'The warehouse id',
},
{
arg: 'lack',
type: 'number',
description: 'The item id',
},
{
arg: 'days',
type: 'number',
description: 'The range days',
}
],
returns: [
{
arg: 'body',
type: ['object'],
root: true
}
],
http: {
path: `/itemLack`,
verb: 'GET'
}
});
Self.itemLack = async(ctx, filter, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
const filterKeyOrder = ['days', 'itemFk', 'longname', 'supplier', 'colour', 'size', 'origen', 'lack', 'warehouseFk'];
delete ctx?.args?.ctx;
delete ctx?.args?.filter;
if (filter)
ctx.args = Object.assign(ctx.args ?? {}, filter);
let procedureParams = [true];
procedureParams.push(...filterKeyOrder.map(clave => ctx.args[clave] ?? null));
if (!procedureParams[1])procedureParams[1] = 2;
const procedureArgs = Array(procedureParams.length).fill('?').join(', ');
let query = `CALL vn.item_getLack(${procedureArgs})`;
const result = await Self.rawSql(query, procedureParams, myOptions);
const itemsIndex = 0;
return result[itemsIndex];
};
};

View File

@ -0,0 +1,98 @@
const {ParameterizedSQL} = require('loopback-connector');
module.exports = Self => {
Self.remoteMethod('itemLackDetail', {
description: 'Retrieve detail from ticket',
accessType: 'READ',
accepts: [
{
arg: 'itemFk',
type: 'number',
description: 'The item id',
},
{
arg: 'filter',
type: 'object',
description: 'Filter defining where, order, offset, and limit - must be a JSON-encoded string',
http: {source: 'query'}
}
],
returns: [
{
arg: 'body',
type: ['object'],
root: true,
},
],
http: {
path: `/itemLack/:itemFk`,
verb: 'GET',
},
});
Self.itemLackDetail = async(itemFk, filter, options) => {
const conn = Self.dataSource.connector;
const myOptions = {};
if (typeof options == 'object') Object.assign(myOptions, options);
const stmt = new ParameterizedSQL(
`
SELECT
s.id saleFk,
st.code,
t.id ticketFk,
t.nickname,
t.shipped,
t.hour,
s.quantity,
ag.name agName,
ts.alertLevel alertLevel,
st.name stateName,
st.id stateId,
s.itemFk itemFk,
s.price price,
al.code alertLevelCode,
z.id zoneFk,
z.name zoneName,
z.hour theoreticalhour,
cn.isRookie,
IF(sc.saleClonedFk, 1, 0 ) as turno,
IF(tr.saleFk , 1, 0 ) as peticionCompra,
t.hour minTimed,
c.id customerId,
c.name customerName,
ot.code observationTypeCode
FROM
vn.sale s
JOIN vn.ticket t ON t.id=s.ticketFk
LEFT JOIN vn.zone z ON z.id = t.zoneFk
LEFT JOIN vn.zoneClosure zc ON zc.zoneFk = t.zoneFk
JOIN vn.client c ON c.id=t.clientFk
LEFT JOIN bs.clientNewBorn cn ON cn.clientFk=c.id
JOIN vn.agencyMode agm ON agm.id=t.agencyModeFk
JOIN vn.agency ag ON ag.id=agm.id
JOIN vn.ticketState ts ON ts.ticketFk=t.id
LEFT JOIN vn.state st ON st.id=ts.state
LEFT JOIN vn.alertLevel al ON al.id = st.alertLevel
LEFT JOIN vn.saleCloned sc ON sc.saleClonedFk = s.id
LEFT JOIN vn.ticketRequest tr ON tr.saleFk = s.id
LEFT JOIN vn.ticketObservation tob ON tob.ticketFk = t.id
LEFT JOIN vn.observationType ot ON ot.id = tob.observationTypeFk
WHERE
s.itemFk = ?
AND t.landed >= util.VN_CURDATE()
AND t.landed < util.VN_CURDATE() + INTERVAL ? + 1 DAY
`,
[itemFk, 2]);
// if (filter.where.alertLevel) {
stmt.merge({
sql: `AND ${filter.where.alertLevel ? '' : 'NOT'} ts.alertLevel=?`, params: [0]});
// }
// stmt.merge(conn.makeWhere(filter.where));
const sql = ParameterizedSQL.join([stmt], ';');
const result = await conn.executeStmt(sql, myOptions);
return result;
};
};

View File

@ -0,0 +1,40 @@
const {ParameterizedSQL} = require('loopback-connector');
module.exports = Self => {
Self.remoteMethod('itemLackOrigin', {
description: 'Insert ticket negative into negativeOrigin',
accessType: 'WRITE',
accepts: [{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}, {arg: 'tickets', type: 'array', http: {source: 'body'}}],
returns:
{
type: 'boolean',
root: true
},
http: {
path: `/itemLackOrigin`,
verb: 'POST'
}
});
Self.itemLackOrigin = async(ctx, data, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const conn = Self.dataSource.connector;
const stmts = data.map(({itemFk, negativeType, lack}) =>
`INSERT INTO vn.negativeOrigin (itemFk, type, quantity)
VALUES (${itemFk}, "${negativeType}", ${lack})
ON DUPLICATE KEY UPDATE quantity = quantity + VALUES(quantity)`) ?? [];
const sql = ParameterizedSQL.join(stmts, ';');
const result = await conn.executeStmt(sql, myOptions);
return result;
};
};

View File

@ -0,0 +1,32 @@
module.exports = Self => {
Self.remoteMethod('negativeOrigin', {
description: 'Get tickets from negativeOrigin',
accessType: 'READ',
accepts: [{
arg: 'ctx',
type: 'Object',
http: {source: 'context'}
}],
returns: {
type: 'object',
root: true
},
http: {
path: `/negativeOrigin`,
verb: 'GET'
}
});
Self.negativeOrigin = async(ctx, data, options) => {
const myOptions = {};
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
const negativesOrigin = await Self.app.models.NegativeOrigin.find();
return negativesOrigin;
};
jsegarra marked this conversation as resolved
Review

esto no puede ser un find de loopback?

esto no puede ser un find de loopback?
Review

La tabla negativeOrigin no está publicada en el model-config.json.
Pensé lo mismo, y supuse que si no se hizo fue por un motivo

La tabla negativeOrigin no está publicada en el model-config.json. Pensé lo mismo, y supuse que si no se hizo fue por un motivo
Review

porque serás el primero que la necesita, publicala.

porque serás el primero que la necesita, publicala.
Review

Okey, tomo nota para ahcer las modificaciones necesarias

Okey, tomo nota para ahcer las modificaciones necesarias
};

View File

@ -0,0 +1,154 @@
const models = require('vn-loopback/server/server').models;
describe('Item Lack', () => {
beforeEach(async() => {
ctx = {
req: {
accessToken: {},
headers: {origin: 'http://localhost'},
}
};
});
it('should return data with NO filters', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.id', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
id: 88
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.longname', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
longname: 'Lack negative'
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
xit('should return data with filter.name', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
name: 1
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.color', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
colour: 'WHT'
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.origen', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
origen: 1
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(2);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.size', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
size: '200'
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data with filter.lack', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const filter = {
lack: '-155'
};
try {
const result = await models.Ticket.itemLack(ctx, filter, options);
expect(result.length).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,52 @@
const models = require('vn-loopback/server/server').models;
describe('Item Lack Detail', () => {
it('should return false if id is null', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const id = null;
const result = await models.Ticket.itemLackDetail(id, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return data if id exists', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const id = 1167;
const result = await models.Ticket.itemLackDetail(id, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should return error is if not exists', async() => {
const tx = await models.Ticket.beginTransaction({});
try {
const options = {transaction: tx};
const id = 0;
const result = await models.Ticket.itemLackDetail(id, options);
expect(result.length).toEqual(0);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,51 @@
const models = require('vn-loopback/server/server').models;
describe('NegativeOrigin', () => {
it('should return OK', async() => {
const tx = await models.Ticket.beginTransaction({});
const ctx = {req: {accessToken: {userId: 9}}};
const options = {transaction: tx};
const data = [
{itemFk: 1, negativeType: 'FALTAS', lack: 1},
{itemFk: 1, negativeType: 'FALTAS', lack: 2}
];
try {
await models.Ticket.itemLackOrigin(ctx, data, options);
const query = 'SELECT * FROM vn.negativeOrigin';
const negativeOrigin = await models.Application.rawSql(query, null, options);
expect(negativeOrigin.length).toEqual(1);
expect(negativeOrigin[0].quantity).toEqual(3);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should add 2 lines', async() => {
const tx = await models.Ticket.beginTransaction({});
const ctx = {req: {accessToken: {userId: 9}}};
const options = {transaction: tx};
const data = [
{itemFk: 2, negativeType: 'FALTAS', lack: 1},
{itemFk: 3, negativeType: 'FALTAS', lack: 2}
];
try {
await models.Ticket.itemLackOrigin(ctx, data, options);
const query = 'SELECT * FROM vn.negativeOrigin';
const negativeOrigin = await models.Application.rawSql(query, null, options);
expect(negativeOrigin.length).toEqual(2);
expect(negativeOrigin[0].quantity).toEqual(1);
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,98 @@
const models = require('vn-loopback/server/server').models;
describe('Split', () => {
beforeAll(async() => {
ctx = {
req: {
accessToken: {},
headers: {origin: 'http://localhost'},
}
};
});
it('should split tickets with count 1', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const data = [
{ticketFk: 7}
];
try {
const result = await models.Ticket.split(ctx, data, options);
expect(result.length).toEqual(1);
expect(result[0].ticket).toEqual(7);
expect(result[0].status).toEqual('noSplit');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should split tickets with count 2 and error', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const data = [
{ticketFk: 8}
];
try {
const result = await models.Ticket.split(ctx, data, options);
expect(result.length).toEqual(1);
expect(result[0].ticket).toEqual(8);
expect(result[0].status).toEqual('error');
expect(result[0].message).toEqual('This ticket is not editable.');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should split tickets with count 2 and other error', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const data = [
{ticketFk: 16}
];
try {
const result = await models.Ticket.split(ctx, data, options);
expect(result.length).toEqual(1);
expect(result[0].ticket).toEqual(16);
expect(result[0].status).toEqual('error');
expect(result[0].message).toEqual('Can\'t transfer claimed sales');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
it('should split tickets with count 2 and success', async() => {
const tx = await models.Ticket.beginTransaction({});
const options = {transaction: tx};
const data = [
{ticketFk: 32}
];
try {
const result = await models.Ticket.split(ctx, data, options);
expect(result.length).toEqual(1);
expect(result[0].ticket).toEqual(32);
expect(result[0].status).toEqual('split');
await tx.rollback();
} catch (e) {
await tx.rollback();
throw e;
}
});
});

View File

@ -0,0 +1,82 @@
module.exports = Self => {
Self.remoteMethodCtx('split', {
description: 'Split n tickets',
accessType: 'WRITE',
accepts: [
{
type: ['Object'],
required: true,
http: {source: 'body'}
}
],
returns: {
type: ['Object'],
root: true
},
http: {
path: `/split`,
verb: 'POST'
}
});
Self.split = async(ctx, tickets, options) => {
const models = Self.app.models;
const myOptions = {};
let tx;
let results = [];
if (typeof options == 'object')
Object.assign(myOptions, options);
if (!myOptions.transaction) {
tx = await Self.beginTransaction({});
myOptions.transaction = tx;
}
try {
const ticketsIds = tickets.map(({ticketFk}, index) => ticketFk);
const ticketsCount = await Self.rawSql(`
Review

si el objetivo es contar tickets te sobraria la tabla sale,
si el objetivo es contar sales te sobraria la tabla ticket

si el objetivo es contar tickets te sobraria la tabla sale, si el objetivo es contar sales te sobraria la tabla ticket
Select t.id tid, s.id sid, count(s.id) count
FROM
vn.ticket t
LEFT JOIN vn.sale s
ON s.ticketFk = t.id
WHERE t.id IN (?) GROUP BY t.id;`,
[ticketsIds], myOptions);
for (const {tid, count} of ticketsCount) {
try {
if (count === 1) {
results.push({ticket: tid, status: 'noSplit'});
continue;
}
const [, [{vNewTicket}]] = await Self.rawSql(`
CALL vn.ticket_clone(?, @vNewTicket);
SELECT @vNewTicket vNewTicket;`,
[tid], myOptions);
if (vNewTicket === 0) continue;
const sales = await models.Sale.find({
where: {ticketFk: tid}
}, myOptions);
const updateIsPicked = sales.map(({sid}) => Self.rawSql(`
UPDATE vn.sale SET isPicked = (id = ?) WHERE ticketFk = ?`,
[sid, tid], myOptions));
await Promise.all(updateIsPicked);
await Self.transferSales(ctx, tid, vNewTicket, sales, myOptions);
await Self.rawSql(`CALL vn.ticket_setState(?, ?)`, [tid, 'FIXING'], myOptions);
results.push({ticket: tid, newTicket: vNewTicket, status: 'split'});
await tx.commit();
} catch ({message}) {
results.push({ticket: tid, status: 'error', message});
}
}
return results;
} catch (e) {
if (tx) await tx.rollback();
throw e;
}
};
};

View File

@ -35,6 +35,9 @@
"PackingSiteConfig": { "PackingSiteConfig": {
"dataSource": "vn" "dataSource": "vn"
}, },
"NegativeOrigin": {
"dataSource": "vn"
},
"ExpeditionMistake": { "ExpeditionMistake": {
"dataSource": "vn" "dataSource": "vn"
}, },

View File

@ -0,0 +1,23 @@
{
"name": "NegativeOrigin",
"base": "VnModel",
"options": {
"mysql": {
"table": "negativeOrigin"
}
},
"properties": {
"id": {
"id": true,
"type": "number",
"description": "Identifier"
}
},
"relations": {
"item": {
"type": "belongsTo",
"model": "Item",
"foreignKey": "itemFk"
}
}
}

View File

@ -13,6 +13,7 @@ module.exports = Self => {
require('../methods/sale/usesMana')(Self); require('../methods/sale/usesMana')(Self);
require('../methods/sale/clone')(Self); require('../methods/sale/clone')(Self);
require('../methods/sale/getFromSectorCollection')(Self); require('../methods/sale/getFromSectorCollection')(Self);
// require('../methods/sale/replaceItem')(Self);
Self.validatesPresenceOf('concept', { Self.validatesPresenceOf('concept', {
message: `Concept cannot be blank` message: `Concept cannot be blank`

View File

@ -45,4 +45,9 @@ module.exports = function(Self) {
require('../methods/ticket/docuwareDownload')(Self); require('../methods/ticket/docuwareDownload')(Self);
require('../methods/ticket/myLastModified')(Self); require('../methods/ticket/myLastModified')(Self);
require('../methods/ticket/setWeight')(Self); require('../methods/ticket/setWeight')(Self);
require('../methods/ticket/itemLack')(Self);
require('../methods/ticket/itemLackDetail')(Self);
require('../methods/ticket/itemLackOrigin')(Self);
require('../methods/ticket/negativeOrigin')(Self);
require('../methods/ticket/split')(Self);
}; };

View File

@ -113,7 +113,7 @@
"yaml-loader": "^0.5.0" "yaml-loader": "^0.5.0"
}, },
"scripts": { "scripts": {
"test:back": "nodemon -q back/tests.js --config back/nodemonConfig.json", "test:back": "nodemon -q back/tests.js --config back/nodemonConfig.json --debug",
"test:e2e": "node e2e/tests.js", "test:e2e": "node e2e/tests.js",
"test:front": "jest --watch", "test:front": "jest --watch",
"back": "nodemon --inspect -w modules ./node_modules/gulp/bin/gulp.js back", "back": "nodemon --inspect -w modules ./node_modules/gulp/bin/gulp.js back",