Merge branch 'dev' of https://gitea.verdnatura.es/verdnatura/salix into 2984-invoiceOut_global
This commit is contained in:
commit
8e84f98629
|
@ -0,0 +1,14 @@
|
||||||
|
create table `vn`.`itemMatchProperties`
|
||||||
|
(
|
||||||
|
itemFk int not null,
|
||||||
|
name varchar(80) not null,
|
||||||
|
producer varchar(80) not null,
|
||||||
|
size int not null,
|
||||||
|
constraint itemMatchProperties_pk
|
||||||
|
primary key (itemFk, name, producer, size),
|
||||||
|
constraint itemFk___fk
|
||||||
|
foreign key (itemFk) references item (id)
|
||||||
|
on update cascade on delete cascade
|
||||||
|
)
|
||||||
|
comment 'Propiedades para encontrar articulos equivalentes en verdnatura';
|
||||||
|
|
|
@ -29,9 +29,6 @@ describe('Entry import, create and edit buys path', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => {
|
it('should fill the form, import the designated JSON file and select items for each import and confirm import', async() => {
|
||||||
await page.write(selectors.entryBuys.ref, 'a reference');
|
|
||||||
await page.write(selectors.entryBuys.observation, 'an observation');
|
|
||||||
|
|
||||||
let currentDir = process.cwd();
|
let currentDir = process.cwd();
|
||||||
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
|
let filePath = `${currentDir}/e2e/assets/07_import_buys.json`;
|
||||||
|
|
||||||
|
@ -41,6 +38,9 @@ describe('Entry import, create and edit buys path', () => {
|
||||||
]);
|
]);
|
||||||
await fileChooser.accept([filePath]);
|
await fileChooser.accept([filePath]);
|
||||||
|
|
||||||
|
await page.waitForTextInField(selectors.entryBuys.ref, '200573095, 200573106, 200573117, 200573506');
|
||||||
|
await page.waitForTextInField(selectors.entryBuys.observation, '729-6340 2846');
|
||||||
|
|
||||||
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm');
|
await page.autocompleteSearch(selectors.entryBuys.firstImportedItem, 'Ranged Reinforced weapon pistol 9mm');
|
||||||
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m');
|
await page.autocompleteSearch(selectors.entryBuys.secondImportedItem, 'Melee Reinforced weapon heavy shield 1x0.5m');
|
||||||
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m');
|
await page.autocompleteSearch(selectors.entryBuys.thirdImportedItem, 'Container medical box 1m');
|
||||||
|
|
|
@ -57,7 +57,14 @@
|
||||||
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
"The postcode doesn't exist. Please enter a correct one": "The postcode doesn't exist. Please enter a correct one",
|
||||||
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
"Can't create stowaway for this ticket": "Can't create stowaway for this ticket",
|
||||||
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
"Swift / BIC can't be empty": "Swift / BIC can't be empty",
|
||||||
"Bought units from buy request": "Bought {{quantity}} units of {{concept}} [{{itemId}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
"Deleted sales from ticket": "I have deleted the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
|
||||||
|
"Added sale to ticket": "I have added the following line to the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
|
||||||
|
"Changed sale discount": "I have changed the following lines discounts from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Created claim": "I have created the claim [{{claimId}}]({{{claimUrl}}}) for the following lines from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Changed sale price": "I have changed the price of [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) from {{oldPrice}}€ ➔ *{{newPrice}}€* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale quantity": "I have changed the quantity of [{{itemId}} {{concept}}]({{{itemUrl}}}) from {{oldQuantity}} ➔ *{{newQuantity}}* of the ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale reserved state": "I have changed the following lines reserved state from the ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Bought units from buy request": "Bought {{quantity}} units of [{{itemId}} {{concept}}]({{{urlItem}}}) for the ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "I have changed the insurence credit of client [{{clientName}} ({{clientId}})]({{{url}}}) to *{{credit}} €*",
|
||||||
"Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
"Changed client paymethod": "I have changed the pay method for client [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "I sent *{{quantity}}* units of [{{concept}} ({{itemId}})]({{{itemUrl}}}) to *\"{{nickname}}\"* coming from ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
@ -99,5 +106,9 @@
|
||||||
"None": "None",
|
"None": "None",
|
||||||
"error densidad = 0": "error densidad = 0",
|
"error densidad = 0": "error densidad = 0",
|
||||||
"This document already exists on this ticket": "This document already exists on this ticket",
|
"This document already exists on this ticket": "This document already exists on this ticket",
|
||||||
"serial non editable": "This serial doesn't allow to set a reference"
|
"serial non editable": "This serial doesn't allow to set a reference",
|
||||||
|
"nickname": "nickname",
|
||||||
|
"State": "State",
|
||||||
|
"regular": "regular",
|
||||||
|
"reserved": "reserved"
|
||||||
}
|
}
|
|
@ -54,7 +54,7 @@
|
||||||
"This address doesn't exist": "Este consignatario no existe",
|
"This address doesn't exist": "Este consignatario no existe",
|
||||||
"You can't create an order for a inactive client": "You can't create an order for a inactive client",
|
"You can't create an order for a inactive client": "You can't create an order for a inactive client",
|
||||||
"You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified",
|
"You can't create an order for a client that doesn't has tax data verified": "You can't create an order for a client that doesn't has tax data verified",
|
||||||
"You must delete the claim id %d first": "Antes debes borrar la reclamacion %d",
|
"You must delete the claim id %d first": "Antes debes borrar la reclamación %d",
|
||||||
"You don't have enough privileges": "No tienes suficientes permisos",
|
"You don't have enough privileges": "No tienes suficientes permisos",
|
||||||
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
|
"Cannot check Equalization Tax in this NIF/CIF": "No se puede marcar RE en este NIF/CIF",
|
||||||
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
|
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos basicos de una orden con artículos",
|
||||||
|
@ -122,7 +122,17 @@
|
||||||
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
"Swift / BIC can't be empty": "Swift / BIC no puede estar vacío",
|
||||||
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
"Customs agent is required for a non UEE member": "El agente de aduanas es requerido para los clientes extracomunitarios",
|
||||||
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
"Incoterms is required for a non UEE member": "El incoterms es requerido para los clientes extracomunitarios",
|
||||||
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de {{concept}} [{{itemId}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
"Deleted sales from ticket": "He eliminado las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{deletions}}}",
|
||||||
|
"Added sale to ticket": "He añadido la siguiente linea al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{addition}}}",
|
||||||
|
"Changed sale discount": "He cambiado el descuento de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Created claim": "He creado la reclamación [{{claimId}}]({{{claimUrl}}}) de las siguientes lineas del ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Changed sale price": "He cambiado el precio de [{{itemId}} {{concept}}]({{{itemUrl}}}) ({{quantity}}) de {{oldPrice}}€ ➔ *{{newPrice}}€* del ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"Changed sale quantity": "He cambiado la cantidad de [{{itemId}} {{concept}}]({{{itemUrl}}}) de {{oldQuantity}} ➔ *{{newQuantity}}* del ticket [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
"State": "Estado",
|
||||||
|
"regular": "normal",
|
||||||
|
"reserved": "reservado",
|
||||||
|
"Changed sale reserved state": "He cambiado el estado reservado de las siguientes lineas al ticket [{{ticketId}}]({{{ticketUrl}}}): {{{changes}}}",
|
||||||
|
"Bought units from buy request": "Se ha comprado {{quantity}} unidades de [{{itemId}} {{concept}}]({{{urlItem}}}) para el ticket id [{{ticketId}}]({{{url}}})",
|
||||||
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
"MESSAGE_INSURANCE_CHANGE": "He cambiado el crédito asegurado del cliente [{{clientName}} ({{clientId}})]({{{url}}}) a *{{credit}} €*",
|
||||||
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
"Changed client paymethod": "He cambiado la forma de pago del cliente [{{clientName}} ({{clientId}})]({{{url}}})",
|
||||||
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
"Sent units from ticket": "Envio *{{quantity}}* unidades de [{{concept}} ({{itemId}})]({{{itemUrl}}}) a *\"{{nickname}}\"* provenientes del ticket id [{{ticketId}}]({{{ticketUrl}}})",
|
||||||
|
|
|
@ -3,17 +3,20 @@ const UserError = require('vn-loopback/util/user-error');
|
||||||
module.exports = Self => {
|
module.exports = Self => {
|
||||||
Self.remoteMethodCtx('createFromSales', {
|
Self.remoteMethodCtx('createFromSales', {
|
||||||
description: 'Create a claim',
|
description: 'Create a claim',
|
||||||
accepts: [{
|
accepts: [
|
||||||
arg: 'ticketId',
|
{
|
||||||
type: 'Number',
|
arg: 'ticketId',
|
||||||
required: true,
|
type: 'number',
|
||||||
description: 'The origin ticket id'
|
required: true,
|
||||||
}, {
|
description: 'The origin ticket id'
|
||||||
arg: 'sales',
|
},
|
||||||
type: ['Object'],
|
{
|
||||||
required: true,
|
arg: 'sales',
|
||||||
description: 'The claimed sales'
|
type: ['object'],
|
||||||
}],
|
required: true,
|
||||||
|
description: 'The claimed sales'
|
||||||
|
}
|
||||||
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'object',
|
type: 'object',
|
||||||
root: true
|
root: true
|
||||||
|
@ -25,6 +28,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.createFromSales = async(ctx, ticketId, sales, options) => {
|
Self.createFromSales = async(ctx, ticketId, sales, options) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
let tx;
|
let tx;
|
||||||
const myOptions = {};
|
const myOptions = {};
|
||||||
|
@ -39,7 +43,20 @@ module.exports = Self => {
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
if (ticket.isDeleted)
|
if (ticket.isDeleted)
|
||||||
throw new UserError(`You can't create a claim for a removed ticket`);
|
throw new UserError(`You can't create a claim for a removed ticket`);
|
||||||
|
|
||||||
|
@ -49,6 +66,8 @@ module.exports = Self => {
|
||||||
ticketCreated: ticket.shipped,
|
ticketCreated: ticket.shipped,
|
||||||
workerFk: userId
|
workerFk: userId
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
|
let changesMade = '';
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (const sale of sales) {
|
for (const sale of sales) {
|
||||||
|
@ -59,10 +78,25 @@ module.exports = Self => {
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
|
|
||||||
promises.push(newClaimBeginning);
|
promises.push(newClaimBeginning);
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Created claim', {
|
||||||
|
claimId: newClaim.id,
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
claimUrl: `${origin}/#!/claim/${newClaim.id}/summary`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
||||||
return newClaim;
|
return newClaim;
|
||||||
|
|
|
@ -7,7 +7,13 @@ describe('Claim createFromSales()', () => {
|
||||||
instance: 0,
|
instance: 0,
|
||||||
quantity: 10
|
quantity: 10
|
||||||
}];
|
}];
|
||||||
const ctx = {req: {accessToken: {userId: 1}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 1},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
it('should create a new claim', async() => {
|
it('should create a new claim', async() => {
|
||||||
const tx = await app.models.Claim.beginTransaction({});
|
const tx = await app.models.Claim.beginTransaction({});
|
||||||
|
|
|
@ -11,11 +11,6 @@ module.exports = Self => {
|
||||||
description: 'The entry id',
|
description: 'The entry id',
|
||||||
http: {source: 'path'}
|
http: {source: 'path'}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
arg: 'options',
|
|
||||||
type: 'object',
|
|
||||||
description: 'Callback options',
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
arg: 'ref',
|
arg: 'ref',
|
||||||
type: 'string',
|
type: 'string',
|
||||||
|
@ -28,11 +23,11 @@ module.exports = Self => {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'buys',
|
arg: 'buys',
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
description: 'The buys',
|
description: 'The buys',
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -41,23 +36,27 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.importBuys = async(ctx, id, options = {}) => {
|
Self.importBuys = async(ctx, id, options) => {
|
||||||
const conn = Self.dataSource.connector;
|
const conn = Self.dataSource.connector;
|
||||||
const args = ctx.args;
|
const args = ctx.args;
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
let tx;
|
let tx;
|
||||||
|
const myOptions = {};
|
||||||
|
|
||||||
if (!options.transaction) {
|
if (typeof options == 'object')
|
||||||
|
Object.assign(myOptions, options);
|
||||||
|
|
||||||
|
if (!myOptions.transaction) {
|
||||||
tx = await Self.beginTransaction({});
|
tx = await Self.beginTransaction({});
|
||||||
options.transaction = tx;
|
myOptions.transaction = tx;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const entry = await models.Entry.findById(id, null, options);
|
const entry = await models.Entry.findById(id, null, myOptions);
|
||||||
await entry.updateAttributes({
|
await entry.updateAttributes({
|
||||||
observation: args.observation,
|
observation: args.observation,
|
||||||
ref: args.ref
|
ref: args.ref
|
||||||
}, options);
|
}, myOptions);
|
||||||
|
|
||||||
const buys = [];
|
const buys = [];
|
||||||
for (let buy of args.buys) {
|
for (let buy of args.buys) {
|
||||||
|
@ -71,9 +70,16 @@ module.exports = Self => {
|
||||||
buyingValue: buy.buyingValue,
|
buyingValue: buy.buyingValue,
|
||||||
packageFk: buy.packageFk
|
packageFk: buy.packageFk
|
||||||
});
|
});
|
||||||
|
|
||||||
|
await models.ItemMatchProperties.upsert({
|
||||||
|
itemFk: buy.itemFk,
|
||||||
|
name: buy.description,
|
||||||
|
producer: buy.companyName,
|
||||||
|
size: buy.size
|
||||||
|
}, myOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
const createdBuys = await models.Buy.create(buys, options);
|
const createdBuys = await models.Buy.create(buys, myOptions);
|
||||||
const buyIds = createdBuys.map(buy => buy.id);
|
const buyIds = createdBuys.map(buy => buy.id);
|
||||||
|
|
||||||
const stmts = [];
|
const stmts = [];
|
||||||
|
@ -90,7 +96,7 @@ module.exports = Self => {
|
||||||
stmts.push('CALL buy_recalcPrices()');
|
stmts.push('CALL buy_recalcPrices()');
|
||||||
|
|
||||||
const sql = ParameterizedSQL.join(stmts, ';');
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
await conn.executeStmt(sql, options);
|
await conn.executeStmt(sql, myOptions);
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (tx) await tx.rollback();
|
if (tx) await tx.rollback();
|
||||||
|
|
|
@ -37,7 +37,21 @@ module.exports = Self => {
|
||||||
where: {volume: {gte: buy.volume}},
|
where: {volume: {gte: buy.volume}},
|
||||||
order: 'volume ASC'
|
order: 'volume ASC'
|
||||||
}, myOptions);
|
}, myOptions);
|
||||||
buy.packageFk = packaging.id;
|
|
||||||
|
if (packaging)
|
||||||
|
buy.packageFk = packaging.id;
|
||||||
|
|
||||||
|
const reference = await models.ItemMatchProperties.findOne({
|
||||||
|
fields: ['itemFk'],
|
||||||
|
where: {
|
||||||
|
name: buy.description,
|
||||||
|
producer: buy.companyName,
|
||||||
|
size: buy.size
|
||||||
|
}
|
||||||
|
}, myOptions);
|
||||||
|
|
||||||
|
if (reference)
|
||||||
|
buy.itemFk = reference.itemFk;
|
||||||
}
|
}
|
||||||
|
|
||||||
return buys;
|
return buys;
|
||||||
|
|
|
@ -5,6 +5,9 @@
|
||||||
"Buy": {
|
"Buy": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
"ItemMatchProperties": {
|
||||||
|
"dataSource": "vn"
|
||||||
|
},
|
||||||
"EntryLog": {
|
"EntryLog": {
|
||||||
"dataSource": "vn"
|
"dataSource": "vn"
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
{
|
||||||
|
"name": "ItemMatchProperties",
|
||||||
|
"base": "VnModel",
|
||||||
|
"options": {
|
||||||
|
"mysql": {
|
||||||
|
"table": "itemMatchProperties"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"itemFk": {
|
||||||
|
"type": "number",
|
||||||
|
"id": true,
|
||||||
|
"description": "Identifier"
|
||||||
|
},
|
||||||
|
"name": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"producer": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"size": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"relations": {
|
||||||
|
"item": {
|
||||||
|
"type": "belongsTo",
|
||||||
|
"model": "Item",
|
||||||
|
"foreignKey": "itemFk"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,20 +9,6 @@
|
||||||
class="vn-ma-md">
|
class="vn-ma-md">
|
||||||
<div class="vn-w-lg">
|
<div class="vn-w-lg">
|
||||||
<vn-card class="vn-pa-lg">
|
<vn-card class="vn-pa-lg">
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textfield vn-focus
|
|
||||||
vn-one
|
|
||||||
label="Reference"
|
|
||||||
ng-model="$ctrl.import.ref">
|
|
||||||
</vn-textfield>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
|
||||||
<vn-textarea
|
|
||||||
vn-one
|
|
||||||
label="Observation"
|
|
||||||
ng-model="$ctrl.import.observation">
|
|
||||||
</vn-textarea>
|
|
||||||
</vn-horizontal>
|
|
||||||
<vn-horizontal>
|
<vn-horizontal>
|
||||||
<vn-input-file
|
<vn-input-file
|
||||||
vn-one
|
vn-one
|
||||||
|
@ -40,6 +26,20 @@
|
||||||
</append>
|
</append>
|
||||||
</vn-input-file>
|
</vn-input-file>
|
||||||
</vn-horizontal>
|
</vn-horizontal>
|
||||||
|
<vn-horizontal ng-show="$ctrl.import.ref">
|
||||||
|
<vn-textfield vn-focus
|
||||||
|
vn-one
|
||||||
|
label="Reference"
|
||||||
|
ng-model="$ctrl.import.ref">
|
||||||
|
</vn-textfield>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal ng-show="$ctrl.import.observation">
|
||||||
|
<vn-textarea
|
||||||
|
vn-one
|
||||||
|
label="Observation"
|
||||||
|
ng-model="$ctrl.import.observation">
|
||||||
|
</vn-textarea>
|
||||||
|
</vn-horizontal>
|
||||||
<vn-horizontal ng-show="$ctrl.import.buys.length > 0">
|
<vn-horizontal ng-show="$ctrl.import.buys.length > 0">
|
||||||
<table class="vn-table">
|
<table class="vn-table">
|
||||||
<thead>
|
<thead>
|
||||||
|
@ -51,7 +51,6 @@
|
||||||
<th translate center>Grouping</th>
|
<th translate center>Grouping</th>
|
||||||
<th translate center>Buying value</th>
|
<th translate center>Buying value</th>
|
||||||
<th translate center>Box</th>
|
<th translate center>Box</th>
|
||||||
<th translate center>Volume</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody ng-repeat="buy in $ctrl.import.buys">
|
<tbody ng-repeat="buy in $ctrl.import.buys">
|
||||||
|
@ -70,20 +69,19 @@
|
||||||
<tpl-item>
|
<tpl-item>
|
||||||
{{::id}} - {{::name}}
|
{{::id}} - {{::name}}
|
||||||
</tpl-item>
|
</tpl-item>
|
||||||
|
<append>
|
||||||
|
<vn-icon-button
|
||||||
|
icon="filter_alt"
|
||||||
|
vn-click-stop="$ctrl.showFilterDialog(buy)"
|
||||||
|
vn-tooltip="Filter...">
|
||||||
|
</vn-icon-button>
|
||||||
|
</append>
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</td>
|
</td>
|
||||||
<td title="{{::buy.description}}" expand>{{::buy.description | dashIfEmpty}}</td>
|
<td title="{{::buy.description}}" expand>{{::buy.description | dashIfEmpty}}</td>
|
||||||
<td center title="{{::buy.size}}">{{::buy.size | dashIfEmpty}}</td>
|
<td center title="{{::buy.size}}">{{::buy.size | dashIfEmpty}}</td>
|
||||||
<td center>
|
<td center>{{::buy.packing | dashIfEmpty}}</td>
|
||||||
<vn-chip>
|
<td center>{{::buy.grouping | dashIfEmpty}}</td>
|
||||||
<span>{{::buy.packing | dashIfEmpty}}</span>
|
|
||||||
</vn-chip>
|
|
||||||
</td>
|
|
||||||
<td center>
|
|
||||||
<vn-chip>
|
|
||||||
<span>{{::buy.grouping | dashIfEmpty}}</span>
|
|
||||||
</vn-chip>
|
|
||||||
</vn-td>
|
|
||||||
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
|
<td>{{::buy.buyingValue | currency: 'EUR':2}}</td>
|
||||||
<td center title="{{::buy.packageFk | dashIfEmpty}}">
|
<td center title="{{::buy.packageFk | dashIfEmpty}}">
|
||||||
<vn-autocomplete
|
<vn-autocomplete
|
||||||
|
@ -95,7 +93,6 @@
|
||||||
ng-model="buy.packageFk">
|
ng-model="buy.packageFk">
|
||||||
</vn-autocomplete>
|
</vn-autocomplete>
|
||||||
</td>
|
</td>
|
||||||
<td center title="{{::buy.volume}}">{{::buy.volume | number}}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
@ -110,7 +107,95 @@
|
||||||
label="Cancel"
|
label="Cancel"
|
||||||
ui-sref="entry.card.buy.index">
|
ui-sref="entry.card.buy.index">
|
||||||
</vn-button>
|
</vn-button>
|
||||||
</vn-button>
|
|
||||||
</vn-button-bar>
|
</vn-button-bar>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
<vn-dialog
|
||||||
|
vn-id="filterDialog"
|
||||||
|
on-accept="$ctrl.addTime()"
|
||||||
|
message="Filter item">
|
||||||
|
<tpl-body class="itemFilter">
|
||||||
|
<vn-horizontal>
|
||||||
|
<vn-textfield
|
||||||
|
label="Name"
|
||||||
|
ng-model="$ctrl.itemFilterParams.name"
|
||||||
|
vn-focus>
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-textfield
|
||||||
|
label="Size"
|
||||||
|
ng-model="$ctrl.itemFilterParams.size">
|
||||||
|
</vn-textfield>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Producer"
|
||||||
|
ng-model="$ctrl.itemFilterParams.producerFk"
|
||||||
|
url="Producers"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Type"
|
||||||
|
ng-model="$ctrl.itemFilterParams.typeFk"
|
||||||
|
url="ItemTypes"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id">
|
||||||
|
</vn-autocomplete>
|
||||||
|
<vn-autocomplete
|
||||||
|
label="Color"
|
||||||
|
ng-model="$ctrl.itemFilterParams.inkFk"
|
||||||
|
url="Inks"
|
||||||
|
show-field="name"
|
||||||
|
value-field="id">
|
||||||
|
</vn-autocomplete>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-horizontal class="vn-mb-md">
|
||||||
|
<vn-button vn-none
|
||||||
|
label="Search"
|
||||||
|
ng-click="$ctrl.filter()">
|
||||||
|
</vn-button>
|
||||||
|
</vn-horizontal>
|
||||||
|
<vn-crud-model
|
||||||
|
vn-id="itemsModel"
|
||||||
|
url="Items"
|
||||||
|
filter="$ctrl.itemFilter"
|
||||||
|
data="items"
|
||||||
|
limit="10">
|
||||||
|
</vn-crud-model>
|
||||||
|
<vn-data-viewer
|
||||||
|
model="itemsModel"
|
||||||
|
class="vn-w-lg">
|
||||||
|
<vn-table class="scrollable">
|
||||||
|
<vn-thead>
|
||||||
|
<vn-tr>
|
||||||
|
<vn-th shrink>ID</vn-th>
|
||||||
|
<vn-th expand>Item</vn-th>
|
||||||
|
<vn-th number>Size</vn-th>
|
||||||
|
<vn-th expand>Producer</vn-th>
|
||||||
|
<vn-th>Color</vn-th>
|
||||||
|
</vn-tr>
|
||||||
|
</vn-thead>
|
||||||
|
<vn-tbody>
|
||||||
|
<a ng-repeat="item in items"
|
||||||
|
class="clickable vn-tr search-result"
|
||||||
|
ng-click="$ctrl.selectItem(item.id)">
|
||||||
|
<vn-td shrink>
|
||||||
|
<span
|
||||||
|
ng-click="itemDescriptor.show($event, item.id)"
|
||||||
|
class="link">
|
||||||
|
{{::item.id}}
|
||||||
|
</span>
|
||||||
|
</vn-td>
|
||||||
|
<vn-td expand>{{::item.name}}</vn-td>
|
||||||
|
<vn-td number>{{::item.size}}</vn-td>
|
||||||
|
<vn-td expand>{{::item.producer.name}}</vn-td>
|
||||||
|
<vn-td>{{::item.ink.name}}</vn-td>
|
||||||
|
</a>
|
||||||
|
</vn-tbody>
|
||||||
|
</vn-table>
|
||||||
|
</vn-data-viewer>
|
||||||
|
<vn-item-descriptor-popover
|
||||||
|
vn-id="item-descriptor"
|
||||||
|
warehouse-fk="$ctrl.vnConfig.warehouseFk">
|
||||||
|
</vn-item-descriptor-popover>
|
||||||
|
</tpl-body>
|
||||||
|
</vn-dialog>
|
|
@ -29,6 +29,7 @@ class Controller extends Section {
|
||||||
this.$.$applyAsync(() => {
|
this.$.$applyAsync(() => {
|
||||||
this.import.observation = invoice.tx_awb;
|
this.import.observation = invoice.tx_awb;
|
||||||
|
|
||||||
|
const companyName = invoice.tx_company;
|
||||||
const boxes = invoice.boxes;
|
const boxes = invoice.boxes;
|
||||||
const buys = [];
|
const buys = [];
|
||||||
for (let box of boxes) {
|
for (let box of boxes) {
|
||||||
|
@ -37,11 +38,12 @@ class Controller extends Section {
|
||||||
const packing = product.nu_stems_bunch * product.nu_bunches;
|
const packing = product.nu_stems_bunch * product.nu_bunches;
|
||||||
buys.push({
|
buys.push({
|
||||||
description: product.nm_product,
|
description: product.nm_product,
|
||||||
|
companyName: companyName,
|
||||||
size: product.nu_length,
|
size: product.nu_length,
|
||||||
packing: packing,
|
packing: packing,
|
||||||
grouping: product.nu_stems_bunch,
|
grouping: product.nu_stems_bunch,
|
||||||
buyingValue: parseFloat(product.mny_rate_stem),
|
buyingValue: parseFloat(product.mny_rate_stem),
|
||||||
volume: boxVolume
|
volume: boxVolume,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -86,6 +88,59 @@ class Controller extends Section {
|
||||||
? {id: $search}
|
? {id: $search}
|
||||||
: {name: {like: '%' + $search + '%'}};
|
: {name: {like: '%' + $search + '%'}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showFilterDialog(buy) {
|
||||||
|
this.activeBuy = buy;
|
||||||
|
this.itemFilterParams = {};
|
||||||
|
this.itemFilter = {
|
||||||
|
include: [
|
||||||
|
{
|
||||||
|
relation: 'producer',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
relation: 'ink',
|
||||||
|
scope: {
|
||||||
|
fields: ['name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
this.$.filterDialog.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
selectItem(id) {
|
||||||
|
this.activeBuy['itemFk'] = id;
|
||||||
|
this.$.filterDialog.hide();
|
||||||
|
}
|
||||||
|
|
||||||
|
filter() {
|
||||||
|
const filter = this.itemFilter;
|
||||||
|
const params = this.itemFilterParams;
|
||||||
|
const where = {};
|
||||||
|
|
||||||
|
for (let key in params) {
|
||||||
|
const value = params[key];
|
||||||
|
if (!value) continue;
|
||||||
|
|
||||||
|
switch (key) {
|
||||||
|
case 'name':
|
||||||
|
where[key] = {like: `%${value}%`};
|
||||||
|
break;
|
||||||
|
case 'producerFk':
|
||||||
|
case 'typeFk':
|
||||||
|
case 'size':
|
||||||
|
case 'ink':
|
||||||
|
where[key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
filter.where = where;
|
||||||
|
this.$.itemsModel.applyFilter(filter);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Controller.$inject = ['$element', '$scope'];
|
Controller.$inject = ['$element', '$scope'];
|
||||||
|
|
|
@ -2,4 +2,10 @@ vn-entry-buy-import {
|
||||||
.vn-table > tbody td:nth-child(1) {
|
.vn-table > tbody td:nth-child(1) {
|
||||||
width: 250px
|
width: 250px
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.itemFilter {
|
||||||
|
vn-table.scrollable {
|
||||||
|
height: 500px
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -3,4 +3,6 @@ Observation: Observación
|
||||||
Box: Embalaje
|
Box: Embalaje
|
||||||
Import buys: Importar compras
|
Import buys: Importar compras
|
||||||
Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo
|
Some of the imported buys doesn't have an item: Algunas de las compras importadas no tienen un artículo
|
||||||
JSON files only: Solo ficheros JSON
|
JSON files only: Solo ficheros JSON
|
||||||
|
Filter item: Filtrar artículo
|
||||||
|
Filter...: Filtrar...
|
|
@ -1,6 +1,7 @@
|
||||||
<vn-crud-model
|
<vn-crud-model
|
||||||
vn-id="ticketsModel"
|
vn-id="ticketsModel"
|
||||||
limit="10"
|
limit="10"
|
||||||
|
auto-load="true"
|
||||||
data="tickets">
|
data="tickets">
|
||||||
</vn-crud-model>
|
</vn-crud-model>
|
||||||
<vn-card class="summary">
|
<vn-card class="summary">
|
||||||
|
|
|
@ -7,7 +7,7 @@ class Controller extends Summary {
|
||||||
this._invoiceOut = value;
|
this._invoiceOut = value;
|
||||||
if (value && value.id) {
|
if (value && value.id) {
|
||||||
this.getSummary();
|
this.getSummary();
|
||||||
this.getTickets();
|
this.$.ticketsModel.url = `InvoiceOuts/${this.invoiceOut.id}/getTickets`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,14 +19,6 @@ class Controller extends Summary {
|
||||||
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`)
|
return this.$http.get(`InvoiceOuts/${this.invoiceOut.id}/summary`)
|
||||||
.then(res => this.summary = res.data);
|
.then(res => this.summary = res.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTickets() {
|
|
||||||
this.$.$applyAsync(() => {
|
|
||||||
const query = `InvoiceOuts/${this.invoiceOut.id}/getTickets`;
|
|
||||||
this.$.ticketsModel.url = query;
|
|
||||||
this.$.ticketsModel.refresh();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ngModule.vnComponent('vnInvoiceOutSummary', {
|
ngModule.vnComponent('vnInvoiceOutSummary', {
|
||||||
|
|
|
@ -27,17 +27,5 @@ describe('InvoiceOut', () => {
|
||||||
expect(controller.summary).toEqual('the data you are looking for');
|
expect(controller.summary).toEqual('the data you are looking for');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getTickets()', () => {
|
|
||||||
it('should perform a and then call to the ticketModel refresh() method', () => {
|
|
||||||
jest.spyOn(controller.$.ticketsModel, 'refresh');
|
|
||||||
|
|
||||||
controller.getTickets();
|
|
||||||
$scope.$apply();
|
|
||||||
|
|
||||||
expect(controller.$.ticketsModel.url).toEqual('InvoiceOuts/1/getTickets');
|
|
||||||
expect(controller.$.ticketsModel.refresh).toHaveBeenCalledWith();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
Ink: Tinta
|
Ink: Tinta
|
||||||
Origin: Origen
|
Origin: Origen
|
||||||
Producer: Productor.
|
Producer: Productor
|
||||||
With visible: Con visible
|
With visible: Con visible
|
||||||
Field: Campo
|
Field: Campo
|
||||||
More fields: Más campos
|
More fields: Más campos
|
||||||
|
|
|
@ -28,30 +28,57 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.getItemTypeAvailable = async(orderId, itemCategoryId) => {
|
Self.getItemTypeAvailable = async(orderId, itemCategoryId) => {
|
||||||
let stmts = [];
|
const stmts = [];
|
||||||
let stmt;
|
let stmt;
|
||||||
|
|
||||||
let order = await app.models.Order.findById(orderId);
|
const order = await app.models.Order.findById(orderId);
|
||||||
stmt = new ParameterizedSQL('call vn.available_calc(?, ?, ?)', [
|
stmt = new ParameterizedSQL('call vn.available_calc(?, ?, ?)', [
|
||||||
order.landed,
|
order.landed,
|
||||||
order.addressFk,
|
order.addressFk,
|
||||||
order.agencyModeFk
|
order.agencyModeFk
|
||||||
]);
|
]);
|
||||||
stmts.push(stmt);
|
stmts.push(stmt);
|
||||||
stmt = new ParameterizedSQL(`
|
|
||||||
SELECT it.id, it.name, ic.name categoryName
|
stmts.push('DROP TEMPORARY TABLE IF EXISTS tmp.item');
|
||||||
|
|
||||||
|
stmt = new ParameterizedSQL(
|
||||||
|
`CREATE TEMPORARY TABLE tmp.item
|
||||||
|
(PRIMARY KEY (itemFk)) ENGINE = MEMORY
|
||||||
|
SELECT DISTINCT
|
||||||
|
i.id AS itemFk,
|
||||||
|
it.id AS typeFk,
|
||||||
|
it.name,
|
||||||
|
ic.name AS categoryName
|
||||||
FROM tmp.availableCalc ac
|
FROM tmp.availableCalc ac
|
||||||
JOIN cache.available a ON a.calc_id = ac.calcFk
|
JOIN cache.available a ON a.calc_id = ac.calcFk
|
||||||
JOIN item i ON i.id = a.item_id
|
JOIN vn.item i ON i.id = a.item_id
|
||||||
JOIN itemType it ON it.id = i.typeFk
|
JOIN vn.itemType it ON it.id = i.typeFk
|
||||||
JOIN itemCategory ic ON ic.id = it.categoryFk
|
JOIN vn.itemCategory ic ON ic.id = it.categoryFk
|
||||||
WHERE it.categoryFk = ?
|
WHERE it.categoryFk = ?`, [itemCategoryId]
|
||||||
GROUP BY it.id`, [itemCategoryId]
|
|
||||||
);
|
);
|
||||||
let categoriesIndex = stmts.push(stmt) - 1;
|
stmts.push(stmt);
|
||||||
|
|
||||||
let sql = ParameterizedSQL.join(stmts, ';');
|
stmt = new ParameterizedSQL(
|
||||||
let result = await Self.rawStmt(sql);
|
'CALL vn.catalog_calculate(?, ?, ?)', [
|
||||||
|
order.landed,
|
||||||
|
order.addressFk,
|
||||||
|
order.agencyModeFk,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
stmts.push(stmt);
|
||||||
|
|
||||||
|
stmt = new ParameterizedSQL(`
|
||||||
|
SELECT i.typeFk AS id, i.name, i.categoryName
|
||||||
|
FROM tmp.item i
|
||||||
|
JOIN tmp.ticketCalculateItem tci ON tci.itemFk = i.itemFk
|
||||||
|
GROUP BY i.typeFk`
|
||||||
|
);
|
||||||
|
const categoriesIndex = stmts.push(stmt) - 1;
|
||||||
|
|
||||||
|
stmts.push('DROP TEMPORARY TABLE tmp.item');
|
||||||
|
|
||||||
|
const sql = ParameterizedSQL.join(stmts, ';');
|
||||||
|
const result = await Self.rawStmt(sql);
|
||||||
|
|
||||||
return result[categoriesIndex];
|
return result[categoriesIndex];
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,18 +6,18 @@ module.exports = Self => {
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [{
|
||||||
arg: 'sales',
|
arg: 'sales',
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The sales to remove'
|
description: 'The sales to remove'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
arg: 'ticketId',
|
arg: 'ticketId',
|
||||||
type: 'Number',
|
type: 'number',
|
||||||
required: true,
|
required: true,
|
||||||
description: 'The ticket id'
|
description: 'The ticket id'
|
||||||
}],
|
}],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -27,10 +27,25 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.deleteSales = async(ctx, sales, ticketId) => {
|
Self.deleteSales = async(ctx, sales, ticketId) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const canEditSales = await models.Sale.canEdit(ctx, sales);
|
const canEditSales = await models.Sale.canEdit(ctx, sales);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
||||||
if (!isTicketEditable)
|
if (!isTicketEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
@ -39,11 +54,26 @@ module.exports = Self => {
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
let deletions = '';
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
const deletedSale = models.Sale.destroyById(sale.id);
|
const deletedSale = models.Sale.destroyById(sale.id);
|
||||||
|
deletions += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
|
|
||||||
promises.push(deletedSale);
|
promises.push(deletedSale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Deleted sales from ticket', {
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
deletions: deletions
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
return Promise.all(promises);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -5,24 +5,27 @@ module.exports = Self => {
|
||||||
Self.remoteMethodCtx('reserve', {
|
Self.remoteMethodCtx('reserve', {
|
||||||
description: 'Change the state of a ticket',
|
description: 'Change the state of a ticket',
|
||||||
accessType: 'WRITE',
|
accessType: 'WRITE',
|
||||||
accepts: [{
|
accepts: [
|
||||||
arg: 'ticketId',
|
{
|
||||||
type: 'Number',
|
arg: 'ticketId',
|
||||||
required: true,
|
type: 'number',
|
||||||
description: 'The ticket id'
|
required: true,
|
||||||
}, {
|
description: 'The ticket id'
|
||||||
arg: 'sales',
|
},
|
||||||
type: ['Object'],
|
{
|
||||||
required: true,
|
arg: 'sales',
|
||||||
description: 'The sale to reserve'
|
type: ['object'],
|
||||||
},
|
required: true,
|
||||||
{
|
description: 'The sale to reserve'
|
||||||
arg: 'reserved',
|
},
|
||||||
type: 'Boolean',
|
{
|
||||||
required: true
|
arg: 'reserved',
|
||||||
}],
|
type: 'boolean',
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: ['Object'],
|
type: ['object'],
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -32,7 +35,9 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.reserve = async(ctx, ticketId, sales, reserved) => {
|
Self.reserve = async(ctx, ticketId, sales, reserved) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
const isTicketEditable = await models.Ticket.isEditable(ctx, ticketId);
|
||||||
if (!isTicketEditable)
|
if (!isTicketEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
@ -42,12 +47,50 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
|
let changesMade = '';
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
|
if (sale.reserved != reserved) {
|
||||||
promises.push(reservedSale);
|
const oldState = sale.reserved ? 'reserved' : 'regular';
|
||||||
|
const newState = reserved ? 'reserved' : 'regular';
|
||||||
|
|
||||||
|
const reservedSale = models.Sale.update({id: sale.id}, {reserved: reserved});
|
||||||
|
|
||||||
|
promises.push(reservedSale);
|
||||||
|
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${$t('State')}: ${$t(oldState)} ➔ *${$t(newState)}*`;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises);
|
const result = await Promise.all(promises);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(ticketId, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Changed sale reserved state', {
|
||||||
|
ticketId: ticketId,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${ticketId}/sale`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -17,7 +17,14 @@ describe('sale deleteSales()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket of the given sales is not editable', async() => {
|
it('should throw an error if the ticket of the given sales is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let error;
|
let error;
|
||||||
|
|
||||||
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
|
const sales = [{id: 1, instance: 0}, {id: 2, instance: 1}];
|
||||||
|
@ -33,7 +40,13 @@ describe('sale deleteSales()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should delete the sale', async() => {
|
it('should delete the sale', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const sales = [{id: newSale.id, instance: 0}];
|
const sales = [{id: newSale.id, instance: 0}];
|
||||||
const ticketId = 16;
|
const ticketId = 16;
|
||||||
|
|
|
@ -1,7 +1,14 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('sale reserve()', () => {
|
describe('sale reserve()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
afterAll(async done => {
|
afterAll(async done => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
let ctx = {req: {accessToken: {userId: 9}}};
|
||||||
let params = {
|
let params = {
|
||||||
|
|
|
@ -17,7 +17,13 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket is not editable', async() => {
|
it('should throw an error if the ticket is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let immutableSaleId = 1;
|
let immutableSaleId = 1;
|
||||||
let price = 5;
|
let price = 5;
|
||||||
|
|
||||||
|
@ -31,7 +37,14 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return 0 if the price is an empty string', async() => {
|
it('should return 0 if the price is an empty string', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let price = '';
|
let price = '';
|
||||||
|
|
||||||
await app.models.Sale.updatePrice(ctx, saleId, price);
|
await app.models.Sale.updatePrice(ctx, saleId, price);
|
||||||
|
@ -46,7 +59,14 @@ describe('sale updatePrice()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should now set price as a number in a string', async() => {
|
it('should now set price as a number in a string', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let price = '8';
|
let price = '8';
|
||||||
|
|
||||||
await app.models.Sale.updatePrice(ctx, saleId, price);
|
await app.models.Sale.updatePrice(ctx, saleId, price);
|
||||||
|
|
|
@ -1,7 +1,13 @@
|
||||||
const app = require('vn-loopback/server/server');
|
const app = require('vn-loopback/server/server');
|
||||||
|
|
||||||
describe('sale updateQuantity()', () => {
|
describe('sale updateQuantity()', () => {
|
||||||
const ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
it('should throw an error if the quantity is not a number', async() => {
|
it('should throw an error if the quantity is not a number', async() => {
|
||||||
let error;
|
let error;
|
||||||
|
|
|
@ -19,7 +19,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
returns: {
|
returns: {
|
||||||
type: 'Number',
|
type: 'number',
|
||||||
root: true
|
root: true
|
||||||
},
|
},
|
||||||
http: {
|
http: {
|
||||||
|
@ -29,29 +29,37 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updatePrice = async(ctx, id, newPrice) => {
|
Self.updatePrice = async(ctx, id, newPrice) => {
|
||||||
let models = Self.app.models;
|
const $t = ctx.req.__; // $translate
|
||||||
let tx = await Self.beginTransaction({});
|
const models = Self.app.models;
|
||||||
|
const tx = await Self.beginTransaction({});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let options = {transaction: tx};
|
const options = {transaction: tx};
|
||||||
|
|
||||||
let filter = {
|
const filter = {
|
||||||
include: {
|
include: {
|
||||||
relation: 'ticket',
|
relation: 'ticket',
|
||||||
scope: {
|
scope: {
|
||||||
include: {
|
include: {
|
||||||
relation: 'client',
|
relation: 'client',
|
||||||
scope: {
|
scope: {
|
||||||
fields: ['salesPersonFk']
|
fields: ['salesPersonFk'],
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
fields: ['id', 'clientFk']
|
fields: ['id', 'clientFk']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let sale = await models.Sale.findById(id, filter, options);
|
|
||||||
|
|
||||||
let isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
|
const sale = await models.Sale.findById(id, filter, options);
|
||||||
|
|
||||||
|
const isEditable = await models.Ticket.isEditable(ctx, sale.ticketFk);
|
||||||
if (!isEditable)
|
if (!isEditable)
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
|
||||||
|
@ -60,21 +68,19 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
|
const oldPrice = sale.price;
|
||||||
const userId = ctx.req.accessToken.userId;
|
const userId = ctx.req.accessToken.userId;
|
||||||
|
const usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
|
||||||
|
const componentCode = usesMana ? 'mana' : 'buyerDiscount';
|
||||||
|
const discount = await models.Component.findOne({where: {code: componentCode}}, options);
|
||||||
|
const componentId = discount.id;
|
||||||
|
const componentValue = newPrice - sale.price;
|
||||||
|
|
||||||
let usesMana = await models.WorkerMana.findOne({where: {workerFk: userId}, fields: 'amount'}, options);
|
const where = {
|
||||||
|
|
||||||
let componentCode = usesMana ? 'mana' : 'buyerDiscount';
|
|
||||||
|
|
||||||
let discount = await models.Component.findOne({where: {code: componentCode}}, options);
|
|
||||||
let componentId = discount.id;
|
|
||||||
let componentValue = newPrice - sale.price;
|
|
||||||
|
|
||||||
let where = {
|
|
||||||
componentFk: componentId,
|
componentFk: componentId,
|
||||||
saleFk: id
|
saleFk: id
|
||||||
};
|
};
|
||||||
let saleComponent = await models.SaleComponent.findOne({where}, options);
|
const saleComponent = await models.SaleComponent.findOne({where}, options);
|
||||||
|
|
||||||
if (saleComponent) {
|
if (saleComponent) {
|
||||||
await models.SaleComponent.updateAll(where, {
|
await models.SaleComponent.updateAll(where, {
|
||||||
|
@ -92,6 +98,22 @@ module.exports = Self => {
|
||||||
query = `CALL vn.manaSpellersRequery(?)`;
|
query = `CALL vn.manaSpellersRequery(?)`;
|
||||||
await Self.rawSql(query, [userId], options);
|
await Self.rawSql(query, [userId], options);
|
||||||
|
|
||||||
|
const salesPerson = sale.ticket().client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
const message = $t('Changed sale price', {
|
||||||
|
ticketId: sale.ticket().id,
|
||||||
|
itemId: sale.itemFk,
|
||||||
|
concept: sale.concept,
|
||||||
|
quantity: sale.quantity,
|
||||||
|
oldPrice: oldPrice,
|
||||||
|
newPrice: newPrice,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
|
||||||
|
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
|
|
||||||
return sale;
|
return sale;
|
||||||
|
|
|
@ -26,7 +26,8 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updateQuantity = async(ctx, id, quantity) => {
|
Self.updateQuantity = async(ctx, id, newQuantity) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const canEditSale = await models.Sale.canEdit(ctx, [id]);
|
const canEditSale = await models.Sale.canEdit(ctx, [id]);
|
||||||
|
@ -34,13 +35,51 @@ module.exports = Self => {
|
||||||
if (!canEditSale)
|
if (!canEditSale)
|
||||||
throw new UserError(`Sale(s) blocked, please contact production`);
|
throw new UserError(`Sale(s) blocked, please contact production`);
|
||||||
|
|
||||||
if (isNaN(quantity))
|
if (isNaN(newQuantity))
|
||||||
throw new UserError(`The value should be a number`);
|
throw new UserError(`The value should be a number`);
|
||||||
|
|
||||||
let currentLine = await models.Sale.findOne({where: {id: id}});
|
const filter = {
|
||||||
if (quantity > currentLine.quantity)
|
include: {
|
||||||
|
relation: 'ticket',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const sale = await models.Sale.findById(id, filter);
|
||||||
|
|
||||||
|
if (newQuantity > sale.quantity)
|
||||||
throw new UserError('The new quantity should be smaller than the old one');
|
throw new UserError('The new quantity should be smaller than the old one');
|
||||||
|
|
||||||
return await currentLine.updateAttributes({quantity: quantity});
|
const oldQuantity = sale.quantity;
|
||||||
|
const result = await sale.updateAttributes({quantity: newQuantity});
|
||||||
|
|
||||||
|
const salesPerson = sale.ticket().client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
const message = $t('Changed sale quantity', {
|
||||||
|
ticketId: sale.ticket().id,
|
||||||
|
itemId: sale.itemFk,
|
||||||
|
concept: sale.concept,
|
||||||
|
oldQuantity: oldQuantity,
|
||||||
|
newQuantity: newQuantity,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${sale.ticket().id}/sale`,
|
||||||
|
itemUrl: `${origin}/#!/item/${sale.itemFk}/summary`
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -33,6 +33,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.addSale = async(ctx, id, itemId, quantity) => {
|
Self.addSale = async(ctx, id, itemId, quantity) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
|
|
||||||
const isEditable = await models.Ticket.isEditable(ctx, id);
|
const isEditable = await models.Ticket.isEditable(ctx, id);
|
||||||
|
@ -40,7 +41,19 @@ module.exports = Self => {
|
||||||
throw new UserError(`The sales of this ticket can't be modified`);
|
throw new UserError(`The sales of this ticket can't be modified`);
|
||||||
|
|
||||||
const item = await models.Item.findById(itemId);
|
const item = await models.Item.findById(itemId);
|
||||||
const ticket = await models.Ticket.findById(id);
|
const ticket = await models.Ticket.findById(id, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
|
const res = await models.Item.getVisibleAvailable(itemId, ticket.warehouseFk, ticket.shipped);
|
||||||
|
|
||||||
|
@ -63,6 +76,20 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const addition = `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity})`;
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Added sale to ticket', {
|
||||||
|
ticketId: id,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${id}/sale`,
|
||||||
|
addition: addition
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
return sale;
|
return sale;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -230,7 +230,7 @@ module.exports = Self => {
|
||||||
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
|
ticketUrl: `${origin}/#!/ticket/${args.id}/sale`,
|
||||||
changes: changesMade
|
changes: changesMade
|
||||||
});
|
});
|
||||||
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message, myOptions);
|
await models.Chat.sendCheckingPresence(ctx, salesPersonId, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (tx) await tx.commit();
|
if (tx) await tx.commit();
|
||||||
|
|
|
@ -12,7 +12,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should create a new sale for the ticket with id 13', async() => {
|
it('should create a new sale for the ticket with id 13', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const itemId = 4;
|
const itemId = 4;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
newSale = await app.models.Ticket.addSale(ctx, ticketId, itemId, quantity);
|
newSale = await app.models.Ticket.addSale(ctx, ticketId, itemId, quantity);
|
||||||
|
@ -21,7 +27,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add a sale if the item quantity is not available', async() => {
|
it('should not be able to add a sale if the item quantity is not available', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const itemId = 11;
|
const itemId = 11;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
|
|
||||||
|
@ -36,7 +48,13 @@ describe('ticket addSale()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not be able to add a sale if the ticket is not editable', async() => {
|
it('should not be able to add a sale if the ticket is not editable', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const notEditableTicketId = 1;
|
const notEditableTicketId = 1;
|
||||||
const itemId = 4;
|
const itemId = 4;
|
||||||
const quantity = 10;
|
const quantity = 10;
|
||||||
|
|
|
@ -35,7 +35,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if no sales were selected', async() => {
|
it('should throw an error if no sales were selected', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [];
|
const sales = [];
|
||||||
|
@ -51,7 +57,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if no sales belong to different tickets', async() => {
|
it('should throw an error if no sales belong to different tickets', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [1, 14];
|
const sales = [1, 14];
|
||||||
|
@ -67,7 +79,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if the ticket is invoiced already', async() => {
|
it('should throw an error if the ticket is invoiced already', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
let error;
|
let error;
|
||||||
const ticketId = 1;
|
const ticketId = 1;
|
||||||
const sales = [1];
|
const sales = [1];
|
||||||
|
@ -83,7 +101,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the discount if the salesPerson has mana', async() => {
|
it('should update the discount if the salesPerson has mana', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 18}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 18},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [originalSaleId];
|
const sales = [originalSaleId];
|
||||||
const newDiscount = 100;
|
const newDiscount = 100;
|
||||||
|
@ -105,7 +129,13 @@ describe('sale updateDiscount()', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should update the discount and add company discount component if the worker does not have mana', async() => {
|
it('should update the discount and add company discount component if the worker does not have mana', async() => {
|
||||||
let ctx = {req: {accessToken: {userId: 9}}};
|
const ctx = {
|
||||||
|
req: {
|
||||||
|
accessToken: {userId: 9},
|
||||||
|
headers: {origin: 'localhost:5000'},
|
||||||
|
__: () => {}
|
||||||
|
}
|
||||||
|
};
|
||||||
const ticketId = 11;
|
const ticketId = 11;
|
||||||
const sales = [originalSaleId];
|
const sales = [originalSaleId];
|
||||||
const newDiscount = 100;
|
const newDiscount = 100;
|
||||||
|
|
|
@ -17,7 +17,8 @@ module.exports = Self => {
|
||||||
description: 'The sales id',
|
description: 'The sales id',
|
||||||
type: ['number'],
|
type: ['number'],
|
||||||
required: true,
|
required: true,
|
||||||
}, {
|
},
|
||||||
|
{
|
||||||
arg: 'newDiscount',
|
arg: 'newDiscount',
|
||||||
description: 'The new discount',
|
description: 'The new discount',
|
||||||
type: 'number',
|
type: 'number',
|
||||||
|
@ -35,6 +36,7 @@ module.exports = Self => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
|
Self.updateDiscount = async(ctx, id, salesIds, newDiscount) => {
|
||||||
|
const $t = ctx.req.__; // $translate
|
||||||
const models = Self.app.models;
|
const models = Self.app.models;
|
||||||
const tx = await Self.beginTransaction({});
|
const tx = await Self.beginTransaction({});
|
||||||
|
|
||||||
|
@ -59,7 +61,7 @@ module.exports = Self => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let sales = await models.Sale.find(filter, options);
|
const sales = await models.Sale.find(filter, options);
|
||||||
|
|
||||||
if (sales.length === 0)
|
if (sales.length === 0)
|
||||||
throw new UserError('Please select at least one sale');
|
throw new UserError('Please select at least one sale');
|
||||||
|
@ -94,8 +96,11 @@ module.exports = Self => {
|
||||||
where: {code: componentCode}}, options);
|
where: {code: componentCode}}, options);
|
||||||
|
|
||||||
const componentId = discountComponent.id;
|
const componentId = discountComponent.id;
|
||||||
let promises = [];
|
const promises = [];
|
||||||
|
let changesMade = '';
|
||||||
|
|
||||||
for (let sale of sales) {
|
for (let sale of sales) {
|
||||||
|
const oldDiscount = sale.discount;
|
||||||
const value = ((-sale.price * newDiscount) / 100);
|
const value = ((-sale.price * newDiscount) / 100);
|
||||||
const newComponent = models.SaleComponent.upsert({
|
const newComponent = models.SaleComponent.upsert({
|
||||||
saleFk: sale.id,
|
saleFk: sale.id,
|
||||||
|
@ -105,6 +110,7 @@ module.exports = Self => {
|
||||||
const updatedSale = sale.updateAttribute('discount', newDiscount, options);
|
const updatedSale = sale.updateAttribute('discount', newDiscount, options);
|
||||||
|
|
||||||
promises.push(newComponent, updatedSale);
|
promises.push(newComponent, updatedSale);
|
||||||
|
changesMade += `\r\n-${sale.itemFk}: ${sale.concept} (${sale.quantity}) ${oldDiscount}% ➔ *${newDiscount}%*`;
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(promises);
|
await Promise.all(promises);
|
||||||
|
@ -112,6 +118,32 @@ module.exports = Self => {
|
||||||
const query = `call vn.manaSpellersRequery(?)`;
|
const query = `call vn.manaSpellersRequery(?)`;
|
||||||
await Self.rawSql(query, [userId], options);
|
await Self.rawSql(query, [userId], options);
|
||||||
|
|
||||||
|
const ticket = await models.Ticket.findById(id, {
|
||||||
|
include: {
|
||||||
|
relation: 'client',
|
||||||
|
scope: {
|
||||||
|
include: {
|
||||||
|
relation: 'salesPersonUser',
|
||||||
|
scope: {
|
||||||
|
fields: ['id', 'name']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, options);
|
||||||
|
|
||||||
|
const salesPerson = ticket.client().salesPersonUser();
|
||||||
|
if (salesPerson) {
|
||||||
|
const origin = ctx.req.headers.origin;
|
||||||
|
|
||||||
|
const message = $t('Changed sale discount', {
|
||||||
|
ticketId: id,
|
||||||
|
ticketUrl: `${origin}/#!/ticket/${id}/sale`,
|
||||||
|
changes: changesMade
|
||||||
|
});
|
||||||
|
await models.Chat.sendCheckingPresence(ctx, salesPerson.id, message);
|
||||||
|
}
|
||||||
|
|
||||||
await tx.commit();
|
await tx.commit();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
await tx.rollback();
|
await tx.rollback();
|
||||||
|
|
|
@ -82,7 +82,10 @@ class Controller extends Section {
|
||||||
if (!this.ticket) return;
|
if (!this.ticket) return;
|
||||||
|
|
||||||
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
|
this.$http.get(`Tickets/${this.ticket.id}/getVolume`)
|
||||||
.then(res => this.ticketVolume = res.data[0].volume);
|
.then(res => {
|
||||||
|
if (res.data.length)
|
||||||
|
this.ticketVolume = res.data[0].volume;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,13 @@ class Controller extends Section {
|
||||||
|
|
||||||
getMana() {
|
getMana() {
|
||||||
this.$http.get(`Tickets/${this.$params.id}/getSalesPersonMana`)
|
this.$http.get(`Tickets/${this.$params.id}/getSalesPersonMana`)
|
||||||
.then(res => this.edit.mana = res.data);
|
.then(res => {
|
||||||
|
this.edit.mana = res.data;
|
||||||
|
this.$.$applyAsync(() => {
|
||||||
|
this.$.editDiscount.relocate();
|
||||||
|
this.$.editPricePopover.relocate();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -42,6 +42,8 @@ describe('Ticket', () => {
|
||||||
$scope.sms = {open: () => {}};
|
$scope.sms = {open: () => {}};
|
||||||
$scope.ticket = ticket;
|
$scope.ticket = ticket;
|
||||||
$scope.model = crudModel;
|
$scope.model = crudModel;
|
||||||
|
$scope.editDiscount = {relocate: () => {}};
|
||||||
|
$scope.editPricePopover = {relocate: () => {}};
|
||||||
$httpBackend = _$httpBackend_;
|
$httpBackend = _$httpBackend_;
|
||||||
Object.defineProperties($state.params, {
|
Object.defineProperties($state.params, {
|
||||||
id: {
|
id: {
|
||||||
|
|
Loading…
Reference in New Issue