5914-transferInvoiceOut #1761
|
@ -0,0 +1,6 @@
|
|||
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
|
||||
VALUES
|
||||
('CplusRectificationType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||
('CplusInvoiceType477', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||
('InvoiceCorrectionType', '*', 'READ', 'ALLOW', 'ROLE', 'administrative'),
|
||||
('InvoiceOut', 'transferInvoice', 'WRITE', 'ALLOW', 'ROLE', 'administrative');
|
|
@ -604,7 +604,7 @@ INSERT INTO `vn`.`invoiceOutSerial` (`code`, `description`, `isTaxed`, `taxAreaF
|
|||
|
||||
INSERT INTO `vn`.`invoiceOut`(`id`, `serial`, `amount`, `issued`,`clientFk`, `created`, `companyFk`, `dued`, `booked`, `bankFk`, `hasPdf`)
|
||||
VALUES
|
||||
(1, 'T', 1014.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(1, 'T', 1026.24, util.VN_CURDATE(), 1101, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(2, 'T', 121.36, util.VN_CURDATE(), 1102, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(3, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
(4, 'T', 8.88, util.VN_CURDATE(), 1103, util.VN_CURDATE(), 442, util.VN_CURDATE(), util.VN_CURDATE(), 1, 0),
|
||||
|
@ -2977,3 +2977,9 @@ INSERT INTO vn.XDiario (id, ASIEN, FECHA, SUBCTA, CONTRA, CONCEPTO, EURODEBE, EU
|
|||
INSERT INTO `vn`.`mistakeType` (`id`, `description`)
|
||||
VALUES
|
||||
(1, 'Incorrect quantity');
|
||||
|
||||
jorgep marked this conversation as resolved
Outdated
|
||||
INSERT INTO `vn`.`invoiceCorrectionType` (`id`, `description`)
|
||||
VALUES
|
||||
(1, 'Error in VAT calculation'),
|
||||
(2, 'Error in sales details'),
|
||||
(3, 'Error in customer data');
|
|
@ -212,7 +212,7 @@ describe('Ticket Edit sale path', () => {
|
|||
|
||||
it('should log in as salesAssistant and navigate to ticket sales', async() => {
|
||||
await page.loginAndModule('salesAssistant', 'ticket');
|
||||
await page.accessToSearchResult('17');
|
||||
await page.accessToSearchResult('15');
|
||||
await page.accessToSection('ticket.card.sale');
|
||||
});
|
||||
|
||||
|
@ -316,7 +316,7 @@ describe('Ticket Edit sale path', () => {
|
|||
});
|
||||
|
||||
it('should confirm the transfered quantity is the correct one', async() => {
|
||||
const result = await page.waitToGetProperty(selectors.ticketSales.secondSaleQuantityCell, 'innerText');
|
||||
const result = await page.waitToGetProperty(selectors.ticketSales.firstSaleQuantityCell, 'innerText');
|
||||
|
||||
expect(result).toContain('20');
|
||||
});
|
||||
|
@ -370,7 +370,7 @@ describe('Ticket Edit sale path', () => {
|
|||
await page.waitToClick(selectors.ticketSales.moveToNewTicketButton);
|
||||
const message = await page.waitForSnackbar();
|
||||
|
||||
expect(message.text).toContain(`You can't create a ticket for a inactive client`);
|
||||
expect(message.text).toContain(`You can't create a ticket for an inactive client`);
|
||||
|
||||
await page.closePopup();
|
||||
});
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
"Agency cannot be blank": "Agency cannot be blank",
|
||||
"The IBAN does not have the correct format": "The IBAN does not have the correct format",
|
||||
"You can't make changes on the basic data of an confirmed order or with rows": "You can't make changes on the basic data of an confirmed order or with rows",
|
||||
"You can't create a ticket for a inactive client": "You can't create a ticket for a inactive client",
|
||||
"You can't create a ticket for an inactive client": "You can't create a ticket for an inactive client",
|
||||
"Worker cannot be blank": "Worker cannot be blank",
|
||||
"You must delete the claim id %d first": "You must delete the claim id %d first",
|
||||
"You don't have enough privileges": "You don't have enough privileges",
|
||||
|
@ -188,7 +188,14 @@
|
|||
"The ticket doesn't exist.": "The ticket doesn't exist.",
|
||||
"The sales do not exists": "The sales do not exists",
|
||||
"Ticket without Route": "Ticket without route",
|
||||
"Select a different client": "Select a different client",
|
||||
"Fill all the fields": "Fill all the fields",
|
||||
"Error while generating PDF": "Error while generating PDF",
|
||||
"Can't invoice to future": "Can't invoice to future",
|
||||
"This ticket is already invoiced": "This ticket is already invoiced",
|
||||
"Negative basis of tickets: 23": "Negative basis of tickets: 23",
|
||||
"Booking completed": "Booking complete",
|
||||
"The ticket is in preparation": "The ticket [{{ticketId}}]({{{ticketUrl}}}) of the sales person {{salesPersonId}} is in preparation",
|
||||
"You can only add negative amounts in refund tickets": "You can only add negative amounts in refund tickets"
|
||||
}
|
||||
|
||||
|
|
|
@ -5,10 +5,10 @@
|
|||
"The default consignee can not be unchecked": "No se puede desmarcar el consignatario predeterminado",
|
||||
"Unable to default a disabled consignee": "No se puede poner predeterminado un consignatario desactivado",
|
||||
"Can't be blank": "No puede estar en blanco",
|
||||
"Invalid TIN": "NIF/CIF invalido",
|
||||
"Invalid TIN": "NIF/CIF inválido",
|
||||
"TIN must be unique": "El NIF/CIF debe ser único",
|
||||
"A client with that Web User name already exists": "Ya existe un cliente con ese Usuario Web",
|
||||
"Is invalid": "Is invalid",
|
||||
"Is invalid": "Es inválido",
|
||||
"Quantity cannot be zero": "La cantidad no puede ser cero",
|
||||
"Enter an integer different to zero": "Introduce un entero distinto de cero",
|
||||
"Package cannot be blank": "El embalaje no puede estar en blanco",
|
||||
|
@ -55,17 +55,17 @@
|
|||
"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",
|
||||
"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",
|
||||
"INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no esta permitido el uso de la letra ñ",
|
||||
"You can't make changes on the basic data of an confirmed order or with rows": "No puedes cambiar los datos básicos de una orden con artículos",
|
||||
"INVALID_USER_NAME": "El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no está permitido el uso de la letra ñ",
|
||||
"You can't create a ticket for a frozen client": "No puedes crear un ticket para un cliente congelado",
|
||||
"You can't create a ticket for a inactive client": "No puedes crear un ticket para un cliente inactivo",
|
||||
"You can't create a ticket for an inactive client": "No puedes crear un ticket para un cliente inactivo",
|
||||
"Tag value cannot be blank": "El valor del tag no puede quedar en blanco",
|
||||
"ORDER_EMPTY": "Cesta vacía",
|
||||
"You don't have enough privileges to do that": "No tienes permisos para cambiar esto",
|
||||
"NO SE PUEDE DESACTIVAR EL CONSIGNAT": "NO SE PUEDE DESACTIVAR EL CONSIGNAT",
|
||||
"Error. El NIF/CIF está repetido": "Error. El NIF/CIF está repetido",
|
||||
"Street cannot be empty": "Dirección no puede estar en blanco",
|
||||
"City cannot be empty": "Cuidad no puede estar en blanco",
|
||||
"City cannot be empty": "Ciudad no puede estar en blanco",
|
||||
"Code cannot be blank": "Código no puede estar en blanco",
|
||||
"You cannot remove this department": "No puedes eliminar este departamento",
|
||||
"The extension must be unique": "La extensión debe ser unica",
|
||||
|
@ -102,8 +102,8 @@
|
|||
"You can't delete a confirmed order": "No puedes borrar un pedido confirmado",
|
||||
"The social name has an invalid format": "El nombre fiscal tiene un formato incorrecto",
|
||||
"Invalid quantity": "Cantidad invalida",
|
||||
"This postal code is not valid": "This postal code is not valid",
|
||||
"is invalid": "is invalid",
|
||||
"This postal code is not valid": "Este código postal no es válido",
|
||||
"is invalid": "es inválido",
|
||||
"The postcode doesn't exist. Please enter a correct one": "El código postal no existe. Por favor, introduce uno correcto",
|
||||
"The department name can't be repeated": "El nombre del departamento no puede repetirse",
|
||||
"This phone already exists": "Este teléfono ya existe",
|
||||
|
@ -112,8 +112,8 @@
|
|||
"You cannot delete a ticket that part of it is being prepared": "No puedes eliminar un ticket en el que una parte que está siendo preparada",
|
||||
"You must delete all the buy requests first": "Debes eliminar todas las peticiones de compra primero",
|
||||
"You should specify a date": "Debes especificar una fecha",
|
||||
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fín",
|
||||
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fín",
|
||||
"You should specify at least a start or end date": "Debes especificar al menos una fecha de inicio o de fin",
|
||||
"Start date should be lower than end date": "La fecha de inicio debe ser menor que la fecha de fin",
|
||||
"You should mark at least one week day": "Debes marcar al menos un día de la semana",
|
||||
"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",
|
||||
|
@ -144,15 +144,15 @@
|
|||
"Unable to clone this travel": "No ha sido posible clonar este travel",
|
||||
"This thermograph id already exists": "La id del termógrafo ya existe",
|
||||
"Choose a date range or days forward": "Selecciona un rango de fechas o días en adelante",
|
||||
"ORDER_ALREADY_CONFIRMED": "ORDER_ALREADY_CONFIRMED",
|
||||
"ORDER_ALREADY_CONFIRMED": "ORDEN YA CONFIRMADA",
|
||||
"Invalid password": "Invalid password",
|
||||
"Password does not meet requirements": "La contraseña no cumple los requisitos",
|
||||
"Role already assigned": "Role already assigned",
|
||||
"Invalid role name": "Invalid role name",
|
||||
"Role name must be written in camelCase": "Role name must be written in camelCase",
|
||||
"Email already exists": "Email already exists",
|
||||
"User already exists": "User already exists",
|
||||
"Absence change notification on the labour calendar": "Notificacion de cambio de ausencia en el calendario laboral",
|
||||
"Role already assigned": "Rol ya asignado",
|
||||
"Invalid role name": "Nombre de rol no válido",
|
||||
"Role name must be written in camelCase": "El nombre del rol debe escribirse en camelCase",
|
||||
"Email already exists": "El correo ya existe",
|
||||
"User already exists": "El/La usuario/a ya existe",
|
||||
"Absence change notification on the labour calendar": "Notificación de cambio de ausencia en el calendario laboral",
|
||||
"Record of hours week": "Registro de horas semana {{week}} año {{year}} ",
|
||||
"Created absence": "El empleado <strong>{{author}}</strong> ha añadido una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> para el día {{dated}}.",
|
||||
"Deleted absence": "El empleado <strong>{{author}}</strong> ha eliminado una ausencia de tipo '{{absenceType}}' a <a href='{{{workerUrl}}}'><strong>{{employee}}</strong></a> del día {{dated}}.",
|
||||
|
@ -317,6 +317,9 @@
|
|||
"The ticket doesn't exist.": "No existe el ticket.",
|
||||
"Social name should be uppercase": "La razón social debe ir en mayúscula",
|
||||
"Street should be uppercase": "La dirección fiscal debe ir en mayúscula",
|
||||
"Ticket without Route": "Ticket sin ruta",
|
||||
"Select a different client": "Seleccione un cliente distinto",
|
||||
"Fill all the fields": "Rellene todos los campos",
|
||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
"NIF/CIF invalido" podría llevar una tilde: "NIF/CIF inválido". "NIF/CIF invalido" podría llevar una tilde: "NIF/CIF inválido".
"Cuidad no puede estar en blanco" tiene un pequeño error tipográfico, debe ser "Ciudad no puede estar en blanco".
"Is invalid" y "is invalid" no se han traducido al español.
"This postal code is not valid" también está en inglés.
"Role already assigned", "Invalid role name", etc. están en inglés y no se han traducido.
"No puedes cambiar los datos basicos de una orden con artículos" podría llevar tilde en "básicos".
"El nombre de usuario solo debe contener letras minúsculas o, a partir del segundo carácter, números o subguiones, no esta permitido el uso de la letra ñ" podría llevar tilde en "está"
"La fecha de inicio debe ser menor que la fecha de fín" sin tilde en "fin".
"Debes especificar una fecha de inicio o de fín" sin tilde en "fin".
[chatgpt]
|
||||
"The response is not a PDF": "La respuesta no es un PDF",
|
||||
"Ticket without Route": "Ticket sin ruta",
|
||||
"Booking completed": "Reserva completada",
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = Self => {
|
|||
}
|
||||
});
|
||||
|
||||
Self.makePdfAndNotify = async function(ctx, id, printerFk) {
|
||||
Self.makePdfAndNotify = async function(ctx, id, printerFk, options) {
|
||||
const models = Self.app.models;
|
||||
|
||||
options = typeof options == 'object'
|
||||
|
|
|
@ -3,7 +3,7 @@ const LoopBackContext = require('loopback-context');
|
|||
|
||||
describe('InvoiceOut refund()', () => {
|
||||
const userId = 5;
|
||||
const ctx = {req: {accessToken: userId}};
|
||||
const ctx = {req: {accessToken: userId}, args: {}};
|
||||
const withWarehouse = true;
|
||||
const activeCtx = {
|
||||
accessToken: {userId: userId},
|
||||
|
|
|
@ -0,0 +1,68 @@
|
|||
|
||||
const models = require('vn-loopback/server/server').models;
|
||||
const LoopBackContext = require('loopback-context');
|
||||
|
||||
describe('InvoiceOut tranferInvoice()', () => {
|
||||
const activeCtx = {
|
||||
accessToken: {userId: 5},
|
||||
http: {
|
||||
req: {
|
||||
headers: {origin: 'http://localhost'}
|
||||
}
|
||||
}
|
||||
};
|
||||
const ctx = {req: activeCtx};
|
||||
|
||||
beforeEach(() => {
|
||||
spyOn(LoopBackContext, 'getCurrentContext').and.returnValue({
|
||||
active: activeCtx
|
||||
});
|
||||
});
|
||||
|
||||
it('should return the id of the created issued invoice', async() => {
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const args = {
|
||||
id: '1',
|
||||
ref: 'T4444444',
|
||||
newClientFk: 1,
|
||||
cplusRectificationId: 1,
|
||||
cplusInvoiceType477Id: 1,
|
||||
invoiceCorrectionTypeId: 1
|
||||
};
|
||||
ctx.args = args;
|
||||
try {
|
||||
const result = await models.InvoiceOut.transferInvoice(
|
||||
ctx,
|
||||
options);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
await tx.rollback();
|
||||
} catch (e) {
|
||||
await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
});
|
||||
|
||||
it('should throw an UserError when it is the same client', async() => {
|
||||
const tx = await models.InvoiceOut.beginTransaction({});
|
||||
const options = {transaction: tx};
|
||||
const args = {
|
||||
id: '1',
|
||||
ref: 'T1111111',
|
||||
newClientFk: 1101,
|
||||
cplusRectificationId: 1,
|
||||
cplusInvoiceType477Id: 1,
|
||||
invoiceCorrectionTypeId: 1
|
||||
};
|
||||
ctx.args = args;
|
||||
try {
|
||||
await models.InvoiceOut.transferInvoice(
|
||||
ctx,
|
||||
options);
|
||||
} catch (e) {
|
||||
expect(e.message).toBe(`Select a different client`);
|
||||
await tx.rollback();
|
||||
}
|
||||
});
|
||||
});
|
|
@ -0,0 +1,111 @@
|
|||
const UserError = require('vn-loopback/util/user-error');
|
||||
|
||||
module.exports = Self => {
|
||||
Self.remoteMethodCtx('transferInvoice', {
|
||||
description: 'Transfer an issued invoice to another client',
|
||||
accessType: 'WRITE',
|
||||
accepts: [
|
||||
{
|
||||
arg: 'id',
|
||||
type: 'number',
|
||||
required: true,
|
||||
description: 'Issued invoice id'
|
||||
},
|
||||
{
|
||||
arg: 'ref',
|
||||
type: 'string',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'newClientFk',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'cplusRectificationId',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'cplusInvoiceType477Id',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'invoiceCorrectionTypeId',
|
||||
type: 'number',
|
||||
required: true
|
||||
},
|
||||
],
|
||||
returns: {
|
||||
type: 'boolean',
|
||||
root: true
|
||||
},
|
||||
http: {
|
||||
path: '/transferInvoice',
|
||||
verb: 'post'
|
||||
}
|
||||
});
|
||||
|
||||
Self.transferInvoice = async(ctx, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {userId: ctx.req.accessToken.userId};
|
||||
const args = ctx.args;
|
||||
let tx;
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
mejor client mejor client
|
||||
const {clientFk} = await models.InvoiceOut.findById(args.id);
|
||||
|
||||
jorgep marked this conversation as resolved
alexm
commented
Per a ficar esta restriccio pots ficar Per a ficar esta restriccio pots ficar
`required: true` en les validacions (dalt)
|
||||
if (clientFk == args.newClientFk)
|
||||
throw new UserError(`Select a different client`);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
try {
|
||||
const filterRef = {where: {refFk: args.ref}};
|
||||
const tickets = await models.Ticket.find(filterRef, myOptions);
|
||||
const ticketsIds = tickets.map(ticket => ticket.id);
|
||||
await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
|
||||
|
||||
const filterTicket = {where: {ticketFk: {inq: ticketsIds}}};
|
||||
|
||||
const services = await models.TicketService.find(filterTicket, myOptions);
|
||||
const servicesIds = services.map(service => service.id);
|
||||
|
||||
const sales = await models.Sale.find(filterTicket, myOptions);
|
||||
const salesIds = sales.map(sale => sale.id);
|
||||
|
||||
const clonedTickets = await models.Sale.clone(ctx, salesIds, servicesIds, null, false, false, myOptions);
|
||||
const clonedTicketIds = [];
|
||||
|
||||
for (const clonedTicket of clonedTickets) {
|
||||
await clonedTicket.updateAttribute('clientFk', args.newClientFk, myOptions);
|
||||
clonedTicketIds.push(clonedTicket.id);
|
||||
}
|
||||
|
||||
const invoiceIds = await models.Ticket.invoiceTickets(ctx, clonedTicketIds, myOptions);
|
||||
const [invoiceId] = invoiceIds;
|
||||
|
||||
await models.InvoiceCorrection.create({
|
||||
correctingFk: invoiceId,
|
||||
correctedFk: args.id,
|
||||
cplusRectificationTypeFk: args.cplusRectificationId,
|
||||
cplusInvoiceType477Fk: args.cplusInvoiceType477Id,
|
||||
invoiceCorrectionTypeFk: args.invoiceCorrectionTypeId
|
||||
}, myOptions);
|
||||
|
||||
if (tx) {
|
||||
await tx.commit();
|
||||
await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null);
|
||||
}
|
||||
|
||||
return invoiceId;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -31,5 +31,17 @@
|
|||
},
|
||||
"ZipConfig": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"CplusRectificationType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"InvoiceCorrectionType": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"InvoiceCorrection": {
|
||||
"dataSource": "vn"
|
||||
},
|
||||
"CplusInvoiceType477": {
|
||||
"dataSource": "vn"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "CplusInvoiceType477",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "cplusInvoiceType477"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "CplusRectificationType",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "cplusRectificationType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "InvoiceCorrectionType",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "invoiceCorrectionType"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"id": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
{
|
||||
"name": "InvoiceCorrection",
|
||||
"base": "VnModel",
|
||||
"options": {
|
||||
"mysql": {
|
||||
"table": "invoiceCorrection"
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"correctingFk": {
|
||||
"id": true,
|
||||
"type": "number",
|
||||
"description": "Identifier"
|
||||
},
|
||||
"correctedFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"cplusRectificationTypeFk": {
|
||||
"type": "number"
|
||||
},
|
||||
"cplusInvoiceType477Fk": {
|
||||
"type": "number"
|
||||
},
|
||||
"invoiceCorrectionTypeFk": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
|
@ -23,6 +23,7 @@ module.exports = Self => {
|
|||
require('../methods/invoiceOut/getInvoiceDate')(Self);
|
||||
require('../methods/invoiceOut/negativeBases')(Self);
|
||||
require('../methods/invoiceOut/negativeBasesCsv')(Self);
|
||||
require('../methods/invoiceOut/transferInvoice')(Self);
|
||||
|
||||
Self.filePath = async function(id, options) {
|
||||
const fields = ['ref', 'issued'];
|
||||
|
|
|
@ -1,3 +1,19 @@
|
|||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="CplusRectificationTypes"
|
||||
data="cplusRectificationTypes"
|
||||
order="description">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="CplusInvoiceType477s"
|
||||
data="cplusInvoiceType477">
|
||||
</vn-crud-model>
|
||||
<vn-crud-model
|
||||
auto-load="true"
|
||||
url="InvoiceCorrectionTypes"
|
||||
data="invoiceCorrectionTypes">
|
||||
</vn-crud-model>
|
||||
|
||||
<vn-icon-button
|
||||
icon="more_vert"
|
||||
|
@ -5,6 +21,13 @@
|
|||
</vn-icon-button>
|
||||
<vn-menu vn-id="menu">
|
||||
<vn-list>
|
||||
<vn-item
|
||||
vn-acl="administrative"
|
||||
jorgep marked this conversation as resolved
jgallego
commented
fijate que aquí sí lo has puesto, pero en los acl no. En próximas ocasiones que vayan "juntos" fijate que aquí sí lo has puesto, pero en los acl no. En próximas ocasiones que vayan "juntos"
|
||||
vn-acl-action="remove"
|
||||
ng-click="transferInvoice.show()"
|
||||
translate>
|
||||
Transfer invoice to...
|
||||
</vn-item>
|
||||
<vn-item class="dropdown"
|
||||
vn-click-stop="showInvoiceMenu.show($event, 'left')"
|
||||
name="showInvoicePdf"
|
||||
|
@ -157,3 +180,71 @@
|
|||
<button response="accept" translate>Confirm</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
||||
|
||||
<vn-dialog
|
||||
vn-id="transferInvoice"
|
||||
title="transferInvoice"
|
||||
on-accept="$ctrl.transferInvoice()">
|
||||
<tpl-body>
|
||||
<section class="transferInvoice">
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="client"
|
||||
required="true"
|
||||
url="Clients"
|
||||
label="Client"
|
||||
show-field="name"
|
||||
value-field="id"
|
||||
search-function="{or: [{id: $search}, {name: {like: '%'+ $search +'%'}}]}"
|
||||
ng-model="$ctrl.invoiceOut.client.id"
|
||||
initial-data="$ctrl.invoiceOut.client.id"
|
||||
order="id">
|
||||
<tpl-item>
|
||||
#{{id}} - {{::name}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="cplusRectificationType"
|
||||
required="true"
|
||||
data="cplusRectificationTypes"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
ng-model="$ctrl.cplusRectificationType"
|
||||
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
|
||||
label="Cplus Type">
|
||||
<tpl-item>
|
||||
{{::description}}
|
||||
</tpl-item>
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
<vn-horizontal>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="cplusInvoiceType"
|
||||
data="cplusInvoiceType477"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
required="true"
|
||||
ng-model="$ctrl.cplusInvoiceType477"
|
||||
search-function="{or: [{id: $search}, {description: {like: '%'+ $search +'%'}}]}"
|
||||
label="Class">
|
||||
</vn-autocomplete>
|
||||
<vn-autocomplete
|
||||
vn-one
|
||||
vn-id="invoiceCorrectionType"
|
||||
data="invoiceCorrectionTypes"
|
||||
ng-model="$ctrl.invoiceCorrectionType"
|
||||
show-field="description"
|
||||
value-field="id"
|
||||
required="true"
|
||||
label="Type">
|
||||
</vn-autocomplete>
|
||||
</vn-horizontal>
|
||||
</section>
|
||||
</tpl-body>
|
||||
<tpl-buttons>
|
||||
<button response="accept" translate>Transfer client</button>
|
||||
</tpl-buttons>
|
||||
</vn-dialog>
|
|
@ -125,6 +125,22 @@ class Controller extends Section {
|
|||
this.$state.go('ticket.card.sale', {id: refundTicket.id});
|
||||
});
|
||||
}
|
||||
|
||||
transferInvoice() {
|
||||
const params = {
|
||||
id: this.invoiceOut.id,
|
||||
ref: this.invoiceOut.ref,
|
||||
newClientFk: this.invoiceOut.client.id,
|
||||
cplusRectificationId: this.cplusRectificationType,
|
||||
cplusInvoiceType477Id: this.cplusInvoiceType477,
|
||||
invoiceCorrectionTypeId: this.invoiceCorrectionType
|
||||
};
|
||||
this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => {
|
||||
const invoiceId = res.data;
|
||||
this.vnApp.showSuccess(this.$t('Invoice trasfered!'));
|
||||
this.$state.go('invoiceOut.card.summary', {id: invoiceId});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
|
||||
|
|
|
@ -1 +1,3 @@
|
|||
The following refund tickets have been created: "The following refund tickets have been created: {{ticketIds}}"
|
||||
Transfer invoice to...: Transfer invoice to...
|
||||
Cplus Type: Cplus Type
|
|
@ -21,3 +21,5 @@ The invoice PDF document has been regenerated: El documento PDF de la factura ha
|
|||
The email can't be empty: El correo no puede estar vacío
|
||||
The following refund tickets have been created: "Se han creado los siguientes tickets de abono: {{ticketIds}}"
|
||||
Refund...: Abono...
|
||||
Transfer invoice to...: Transferir factura a...
|
||||
Cplus Type: Cplus Tipo
|
||||
|
|
|
@ -21,4 +21,10 @@ vn-invoice-out-descriptor-menu {
|
|||
font-size: 1.75rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@media screen and (min-width: 1000px) {
|
||||
jorgep marked this conversation as resolved
jgallego
commented
el 1000 no lo podemos quitar? el 1000 no lo podemos quitar?
|
||||
.transferInvoice {
|
||||
min-width: $width-md;
|
||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
@alexm valorad si es estrictamente necesario y si lo es no poner un 1000px si no algo relativo @alexm valorad si es estrictamente necesario y si lo es no poner un 1000px si no algo relativo
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
module.exports = Self => {
|
||||
jorgep marked this conversation as resolved
alexm
commented
Si quieres usar refundTickets en varios sitios no seria mejor ponerlo fuera de las funciones? (No estoy seguro de que funcione) Si quieres usar refundTickets en varios sitios no seria mejor ponerlo fuera de las funciones? (No estoy seguro de que funcione)
jorgep
commented
Mirado juntos. Mirado juntos.
|
||||
Self.clone = async(ctx, salesIds, servicesIds, withWarehouse, group, negative, options) => {
|
||||
const models = Self.app.models;
|
||||
const myOptions = {};
|
||||
let tx;
|
||||
const newTickets = [];
|
||||
|
||||
if (typeof options == 'object')
|
||||
Object.assign(myOptions, options);
|
||||
|
||||
if (!myOptions.transaction) {
|
||||
tx = await Self.beginTransaction({});
|
||||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
try {
|
||||
const salesFilter = {
|
||||
where: {id: {inq: salesIds}},
|
||||
include: {
|
||||
relation: 'components',
|
||||
scope: {
|
||||
fields: ['saleFk', 'componentFk', 'value']
|
||||
}
|
||||
}
|
||||
};
|
||||
const sales = await models.Sale.find(salesFilter, myOptions);
|
||||
let ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
|
||||
|
||||
const mappedTickets = new Map();
|
||||
|
||||
if (group) ticketsIds = [ticketsIds[0]];
|
||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
y te ahorres fer if y else ```
if(group) ticketsIds = ticketsIds[0]
for (let ticketId of ticketsIds) {
await createTicketRefund(
ticketId,
withWarehouse,
refundTickets,
mappedTickets,
now,
myOptions
);
}
```
y te ahorres fer if y else
|
||||
|
||||
for (let ticketId of ticketsIds) {
|
||||
const newTicket = await createTicket(
|
||||
ctx,
|
||||
ticketId,
|
||||
withWarehouse,
|
||||
negative,
|
||||
myOptions
|
||||
jorgep marked this conversation as resolved
alexm
commented
Aqui Aqui
|
||||
);
|
||||
newTickets.push(newTicket);
|
||||
mappedTickets.set(ticketId, newTicket.id);
|
||||
}
|
||||
|
||||
for (const sale of sales) {
|
||||
const newTicketId = mappedTickets.get(sale.ticketFk);
|
||||
|
||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
El map de mappedTickets realment el pots omplir fer el bucle El map de mappedTickets realment el pots omplir fer el bucle
|
||||
const createdSale = await models.Sale.create({
|
||||
ticketFk: newTicketId,
|
||||
itemFk: sale.itemFk,
|
||||
quantity: negative ? - sale.quantity : sale.quantity,
|
||||
concept: sale.concept,
|
||||
price: sale.price,
|
||||
discount: sale.discount,
|
||||
}, myOptions);
|
||||
|
||||
const components = sale.components();
|
||||
for (const component of components)
|
||||
component.saleFk = createdSale.id;
|
||||
|
||||
await models.SaleComponent.create(components, myOptions);
|
||||
}
|
||||
|
||||
if (servicesIds && servicesIds.length) {
|
||||
const servicesFilter = {
|
||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
if (servicesIds && servicesIds.length) if (servicesIds && servicesIds.length)
|
||||
where: {id: {inq: servicesIds}}
|
||||
};
|
||||
const services = await models.TicketService.find(servicesFilter, myOptions);
|
||||
|
||||
for (const service of services) {
|
||||
const newTicketId = mappedTickets.get(service.ticketFk);
|
||||
|
||||
await models.TicketService.create({
|
||||
description: service.description,
|
||||
quantity: negative ? - service.quantity : service.quantity,
|
||||
price: service.price,
|
||||
taxClassFk: service.taxClassFk,
|
||||
ticketFk: newTicketId,
|
||||
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
||||
}, myOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return newTickets;
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
Pero no quedarem en que clone.js no debia tindre res relacionat en refund Pero no quedarem en que clone.js no debia tindre res relacionat en refund
|
||||
async function createTicket(
|
||||
ctx,
|
||||
ticketId,
|
||||
withWarehouse,
|
||||
negative,
|
||||
myOptions
|
||||
) {
|
||||
const models = Self.app.models;
|
||||
const now = Date.vnNew();
|
||||
|
||||
const ticket = await models.Ticket.findById(ticketId, null, myOptions);
|
||||
ctx.args.clientId = ticket.clientFk;
|
||||
ctx.args.shipped = now;
|
||||
ctx.args.landed = now;
|
||||
ctx.args.warehouseId = withWarehouse ? ticket.warehouseFk : null;
|
||||
ctx.args.companyId = ticket.companyFk;
|
||||
ctx.args.addressId = ticket.addressFk;
|
||||
|
||||
const newTicket = await models.Ticket.new(ctx, myOptions);
|
||||
|
||||
if (negative) {
|
||||
alexm
commented
En un futur se podria refactoritzar i extraure esta part a refund o un hipotetic ticket/clone En un futur se podria refactoritzar i extraure esta part a refund o un hipotetic ticket/clone
|
||||
await models.TicketRefund.create({
|
||||
originalTicketFk: ticketId,
|
||||
refundTicketFk: newTicket.id
|
||||
}, myOptions);
|
||||
}
|
||||
jorgep marked this conversation as resolved
Outdated
alexm
commented
Esta funcio te la pots ahorrar no? Esta funcio te la pots ahorrar no?
Fent mappedTickets.get(ticketId) ja te torna lo que vols pq quan has fet el set esta ja agrupat o no
|
||||
|
||||
return newTicket;
|
||||
}
|
||||
};
|
||||
};
|
|
@ -5,7 +5,8 @@ module.exports = Self => {
|
|||
accepts: [
|
||||
{
|
||||
arg: 'salesIds',
|
||||
type: ['number']
|
||||
type: ['number'],
|
||||
required: true
|
||||
},
|
||||
{
|
||||
arg: 'servicesIds',
|
||||
|
@ -40,122 +41,23 @@ module.exports = Self => {
|
|||
myOptions.transaction = tx;
|
||||
}
|
||||
|
||||
let refundTicket = null;
|
||||
try {
|
||||
const refundAgencyMode = await models.AgencyMode.findOne({
|
||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
esta logica donde esta ahora? esta logica donde esta ahora?
jorgep
commented
Está dentro del método clone Está dentro del método clone
|
||||
include: {
|
||||
relation: 'zones',
|
||||
scope: {
|
||||
limit: 1,
|
||||
field: ['id', 'name']
|
||||
}
|
||||
},
|
||||
where: {code: 'refund'}
|
||||
}, myOptions);
|
||||
|
||||
const refoundZoneId = refundAgencyMode.zones()[0].id;
|
||||
|
||||
if (salesIds.length) {
|
||||
const salesFilter = {
|
||||
where: {id: {inq: salesIds}},
|
||||
include: {
|
||||
relation: 'components',
|
||||
scope: {
|
||||
fields: ['saleFk', 'componentFk', 'value']
|
||||
}
|
||||
}
|
||||
};
|
||||
const sales = await models.Sale.find(salesFilter, myOptions);
|
||||
const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
|
||||
|
||||
const now = Date.vnNew();
|
||||
const [firstTicketId] = ticketsIds;
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
|
||||
|
||||
for (const sale of sales) {
|
||||
const createdSale = await models.Sale.create({
|
||||
ticketFk: refundTicket.id,
|
||||
itemFk: sale.itemFk,
|
||||
quantity: - sale.quantity,
|
||||
concept: sale.concept,
|
||||
price: sale.price,
|
||||
discount: sale.discount,
|
||||
}, myOptions);
|
||||
|
||||
const components = sale.components();
|
||||
for (const component of components)
|
||||
component.saleFk = createdSale.id;
|
||||
|
||||
await models.SaleComponent.create(components, myOptions);
|
||||
}
|
||||
}
|
||||
if (!refundTicket) {
|
||||
const servicesFilter = {
|
||||
where: {id: {inq: servicesIds}}
|
||||
};
|
||||
const services = await models.TicketService.find(servicesFilter, myOptions);
|
||||
const firstTicketId = services[0].ticketFk;
|
||||
|
||||
const now = Date.vnNew();
|
||||
|
||||
// eslint-disable-next-line max-len
|
||||
refundTicket = await createTicketRefund(firstTicketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions);
|
||||
}
|
||||
|
||||
if (servicesIds && servicesIds.length > 0) {
|
||||
const servicesFilter = {
|
||||
where: {id: {inq: servicesIds}}
|
||||
};
|
||||
const services = await models.TicketService.find(servicesFilter, myOptions);
|
||||
for (const service of services) {
|
||||
await models.TicketService.create({
|
||||
description: service.description,
|
||||
quantity: - service.quantity,
|
||||
price: service.price,
|
||||
taxClassFk: service.taxClassFk,
|
||||
ticketFk: refundTicket.id,
|
||||
ticketServiceTypeFk: service.ticketServiceTypeFk,
|
||||
}, myOptions);
|
||||
}
|
||||
}
|
||||
|
||||
const query = `CALL vn.ticket_recalc(?, NULL)`;
|
||||
await Self.rawSql(query, [refundTicket.id], myOptions);
|
||||
const refundsTicket = await models.Sale.clone(
|
||||
ctx,
|
||||
salesIds,
|
||||
servicesIds,
|
||||
withWarehouse,
|
||||
false,
|
||||
true,
|
||||
myOptions
|
||||
);
|
||||
|
||||
jorgep marked this conversation as resolved
alexm
commented
Deuria ser per aci on fa que el ticket siga refund Deuria ser per aci on fa que el ticket siga refund
|
||||
if (tx) await tx.commit();
|
||||
|
||||
return refundTicket;
|
||||
return refundsTicket[0];
|
||||
} catch (e) {
|
||||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
};
|
||||
|
||||
async function createTicketRefund(ticketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions) {
|
||||
const models = Self.app.models;
|
||||
|
||||
const filter = {include: {relation: 'address'}};
|
||||
const ticket = await models.Ticket.findById(ticketId, filter, myOptions);
|
||||
|
||||
const refundTicket = await models.Ticket.create({
|
||||
clientFk: ticket.clientFk,
|
||||
shipped: now,
|
||||
addressFk: ticket.address().id,
|
||||
agencyModeFk: refundAgencyMode.id,
|
||||
nickname: ticket.address().nickname,
|
||||
warehouseFk: withWarehouse ? ticket.warehouseFk : null,
|
||||
companyFk: ticket.companyFk,
|
||||
landed: now,
|
||||
zoneFk: refoundZoneId
|
||||
}, myOptions);
|
||||
|
||||
await models.TicketRefund.create({
|
||||
refundTicketFk: refundTicket.id,
|
||||
originalTicketFk: ticket.id,
|
||||
}, myOptions);
|
||||
|
||||
return refundTicket;
|
||||
}
|
||||
};
|
||||
|
|
|
@ -3,7 +3,7 @@ const LoopBackContext = require('loopback-context');
|
|||
|
||||
describe('Sale refund()', () => {
|
||||
const userId = 5;
|
||||
const ctx = {req: {accessToken: userId}};
|
||||
const ctx = {req: {accessToken: userId}, args: {}};
|
||||
const activeCtx = {
|
||||
accessToken: {userId},
|
||||
};
|
||||
|
@ -40,6 +40,7 @@ describe('Sale refund()', () => {
|
|||
|
||||
try {
|
||||
const options = {transaction: tx};
|
||||
const ticketsBefore = await models.Ticket.find({}, options);
|
||||
|
||||
const ticket = await models.Sale.refund(ctx, salesIds, servicesIds, withWarehouse, options);
|
||||
|
||||
|
@ -61,12 +62,13 @@ describe('Sale refund()', () => {
|
|||
}
|
||||
]
|
||||
}, options);
|
||||
|
||||
const ticketsAfter = await models.Ticket.find({}, options);
|
||||
const salesLength = refundedTicket.ticketSales().length;
|
||||
const componentsLength = refundedTicket.ticketSales()[0].components().length;
|
||||
|
||||
expect(refundedTicket).toBeDefined();
|
||||
expect(salesLength).toEqual(2);
|
||||
expect(salesLength).toEqual(1);
|
||||
expect(ticketsBefore.length).toEqual(ticketsAfter.length - 2);
|
||||
expect(componentsLength).toEqual(4);
|
||||
|
||||
await tx.rollback();
|
||||
|
|
|
@ -77,9 +77,10 @@ module.exports = function(Self) {
|
|||
if (tx) await tx.rollback();
|
||||
throw e;
|
||||
}
|
||||
|
||||
for (const invoiceId of invoicesIds)
|
||||
await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null);
|
||||
if (tx) {
|
||||
for (const invoiceId of invoicesIds)
|
||||
await models.InvoiceOut.makePdfAndNotify(ctx, invoiceId, null);
|
||||
}
|
||||
|
||||
return invoicesIds;
|
||||
};
|
||||
|
|
|
@ -96,7 +96,7 @@ module.exports = Self => {
|
|||
if (address.client().type().code === 'normal' && (!agencyMode || agencyMode.code != 'refund')) {
|
||||
const canCreateTicket = await models.Client.canCreateTicket(args.clientId, myOptions);
|
||||
if (!canCreateTicket)
|
||||
throw new UserError(`You can't create a ticket for a inactive client`);
|
||||
throw new UserError(`You can't create a ticket for an inactive client`);
|
||||
}
|
||||
|
||||
if (!args.shipped && args.landed) {
|
||||
|
|
|
@ -39,7 +39,6 @@ module.exports = Self => {
|
|||
|
||||
jorgep marked this conversation as resolved
Outdated
jgallego
commented
modules/ticket/back/methods/ticket/invoiceTicketsWithPdf.js este porque sale?
modules/ticket/back/methods/ticket/invoiceTicketsWithPdf.js este porque sale?
jorgep
commented
Se me olvidó borrarlo Se me olvidó borrarlo
|
||||
try {
|
||||
const filter = {where: {ticketFk: {inq: ticketsIds}}};
|
||||
|
||||
const sales = await models.Sale.find(filter, myOptions);
|
||||
const salesIds = sales.map(sale => sale.id);
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ describe('ticket new()', () => {
|
|||
await tx.rollback();
|
||||
}
|
||||
|
||||
expect(error).toEqual(new UserError(`You can't create a ticket for a inactive client`));
|
||||
expect(error).toEqual(new UserError(`You can't create a ticket for an inactive client`));
|
||||
});
|
||||
|
||||
it('should throw an error if the address doesnt exist', async() => {
|
||||
|
|
|
@ -66,7 +66,7 @@ module.exports = Self => {
|
|||
const ticket = await models.Ticket.findById(id);
|
||||
const canCreateTicket = await models.Client.canCreateTicket(ticket.clientFk);
|
||||
if (!canCreateTicket)
|
||||
throw new UserError(`You can't create a ticket for a inactive client`);
|
||||
throw new UserError(`You can't create a ticket for an inactive client`);
|
||||
|
||||
ticketId = await cloneTicket(originalTicket, myOptions);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ module.exports = Self => {
|
|||
require('../methods/sale/refund')(Self);
|
||||
require('../methods/sale/canEdit')(Self);
|
||||
require('../methods/sale/usesMana')(Self);
|
||||
require('../methods/sale/clone')(Self);
|
||||
|
||||
Self.validatesPresenceOf('concept', {
|
||||
message: `Concept cannot be blank`
|
||||
|
|
|
@ -7,6 +7,7 @@ module.exports = {
|
|||
mixins: [vnReport],
|
||||
async serverPrefetch() {
|
||||
this.invoice = await this.findOneFromDef('invoice', [this.reference]);
|
||||
|
||||
this.checkMainEntity(this.invoice);
|
||||
this.client = await this.findOneFromDef('client', [this.reference]);
|
||||
this.taxes = await this.rawSqlFromDef(`taxes`, [this.reference]);
|
||||
|
|
En ingles