diff --git a/db/changes/233201/00-transferInvoiceOutACL.sql b/db/changes/233201/00-transferInvoiceOutACL.sql
new file mode 100644
index 000000000..b549e52a8
--- /dev/null
+++ b/db/changes/233201/00-transferInvoiceOutACL.sql
@@ -0,0 +1,6 @@
+INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
+ VALUES
+ ('CplusRectificationType', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
+ ('CplusCorrectingType', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
+ ('InvoiceCorrectionType', '*', 'READ', 'ALLOW', 'ROLE', 'employee'),
+ ('InvoiceOut', 'transferInvoiceOut', 'WRITE', 'ALLOW', 'ROLE', 'employee');
\ No newline at end of file
diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql
index eaa00a3de..0a17e2e42 100644
--- a/db/dump/fixtures.sql
+++ b/db/dump/fixtures.sql
@@ -2958,3 +2958,16 @@ INSERT INTO `vn`.`invoiceInSerial` (`code`, `description`, `cplusTerIdNifFk`, `t
INSERT INTO `hedera`.`imageConfig` (`id`, `maxSize`, `useXsendfile`, `url`)
VALUES
(1, 0, 0, 'marvel.com');
+
+INSERT INTO `vn`.`cplusCorrectingType` (`description`)
+ VALUES
+ ('Embalajes'),
+ ('Anulación'),
+ ('Impagado'),
+ ('Moroso');
+
+INSERT INTO `vn`.`invoiceCorrectionType` (`description`)
+ VALUES
+ ('Error en el cálculo del IVA'),
+ ('Error en el detalle de las ventas'),
+ ('Error en los datos del cliente');
diff --git a/loopback/locale/es.json b/loopback/locale/es.json
index ac62d62e1..3439adcde 100644
--- a/loopback/locale/es.json
+++ b/loopback/locale/es.json
@@ -311,5 +311,6 @@
"You don't have enough privileges.": "No tienes suficientes permisos.",
"This ticket is locked.": "Este ticket está bloqueado.",
"This ticket is not editable.": "Este ticket no es editable.",
- "The ticket doesn't exist.": "No existe el ticket."
+ "The ticket doesn't exist.": "No existe el ticket.",
+ "There are missing fields.": "There are missing fields."
}
\ No newline at end of file
diff --git a/modules/invoiceOut/back/methods/invoiceOut/transferInvoiceOut.js b/modules/invoiceOut/back/methods/invoiceOut/transferInvoiceOut.js
new file mode 100644
index 000000000..b50e4b1a7
--- /dev/null
+++ b/modules/invoiceOut/back/methods/invoiceOut/transferInvoiceOut.js
@@ -0,0 +1,81 @@
+const UserError = require('vn-loopback/util/user-error');
+
+module.exports = Self => {
+ Self.remoteMethodCtx('transferInvoiceOut', {
+ description: 'Transfer an invoice out to another client',
+ accessType: 'WRITE',
+ accepts: [
+ {
+ arg: 'data',
+ type: 'Object',
+ required: true
+ }
+ ],
+ returns: {
+ type: 'boolean',
+ root: true
+ },
+ http: {
+ path: '/transferInvoice',
+ verb: 'post'
+ }
+ });
+
+ Self.transferInvoiceOut = async(ctx, params, options) => {
+ const models = Self.app.models;
+ const myOptions = {};
+ let tx;
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+ try {
+ const {ref, newClientFk, cplusRectificationId, cplusCorrectingTypeId, invoiceCorrectionTypeId} = params;
+ if (!ref || !newClientFk || !cplusRectificationId || !cplusCorrectingTypeId || !invoiceCorrectionTypeId)
+ throw new UserError(`There are missing fields.`);
+
+ const filter = {where: {refFk: ref}};
+ const tickets = await models.Ticket.find(filter, myOptions);
+ const ticketsIds = tickets.map(ticket => ticket.id);
+ const refundTicket = await models.Ticket.refund(ctx, ticketsIds, null, myOptions);
+ // Clonar tickets
+ const refundAgencyMode = await models.AgencyMode.findOne({
+ include: {
+ relation: 'zones',
+ scope: {
+ limit: 1,
+ field: ['id', 'name']
+ }
+ },
+ where: {code: 'refund'}
+ }, myOptions);
+ const refoundZoneId = refundAgencyMode.zones()[0].id;
+ const services = await models.TicketService.find(filter, myOptions);
+ const servicesIds = services.map(service => service.id);
+ const salesFilter = {
+ where: {id: {inq: salesIds}},
+ include: {
+ relation: 'components',
+ scope: {
+ fields: ['saleFk', 'componentFk', 'value']
+ }
+ }
+ };
+ const sales = await models.Sale.find(salesFilter, myOptions);
+ // Actualizar cliente
+
+ // Invoice Ticket - Factura rápida ??
+
+ // Insert InvoiceCorrection
+ if (tx) await tx.commit();
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ return true;
+ };
+};
diff --git a/modules/invoiceOut/back/model-config.json b/modules/invoiceOut/back/model-config.json
index 9e8b119ab..995ea976b 100644
--- a/modules/invoiceOut/back/model-config.json
+++ b/modules/invoiceOut/back/model-config.json
@@ -31,5 +31,17 @@
},
"ZipConfig": {
"dataSource": "vn"
+ },
+ "CplusRectificationType": {
+ "dataSource": "vn"
+ },
+ "CplusCorrectingType": {
+ "dataSource": "vn"
+ },
+ "InvoiceCorrectionType": {
+ "dataSource": "vn"
+ },
+ "InvoiceCorrection": {
+ "dataSource": "vn"
}
}
diff --git a/modules/invoiceOut/back/models/cplus-correcting-type.json b/modules/invoiceOut/back/models/cplus-correcting-type.json
new file mode 100644
index 000000000..660f60008
--- /dev/null
+++ b/modules/invoiceOut/back/models/cplus-correcting-type.json
@@ -0,0 +1,19 @@
+{
+ "name": "CplusCorrectingType",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "cplusCorrectingType"
+ }
+ },
+ "properties": {
+ "id": {
+ "id": true,
+ "type": "number",
+ "description": "Identifier"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/invoiceOut/back/models/cplus-rectification-type.json b/modules/invoiceOut/back/models/cplus-rectification-type.json
new file mode 100644
index 000000000..e7bfb957f
--- /dev/null
+++ b/modules/invoiceOut/back/models/cplus-rectification-type.json
@@ -0,0 +1,19 @@
+{
+ "name": "CplusRectificationType",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "cplusRectificationType"
+ }
+ },
+ "properties": {
+ "id": {
+ "id": true,
+ "type": "number",
+ "description": "Identifier"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/invoiceOut/back/models/invoice-correction-type.json b/modules/invoiceOut/back/models/invoice-correction-type.json
new file mode 100644
index 000000000..ad3f034ea
--- /dev/null
+++ b/modules/invoiceOut/back/models/invoice-correction-type.json
@@ -0,0 +1,19 @@
+{
+ "name": "InvoiceCorrectionType",
+ "base": "VnModel",
+ "options": {
+ "mysql": {
+ "table": "invoiceCorrectionType"
+ }
+ },
+ "properties": {
+ "id": {
+ "id": true,
+ "type": "number",
+ "description": "Identifier"
+ },
+ "description": {
+ "type": "string"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/invoiceOut/back/models/invoice-out.js b/modules/invoiceOut/back/models/invoice-out.js
index d3aaf3b3d..0bb31ce12 100644
--- a/modules/invoiceOut/back/models/invoice-out.js
+++ b/modules/invoiceOut/back/models/invoice-out.js
@@ -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/transferInvoiceOut')(Self);
Self.filePath = async function(id, options) {
const fields = ['ref', 'issued'];
diff --git a/modules/invoiceOut/back/models/invoiceCorrection.json b/modules/invoiceOut/back/models/invoiceCorrection.json
new file mode 100644
index 000000000..48bd172a6
--- /dev/null
+++ b/modules/invoiceOut/back/models/invoiceCorrection.json
@@ -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"
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/invoiceOut/front/descriptor-menu/index.html b/modules/invoiceOut/front/descriptor-menu/index.html
index 106f8e3cc..d6eaa1cc7 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.html
+++ b/modules/invoiceOut/front/descriptor-menu/index.html
@@ -1,3 +1,19 @@
+
+
+
+
+
+
+
+ Transfer invoice to...
+
Confirm
+
+
+
+
+
+
+ #{{id}} - {{::name}}
+
+
+
+
+ {{::description}}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/modules/invoiceOut/front/descriptor-menu/index.js b/modules/invoiceOut/front/descriptor-menu/index.js
index 38c3c9434..0b43b73a7 100644
--- a/modules/invoiceOut/front/descriptor-menu/index.js
+++ b/modules/invoiceOut/front/descriptor-menu/index.js
@@ -125,6 +125,19 @@ class Controller extends Section {
this.$state.go('ticket.card.sale', {id: refundTicket.id});
});
}
+
+ transferInvoice() {
+ const params = {
+ data: {
+ ref: this.invoiceOut.ref,
+ newClientFk: this.invoiceOut.client.id,
+ cplusRectificationId: this.cplusRectificationType,
+ cplusCorrectingTypeId: this.cplusCorrectingType,
+ invoiceCorrectionTypeId: this.invoiceCorrectionType
+ }
+ };
+ this.$http.post(`InvoiceOuts/transferInvoice`, params).then(res => console.log(res.data));
+ }
}
Controller.$inject = ['$element', '$scope', 'vnReport', 'vnEmail'];
diff --git a/modules/invoiceOut/front/descriptor-menu/locale/en.yml b/modules/invoiceOut/front/descriptor-menu/locale/en.yml
index d299155d7..8fad5f25e 100644
--- a/modules/invoiceOut/front/descriptor-menu/locale/en.yml
+++ b/modules/invoiceOut/front/descriptor-menu/locale/en.yml
@@ -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
\ No newline at end of file
diff --git a/modules/invoiceOut/front/descriptor-menu/locale/es.yml b/modules/invoiceOut/front/descriptor-menu/locale/es.yml
index 393efd58c..0f74b5fec 100644
--- a/modules/invoiceOut/front/descriptor-menu/locale/es.yml
+++ b/modules/invoiceOut/front/descriptor-menu/locale/es.yml
@@ -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
diff --git a/modules/ticket/back/methods/sale/clone.js b/modules/ticket/back/methods/sale/clone.js
new file mode 100644
index 000000000..1d69ca158
--- /dev/null
+++ b/modules/ticket/back/methods/sale/clone.js
@@ -0,0 +1,76 @@
+module.exports = async function clone(ctx, Self, sales, refundAgencyMode, refoundZoneId, servicesIds, withWarehouse, group, isRefund, myOptions) {
+ const models = Self.app.models;
+ const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
+ const [firstTicketId] = ticketsIds;
+
+ const now = Date.vnNew();
+ let refundTickets = [];
+ let refundTicket;
+
+ if (!group) {
+ for (const ticketId of ticketsIds) {
+ refundTicket = await createTicketRefund(
+ ticketId,
+ now,
+ refundAgencyMode,
+ refoundZoneId,
+ null,
+ myOptions
+ );
+ refundTickets.push(refundTicket);
+ }
+ } else {
+ refundTicket = await createTicketRefund(
+ firstTicketId,
+ now,
+ refundAgencyMode,
+ refoundZoneId,
+ withWarehouse,
+ myOptions
+ );
+ }
+
+ for (const sale of sales) {
+ const createdSale = await models.Sale.create({
+ ticketFk: (group) ? refundTicket.id : sale.ticketFk,
+ itemFk: sale.itemFk,
+ quantity: (isRefund) ? - 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 > 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: (isRefund) ? - service.quantity : service.quantity,
+ price: service.price,
+ taxClassFk: service.taxClassFk,
+ ticketFk: (group) ? refundTicket.id : service.ticketFk,
+ ticketServiceTypeFk: service.ticketServiceTypeFk,
+ }, myOptions);
+ }
+ }
+
+ const query = `CALL vn.ticket_recalc(?, NULL)`;
+ if (refundTickets.length > 0) {
+ for (const refundTicket of refundTickets)
+ await Self.rawSql(query, [refundTicket.id], myOptions);
+ return refundTickets.map(refundTicket => refundTicket.id);
+ } else {
+ await Self.rawSql(query, [refundTicket.id], myOptions);
+ return refundTicket;
+ }
+};
diff --git a/modules/ticket/back/methods/sale/createTicketRefund.js b/modules/ticket/back/methods/sale/createTicketRefund.js
new file mode 100644
index 000000000..0ecc62e0c
--- /dev/null
+++ b/modules/ticket/back/methods/sale/createTicketRefund.js
@@ -0,0 +1,25 @@
+module.exports = async function createTicketRefund(models, 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;
+};
diff --git a/modules/ticket/back/methods/sale/refund.js b/modules/ticket/back/methods/sale/refund.js
index a8191610a..fc6d8bbc2 100644
--- a/modules/ticket/back/methods/sale/refund.js
+++ b/modules/ticket/back/methods/sale/refund.js
@@ -28,7 +28,7 @@ module.exports = Self => {
}
});
- Self.refund = async(ctx, salesIds, servicesIds, withWarehouse, options) => {
+ /* Self.refund = async(ctx, salesIds, servicesIds, withWarehouse, options) => {
const models = Self.app.models;
const myOptions = {userId: ctx.req.accessToken.userId};
let tx;
@@ -111,6 +111,64 @@ module.exports = Self => {
if (tx) await tx.commit();
+ return refundTicket;
+ } catch (e) {
+ if (tx) await tx.rollback();
+ throw e;
+ }
+ }; */
+
+ Self.refund = async(ctx, salesIds, servicesIds, withWarehouse, options) => {
+ const models = Self.app.models;
+ const myOptions = {userId: ctx.req.accessToken.userId};
+ let tx;
+
+ if (typeof options == 'object')
+ Object.assign(myOptions, options);
+
+ if (!myOptions.transaction) {
+ tx = await Self.beginTransaction({});
+ myOptions.transaction = tx;
+ }
+
+ try {
+ const refundAgencyMode = await models.AgencyMode.findOne({
+ include: {
+ relation: 'zones',
+ scope: {
+ limit: 1,
+ field: ['id', 'name']
+ }
+ },
+ where: {code: 'refund'}
+ }, myOptions);
+
+ const refoundZoneId = refundAgencyMode.zones()[0].id;
+
+ const salesFilter = {
+ where: {id: {inq: salesIds}},
+ include: {
+ relation: 'components',
+ scope: {
+ fields: ['saleFk', 'componentFk', 'value']
+ }
+ }
+ };
+ const sales = await models.Sale.find(salesFilter, myOptions);
+ const group = true;
+ const isRefund = true;
+
+ const refundTicket = await clone(
+ sales,
+ refundAgencyMode,
+ refoundZoneId, servicesIds,
+ withWarehouse,
+ group,
+ isRefund,
+ myOptions
+ );
+ if (tx) await tx.commit();
+
return refundTicket;
} catch (e) {
if (tx) await tx.rollback();
@@ -118,6 +176,83 @@ module.exports = Self => {
}
};
+ async function clone(sales, refundAgencyMode, refoundZoneId, servicesIds, withWarehouse, group, isRefund, myOptions) {
+ const models = Self.app.models;
+ const ticketsIds = [...new Set(sales.map(sale => sale.ticketFk))];
+ const [firstTicketId] = ticketsIds;
+
+ const now = Date.vnNew();
+ let refundTickets = [];
+ let refundTicket;
+
+ if (!group) {
+ for (const ticketId of ticketsIds) {
+ refundTicket = await createTicketRefund(
+ ticketId,
+ now,
+ refundAgencyMode,
+ refoundZoneId,
+ null,
+ myOptions
+ );
+ refundTickets.push(refundTicket);
+ }
+ } else {
+ refundTicket = await createTicketRefund(
+ firstTicketId,
+ now,
+ refundAgencyMode,
+ refoundZoneId,
+ withWarehouse,
+ myOptions
+ );
+ }
+
+ for (const sale of sales) {
+ const createdSale = await models.Sale.create({
+ ticketFk: (group) ? refundTicket.id : sale.ticketFk,
+ itemFk: sale.itemFk,
+ quantity: (isRefund) ? - 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 > 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: (isRefund) ? - service.quantity : service.quantity,
+ price: service.price,
+ taxClassFk: service.taxClassFk,
+ ticketFk: (group) ? refundTicket.id : service.ticketFk,
+ ticketServiceTypeFk: service.ticketServiceTypeFk,
+ }, myOptions);
+ }
+ }
+
+ const query = `CALL vn.ticket_recalc(?, NULL)`;
+ if (refundTickets.length > 0) {
+ for (const refundTicket of refundTickets)
+ await Self.rawSql(query, [refundTicket.id], myOptions);
+ return refundTickets.map(refundTicket => refundTicket.id);
+ } else {
+ await Self.rawSql(query, [refundTicket.id], myOptions);
+ return refundTicket;
+ }
+ }
+
async function createTicketRefund(ticketId, now, refundAgencyMode, refoundZoneId, withWarehouse, myOptions) {
const models = Self.app.models;