2940-invoiceInTax-section #680

Merged
joan merged 36 commits from 2940-invoiceInTax-section into dev 2021-08-13 06:11:07 +00:00
29 changed files with 640 additions and 4802 deletions

View File

@ -2,6 +2,12 @@ DELETE FROM `salix`.`ACL` WHERE id = 189;
DELETE FROM `salix`.`ACL` WHERE id = 188; DELETE FROM `salix`.`ACL` WHERE id = 188;
UPDATE `salix`.`ACL` tdms SET tdms.accessType = '*' UPDATE `salix`.`ACL` tdms SET tdms.accessType = '*'
WHERE tdms.id = 165; WHERE tdms.id = 165;
INSERT INTO `salix`.`ACL` (model, principalId, property, accessType)
VALUES
('InvoiceInTax','administrative', '*', '*'),
('InvoiceInLog','administrative', '*', 'READ');
INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId) INSERT INTO `salix`.`ACL` (model, property, accessType, permission, principalType, principalId)
VALUES VALUES
('InvoiceOut', 'createManualInvoice', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'), ('InvoiceOut', 'createManualInvoice', 'WRITE', 'ALLOW', 'ROLE', 'invoicing'),

View File

@ -2340,13 +2340,20 @@ INSERT INTO `vn`.`duaInvoiceIn`(`id`, `duaFk`, `invoiceInFk`)
(9, 9, 9), (9, 9, 9),
(10, 10, 10); (10, 10, 10);
INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxCodeFk`, `taxableBase`, `expenceFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`, `created`) INSERT INTO `vn`.`invoiceInTax` (`invoiceInFk`, `taxableBase`, `expenceFk`, `foreignValue`, `taxTypeSageFk`, `transactionTypeSageFk`)
VALUES VALUES
(1, 4, 99.99, '2000000000', null, null, null, CURDATE()), (1, 99.99, '2000000000', null, null, null),
(2, 4, 999.99, '2000000000', null, null, null, CURDATE()), (2, 999.99, '2000000000', null, null, null),
(3, 4, 1000.50, '2000000000', null, null, null, CURDATE()), (3, 1000.50, '2000000000', null, null, null),
(4, 4, 0.50, '2000000000', null, null, null, CURDATE()), (4, 0.50, '2000000000', null, null, null),
(5, 4, 150.50, '2000000000', null, null, null, CURDATE()); (5, 150.50, '2000000000', null, null, null),
(1, 252.25, '4751000000', NULL, 7, 61),
(2, 223.17, '6210000567', NULL, 8, 20),
(3, 95.60, '7001000000', NULL, 8, 35),
(4, 446.63, '7001000000', NULL, 6, 61),
(5, 64.23, '6210000567', NULL, 8, 20),
(6, 29.95, '7001000000', NULL, 7, 20),
(7, 58.64, '6210000567', NULL, 8, 20);
INSERT INTO `vn`.`ticketRecalc`(`ticketFk`) INSERT INTO `vn`.`ticketRecalc`(`ticketFk`)
SELECT `id` SELECT `id`

View File

@ -946,9 +946,11 @@ export default {
ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)' ticketTwo: 'vn-invoice-out-summary > vn-card > vn-horizontal > vn-auto > vn-table > div > vn-tbody > vn-tr:nth-child(2)'
}, },
invoiceInSummary: { invoiceInSummary: {
totalTaxableBase: 'vn-invoice-in-summary > vn-card > vn-horizontal > vn-auto > vn-horizontal > vn-one.taxes > span',
supplierRef: 'vn-invoice-in-summary vn-label-value:nth-child(2) > section > span' supplierRef: 'vn-invoice-in-summary vn-label-value:nth-child(2) > section > span'
}, },
invoiceInDescriptor: { invoiceInDescriptor: {
summaryIcon: 'vn-invoice-in-descriptor a[title="Preview"]',
moreMenu: 'vn-invoice-in-descriptor vn-icon-button[icon=more_vert]', moreMenu: 'vn-invoice-in-descriptor vn-icon-button[icon=more_vert]',
moreMenuDeleteInvoiceIn: '.vn-menu [name="deleteInvoice"]', moreMenuDeleteInvoiceIn: '.vn-menu [name="deleteInvoice"]',
moreMenuCloneInvoiceIn: '.vn-menu [name="cloneInvoice"]', moreMenuCloneInvoiceIn: '.vn-menu [name="cloneInvoice"]',
@ -965,6 +967,17 @@ export default {
company: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.companyFk"]', company: 'vn-invoice-in-basic-data vn-autocomplete[ng-model="$ctrl.invoiceIn.companyFk"]',
save: 'vn-invoice-in-basic-data button[type=submit]' save: 'vn-invoice-in-basic-data button[type=submit]'
}, },
invoiceInTax: {
addTaxButton: 'vn-invoice-in-tax vn-icon-button[icon="add_circle"]',
thirdExpence: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.expenseFk"]',
thirdTaxableBase: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-input-number[ng-model="invoiceInTax.taxableBase"]',
thirdTaxType: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.taxTypeSageFk"]',
thirdTransactionType: 'vn-invoice-in-tax vn-horizontal:nth-child(3) > vn-autocomplete[ng-model="invoiceInTax.transactionTypeSageFk"]',
thirdRate: 'vn-invoice-in-tax > form > vn-card > vn-horizontal:nth-child(3) > vn-textfield',
thirdDeleteButton: 'vn-invoice-in-tax vn-horizontal:nth-child(3) vn-icon[icon="delete"]',
saveButton: 'vn-invoice-in-tax vn-submit',
},
travelIndex: { travelIndex: {
anySearchResult: 'vn-travel-index vn-tbody > a', anySearchResult: 'vn-travel-index vn-tbody > a',
firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)', firstSearchResult: 'vn-travel-index vn-tbody > a:nth-child(1)',

View File

@ -26,7 +26,7 @@ describe('InvoiceIn basic data path', () => {
await page.write(selectors.invoiceInBasicData.supplierRef, '9999'); await page.write(selectors.invoiceInBasicData.supplierRef, '9999');
await page.pickDate(selectors.invoiceInBasicData.bookEntried, now); await page.pickDate(selectors.invoiceInBasicData.bookEntried, now);
await page.pickDate(selectors.invoiceInBasicData.booked, now); await page.pickDate(selectors.invoiceInBasicData.booked, now);
await page.autocompleteSearch(selectors.invoiceInBasicData.currency, 'Dollar USA'); await page.autocompleteSearch(selectors.invoiceInBasicData.currency, 'USD');
await page.autocompleteSearch(selectors.invoiceInBasicData.company, 'ORN'); await page.autocompleteSearch(selectors.invoiceInBasicData.company, 'ORN');
await page.waitToClick(selectors.invoiceInBasicData.save); await page.waitToClick(selectors.invoiceInBasicData.save);
const message = await page.waitForSnackbar(); const message = await page.waitForSnackbar();
@ -52,7 +52,7 @@ describe('InvoiceIn basic data path', () => {
const result = await page const result = await page
.waitToGetProperty(selectors.invoiceInBasicData.currency, 'value'); .waitToGetProperty(selectors.invoiceInBasicData.currency, 'value');
expect(result).toEqual('Dollar USA'); expect(result).toEqual('USD');
}); });
it(`should confirm the invoiceIn company was edited`, async() => { it(`should confirm the invoiceIn company was edited`, async() => {

View File

@ -0,0 +1,78 @@
import selectors from '../../helpers/selectors.js';
import getBrowser from '../../helpers/puppeteer';
describe('InvoiceIn tax path', () => {
let browser;
let page;
beforeAll(async() => {
browser = await getBrowser();
page = browser.page;
await page.loginAndModule('developer', 'invoiceIn');
await page.accessToSearchResult('2');
await page.accessToSection('invoiceIn.card.tax');
});
afterAll(async() => {
await browser.close();
});
it('should add a new tax', async() => {
await page.waitToClick(selectors.invoiceInTax.addTaxButton);
await page.autocompleteSearch(selectors.invoiceInTax.thirdExpence, '6210000567');
await page.write(selectors.invoiceInTax.thirdTaxableBase, '100');
await page.autocompleteSearch(selectors.invoiceInTax.thirdTaxType, '6');
await page.autocompleteSearch(selectors.invoiceInTax.thirdTransactionType, 'Operaciones exentas');
await page.waitToClick(selectors.invoiceInTax.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
it('should navigate to the summary and check the taxable base sum is correct', async() => {
await page.waitToClick(selectors.invoiceInDescriptor.summaryIcon);
await page.waitForState('invoiceIn.card.summary');
const result = await page.waitToGetProperty(selectors.invoiceInSummary.totalTaxableBase, 'innerText');
expect(result).toEqual('Taxable base €1,323.16');
});
it('should navigate back to the tax section and check the reciently added line contains the expected expense', async() => {
await page.accessToSection('invoiceIn.card.tax');
const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdExpence, 'value');
expect(result).toEqual('6210000567: Alquiler VNH');
});
it('should check the reciently added line contains the expected taxable base', async() => {
const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdTaxableBase, 'value');
expect(result).toEqual('100');
});
it('should check the reciently added line contains the expected tax type', async() => {
const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdTaxType, 'value');
expect(result).toEqual('6: H.P. IVA 4% CEE');
});
it('should check the reciently added line contains the expected transaction type', async() => {
const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdTransactionType, 'value');
expect(result).toEqual('37: Operaciones exentas');
});
it('should check the reciently added line contains the expected rate', async() => {
const result = await page.waitToGetProperty(selectors.invoiceInTax.thirdRate, 'value');
expect(result).toEqual('€4.00');
});
it('should delete the added line', async() => {
await page.waitToClick(selectors.invoiceInTax.thirdDeleteButton);
await page.waitToClick(selectors.invoiceInTax.saveButton);
const message = await page.waitForSnackbar();
expect(message.text).toContain('Data saved!');
});
});

View File

@ -113,5 +113,6 @@
"reserved": "reserved", "reserved": "reserved",
"Global invoicing failed": "[Global invoicing] Wasn't able to invoice some of the clients", "Global invoicing failed": "[Global invoicing] Wasn't able to invoice some of the clients",
"A ticket with a negative base can't be invoiced": "A ticket with a negative base can't be invoiced", "A ticket with a negative base can't be invoiced": "A ticket with a negative base can't be invoiced",
"This client is not invoiceable": "This client is not invoiceable" "This client is not invoiceable": "This client is not invoiceable",
"INACTIVE_PROVIDER": "Inactive provider"
} }

View File

@ -195,6 +195,7 @@
"This document already exists on this ticket": "Este documento ya existe en el ticket", "This document already exists on this ticket": "Este documento ya existe en el ticket",
"Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables", "Some of the selected tickets are not billable": "Algunos de los tickets seleccionados no son facturables",
"You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes", "You can't invoice tickets from multiple clients": "No puedes facturar tickets de multiples clientes",
"INACTIVE_PROVIDER": "Proveedor inactivo",
Outdated
Review

Añadir traducción

Añadir traducción
"This client is not invoiceable": "Este cliente no es facturable", "This client is not invoiceable": "Este cliente no es facturable",
"serial non editable": "Esta serie no permite asignar la referencia", "serial non editable": "Esta serie no permite asignar la referencia",
"Max shipped required": "La fecha límite es requerida", "Max shipped required": "La fecha límite es requerida",

View File

@ -20,6 +20,12 @@
"mysql": { "mysql": {
"columnName": "Iva" "columnName": "Iva"
} }
},
"rate": {
"type": "number",
"mysql": {
"columnName": "PorcentajeIva"
}
} }
}, },
"acls": [ "acls": [

View File

@ -69,25 +69,13 @@ module.exports = Self => {
deductibleExpenseFk: sourceInvoiceIn.deductibleExpenseFk, deductibleExpenseFk: sourceInvoiceIn.deductibleExpenseFk,
}, myOptions); }, 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 = []; const promises = [];
for (let tax of sourceInvoiceInTax) { for (let tax of sourceInvoiceInTax) {
promises.push(models.InvoiceInTax.create({ promises.push(models.InvoiceInTax.create({
invoiceInFk: clone.id, invoiceInFk: clone.id,
taxableBase: tax.taxableBase, taxableBase: tax.taxableBase,
expenceFk: tax.expenceFk, expenseFk: tax.expenseFk,
foreignValue: tax.foreignValue, foreignValue: tax.foreignValue,
taxTypeSageFk: tax.taxTypeSageFk, taxTypeSageFk: tax.taxTypeSageFk,
transactionTypeSageFk: tax.transactionTypeSageFk transactionTypeSageFk: tax.transactionTypeSageFk

View File

@ -21,7 +21,7 @@ describe('invoiceIn clone()', () => {
const invoiceInTaxes = await models.InvoiceInTax.find({where: {invoiceInFk: clone.id}}, options); const invoiceInTaxes = await models.InvoiceInTax.find({where: {invoiceInFk: clone.id}}, options);
expect(invoiceInTaxes.length).toEqual(1); expect(invoiceInTaxes.length).toEqual(2);
const invoiceInDueDays = await models.InvoiceInDueDay.find({where: {invoiceInFk: clone.id}}, options); const invoiceInDueDays = await models.InvoiceInDueDay.find({where: {invoiceInFk: clone.id}}, options);

View File

@ -9,6 +9,7 @@ describe('invoiceIn summary()', () => {
const summary = await models.InvoiceIn.summary(1, options); const summary = await models.InvoiceIn.summary(1, options);
expect(summary.supplierRef).toEqual('1234'); expect(summary.supplierRef).toEqual('1234');
expect(summary.totals.totalTaxableBase).toEqual(352.24);
await tx.rollback(); await tx.rollback();
} catch (e) { } catch (e) {

View File

@ -45,6 +45,31 @@ module.exports = Self => {
fields: ['withholding'] fields: ['withholding']
} }
joan marked this conversation as resolved Outdated

lines longer than 80 to 120 characters require indentation.

lines longer than 80 to 120 characters require indentation.
}, },
{
relation: 'invoiceInTax',
scope: {
fields: [
'id',
'invoiceInFk',
'taxableBase',
'expenseFk',
'taxTypeSageFk',
'transactionTypeSageFk',
'foreignValue'],
include: [{
relation: 'transactionTypeSage',
scope: {
fields: ['transaction']
}
},
{
relation: 'taxTypeSage',
scope: {
fields: ['vat']
}
}]
}
},
{ {
relation: 'expenseDeductible', relation: 'expenseDeductible',
scope: { scope: {
@ -54,12 +79,31 @@ module.exports = Self => {
{ {
relation: 'currency', relation: 'currency',
scope: { scope: {
fields: ['id', 'name'] fields: ['id', 'code']
} }
} }
] ]
}; };
return Self.app.models.InvoiceIn.findById(id, filter, myOptions); let summaryObj = await Self.app.models.InvoiceIn.findById(id, filter, myOptions);
summaryObj.totals = await getTotals(id);
return summaryObj;
}; };
async function getTotals(invoiceInFk) {
return (await Self.rawSql(`
SELECT iit.*,
SUM(iidd.amount) totalDueDay
FROM vn.invoiceIn ii
LEFT JOIN (SELECT SUM(iit.taxableBase) totalTaxableBase,
SUM(iit.taxableBase * (1 + (ti.PorcentajeIva / 100))) totalVat
FROM vn.invoiceInTax iit
LEFT JOIN sage.TiposIva ti ON ti.CodigoIva = iit.taxTypeSageFk
WHERE iit.invoiceInFk = ?) iit ON TRUE
LEFT JOIN vn.invoiceInDueDay iidd ON iidd.invoiceInFk = ii.id
WHERE
ii.id = ?`, [invoiceInFk, invoiceInFk]))[0];
}
}; };

View File

@ -1,6 +1,10 @@
{ {
"name": "InvoiceInTax", "name": "InvoiceInTax",
"base": "VnModel", "base": "Loggable",
"log": {
"model": "InvoiceInLog",
"relation": "invoiceIn"
},
"options": { "options": {
"mysql": { "mysql": {
"table": "invoiceInTax" "table": "invoiceInTax"
@ -12,31 +16,43 @@
"type": "number", "type": "number",
"description": "Identifier" "description": "Identifier"
}, },
"invoiceInFk": {
"type": "number"
},
"taxCodeFk": {
"type": "number"
},
"taxableBase": { "taxableBase": {
"type": "number" "type": "number"
}, },
"expenceFk": {
"type": "string"
},
"foreignValue": { "foreignValue": {
"type": "number" "type": "number"
}, },
"taxTypeSageFk": { "expenseFk": {
"type": "number" "type": "number",
}, "mysql": {
"transactionTypeSageFk": { "columnName": "expenceFk"
"type": "number" }
}, },
"created": { "created": {
"type": "date" "type": "date"
} }
},
"relations": {
"invoiceIn": {
"type": "belongsTo",
"model": "InvoiceIn",
"foreignKey": "invoiceInFk"
},
"expense": {
"type": "belongsTo",
"model": "Expense",
"foreignKey": "expenseFk"
},
"taxTypeSage": {
"type": "belongsTo",
"model": "SageTaxType",
"foreignKey": "taxTypeSageFk"
},
"transactionTypeSage": {
"type": "belongsTo",
"model": "SageTransactionType",
"foreignKey": "transactionTypeSageFk"
}
} }
} }

View File

@ -64,6 +64,11 @@
"model": "InvoiceInDueDay", "model": "InvoiceInDueDay",
"foreignKey": "invoiceInFk" "foreignKey": "invoiceInFk"
}, },
"invoiceInTax": {
"type": "hasMany",
"model": "InvoiceInTax",
"foreignKey": "invoiceInFk"
},
"sageWithholding": { "sageWithholding": {
"type": "belongsTo", "type": "belongsTo",
"model": "SageWithholding", "model": "SageWithholding",

View File

@ -78,7 +78,7 @@
label="Currency" label="Currency"
ng-model="$ctrl.invoiceIn.currencyFk" ng-model="$ctrl.invoiceIn.currencyFk"
url="Currencies" url="Currencies"
show-field="name" show-field="code"
value-field="id" value-field="id"
rule> rule>
</vn-autocomplete> </vn-autocomplete>

View File

@ -13,6 +13,9 @@ class Controller extends ModuleCard {
}, },
{ {
relation: 'company' relation: 'company'
},
{
relation: 'currency'
} }
]}; ]};

View File

@ -8,5 +8,6 @@ import './descriptor';
import './descriptor-popover'; import './descriptor-popover';
import './summary'; import './summary';
import './basic-data'; import './basic-data';
import './tax';
import './create'; import './create';
import './log'; import './log';

View File

@ -1,6 +1,13 @@
InvoiceIn: Facturas recibidas InvoiceIn: Facturas recibidas
Search invoices in by reference: Buscar facturas recibidas por referencia Search invoices in by reference: Buscar facturas recibidas por referencia
Entries list: Listado de entradas Entries list: Listado de entradas
Invoice list: Listado de facturas recibidas
InvoiceIn deleted: Factura eliminada InvoiceIn deleted: Factura eliminada
InvoiceIn cloned: Factura clonada Remove tax: Quitar iva
Add tax: Añadir iva
Sage tax: Sage iva
Sage transaction: Sage transaccion
Foreign value: Divisa
Due day: Vencimiento
Invoice list: Listado de facturas recibidas
InvoiceIn cloned: Factura clonada

View File

@ -19,6 +19,10 @@
"state": "invoiceIn.card.basicData", "state": "invoiceIn.card.basicData",
"icon": "settings" "icon": "settings"
}, },
{
"state": "invoiceIn.card.tax",
"icon": "icon-lines"
},
{ {
"state": "invoiceIn.card.log", "state": "invoiceIn.card.log",
"icon": "history" "icon": "history"
@ -81,6 +85,16 @@
"administrative" "administrative"
] ]
}, },
{
"url": "/tax",
"state": "invoiceIn.card.tax",
"component": "vn-invoice-in-tax",
"description": "Tax",
"params": {
"invoice-in": "$ctrl.invoiceIn"
},
"acl": ["administrative"]
},
{ {
"url": "/log", "url": "/log",
"state": "invoiceIn.card.log", "state": "invoiceIn.card.log",

View File

@ -18,7 +18,7 @@
</vn-label-value> </vn-label-value>
<vn-label-value label="Supplier ref" value="{{$ctrl.summary.supplierRef}}"> <vn-label-value label="Supplier ref" value="{{$ctrl.summary.supplierRef}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Currency" value="{{$ctrl.summary.currency.name}}"> <vn-label-value label="Currency" value="{{$ctrl.summary.currency.code}}">
</vn-label-value> </vn-label-value>
<vn-label-value label="Doc number" value="{{$ctrl.summary.serial}}/{{$ctrl.summary.serialNumber}}"> <vn-label-value label="Doc number" value="{{$ctrl.summary.serial}}/{{$ctrl.summary.serialNumber}}">
</vn-label-value> </vn-label-value>
@ -45,8 +45,51 @@
</vn-check> </vn-check>
</vn-vertical> </vn-vertical>
</vn-one> </vn-one>
<vn-one tbody class="taxes">
<span td class="chip"><vn-label translate>Taxable base</vn-label> {{$ctrl.summary.totals.totalTaxableBase | currency: 'EUR':2 | dashIfEmpty}}</span>
<p><vn-label translate>Total</vn-label> {{$ctrl.summary.totals.totalVat | currency: 'EUR':2 | dashIfEmpty}}</p>
<vn-label translate>Due day</vn-label>
<vn-chip
class="transparent"
ng-class="{'alert': $ctrl.amountsNotMatch}"
joan marked this conversation as resolved Outdated

extraction to a getter would be nice, making it more readable.

Also using translate-attr with spanish tittle is inconsistent.

extraction to a getter would be nice, making it more readable. Also using translate-attr with spanish tittle is inconsistent.
translate-attr="{title: $ctrl.amountsNotMatch ? 'Do not match' : 'Due day'}"
>{{$ctrl.summary.totals.totalDueDay | currency: 'EUR':2 | dashIfEmpty}}
</vn-chip>
</vn-one>
</vn-horizontal> </vn-horizontal>
</vn-auto> </vn-auto>
<vn-one ng-if="$ctrl.summary.invoiceInTax.length != 0">
<h4>
<a
ui-sref="invoiceIn.card.tax({id:$ctrl.invoiceIn.id})"
target="_self">
<span translate vn-tooltip="Go to">Vat</span>
</a>
</h4>
<vn-table model="model">
<vn-thead>
<vn-tr>
<vn-th number>Expense</vn-th>
<vn-th shrink>Taxable base</vn-th>
<vn-th>Sage vat</vn-th>
<vn-th>Sage transaction</vn-th>
<vn-th number shrink>Rate</vn-th>
<vn-th number shrink>Foreign value</vn-th>
</vn-tr>
</vn-thead>
<vn-tbody>
<vn-tr ng-repeat="tax in $ctrl.summary.invoiceInTax">
<vn-td number expand>{{::tax.expenseFk}}</vn-td>
<vn-td shrink>{{::tax.taxableBase | currency: 'EUR':2}}</vn-td>
<vn-td>{{::tax.taxTypeSage.vat}}</vn-td>
<vn-td>{{::tax.transactionTypeSage.transaction}}</vn-td>
joan marked this conversation as resolved Outdated

is 0.2 an static value?

also using :: before static values is incorrect.

is 0.2 an static value? also using :: before static values is incorrect.
<vn-td number shrink>{{::tax.taxRate | percentage}}</vn-td>
<vn-td number shrink>{{::tax.foreignValue | currency: 'USD':2}}</vn-td>
</vn-tr>
</vn-tbody>
</vn-table>
</vn-one>
</vn-horizontal> </vn-horizontal>
</vn-card> </vn-card>
<vn-supplier-descriptor-popover <vn-supplier-descriptor-popover

View File

@ -17,6 +17,13 @@ class Controller extends Summary {
return this.$http.get(`InvoiceIns/${this.invoiceIn.id}/summary`) return this.$http.get(`InvoiceIns/${this.invoiceIn.id}/summary`)
.then(res => this.summary = res.data); .then(res => this.summary = res.data);
} }
get amountsNotMatch() {
if (!this.summary) return false;
const total = this.summary.totals;
return total.totalDueDay != total.totalTaxableBase && total.totalDueDay != total.totalVat;
}
} }
ngModule.vnComponent('vnInvoiceInSummary', { ngModule.vnComponent('vnInvoiceInSummary', {

View File

@ -25,5 +25,34 @@ describe('InvoiceIn', () => {
expect(controller.summary).toEqual('the data you are looking for'); expect(controller.summary).toEqual('the data you are looking for');
}); });
}); });
describe('amountsNotMatch getter()', () => {
it('should get false when taxamount match with due day amount', () => {
controller.summary =
{
totals: {
totalDueDay: 'amount match',
totalTaxableBase: 'amount match',
totalVat: 'no care'
}
};
expect(controller.amountsNotMatch).toBeFalsy();
});
it('should get true when taxamount does not match with due day amount', () => {
controller.summary =
{
totals: {
totalDueDay: 'amount does not match',
totalTaxableBase: 'neither match',
totalVat: 'no care'
}
};
console.log(controller);
expect(controller.amountsNotMatch).toBeTruthy();
});
});
}); });
}); });

View File

@ -7,4 +7,5 @@ Booked date: Fecha contable
Accounted date: Fecha contable Accounted date: Fecha contable
Doc number: Numero documento Doc number: Numero documento
Sage withholding: Retención sage Sage withholding: Retención sage
Undeductible VAT: Iva no deducible Undeductible VAT: Iva no deducible
Do not match: No coinciden

View File

@ -1,5 +1,13 @@
@import "variables"; @import "./variables";
joan marked this conversation as resolved Outdated

this import doesn't seem to be needed

this import doesn't seem to be needed
vn-invoice-in-summary .summary { vn-invoice-in-summary .summary {
width: $width-lg; max-width: $width-xl;
.taxes {
border: $border-thin-light;
text-align: right;
padding: 8px;
font-size: 1.2rem;
margin: 3px;
}
} }

View File

@ -0,0 +1,99 @@
<vn-crud-model
vn-id="model"
url="InvoiceInTaxes"
fields="[
joan marked this conversation as resolved Outdated

lines longer than 80 to 120 characters shoueld be indented

lines longer than 80 to 120 characters shoueld be indented
'id',
'invoiceInFk',
'taxableBase',
'expenseFk',
'foreignValue',
'taxTypeSageFk',
'transactionTypeSageFk']"
link="{invoiceInFk: $ctrl.$params.id}"
data="$ctrl.taxes"
auto-load="true">
</vn-crud-model>
<vn-crud-model
url="Expenses"
data="expenses"
auto-load="true">
</vn-crud-model>
<vn-watcher
vn-id="watcher"
data="$ctrl.taxes"
form="form">
</vn-watcher>
<form name="form" ng-submit="$ctrl.onSubmit()">
<vn-card class="vn-pa-lg">
<vn-horizontal ng-repeat="invoiceInTax in $ctrl.taxes">
<vn-autocomplete vn-three vn-id="expense" vn-focus
label="Expense"
ng-model="invoiceInTax.expenseFk"
data="expenses"
show-field="id"
rule>
<tpl-item>{{id}}: {{name}}</tpl-item>
</vn-autocomplete>
<vn-input-number vn-one
disabled="$ctrl.invoiceIn.currency.code != 'EUR'"
step="0.01"
label="Taxable base"
ng-model="invoiceInTax.taxableBase"
rule>
</vn-input-number>
<vn-autocomplete vn-three
label="Sage tax"
ng-model="invoiceInTax.taxTypeSageFk"
url="SageTaxTypes"
show-field="vat"
fields="['id', 'vat', 'rate']"
search-function="{or: [{id: $search}, {vat: {like: '%'+ $search +'%'}}]}"
joan marked this conversation as resolved Outdated

trailling space not needed betweent hatml tags in this case

trailling space not needed betweent hatml tags in this case
selection="taxRateSelection"
rule>
<tpl-item>{{id}}: {{vat}}</tpl-item>
</vn-autocomplete>
<vn-autocomplete vn-three
label="Sage transaction"
ng-model="invoiceInTax.transactionTypeSageFk"
url="SageTransactionTypes"
show-field="transaction"
search-function="{or: [{id: $search}, {transaction: {like: '%'+ $search +'%'}}]}"
rule>
<tpl-item>{{id}}: {{transaction}}</tpl-item>
</vn-autocomplete>
<vn-textfield
disabled="true"
label="Rate"
field="$ctrl.taxRate(invoiceInTax, taxRateSelection) | currency:'EUR':2">
</vn-textfield>
<vn-input-number
disabled="$ctrl.invoiceIn.currency.code == 'EUR'"
label="Foreign value"
ng-model="invoiceInTax.foreignValue"
rule>
</vn-input-number>
<vn-none>
<vn-icon-button
vn-tooltip="Remove tax"
icon="delete"
ng-click="model.remove($index)"
tabindex="-1">
</vn-icon-button>
joan marked this conversation as resolved Outdated

vn-input-number doesn't require the property type="number"

vn-input-number doesn't require the property type="number"
</vn-none>
</vn-horizontal>
<vn-one>
<vn-icon-button
vn-bind="+"
vn-tooltip="Add tax"
icon="add_circle"
ng-click="$ctrl.add()">
</vn-icon-button>
</vn-one>
</vn-card>
<vn-button-bar>
<vn-submit
disabled="!watcher.dataChanged()"
label="Save">
</vn-submit>
</vn-button-bar>
</form>

View File

@ -0,0 +1,40 @@
import ngModule from '../module';
import Section from 'salix/components/section';
class Controller extends Section {
taxRate(invoiceInTax, taxRateSelection) {
const taxTypeSage = taxRateSelection && taxRateSelection.rate;
const taxableBase = invoiceInTax && invoiceInTax.taxableBase;
if (taxTypeSage && taxableBase)
joan marked this conversation as resolved Outdated

rename to taxRateSelection

rename to taxRateSelection
return (taxTypeSage / 100) * taxableBase;
return 0;
}
add() {
this.$.model.insert({
invoiceIn: this.$params.id
});
}
onSubmit() {
this.$.watcher.check();
this.$.model.save().then(() => {
this.$.watcher.notifySaved();
this.$.watcher.updateOriginalData();
this.card.reload();
});
}
}
ngModule.vnComponent('vnInvoiceInTax', {
template: require('./index.html'),
controller: Controller,
require: {
card: '^vnInvoiceInCard'
},
bindings: {
invoiceIn: '<'
}
});

View File

@ -0,0 +1,59 @@
import './index.js';
import watcher from 'core/mocks/watcher';
import crudModel from 'core/mocks/crud-model';
describe('InvoiceIn', () => {
describe('Component tax', () => {
let controller;
let $scope;
let vnApp;
beforeEach(ngModule('invoiceIn'));
beforeEach(inject(($componentController, _$httpBackend_, $rootScope, _vnApp_) => {
vnApp = _vnApp_;
jest.spyOn(vnApp, 'showError');
$scope = $rootScope.$new();
$scope.model = crudModel;
$scope.watcher = watcher;
const $element = angular.element('<vn-invoice-in-tax></vn-invoice-in-tax>');
controller = $componentController('vnInvoiceInTax', {$element, $scope});
controller.invoiceIn = {id: 1};
}));
describe('taxRate()', () => {
it('should set tax rate with the Sage tax type value', () => {
const taxRateSelection = {
rate: 21
};
const invoiceInTax = {
taxableBase: 200
};
const taxRate = controller.taxRate(invoiceInTax, taxRateSelection);
expect(taxRate).toEqual(42);
});
});
describe('onSubmit()', () => {
it('should make HTTP POST request to save tax values', () => {
controller.card = {reload: () => {}};
jest.spyOn($scope.watcher, 'check');
jest.spyOn($scope.watcher, 'notifySaved');
jest.spyOn($scope.watcher, 'updateOriginalData');
jest.spyOn(controller.card, 'reload');
jest.spyOn($scope.model, 'save');
controller.onSubmit();
expect($scope.model.save).toHaveBeenCalledWith();
expect($scope.watcher.updateOriginalData).toHaveBeenCalledWith();
expect($scope.watcher.check).toHaveBeenCalledWith();
expect($scope.watcher.notifySaved).toHaveBeenCalledWith();
expect(controller.card.reload).toHaveBeenCalledWith();
});
});
});
});

4864
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -42,6 +42,7 @@
"strong-error-handler": "^2.3.2", "strong-error-handler": "^2.3.2",
"uuid": "^3.3.3", "uuid": "^3.3.3",
"vn-loopback": "file:./loopback", "vn-loopback": "file:./loopback",
"vn-mysql": "^1.0.3",
"xml2js": "^0.4.23" "xml2js": "^0.4.23"
}, },
"devDependencies": { "devDependencies": {