This commit is contained in:
parent
a5486ec694
commit
0918f154e9
|
@ -0,0 +1,6 @@
|
|||
INSERT INTO `salix`.`ACL` (`model`, `property`, `accessType`, `permission`, `principalType`, `principalId`)
|
||||
VALUES
|
||||
('Dms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('ClaimDms', 'removeFile', 'WRITE', 'ALLOW', 'ROLE', 'employee'),
|
||||
('ClaimDms', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
|
||||
('Claim', 'uploadFile', 'WRITE', 'ALLOW', 'ROLE', 'employee');
|
|
@ -1838,7 +1838,7 @@ INSERT INTO `vn`.`dmsType`(`id`, `name`, `path`, `readRoleFk`, `writeRoleFk`, `c
|
|||
(17, 'cmr', 'cmr', NULL, NULL, 'cmr'),
|
||||
(18, 'dua', 'dua', NULL, NULL, 'dua'),
|
||||
(19, 'inmovilizado', 'inmovilizado', NULL, NULL, 'fixedAssets'),
|
||||
(20, 'Reclamación', 'reclamacion', NULL, NULL, 'claim');
|
||||
(20, 'Reclamación', 'reclamacion', 1, 1, 'claim');
|
||||
|
||||
INSERT INTO `vn`.`dms`(`id`, `dmsTypeFk`, `file`, `contentType`, `workerFk`, `warehouseFk`, `companyFk`, `hardCopyNumber`, `hasFile`, `reference`, `description`, `created`)
|
||||
VALUES
|
||||
|
|
|
@ -422,7 +422,7 @@ export default {
|
|||
thirdSaleCheckbox: 'vn-ticket-sale vn-tr:nth-child(3) vn-check[field="sale.checked"] md-checkbox',
|
||||
deleteSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="delete"]',
|
||||
transferSaleButton: 'vn-ticket-sale vn-tool-bar > vn-button[icon="call_split"]',
|
||||
moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.receiverTicketId"] input',
|
||||
moveToTicketInput: 'vn-ticket-sale vn-popover.transfer vn-textfield[model="$ctrl.transfer.ticketId"] input',
|
||||
moveToTicketInputClearButton: 'vn-popover.shown i[title="Clear"]',
|
||||
moveToTicketButton: 'vn-ticket-sale vn-popover.transfer vn-icon[icon="arrow_forward_ios"]',
|
||||
moveToNewTicketButton: 'vn-ticket-sale vn-popover.transfer vn-button[label="New ticket"]',
|
||||
|
|
|
@ -1,35 +1,39 @@
|
|||
module.exports = Self => {
|
||||
Self.remoteMethod('threeLastActive', {
|
||||
Self.remoteMethod('lastActiveTickets', {
|
||||
description: 'Returns the last three tickets of a client that have the alertLevel at 0 and the shiped day is gt today',
|
||||
accessType: 'READ',
|
||||
accepts: [{
|
||||
arg: 'filter',
|
||||
type: 'object',
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
description: 'client id, ticketFk'
|
||||
description: 'Client id',
|
||||
http: {source: 'path'}
|
||||
}, {
|
||||
arg: 'ticketId',
|
||||
type: 'Number',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: [this.modelName],
|
||||
type: ['Object'],
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/threeLastActive`,
|
||||
path: `/:id/lastActiveTickets`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.threeLastActive = async params => {
|
||||
let query = `
|
||||
Self.lastActiveTickets = async(id, ticketId) => {
|
||||
const query = `
|
||||
SELECT t.id, t.shipped, a.name AS agencyName, w.name AS warehouseName
|
||||
FROM vn.ticket t
|
||||
JOIN vn.ticketState ts ON t.id = ts.ticketFk
|
||||
JOIN vn.agencyMode a ON t.agencyModeFk = a.id
|
||||
JOIN vn.warehouse w ON t.warehouseFk = w.id
|
||||
WHERE t.shipped >= CURDATE() AND t.clientFk = ?
|
||||
AND ts.alertLevel = 0 AND t.id <> ?
|
||||
WHERE t.shipped >= CURDATE() AND t.clientFk = ? AND ts.alertLevel = 0 AND t.id <> ?
|
||||
ORDER BY t.shipped
|
||||
LIMIT 5`;
|
||||
let tickets = await Self.rawSql(query, [params.clientFk, params.ticketFk]);
|
||||
return tickets;
|
||||
LIMIT 3`;
|
||||
|
||||
return Self.rawSql(query, [id, ticketId]);
|
||||
};
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('client lastActiveTickets()', () => {
|
||||
it('should return the last three active tickets', async() => {
|
||||
const clientId = 109;
|
||||
const ticketId = 19;
|
||||
|
||||
let result = await app.models.Client.lastActiveTickets(clientId, ticketId);
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
});
|
||||
});
|
|
@ -21,6 +21,7 @@ module.exports = Self => {
|
|||
require('../methods/client/confirmTransaction')(Self);
|
||||
require('../methods/client/canBeInvoiced')(Self);
|
||||
require('../methods/client/uploadFile')(Self);
|
||||
require('../methods/client/lastActiveTickets')(Self);
|
||||
|
||||
// Validations
|
||||
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('moveToTicket', {
|
||||
description: 'Moves lines to a new or a given ticket',
|
||||
accepts: [{
|
||||
arg: 'params',
|
||||
type: 'object',
|
||||
required: true,
|
||||
description: 'currentTicket, receiverTicket, [sales IDs], removeEmptyTicket',
|
||||
http: {source: 'body'}
|
||||
}],
|
||||
returns: {
|
||||
type: 'string',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/moveToTicket`,
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.moveToTicket = async(ctx, params) => {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
let currentTicket = await models.Ticket.findById(params.currentTicket.currentTicketId);
|
||||
let newTicketData = {};
|
||||
let receiverTicket = params.receiverTicket;
|
||||
|
||||
let isCurrentTicketEditable = await models.Ticket.isEditable(ctx, params.currentTicket.currentTicketId);
|
||||
if (!isCurrentTicketEditable)
|
||||
throw new UserError(`The sales of the current ticket can't be modified`);
|
||||
|
||||
if (params.receiverTicket.id) {
|
||||
let isReceiverTicketEditable = await models.Ticket.isEditable(ctx, params.receiverTicket.id);
|
||||
if (!isReceiverTicketEditable)
|
||||
throw new UserError(`The sales of the receiver ticket can't be modified`);
|
||||
}
|
||||
|
||||
if (!params.receiverTicket.id) {
|
||||
let travelDates = await models.Agency.getFirstShipped(params.currentTicket);
|
||||
|
||||
if (!travelDates)
|
||||
throw new UserError(`Invalid parameters to create a new ticket`);
|
||||
let shipped = new Date(travelDates.shipped);
|
||||
let landed = new Date(travelDates.landed);
|
||||
|
||||
newTicketData = {
|
||||
clientFk: params.currentTicket.clientFk,
|
||||
addressFk: params.currentTicket.addressFk,
|
||||
agencyModeFk: params.currentTicket.agencyModeFk,
|
||||
warehouseFk: params.currentTicket.warehouseFk,
|
||||
shipped: shipped,
|
||||
landed: landed,
|
||||
userId: userId
|
||||
};
|
||||
}
|
||||
|
||||
let tx = await Self.beginTransaction({});
|
||||
|
||||
try {
|
||||
let options = {transaction: tx};
|
||||
|
||||
if (!params.receiverTicket.id)
|
||||
receiverTicket = await models.Ticket.new(ctx, newTicketData, options);
|
||||
|
||||
let promises = [];
|
||||
for (let sale of params.sales) {
|
||||
promises.push(
|
||||
models.Sale.update(
|
||||
{id: sale.id},
|
||||
{ticketFk: receiverTicket.id},
|
||||
options
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (params.removeEmptyTicket)
|
||||
promises.push(currentTicket.updateAttributes({isDeleted: true}, options));
|
||||
|
||||
await Promise.all(promises);
|
||||
await tx.commit();
|
||||
|
||||
return receiverTicket;
|
||||
} catch (error) {
|
||||
await tx.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -1,37 +0,0 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethod('checkEmptiness', {
|
||||
description: 'Checks if the ticket has no packages, componenets and purchase requests',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Ticket id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
arg: 'data',
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/checkEmptiness`,
|
||||
verb: 'get'
|
||||
}
|
||||
});
|
||||
|
||||
Self.checkEmptiness = async id => {
|
||||
const packages = await Self.app.models.TicketPackaging.find({where: {ticketFk: id}});
|
||||
const services = await Self.app.models.TicketService.find({where: {ticketFk: id}});
|
||||
const purchaseRequests = await Self.app.models.TicketRequest.find({where: {ticketFk: id}});
|
||||
|
||||
emptyTicket = !packages.length && !services.length && !purchaseRequests.length;
|
||||
|
||||
if (emptyTicket)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
};
|
||||
};
|
|
@ -0,0 +1,51 @@
|
|||
module.exports = function(Self) {
|
||||
Self.remoteMethod('isEmpty', {
|
||||
description: 'Checks if the ticket has no packages, componenets and purchase requests',
|
||||
accessType: 'READ',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Ticket id',
|
||||
http: {source: 'path'}
|
||||
}
|
||||
],
|
||||
returns: {
|
||||
type: 'Boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/isEmpty`,
|
||||
verb: 'GET'
|
||||
}
|
||||
});
|
||||
|
||||
Self.isEmpty = async(id, options) => {
|
||||
const models = Self.app.models;
|
||||
|
||||
if ((typeof options) != 'object')
|
||||
options = {};
|
||||
|
||||
const hasSales = await models.Sale.count({
|
||||
ticketFk: id
|
||||
}, options);
|
||||
|
||||
const hasPackages = await models.TicketPackaging.count({
|
||||
ticketFk: id
|
||||
}, options);
|
||||
|
||||
const hasServices = await models.TicketService.count({
|
||||
ticketFk: id
|
||||
}, options);
|
||||
|
||||
const hasPurchaseRequests = await models.TicketRequest.count({
|
||||
ticketFk: id
|
||||
}, options);
|
||||
|
||||
const isEmpty = !hasSales && !hasPackages &&
|
||||
!hasServices && !hasPurchaseRequests;
|
||||
|
||||
return isEmpty;
|
||||
};
|
||||
};
|
|
@ -1,27 +0,0 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket checkEmptiness()', () => {
|
||||
it('should return false if the ticket contains any packages', async() => {
|
||||
let result = await app.models.Ticket.checkEmptiness(3);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false if the ticket contains any services', async() => {
|
||||
let result = await app.models.Ticket.checkEmptiness(8);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false if the ticket contains any purchase request', async() => {
|
||||
let result = await app.models.Ticket.checkEmptiness(11);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return true if the ticket does not contain any packages, services or purchase request', async() => {
|
||||
let result = await app.models.Ticket.checkEmptiness(4);
|
||||
|
||||
expect(result).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,27 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket isEmpty()', () => {
|
||||
it('should return false if the ticket contains any packages', async() => {
|
||||
let result = await app.models.Ticket.isEmpty(3);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false if the ticket contains any services', async() => {
|
||||
let result = await app.models.Ticket.isEmpty(8);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false if the ticket contains any purchase request', async() => {
|
||||
let result = await app.models.Ticket.isEmpty(11);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
|
||||
it('should return false if the ticket contains any sale', async() => {
|
||||
let result = await app.models.Ticket.isEmpty(4);
|
||||
|
||||
expect(result).toBeFalsy();
|
||||
});
|
||||
});
|
|
@ -1,10 +0,0 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('ticket threeLastActive()', () => {
|
||||
it('should return the last three active tickets', async() => {
|
||||
let params = {clientFk: 109, ticketFk: 19};
|
||||
let result = await app.models.Ticket.threeLastActive(params);
|
||||
|
||||
expect(result.length).toEqual(3);
|
||||
});
|
||||
});
|
|
@ -1,6 +1,6 @@
|
|||
const app = require('vn-loopback/server/server');
|
||||
|
||||
describe('sale moveToTicket()', () => {
|
||||
describe('sale transferSales()', () => {
|
||||
let createdTicketId;
|
||||
|
||||
afterAll(async done => {
|
||||
|
@ -12,11 +12,13 @@ describe('sale moveToTicket()', () => {
|
|||
const ctx = {req: {accessToken: {userId: 101}}};
|
||||
let error;
|
||||
|
||||
const params = {currentTicket: {currentTicketId: 10}};
|
||||
const currentTicketId = 10;
|
||||
const receiverTicketId = undefined;
|
||||
const sales = [];
|
||||
|
||||
await app.models.Sale.moveToTicket(ctx, params)
|
||||
await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales)
|
||||
.catch(response => {
|
||||
expect(response.message).toEqual(`The sales of the current ticket can't be modified`);
|
||||
expect(response.message).toEqual(`The sales of this ticket can't be modified`);
|
||||
error = response;
|
||||
});
|
||||
|
||||
|
@ -27,9 +29,11 @@ describe('sale moveToTicket()', () => {
|
|||
const ctx = {req: {accessToken: {userId: 101}}};
|
||||
let error;
|
||||
|
||||
const params = {currentTicket: {currentTicketId: 16}, receiverTicket: {id: 1}};
|
||||
const currentTicketId = 16;
|
||||
const receiverTicketId = 1;
|
||||
const sales = [];
|
||||
|
||||
await app.models.Sale.moveToTicket(ctx, params)
|
||||
await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales)
|
||||
.catch(response => {
|
||||
expect(response.message).toEqual(`The sales of the receiver ticket can't be modified`);
|
||||
error = response;
|
||||
|
@ -42,9 +46,11 @@ describe('sale moveToTicket()', () => {
|
|||
const ctx = {req: {accessToken: {userId: 101}}};
|
||||
let error;
|
||||
|
||||
const params = {currentTicket: {currentTicketId: 18}, receiverTicket: {id: undefined}};
|
||||
const currentTicketId = 18;
|
||||
const receiverTicketId = undefined;
|
||||
const sales = [];
|
||||
|
||||
await app.models.Sale.moveToTicket(ctx, params)
|
||||
await app.models.Ticket.transferSales(ctx, currentTicketId, receiverTicketId, sales)
|
||||
.catch(response => {
|
||||
expect(response.message).toEqual(`Invalid parameters to create a new ticket`);
|
||||
error = response;
|
||||
|
@ -56,26 +62,19 @@ describe('sale moveToTicket()', () => {
|
|||
it('should transfer the sales from one ticket to a new one', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 101}}};
|
||||
let currentTicket = await app.models.Ticket.findById(11);
|
||||
currentTicket.currentTicketId = currentTicket.id;
|
||||
currentTicket.id = undefined;
|
||||
|
||||
let currentTicketSales = await app.models.Ticket.getSales(currentTicket.currentTicketId);
|
||||
let currentTicketSales = await app.models.Ticket.getSales(currentTicket.id);
|
||||
|
||||
expect(currentTicketSales.length).toEqual(2);
|
||||
|
||||
let params = {
|
||||
currentTicket: currentTicket,
|
||||
receiverTicket: {id: undefined},
|
||||
sales: [
|
||||
{id: currentTicketSales[0].id},
|
||||
{id: currentTicketSales[1].id}
|
||||
]
|
||||
};
|
||||
const currentTicketId = currentTicket.id;
|
||||
const receiverTicketId = undefined;
|
||||
const sales = currentTicketSales;
|
||||
|
||||
let createdTicket = await app.models.Sale.moveToTicket(ctx, params);
|
||||
let createdTicket = await app.models.Ticket.transferSales(
|
||||
ctx, currentTicketId, receiverTicketId, sales);
|
||||
createdTicketId = createdTicket.id;
|
||||
|
||||
currentTicketSales = await app.models.Ticket.getSales(currentTicket.currentTicketId);
|
||||
currentTicketSales = await app.models.Ticket.getSales(currentTicket.id);
|
||||
receiverTicketSales = await app.models.Ticket.getSales(createdTicket.id);
|
||||
|
||||
expect(currentTicketSales.length).toEqual(0);
|
||||
|
@ -84,33 +83,24 @@ describe('sale moveToTicket()', () => {
|
|||
|
||||
it('should transfer back the sales and set the created ticket as deleted', async() => {
|
||||
const ctx = {req: {accessToken: {userId: 101}}};
|
||||
let receiverTicketId = 11;
|
||||
let currentTicket = await app.models.Ticket.findById(createdTicketId);
|
||||
currentTicket.currentTicketId = createdTicketId;
|
||||
currentTicket.id = undefined;
|
||||
const currentTicket = await app.models.Ticket.findById(createdTicketId);
|
||||
const receiverTicketId = 11;
|
||||
|
||||
let createdTicket = await app.models.Ticket.findById(createdTicketId);
|
||||
let createdTicketSales = await app.models.Ticket.getSales(createdTicketId);
|
||||
let receiverTicketSales = await app.models.Ticket.getSales(receiverTicketId);
|
||||
|
||||
const currentTicketId = currentTicket.id;
|
||||
const sales = createdTicketSales;
|
||||
|
||||
expect(createdTicket.isDeleted).toBeFalsy();
|
||||
expect(createdTicketSales.length).toEqual(2);
|
||||
expect(receiverTicketSales.length).toEqual(0);
|
||||
|
||||
let params = {
|
||||
removeEmptyTicket: true,
|
||||
currentTicket: currentTicket,
|
||||
receiverTicket: {id: receiverTicketId},
|
||||
sales: [
|
||||
{id: createdTicketSales[0].id},
|
||||
{id: createdTicketSales[1].id}
|
||||
]
|
||||
};
|
||||
|
||||
await app.models.Sale.moveToTicket(ctx, params);
|
||||
await app.models.Ticket.transferSales(
|
||||
ctx, currentTicketId, receiverTicketId, sales);
|
||||
|
||||
createdTicket = await app.models.Ticket.findById(createdTicketId);
|
||||
|
||||
createdTicketSales = await app.models.Ticket.getSales(createdTicketId);
|
||||
receiverTicketSales = await app.models.Ticket.getSales(receiverTicketId);
|
||||
|
|
@ -0,0 +1,158 @@
|
|||
let UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('transferSales', {
|
||||
description: 'Transfer sales to a new or a given ticket',
|
||||
accepts: [{
|
||||
arg: 'id',
|
||||
type: 'Number',
|
||||
required: true,
|
||||
description: 'Origin ticket id',
|
||||
http: {source: 'path'}
|
||||
},
|
||||
{
|
||||
arg: 'ticketId',
|
||||
type: 'Number',
|
||||
description: 'Destination ticket id',
|
||||
required: false
|
||||
},
|
||||
{
|
||||
arg: 'sales',
|
||||
type: ['Object'],
|
||||
description: 'The sales to transfer',
|
||||
required: true
|
||||
}],
|
||||
returns: {
|
||||
type: 'Object',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: `/:id/transferSales`,
|
||||
verb: 'POST'
|
||||
}
|
||||
});
|
||||
|
||||
Self.transferSales = async(ctx, id, ticketId, sales) => {
|
||||
const models = Self.app.models;
|
||||
|
||||
const isEditable = await models.Ticket.isEditable(ctx, id);
|
||||
if (!isEditable)
|
||||
throw new UserError(`The sales of this ticket can't be modified`);
|
||||
|
||||
if (ticketId) {
|
||||
const isReceiverEditable = await models.Ticket.isEditable(ctx, ticketId);
|
||||
if (!isReceiverEditable)
|
||||
throw new UserError(`The sales of the receiver ticket can't be modified`);
|
||||
}
|
||||
|
||||
let tx = await Self.beginTransaction({});
|
||||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const originalTicket = await models.Ticket.findById(id, null, options);
|
||||
const originalSales = await models.Sale.find({
|
||||
where: {ticketFk: id}
|
||||
}, options);
|
||||
|
||||
if (!ticketId)
|
||||
ticketId = await cloneTicket(ctx, originalTicket, options);
|
||||
|
||||
const map = new Map();
|
||||
for (const sale of originalSales)
|
||||
map.set(sale.id, sale);
|
||||
|
||||
const promises = [];
|
||||
for (const sale of sales) {
|
||||
const originalSale = map.get(sale.id);
|
||||
|
||||
if (sale.quantity == originalSale.quantity) {
|
||||
const updatedSale = models.Sale.updateAll({
|
||||
id: sale.id
|
||||
}, {ticketFk: ticketId}, options);
|
||||
|
||||
promises.push(updatedSale);
|
||||
} else if (sale.quantity < originalSale.quantity) {
|
||||
const transferedSale = await transferPartialSale(
|
||||
ticketId, originalSale, sale, options);
|
||||
|
||||
promises.push(transferedSale);
|
||||
}
|
||||
}
|
||||
|
||||
const isTicketEmpty = await models.Ticket.isEmpty(id, options);
|
||||
if (isTicketEmpty) {
|
||||
originalTicket.updateAttributes({
|
||||
isDeleted: true
|
||||
}, options);
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
await tx.commit();
|
||||
|
||||
return {id: ticketId};
|
||||
} catch (error) {
|
||||
await tx.rollback();
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
async function cloneTicket(ctx, ticket, options) {
|
||||
const models = Self.app.models;
|
||||
const userId = ctx.req.accessToken.userId;
|
||||
|
||||
const travelDates = await models.Agency.getFirstShipped({
|
||||
agencyModeFk: ticket.agencyModeFk,
|
||||
addressFk: ticket.addressFk,
|
||||
warehouseFk: ticket.warehouseFk
|
||||
});
|
||||
|
||||
if (!travelDates)
|
||||
throw new UserError(`Invalid parameters to create a new ticket`);
|
||||
|
||||
let shipped = new Date(travelDates.shipped);
|
||||
let landed = new Date(travelDates.landed);
|
||||
|
||||
const newTicket = await models.Ticket.new(ctx, {
|
||||
clientFk: ticket.clientFk,
|
||||
addressFk: ticket.addressFk,
|
||||
agencyModeFk: ticket.agencyModeFk,
|
||||
warehouseFk: ticket.warehouseFk,
|
||||
shipped: shipped,
|
||||
landed: landed,
|
||||
userId: userId
|
||||
}, options);
|
||||
|
||||
return newTicket.id;
|
||||
}
|
||||
|
||||
async function transferPartialSale(ticketId, originalSale, sale, options) {
|
||||
const models = Self.app.models;
|
||||
// Update original sale
|
||||
const rest = originalSale.quantity - sale.quantity;
|
||||
const updatedSale = models.Sale.updateAll({
|
||||
id: sale.id
|
||||
}, {quantity: rest}, options);
|
||||
|
||||
// Clone sale with new quantity
|
||||
const newSale = originalSale;
|
||||
newSale.id = undefined;
|
||||
newSale.ticketFk = ticketId;
|
||||
newSale.quantity = sale.quantity;
|
||||
|
||||
const createdSale = await models.Sale.create(newSale, options);
|
||||
|
||||
// Clone sale components
|
||||
const saleComponents = await models.SaleComponent.find({
|
||||
where: {saleFk: sale.id}
|
||||
}, options);
|
||||
const newComponents = saleComponents.map(component => {
|
||||
component.saleFk = createdSale.id;
|
||||
|
||||
return component;
|
||||
});
|
||||
const createdComponents = models.SaleComponent
|
||||
.create(newComponents, options);
|
||||
|
||||
return [updatedSale, createdComponents];
|
||||
}
|
||||
};
|
|
@ -1,6 +1,5 @@
|
|||
module.exports = Self => {
|
||||
require('../methods/sale/getClaimableFromTicket')(Self);
|
||||
require('../methods/sale/moveToTicket')(Self);
|
||||
require('../methods/sale/reserve')(Self);
|
||||
require('../methods/sale/removes')(Self);
|
||||
require('../methods/sale/updatePrice')(Self);
|
||||
|
|
|
@ -11,7 +11,6 @@ module.exports = Self => {
|
|||
require('../methods/ticket/componentUpdate')(Self);
|
||||
require('../methods/ticket/new')(Self);
|
||||
require('../methods/ticket/isEditable')(Self);
|
||||
require('../methods/ticket/threeLastActive')(Self);
|
||||
require('../methods/ticket/delete')(Self);
|
||||
require('../methods/ticket/getVAT')(Self);
|
||||
require('../methods/ticket/getSales')(Self);
|
||||
|
@ -21,10 +20,11 @@ module.exports = Self => {
|
|||
require('../methods/ticket/canBeInvoiced')(Self);
|
||||
require('../methods/ticket/makeInvoice')(Self);
|
||||
require('../methods/ticket/updateEditableTicket')(Self);
|
||||
require('../methods/ticket/checkEmptiness')(Self);
|
||||
require('../methods/ticket/isEmpty')(Self);
|
||||
require('../methods/ticket/updateDiscount')(Self);
|
||||
require('../methods/ticket/uploadFile')(Self);
|
||||
require('../methods/ticket/addSale')(Self);
|
||||
require('../methods/ticket/transferSales')(Self);
|
||||
|
||||
Self.observe('before save', async function(ctx) {
|
||||
if (ctx.isNewInstance) return;
|
||||
|
|
|
@ -115,7 +115,11 @@
|
|||
field="sale.itemFk"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}">
|
||||
search-function="{or: [{id: $search}, {name: {like: '%' + $search + '%'}}]}"
|
||||
order="id DESC">
|
||||
<tpl-item>
|
||||
{{id}} - {{name}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
|
@ -258,51 +262,87 @@
|
|||
<!-- Transfer Popover -->
|
||||
<vn-popover class="transfer" vn-id="transfer">
|
||||
<div pad-medium>
|
||||
<vn-horizontal>
|
||||
<vn-one>
|
||||
<h4 translate>Sales to transfer</h4>
|
||||
<vn-table>
|
||||
<vn-thead>
|
||||
<vn-tr>
|
||||
<vn-th number shrink>Id</vn-th>
|
||||
<vn-th shrink>Item</vn-th>
|
||||
<vn-th number shrink>Quantity</vn-th>
|
||||
</vn-tr>
|
||||
</vn-thead>
|
||||
<vn-tbody>
|
||||
<vn-tr ng-repeat="sale in $ctrl.transfer.sales">
|
||||
<vn-td number shrink>{{::sale.itemFk | zeroFill:6}}</vn-td>
|
||||
<vn-td shrink>
|
||||
<span title="{{::sale.concept}}">{{::sale.concept}}</span>
|
||||
</vn-td>
|
||||
<vn-td-editable number>
|
||||
<text>{{sale.quantity}}</text>
|
||||
<field>
|
||||
<vn-input-number vn-focus
|
||||
model="sale.quantity">
|
||||
</vn-input-number>
|
||||
</field>
|
||||
</vn-td-editable>
|
||||
</vn-tr>
|
||||
</vn-tbody>
|
||||
</vn-table>
|
||||
</vn-one>
|
||||
<vn-one>
|
||||
<vn-horizontal>
|
||||
<h4 vn-one translate>Destination ticket</h4>
|
||||
<vn-icon vn-none
|
||||
color-secondary
|
||||
vn-tooltip="You have to allow pop-ups in your web browser to use this functionality"
|
||||
icon="info">
|
||||
</vn-icon>
|
||||
</vn-horizontal>
|
||||
<table class="vn-grid">
|
||||
<thead>
|
||||
<tr>
|
||||
<th number translate>ID</th>
|
||||
<th number translate>F. envio</th>
|
||||
<th number translate>Agencia</th>
|
||||
<th number translate>Almacen</th>
|
||||
<th number>ID</th>
|
||||
<th number>F. envio</th>
|
||||
<th number>Agencia</th>
|
||||
<th number>Almacen</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-if="$ctrl.lastThreeTickets.length === 0" >
|
||||
<tr ng-if="$ctrl.transfer.lastActiveTickets.length === 0" >
|
||||
<td colspan="4" style="text-align: center" translate>No results</td>
|
||||
</tr>
|
||||
<tr
|
||||
class="clickable"
|
||||
ng-repeat="ticket in $ctrl.lastThreeTickets track by ticket.id"
|
||||
ng-click="$ctrl.checkEmptiness(ticket.id)">
|
||||
ng-repeat="ticket in $ctrl.transfer.lastActiveTickets track by ticket.id"
|
||||
ng-click="$ctrl.transferSales(ticket.id)">
|
||||
<td number>{{::ticket.id}}</td>
|
||||
<td number>{{::ticket.shipped | dateTime: 'dd/MM/yyyy HH:mm'}}</td>
|
||||
<td number>{{::ticket.shipped | dateTime: 'dd/MM/yyyy'}}</td>
|
||||
<td number>{{::ticket.agencyName}}</td>
|
||||
<td number>{{::ticket.warehouseName}}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<vn-horizontal>
|
||||
<vn-textfield
|
||||
label="Move to ticket"
|
||||
model="$ctrl.receiverTicketId"
|
||||
|
||||
<vn-horizontal pad-medium-v>
|
||||
<vn-textfield vn-one
|
||||
label="Transfer to ticket"
|
||||
model="$ctrl.transfer.ticketId"
|
||||
type="number">
|
||||
</vn-textfield>
|
||||
<vn-icon-button
|
||||
<vn-icon-button vn-none
|
||||
icon="arrow_forward_ios"
|
||||
ng-click="$ctrl.checkEmptiness($ctrl.receiverTicketId)">
|
||||
ng-click="$ctrl.transferSales($ctrl.transfer.ticketId)">
|
||||
</vn-icon-button>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-horizontal pad-medium-v>
|
||||
<vn-button
|
||||
label="New ticket"
|
||||
ng-click="$ctrl.checkEmptiness()">
|
||||
ng-click="$ctrl.transferSales()">
|
||||
</vn-button>
|
||||
<vn-icon
|
||||
color-secondary
|
||||
vn-tooltip="You have to allow pop-ups in your web browser to use this functionality"
|
||||
icon="info">
|
||||
</vn-icon>
|
||||
</vn-horizontal>
|
||||
</vn-one>
|
||||
</vn-horizontal>
|
||||
</div>
|
||||
</vn-popover>
|
||||
|
@ -321,7 +361,7 @@
|
|||
vn-id="delete-ticket"
|
||||
question="Do you want to delete it?"
|
||||
message="This ticket is now empty"
|
||||
on-response="$ctrl.moveLines(response)">
|
||||
on-response="$ctrl.transferSales($ctrl.transfer.ticketId, response)">
|
||||
</vn-confirm>
|
||||
<vn-float-button
|
||||
ng-show="$ctrl.isEditable"
|
||||
|
|
|
@ -99,6 +99,7 @@ class Controller {
|
|||
this.isEditable = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
get isChecked() {
|
||||
if (this.sales) {
|
||||
for (let instance of this.sales)
|
||||
|
@ -113,7 +114,7 @@ class Controller {
|
|||
*
|
||||
* @return {Array} Checked instances
|
||||
*/
|
||||
getCheckedLines() {
|
||||
checkedLines() {
|
||||
if (!this.sales) return;
|
||||
|
||||
return this.sales.filter(sale => {
|
||||
|
@ -121,13 +122,14 @@ class Controller {
|
|||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns an array of indexes
|
||||
* from checked instances
|
||||
*
|
||||
* @return {Array} Indexes of checked instances
|
||||
*/
|
||||
getCheckedLinesIndex() {
|
||||
checkedLinesIndex() {
|
||||
if (!this.sales) return;
|
||||
|
||||
let indexes = [];
|
||||
|
@ -138,8 +140,25 @@ class Controller {
|
|||
return indexes;
|
||||
}
|
||||
|
||||
firstCheckedLine() {
|
||||
const checkedLines = this.checkedLines();
|
||||
if (checkedLines)
|
||||
return checkedLines[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the total of checked instances
|
||||
*
|
||||
* @return {Number} Total checked instances
|
||||
*/
|
||||
totalCheckedLines() {
|
||||
const checkedLines = this.checkedLines();
|
||||
if (checkedLines)
|
||||
return checkedLines.length;
|
||||
}
|
||||
|
||||
removeCheckedLines() {
|
||||
const sales = this.getCheckedLines();
|
||||
const sales = this.checkedLines();
|
||||
|
||||
sales.forEach(sale => {
|
||||
const index = this.sales.indexOf(sale);
|
||||
|
@ -167,7 +186,7 @@ class Controller {
|
|||
|
||||
onRemoveLinesClick(response) {
|
||||
if (response === 'ACCEPT') {
|
||||
let sales = this.getCheckedLines();
|
||||
let sales = this.checkedLines();
|
||||
|
||||
// Remove unsaved instances
|
||||
sales.forEach((sale, index) => {
|
||||
|
@ -188,60 +207,36 @@ class Controller {
|
|||
}
|
||||
|
||||
showTransferPopover(event) {
|
||||
let filter = {clientFk: this.ticket.clientFk, ticketFk: this.ticket.id};
|
||||
let json = encodeURIComponent(JSON.stringify(filter));
|
||||
let query = `/api/Tickets/threeLastActive?filter=${json}`;
|
||||
this.$http.get(query).then(res => {
|
||||
this.lastThreeTickets = res.data;
|
||||
});
|
||||
this.setTransferParams();
|
||||
this.$scope.transfer.parent = event.target;
|
||||
this.$scope.transfer.show();
|
||||
}
|
||||
|
||||
setTransferParams() {
|
||||
const checkedSales = JSON.stringify(this.checkedLines());
|
||||
const sales = JSON.parse(checkedSales);
|
||||
this.transfer = {
|
||||
lastActiveTickets: [],
|
||||
sales: sales
|
||||
};
|
||||
|
||||
checkEmptiness(receiverTicketId) {
|
||||
let sales = this.getCheckedLines();
|
||||
let areAllSalesSelected = sales.length === this.$scope.model.data.length;
|
||||
this.receiverTicketId = receiverTicketId;
|
||||
|
||||
|
||||
if (areAllSalesSelected) {
|
||||
let query = `/api/Tickets/${this.ticket.id}/checkEmptiness`;
|
||||
this.$http.get(query).then(res => {
|
||||
if (res.data)
|
||||
this.$scope.deleteTicket.show();
|
||||
if (!res.data)
|
||||
this.moveLines(false);
|
||||
const params = {ticketId: this.ticket.id};
|
||||
const query = `/api/clients/${this.ticket.clientFk}/lastActiveTickets`;
|
||||
this.$http.get(query, {params}).then(res => {
|
||||
this.transfer.lastActiveTickets = res.data;
|
||||
});
|
||||
}
|
||||
|
||||
if (!areAllSalesSelected)
|
||||
this.moveLines(false);
|
||||
}
|
||||
|
||||
moveLines(removeEmptyTicket) {
|
||||
let sales = this.getCheckedLines();
|
||||
|
||||
let currentTicketData = {
|
||||
currentTicketId: this.ticket.id,
|
||||
clientFk: this.ticket.clientFk,
|
||||
addressFk: this.ticket.addressFk,
|
||||
agencyModeFk: this.ticket.agencyModeFk,
|
||||
warehouseFk: this.ticket.warehouseFk
|
||||
transferSales(ticketId) {
|
||||
const params = {
|
||||
ticketId: ticketId,
|
||||
sales: this.transfer.sales
|
||||
};
|
||||
|
||||
let params = {
|
||||
currentTicket: currentTicketData,
|
||||
receiverTicket: this.receiverTicketId ? {id: this.receiverTicketId} : currentTicketData,
|
||||
sales: sales,
|
||||
removeEmptyTicket: removeEmptyTicket
|
||||
};
|
||||
|
||||
this.$http.post(`/api/Sales/moveToTicket`, params).then(res => {
|
||||
if (res.data) {
|
||||
this.receiverTicketId = null;
|
||||
const query = `/api/tickets/${this.ticket.id}/transferSales`;
|
||||
this.$http.post(query, params).then(res => {
|
||||
if (res.data)
|
||||
this.goToTicket(res.data.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -251,14 +246,14 @@ class Controller {
|
|||
clientFk: this.ticket.clientFk,
|
||||
ticketCreated: this.ticket.shipped
|
||||
};
|
||||
const sales = this.getCheckedLines();
|
||||
const sales = this.checkedLines();
|
||||
this.$http.post(`/api/Claims/createFromSales`, {claim: claim, sales: sales}).then(res => {
|
||||
this.$state.go('claim.card.basicData', {id: res.data.id});
|
||||
});
|
||||
}
|
||||
|
||||
goToTicket(ticketID) {
|
||||
this.$state.go('ticket.card.sale', {id: ticketID});
|
||||
goToTicket(ticketId) {
|
||||
this.$state.go('ticket.card.sale', {id: ticketId});
|
||||
}
|
||||
|
||||
// Slesperson Mana
|
||||
|
@ -336,7 +331,7 @@ class Controller {
|
|||
}
|
||||
|
||||
showEditDialog() {
|
||||
this.edit = this.getCheckedLines();
|
||||
this.edit = this.checkedLines();
|
||||
this.$scope.editDialog.show();
|
||||
}
|
||||
|
||||
|
@ -365,7 +360,7 @@ class Controller {
|
|||
}
|
||||
|
||||
setReserved(reserved) {
|
||||
let selectedSales = this.getCheckedLines();
|
||||
let selectedSales = this.checkedLines();
|
||||
let params = {sales: selectedSales, ticketFk: this.ticket.id, reserved: reserved};
|
||||
|
||||
let reservedSales = new Map();
|
||||
|
@ -393,7 +388,7 @@ class Controller {
|
|||
|
||||
showSMSDialog() {
|
||||
const address = this.ticket.address;
|
||||
const sales = this.getCheckedLines();
|
||||
const sales = this.checkedLines();
|
||||
const items = sales.map(sale => {
|
||||
return `${sale.quantity} ${sale.concept}`;
|
||||
});
|
||||
|
|
|
@ -7,7 +7,7 @@ Unmark as reserved: Desmarcar como reservado
|
|||
Update discount: Actualizar descuento
|
||||
There is no changes to save: No hay cambios que guardar
|
||||
Edit discount: Editar descuento
|
||||
Move to ticket: Mover a ticket
|
||||
Transfer to ticket: Transferir a ticket
|
||||
New ticket: Nuevo ticket
|
||||
Edit price: Editar precio
|
||||
You are going to delete lines of the ticket: Vas a borrar lineas del ticket
|
||||
|
@ -19,7 +19,8 @@ Available: Disponible
|
|||
In which day you want to add the ticket?: ¿A que dia quieres añadir el ticket?
|
||||
Add claim: Crear reclamación
|
||||
Claim: Reclamación
|
||||
Transfer lines: Transferir líneas
|
||||
Sales to transfer: Líneas a transferir
|
||||
Destination ticket: Ticket destinatario
|
||||
Change ticket state to 'Ok': Cambiar estado del ticket a 'Ok'
|
||||
Reserved: Reservado
|
||||
SMSAvailability: >-
|
||||
|
|
|
@ -9,7 +9,7 @@ describe('Ticket', () => {
|
|||
|
||||
let ticket = {
|
||||
id: 1,
|
||||
clientFk: 1,
|
||||
clientFk: 101,
|
||||
shipped: 1,
|
||||
created: new Date(),
|
||||
client: {salesPersonFk: 1},
|
||||
|
@ -92,13 +92,13 @@ describe('Ticket', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('getCheckedLines()', () => {
|
||||
describe('checkedLines()', () => {
|
||||
it('should make an array of the instances with the property checked true()', () => {
|
||||
let sale = controller.sales[1];
|
||||
sale.checked = true;
|
||||
let expectedResult = [sale];
|
||||
|
||||
expect(controller.getCheckedLines()).toEqual(expectedResult);
|
||||
expect(controller.checkedLines()).toEqual(expectedResult);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -235,5 +235,46 @@ describe('Ticket', () => {
|
|||
$httpBackend.flush();
|
||||
});
|
||||
});
|
||||
|
||||
describe('transferSales()', () => {
|
||||
it('should transfer sales to a ticket', () => {
|
||||
spyOn(controller, 'goToTicket');
|
||||
controller.transfer = {
|
||||
sales: [{id: 1, itemFk: 1}, {id: 2, itemFk: 4}]
|
||||
};
|
||||
|
||||
const expectedResponse = {id: 13};
|
||||
const params = {
|
||||
ticketId: 13,
|
||||
sales: controller.transfer.sales
|
||||
};
|
||||
|
||||
$httpBackend.when('POST', `/api/tickets/1/transferSales`, params).respond(expectedResponse);
|
||||
$httpBackend.expect('POST', `/api/tickets/1/transferSales`, params).respond(expectedResponse);
|
||||
controller.transferSales(13);
|
||||
$httpBackend.flush();
|
||||
|
||||
expect(controller.goToTicket).toHaveBeenCalledWith(13);
|
||||
});
|
||||
});
|
||||
|
||||
describe('setTransferParams()', () => {
|
||||
it('should define the transfer object on the controller and its default properties', () => {
|
||||
let sale = controller.sales[1];
|
||||
sale.checked = true;
|
||||
const expectedResponse = [sale];
|
||||
|
||||
$httpBackend.when('GET', `/api/clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse);
|
||||
$httpBackend.expect('GET', `/api/clients/101/lastActiveTickets?ticketId=1`).respond(expectedResponse);
|
||||
controller.setTransferParams();
|
||||
$httpBackend.flush();
|
||||
|
||||
const lastActiveTickets = controller.transfer.lastActiveTickets;
|
||||
|
||||
expect(controller.transfer).toBeDefined();
|
||||
expect(lastActiveTickets).toBeDefined();
|
||||
expect(lastActiveTickets[0].id).toEqual(4);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -61,13 +61,30 @@ vn-ticket-sale {
|
|||
}
|
||||
|
||||
vn-popover.transfer{
|
||||
vn-table {
|
||||
min-width: 650px;
|
||||
vn-textfield {
|
||||
margin: 0
|
||||
}
|
||||
|
||||
vn-horizontal {
|
||||
& > vn-one:nth-child(1){
|
||||
border-right: 1px solid $color-bg;
|
||||
padding-right: 1em;
|
||||
}
|
||||
|
||||
& > vn-one:nth-child(2){
|
||||
margin-left: 1em
|
||||
}
|
||||
}
|
||||
vn-table, table {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
vn-icon:nth-child(1) {
|
||||
padding-top: 0.2em;
|
||||
font-size: 1.7em;
|
||||
|
||||
vn-table {
|
||||
width: 20em
|
||||
}
|
||||
|
||||
table {
|
||||
width: 25em
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue