diff --git a/back/models/sage-withholding.json b/back/models/sage-withholding.json index 8d93daeae..dddbcfd74 100644 --- a/back/models/sage-withholding.json +++ b/back/models/sage-withholding.json @@ -6,6 +6,9 @@ "table": "sage.TiposRetencion" } }, + "log": { + "showField": "withholding" + }, "properties": { "id": { "type": "Number", diff --git a/db/dump/fixtures.sql b/db/dump/fixtures.sql index e1aaa2cd6..c44191381 100644 --- a/db/dump/fixtures.sql +++ b/db/dump/fixtures.sql @@ -2339,6 +2339,14 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`) (9, 9, 9), (10, 10, 10); +INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxCodeFk`, `taxableBase`, `expenceFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`, `created`) + VALUES + (1, 4, 99.99, '2000000000', null, null, null, CURDATE()), + (2, 4, 999.99, '2000000000', null, null, null, CURDATE()), + (3, 4, 1000.50, '2000000000', null, null, null, CURDATE()), + (4, 4, 0.50, '2000000000', null, null, null, CURDATE()), + (5, 4, 150.50, '2000000000', null, null, null, CURDATE()); + INSERT INTO `vn`.`ticketRecalc`(`ticketFk`) SELECT `id` FROM `vn`.`ticket` t diff --git a/modules/invoiceIn/back/methods/invoice-in/clone.js b/modules/invoiceIn/back/methods/invoice-in/clone.js new file mode 100644 index 000000000..1593a68b7 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/clone.js @@ -0,0 +1,120 @@ + +const loggable = require('vn-loopback/util/log'); + +module.exports = Self => { + Self.remoteMethodCtx('clone', { + description: 'Clone the invoiceIn and as many invoiceInTax and invoiceInDueDay referencing it', + accessType: 'WRITE', + accepts: { + arg: 'id', + type: 'string', + required: true, + description: 'The invoiceIn id', + http: {source: 'path'} + }, + returns: { + type: 'object', + root: true + }, + http: { + path: '/:id/clone', + verb: 'POST' + } + }); + + Self.clone = async(ctx, id, options) => { + const userId = ctx.req.accessToken.userId; + const models = Self.app.models; + let tx; + const myOptions = {}; + + if (typeof options == 'object') + Object.assign(myOptions, options); + + if (!myOptions.transaction) { + tx = await Self.beginTransaction({}); + myOptions.transaction = tx; + } + + try { + const sourceInvoiceIn = await Self.findById(id, { + fields: [ + 'id', + 'serial', + 'supplierRef', + 'supplierFk', + 'issued', + 'currencyFk', + 'companyFk', + 'isVatDeductible', + 'withholdingSageFk', + 'deductibleExpenseFk', + ] + }, myOptions); + const sourceInvoiceInTax = await models.InvoiceInTax.find({where: {invoiceInFk: id}}, myOptions); + const sourceInvoiceInDueDay = await models.InvoiceInDueDay.find({where: {invoiceInFk: id}}, myOptions); + + const issued = new Date(sourceInvoiceIn.issued); + issued.setMonth(issued.getMonth() + 1); + + const clone = await models.InvoiceIn.create({ + serial: sourceInvoiceIn.serial, + supplierRef: sourceInvoiceIn.supplierRef, + supplierFk: sourceInvoiceIn.supplierFk, + issued: issued, + currencyFk: sourceInvoiceIn.currencyFk, + companyFk: sourceInvoiceIn.companyFk, + isVatDeductible: sourceInvoiceIn.isVatDeductible, + withholdingSageFk: sourceInvoiceIn.withholdingSageFk, + deductibleExpenseFk: sourceInvoiceIn.deductibleExpenseFk, + }, myOptions); + + const oldProperties = await loggable.translateValues(Self, sourceInvoiceIn); + const newProperties = await loggable.translateValues(Self, clone); + await models.InvoiceInLog.create({ + originFk: clone.id, + userFk: userId, + action: 'insert', + changedModel: 'InvoiceIn', + changedModelId: clone.id, + oldInstance: oldProperties, + newInstance: newProperties + }, myOptions); + + const promises = []; + + for (let tax of sourceInvoiceInTax) { + promises.push(models.InvoiceInTax.create({ + invoiceInFk: clone.id, + taxableBase: tax.taxableBase, + expenceFk: tax.expenceFk, + foreignValue: tax.foreignValue, + taxTypeSageFk: tax.taxTypeSageFk, + transactionTypeSageFk: tax.transactionTypeSageFk + }, myOptions)); + } + + for (let dueDay of sourceInvoiceInDueDay) { + const dueDated = dueDay.dueDated; + dueDated.setMonth(dueDated.getMonth() + 1); + + promises.push(models.InvoiceInDueDay.create({ + invoiceInFk: clone.id, + dueDated: dueDated, + bankFk: dueDay.bankFk, + amount: dueDay.amount, + foreignValue: dueDated.foreignValue, + }, myOptions)); + } + + await Promise.all(promises); + + if (tx) await tx.commit(); + + return clone; + } catch (e) { + if (tx) await tx.rollback(); + throw e; + } + }; +}; diff --git a/modules/invoiceIn/back/methods/invoice-in/specs/clone.spec.js b/modules/invoiceIn/back/methods/invoice-in/specs/clone.spec.js new file mode 100644 index 000000000..09b7e6019 --- /dev/null +++ b/modules/invoiceIn/back/methods/invoice-in/specs/clone.spec.js @@ -0,0 +1,36 @@ +const models = require('vn-loopback/server/server').models; + +describe('invoiceIn clone()', () => { + it('should return the cloned invoiceIn and also clone invoiceInDueDays and invoiceInTaxes if there are any referencing the invoiceIn', async() => { + const userId = 1; + const ctx = { + req: { + + accessToken: {userId: userId}, + headers: {origin: 'http://localhost:5000'}, + } + }; + + const tx = await models.InvoiceIn.beginTransaction({}); + const options = {transaction: tx}; + + try { + const clone = await models.InvoiceIn.clone(ctx, 1, options); + + expect(clone.supplierRef).toEqual('1234'); + + const invoiceInTaxes = await models.InvoiceInTax.find({where: {invoiceInFk: clone.id}}, options); + + expect(invoiceInTaxes.length).toEqual(1); + + const invoiceInDueDays = await models.InvoiceInDueDay.find({where: {invoiceInFk: clone.id}}, options); + + expect(invoiceInDueDays.length).toEqual(2); + + await tx.rollback(); + } catch (e) { + await tx.rollback(); + throw e; + } + }); +}); diff --git a/modules/invoiceIn/back/model-config.json b/modules/invoiceIn/back/model-config.json index 467d6f7f9..f0745f53b 100644 --- a/modules/invoiceIn/back/model-config.json +++ b/modules/invoiceIn/back/model-config.json @@ -2,6 +2,9 @@ "InvoiceIn": { "dataSource": "vn" }, + "InvoiceInTax": { + "dataSource": "vn" + }, "InvoiceInDueDay": { "dataSource": "vn" }, diff --git a/modules/invoiceIn/back/models/invoice-in-due-day.json b/modules/invoiceIn/back/models/invoice-in-due-day.json index 6c27dcd6c..5a66ecd8b 100644 --- a/modules/invoiceIn/back/models/invoice-in-due-day.json +++ b/modules/invoiceIn/back/models/invoice-in-due-day.json @@ -24,6 +24,9 @@ "amount": { "type": "number" }, + "foreignValue": { + "type": "number" + }, "created": { "type": "date" } diff --git a/modules/invoiceIn/back/models/invoice-in-tax.json b/modules/invoiceIn/back/models/invoice-in-tax.json new file mode 100644 index 000000000..abc137ecd --- /dev/null +++ b/modules/invoiceIn/back/models/invoice-in-tax.json @@ -0,0 +1,42 @@ +{ + "name": "InvoiceInTax", + "base": "VnModel", + "options": { + "mysql": { + "table": "invoiceInTax" + } + }, + "properties": { + "id": { + "id": true, + "type": "number", + "description": "Identifier" + }, + "invoiceInFk": { + "type": "number" + }, + "taxCodeFk": { + "type": "number" + }, + "taxableBase": { + "type": "number" + }, + "expenceFk": { + "type": "string" + }, + "foreignValue": { + "type": "number" + }, + "taxTypeSageFk": { + "type": "number" + }, + "transactionTypeSageFk": { + "type": "number" + }, + "created": { + "type": "date" + } + } +} + + diff --git a/modules/invoiceIn/back/models/invoice-in.js b/modules/invoiceIn/back/models/invoice-in.js index 7c5b16358..7754890ca 100644 --- a/modules/invoiceIn/back/models/invoice-in.js +++ b/modules/invoiceIn/back/models/invoice-in.js @@ -1,4 +1,5 @@ module.exports = Self => { require('../methods/invoice-in/filter')(Self); require('../methods/invoice-in/summary')(Self); + require('../methods/invoice-in/clone')(Self); }; diff --git a/modules/invoiceIn/back/models/invoice-in.json b/modules/invoiceIn/back/models/invoice-in.json index 468972523..9ec08c759 100644 --- a/modules/invoiceIn/back/models/invoice-in.json +++ b/modules/invoiceIn/back/models/invoice-in.json @@ -12,7 +12,7 @@ "properties": { "id": { "id": true, - "type": "Number", + "type": "number", "description": "Identifier" }, "serialNumber": { @@ -36,6 +36,9 @@ "booked": { "type": "date" }, + "isVatDeductible": { + "type": "boolean" + }, "operated": { "type": "date" }, diff --git a/modules/invoiceIn/front/descriptor/index.html b/modules/invoiceIn/front/descriptor/index.html index 42a946913..6829a0daf 100644 --- a/modules/invoiceIn/front/descriptor/index.html +++ b/modules/invoiceIn/front/descriptor/index.html @@ -8,6 +8,13 @@ translate> Delete Invoice + + Clone Invoice +
@@ -42,8 +49,16 @@
- - + + + diff --git a/modules/invoiceIn/front/descriptor/index.js b/modules/invoiceIn/front/descriptor/index.js index be507e0d4..a767f4b5c 100644 --- a/modules/invoiceIn/front/descriptor/index.js +++ b/modules/invoiceIn/front/descriptor/index.js @@ -30,6 +30,12 @@ class Controller extends Descriptor { .then(() => this.vnApp.showSuccess(this.$t('InvoiceIn deleted'))); } + cloneInvoiceIn() { + return this.$http.post(`InvoiceIns/${this.id}/clone`) + .then(res => this.$state.go('invoiceIn.card.summary', {id: res.data.id})) + .then(() => this.vnApp.showSuccess(this.$t('InvoiceIn cloned'))); + } + loadData() { const filter = { include: [ diff --git a/modules/invoiceIn/front/locale/es.yml b/modules/invoiceIn/front/locale/es.yml index ac432aba8..dee0d2bc1 100644 --- a/modules/invoiceIn/front/locale/es.yml +++ b/modules/invoiceIn/front/locale/es.yml @@ -2,4 +2,5 @@ InvoiceIn: Facturas recibidas Search invoices in by reference: Buscar facturas recibidas por referencia Entries list: Listado de entradas Invoice list: Listado de facturas recibidas -InvoiceIn deleted: Factura eliminada \ No newline at end of file +InvoiceIn deleted: Factura eliminada +InvoiceIn cloned: Factura clonada \ No newline at end of file diff --git a/modules/invoiceOut/front/descriptor/locale/es.yml b/modules/invoiceOut/front/descriptor/locale/es.yml index ae4396a01..ec9cd3310 100644 --- a/modules/invoiceOut/front/descriptor/locale/es.yml +++ b/modules/invoiceOut/front/descriptor/locale/es.yml @@ -5,8 +5,10 @@ Invoice ticket list: Listado de tickets de la factura Show invoice PDF: Ver factura en PDF Send invoice PDF: Enviar factura en PDF Delete Invoice: Eliminar factura +Clone Invoice: Clonar factura InvoiceOut deleted: Factura eliminada Are you sure you want to delete this invoice?: Estas seguro de eliminar esta factura? +Are you sure you want to clone this invoice?: Estas seguro de clonar esta factura? Book invoice: Asentar factura InvoiceOut booked: Factura asentada Are you sure you want to book this invoice?: Estas seguro de querer asentar esta factura?