diff --git a/accounting/ledgerPosting.js b/accounting/ledgerPosting.js index 8a1ac948..486f92dc 100644 --- a/accounting/ledgerPosting.js +++ b/accounting/ledgerPosting.js @@ -7,24 +7,24 @@ module.exports = class LedgerPosting { this.entryMap = {}; } - debit(account, amount) { - const entry = this.getEntry(account); + debit(account, amount, referenceType, referenceName) { + const entry = this.getEntry(account, referenceType, referenceName); entry.debit += amount; } - credit(account, amount) { - const entry = this.getEntry(account); + credit(account, amount, referenceType, referenceName) { + const entry = this.getEntry(account, referenceType, referenceName); entry.credit += amount; } - getEntry(account) { + getEntry(account, referenceType, referenceName) { if (!this.entryMap[account]) { const entry = { account: account, party: this.party, date: this.date || this.reference.date, - referenceType: this.reference.doctype, - referenceName: this.reference.name, + referenceType: referenceType || this.reference.doctype, + referenceName: referenceName || this.reference.name, description: this.description, debit: 0, credit: 0 diff --git a/accounting/utils.js b/accounting/utils.js new file mode 100644 index 00000000..162bcb02 --- /dev/null +++ b/accounting/utils.js @@ -0,0 +1,17 @@ +module.exports = { + ledgerLink: { + label: 'Ledger Entries', + condition: form => form.doc.submitted, + action: form => { + return { + route: ['report', 'general-ledger'], + params: { + filters: { + referenceType: form.doc.doctype, + referenceName: form.doc.name + } + } + }; + } + }, +} \ No newline at end of file diff --git a/models/doctype/AccountingSettings/AccountingSettings.js b/models/doctype/AccountingSettings/AccountingSettings.js new file mode 100644 index 00000000..27b2205f --- /dev/null +++ b/models/doctype/AccountingSettings/AccountingSettings.js @@ -0,0 +1,24 @@ +module.exports = { + name: "AccountingSettings", + label: "AccountingSettings", + naming: "name", // {random|autoincrement} + isSingle: 1, + isChild: 0, + isSubmittable: 0, + settings: null, + keywordFields: [], + fields: [ + { + fieldname: "Company Name", + label: "companyName", + fieldtype: "Data", + required: 1 + }, + { + fieldname: "Writeoff Account", + label: "writeOffAccount", + fieldtype: "Account" + } + + ] +} \ No newline at end of file diff --git a/models/doctype/Invoice/Invoice.js b/models/doctype/Invoice/Invoice.js index 60407329..04201f8e 100644 --- a/models/doctype/Invoice/Invoice.js +++ b/models/doctype/Invoice/Invoice.js @@ -1,4 +1,5 @@ const frappe = require('frappejs'); +const utils = require('../../../accounting/utils'); module.exports = { "name": "Invoice", @@ -100,21 +101,7 @@ module.exports = { ], links: [ - { - label: 'Ledger Entries', - condition: form => form.doc.submitted, - action: form => { - return { - route: ['table', 'AccountingLedgerEntry'], - params: { - filters: { - referenceType: 'Invoice', - referenceName: form.doc.name - } - } - }; - } - }, + utils.ledgerLink, { label: 'Make Payment', condition: form => form.doc.submitted, diff --git a/models/doctype/InvoiceSettings/InvoiceSettings.js b/models/doctype/InvoiceSettings/InvoiceSettings.js index 50673c74..9feeb779 100644 --- a/models/doctype/InvoiceSettings/InvoiceSettings.js +++ b/models/doctype/InvoiceSettings/InvoiceSettings.js @@ -11,7 +11,8 @@ module.exports = { "label": "Number Series", "fieldtype": "Link", "target": "NumberSeries", - "required": 1 + "required": 1, + "default": "INV" } ] } \ No newline at end of file diff --git a/models/doctype/Payment/Payment.js b/models/doctype/Payment/Payment.js index 7131ad78..1374c2fe 100644 --- a/models/doctype/Payment/Payment.js +++ b/models/doctype/Payment/Payment.js @@ -1,10 +1,13 @@ +const utils = require('../../../accounting/utils'); + module.exports = { name: "Payment", label: "Payment", - naming: "name", // {random|autoincrement} isSingle: 0, isChild: 0, + isSubmittable: 1, keywordFields: [], + settings: "PaymentSettings", fields: [ { "fieldname": "date", @@ -67,5 +70,9 @@ module.exports = { { fields: ['amount', 'writeoff'] } + ], + + links: [ + utils.ledgerLink ] } \ No newline at end of file diff --git a/models/doctype/Payment/PaymentServer.js b/models/doctype/Payment/PaymentServer.js new file mode 100644 index 00000000..e09ef24b --- /dev/null +++ b/models/doctype/Payment/PaymentServer.js @@ -0,0 +1,25 @@ +const BaseDocument = require('frappejs/model/document'); +const frappe = require('frappejs'); +const LedgerPosting = require.main.require('./accounting/ledgerPosting'); + +module.exports = class PaymentServer extends BaseDocument { + getPosting() { + let entries = new LedgerPosting({reference: this, party: this.party}); + entries.debit(this.paymentAccount, this.amount); + + for (let row of this.for) { + entries.credit(this.account, row.amount, row.referenceType, row.referenceName); + } + + return entries; + + } + + async afterSubmit() { + await this.getPosting().post(); + } + + async afterRevert() { + await this.getPosting().postReverse(); + } +} \ No newline at end of file diff --git a/models/doctype/PaymentSettings/PaymentSettings.js b/models/doctype/PaymentSettings/PaymentSettings.js new file mode 100644 index 00000000..418fc639 --- /dev/null +++ b/models/doctype/PaymentSettings/PaymentSettings.js @@ -0,0 +1,17 @@ +module.exports = { + name: "PaymentSettings", + label: "Payment Settings", + isSingle: 1, + isChild: 0, + keywordFields: [], + "fields": [ + { + "fieldname": "numberSeries", + "label": "Number Series", + "fieldtype": "Link", + "target": "NumberSeries", + "required": 1, + "default": "PAY" + } + ] +} \ No newline at end of file diff --git a/models/index.js b/models/index.js index 7efaa682..a0897ac3 100644 --- a/models/index.js +++ b/models/index.js @@ -3,12 +3,17 @@ module.exports = { Account: require('./doctype/Account/Account.js'), AccountingLedgerEntry: require('./doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'), Party: require('./doctype/Party/Party.js'), + Payment: require('./doctype/Payment/Payment.js'), PaymentFor: require('./doctype/PaymentFor/PaymentFor.js'), + PaymentSettings: require('./doctype/PaymentSettings/PaymentSettings.js'), + Item: require('./doctype/Item/Item.js'), + Invoice: require('./doctype/Invoice/Invoice.js'), InvoiceItem: require('./doctype/InvoiceItem/InvoiceItem.js'), InvoiceSettings: require('./doctype/InvoiceSettings/InvoiceSettings.js'), + Tax: require('./doctype/Tax/Tax.js'), TaxDetail: require('./doctype/TaxDetail/TaxDetail.js'), TaxSummary: require('./doctype/TaxSummary/TaxSummary.js') diff --git a/reports/generalLedger/GeneralLedger.js b/reports/generalLedger/GeneralLedger.js index 86c7feea..e5f49760 100644 --- a/reports/generalLedger/GeneralLedger.js +++ b/reports/generalLedger/GeneralLedger.js @@ -1,12 +1,18 @@ const frappe = require('frappejs'); -module.exports = class GeneralLedger { +module.exports = function execute(params) { + return new GeneralLedger().run(params); +} + +class GeneralLedger { async run(params) { const filters = {}; if (params.account) filters.account = params.account; if (params.party) filters.party = params.party; - if (params.from_date) filters.date = ('>=', params.from_date); - if (params.to_date) filters.date = ('<=', params.to_date); + if (params.referenceType) filters.referenceType = params.referenceType; + if (params.referenceName) filters.referenceName = params.referenceName; + if (params.fromDate) filters.date = ['>=', params.fromDate]; + if (params.toDate) filters.date = ['<=', params.toDate]; let data = await frappe.db.getAll({ doctype: 'AccountingLedgerEntry', diff --git a/reports/generalLedger/GeneralLedgerView.js b/reports/generalLedger/GeneralLedgerView.js index 037d23cf..a76faee0 100644 --- a/reports/generalLedger/GeneralLedgerView.js +++ b/reports/generalLedger/GeneralLedgerView.js @@ -3,12 +3,19 @@ const frappe = require('frappejs'); module.exports = class GeneralLedgerView extends ReportPage { constructor() { - super({title: frappe._('General Ledger')}); - - this.addFilter({fieldtype: 'Link', target: 'Account', label: 'Account'}); - this.addFilter({fieldtype: 'Link', target: 'Party', label: 'Party'}); - this.addFilter({fieldtype: 'Date', label: 'From Date'}); - this.addFilter({fieldtype: 'Date', label: 'To Date'}); + super({ + title: frappe._('General Ledger'), + filterFields: [ + {fieldtype: 'Select', options: ['', 'Invoice', 'Payment'], + label: 'Reference Type', fieldname: 'referenceType'}, + {fieldtype: 'DynamicLink', references: 'referenceType', + label: 'Reference Name', fieldname: 'referenceName'}, + {fieldtype: 'Link', target: 'Account', label: 'Account'}, + {fieldtype: 'Link', target: 'Party', label: 'Party'}, + {fieldtype: 'Date', label: 'From Date'}, + {fieldtype: 'Date', label: 'To Date'} + ] + }); this.method = 'general-ledger'; } diff --git a/server/index.js b/server/index.js index aa609116..2c139b2b 100644 --- a/server/index.js +++ b/server/index.js @@ -1,6 +1,7 @@ const server = require('frappejs/server'); const frappe = require('frappejs'); const GeneralLedger = require('../reports/generalLedger/GeneralLedger') +const naming = require('frappejs/model/naming') module.exports = { async start() { @@ -13,16 +14,19 @@ module.exports = { // set server-side modules frappe.models.Invoice.documentClass = require('../models/doctype/Invoice/InvoiceServer.js'); + frappe.models.Payment.documentClass = require('../models/doctype/Payment/PaymentServer.js'); + frappe.metaCache = {}; frappe.syncDoc(require('../fixtures/invoicePrint')); + // init naming series if missing + await naming.createNumberSeries('INV-', 'InvoiceSetting'); + await naming.createNumberSeries('PAY-', 'PaymentSetting'); + frappe.registerMethod({ method: 'general-ledger', - handler: async (args) => { - const generalLedger = new GeneralLedger(); - return await generalLedger.run(args); - } + handler: args => GeneralLedger(args) }); } } diff --git a/www/dist/css/style.css b/www/dist/css/style.css index 9b587a94..b9606919 100644 --- a/www/dist/css/style.css +++ b/www/dist/css/style.css @@ -7396,7 +7396,8 @@ html { .form-body .alert { margin-top: 1rem; } .form-inline .form-group { - margin-right: 0.5rem; } + margin-right: 1rem; + margin-bottom: 1rem; } .list-search { padding: 1rem 2rem; } .list-body .list-row { diff --git a/www/dist/js/bundle.js b/www/dist/js/bundle.js index 2ecf7c8e..669357b8 100644 --- a/www/dist/js/bundle.js +++ b/www/dist/js/bundle.js @@ -9367,6 +9367,16 @@ var observable = class Observable { }; } + // getter, setter stubs, so Observable can be used as a simple Document + get(key) { + return this[key]; + } + + set(key, value) { + this[key] = value; + this.trigger('change', {doc: this, fieldname: key}); + } + on(event, listener) { this._addListener('listeners', event, listener); if (this._observable.socketClient) { @@ -9538,22 +9548,36 @@ var naming = { if (!e.status_code || e.status_code !== 404) { throw e; } - series = frappejs.newDoc({doctype: 'NumberSeries', name: prefix, current: 0}); - await series.insert(); + await this.createNumberSeries(prefix); } let next = await series.next(); return prefix + next; + }, + + async createNumberSeries(prefix, setting, start=1000) { + if (!(await frappejs.db.exists('NumberSeries', prefix))) { + const series = frappejs.newDoc({doctype: 'NumberSeries', name: prefix, current: start}); + await series.insert(); + + if (setting) { + const settingDoc = await frappejs.getSingle(setting); + settingDoc.numberSeries = series.name; + await settingDoc.update(); + } + } } }; var document$1 = class BaseDocument extends observable { constructor(data) { super(); - this.fetchValues = {}; + this.fetchValuesCache = {}; this.flags = {}; this.setup(); Object.assign(this, data); - frappejs.db.on('change', (params) => this.fetchValues[`${params.doctype}:${params.name}`] = {}); + + // clear fetch-values cache + frappejs.db.on('change', (params) => this.fetchValuesCache[`${params.doctype}:${params.name}`] = {}); } setup() { @@ -9574,10 +9598,6 @@ var document$1 = class BaseDocument extends observable { return this._settings; } - get(fieldname) { - return this[fieldname]; - } - // set value and trigger change async set(fieldname, value) { if (this[fieldname] !== value) { @@ -9842,7 +9862,7 @@ var document$1 = class BaseDocument extends observable { async getFrom(doctype, name, fieldname) { if (!name) return ''; - let _values = this.fetchValues[`${doctype}:${name}`] || (this.fetchValues[`${doctype}:${name}`] = {}); + let _values = this.fetchValuesCache[`${doctype}:${name}`] || (this.fetchValuesCache[`${doctype}:${name}`] = {}); if (!_values[fieldname]) { _values[fieldname] = await frappejs.db.getValue(doctype, name, fieldname); } @@ -27207,8 +27227,8 @@ var page = class Page extends observable { } this.make(); this.dropdowns = {}; - if(this.title) { + this.wrapper.setAttribute('title', this.title); this.setTitle(this.title); } } @@ -27745,8 +27765,8 @@ class BaseControl { if (!this.onlyInput) { this.makeDescription(); } - if (this.placeholder) { - this.input.setAttribute('placeholder', this.placeholder); + if (this.placeholder || this.inline) { + this.input.setAttribute('placeholder', this.placeholder || this.label); } } @@ -41971,45 +41991,6 @@ class DateControl extends base { var date = DateControl; -class FloatControl extends base { - make() { - super.make(); - this.input.setAttribute('type', 'text'); - this.input.classList.add('text-right'); - this.input.addEventListener('focus', () => { - setTimeout(() => { - this.input.select(); - }, 100); - }); - } - parse(value) { - value = parseFloat(value); - return isNaN(value) ? 0 : value; - } -} - -var float_1 = FloatControl; - -class CurrencyControl extends float_1 { - parse(value) { - return frappejs.parse_number(value); - } - format(value) { - return frappejs.format_number(value); - } -} - -var currency = CurrencyControl; - -class IntControl extends float_1 { - parse(value) { - value = parseInt(value); - return isNaN(value) ? 0 : value; - } -} - -var int_1 = IntControl; - var awesomplete = createCommonjsModule(function (module) { /** * Simple, lightweight, usable local autocomplete library for modern browsers @@ -42541,8 +42522,8 @@ class LinkControl extends base { this.input.addEventListener('awesomplete-select', async (e) => { if (e.text && e.text.value === '__newItem') { e.preventDefault(); - const newDoc = await frappejs.getNewDoc(this.target); - const formModal = await frappejs.desk.showFormModal(this.target, newDoc.name); + const newDoc = await frappejs.getNewDoc(this.getTarget()); + const formModal = await frappejs.desk.showFormModal(this.getTarget(), newDoc.name); if (formModal.form.doc.meta.hasField('name')) { formModal.form.doc.set('name', this.input.value); } @@ -42557,7 +42538,7 @@ class LinkControl extends base { async getList(query) { return (await frappejs.db.getAll({ - doctype: this.target, + doctype: this.getTarget(), filters: this.getFilters(query, this), limit: 50 })).map(d => d.name); @@ -42566,10 +42547,61 @@ class LinkControl extends base { getFilters(query) { return { keywords: ["like", query] } } + + getTarget() { + return this.target; + } } var link = LinkControl; +class DynamicLinkControl extends link { + getTarget() { + return this.doc[this.references]; + } +} + +var dynamicLink = DynamicLinkControl; + +class FloatControl extends base { + make() { + super.make(); + this.input.setAttribute('type', 'text'); + this.input.classList.add('text-right'); + this.input.addEventListener('focus', () => { + setTimeout(() => { + this.input.select(); + }, 100); + }); + } + parse(value) { + value = parseFloat(value); + return isNaN(value) ? 0 : value; + } +} + +var float_1 = FloatControl; + +class CurrencyControl extends float_1 { + parse(value) { + return frappejs.parse_number(value); + } + format(value) { + return frappejs.format_number(value); + } +} + +var currency = CurrencyControl; + +class IntControl extends float_1 { + parse(value) { + value = parseInt(value); + return isNaN(value) ? 0 : value; + } +} + +var int_1 = IntControl; + class PasswordControl extends base { make() { super.make(); @@ -48468,6 +48500,7 @@ const controlClasses = { Code: code, Data: data, Date: date, + DynamicLink: dynamicLink, Currency: currency, Float: float_1, Int: int_1, @@ -48490,7 +48523,7 @@ var controls$1 = { }; var form = class BaseForm extends observable { - constructor({doctype, parent, submit_label='Submit', container}) { + constructor({doctype, parent, submit_label='Submit', container, meta, inline=false}) { super(); Object.assign(this, arguments[0]); this.controls = {}; @@ -48498,12 +48531,21 @@ var form = class BaseForm extends observable { this.sections = []; this.links = []; - this.meta = frappejs.getMeta(this.doctype); + if (!this.meta) { + this.meta = frappejs.getMeta(this.doctype); + } + if (this.setup) { this.setup(); } + this.make(); this.bindFormEvents(); + + if (this.doc) { + // bootstrapped with a doc + this.bindEvents(this.doc); + } } make() { @@ -48511,10 +48553,22 @@ var form = class BaseForm extends observable { return; } - this.body = frappejs.ui.add('div', 'form-body', this.parent); - this.makeToolbar(); + if (this.inline) { + this.body = this.parent; + } else { + this.body = frappejs.ui.add('div', 'form-body', this.parent); + } + + if (this.actions) { + this.makeToolbar(); + } this.form = frappejs.ui.add('form', 'form-container', this.body); + + if (this.inline) { + this.form.classList.add('form-inline'); + } + this.form.onValidate = true; this.makeLayout(); @@ -48560,6 +48614,9 @@ var form = class BaseForm extends observable { makeControls(fields, parent) { for(let field of fields) { if (!field.hidden && controls$1.getControlClass(field.fieldtype)) { + if (this.inline) { + field.inline = true; + } let control = controls$1.makeControl({field: field, form: this, parent: parent}); this.controlList.push(control); this.controls[field.fieldname] = control; @@ -48666,6 +48723,8 @@ var form = class BaseForm extends observable { } setTitle() { + if (!this.container) return; + const doctypeLabel = this.doc.meta.label || this.doc.meta.name; if (this.doc.meta.isSingle || this.doc.meta.naming === 'random') { @@ -48702,6 +48761,8 @@ var form = class BaseForm extends observable { } refreshLinks(links) { + if (!this.container) return; + this.container.clearLinks(); for(let link of links) { // make the link @@ -56768,7 +56829,7 @@ var tablepage = class TablePage extends page { this.filterSelector.reset(this.doctype); } - if (frappejs.params.filters) { + if (frappejs.params && frappejs.params.filters) { this.filterSelector.setFilters(frappejs.params.filters); } frappejs.params = null; @@ -57586,48 +57647,45 @@ var client = { }; var reportpage = class ReportPage extends page { - constructor({title, }) { + constructor({title, filterFields}) { super({title: title, hasRoute: true}); this.fullPage = true; + this.filterFields = filterFields; - this.filterWrapper = frappejs.ui.add('div', 'filter-toolbar form-inline', this.body); + this.filterWrapper = frappejs.ui.add('div', 'filter-toolbar', this.body); this.tableWrapper = frappejs.ui.add('div', 'table-page-wrapper', this.body); this.btnNew = this.addButton(frappejs._('Refresh'), 'btn-primary', async () => { await this.run(); }); - this.filters = {}; + this.makeFilters(); } getColumns() { // overrride } - addFilter(field) { - if (!field.fieldname) { - field.fieldname = frappejs.slug(field.label); - } - - field.placeholder = field.label; - field.inline = true; - - this.filters[field.fieldname] = controls$1.makeControl({field: field, form: this, parent: this.filterWrapper}); - return this.filters[field.fieldname]; + makeFilters() { + this.form = new form({ + parent: this.filterWrapper, + meta: { fields: this.filterFields }, + doc: new observable(), + inline: true, + container: this + }); } getFilterValues() { const values = {}; - for (let fieldname in this.filters) { - let control = this.filters[fieldname]; - values[fieldname] = control.getInputValue(); - if (control.required && !values[fieldname]) { + for (let control of this.form.controlList) { + values[control.fieldname] = control.getInputValue(); + if (control.required && !values[control.fieldname]) { frappejs.ui.showAlert({message: frappejs._('{0} is mandatory', control.label), color: 'red'}); return false; } } - console.log(values); return values; } @@ -57637,6 +57695,16 @@ var reportpage = class ReportPage extends page { } async run() { + if (frappejs.params && frappejs.params.filters) { + for (let key in frappejs.params.filters) { + if (this.form.controls[key]) { + this.form.controls[key].setInputValue(frappejs.params.filters[key]); + } + } + } + frappejs.params = null; + + if (!this.datatable) { this.makeDataTable(); } @@ -57659,12 +57727,19 @@ var reportpage = class ReportPage extends page { var GeneralLedgerView_1 = class GeneralLedgerView extends reportpage { constructor() { - super({title: frappejs._('General Ledger')}); - - this.addFilter({fieldtype: 'Link', target: 'Account', label: 'Account'}); - this.addFilter({fieldtype: 'Link', target: 'Party', label: 'Party'}); - this.addFilter({fieldtype: 'Date', label: 'From Date'}); - this.addFilter({fieldtype: 'Date', label: 'To Date'}); + super({ + title: frappejs._('General Ledger'), + filterFields: [ + {fieldtype: 'Select', options: ['', 'Invoice', 'Payment'], + label: 'Reference Type', fieldname: 'referenceType'}, + {fieldtype: 'DynamicLink', references: 'referenceType', + label: 'Reference Name', fieldname: 'referenceName'}, + {fieldtype: 'Link', target: 'Account', label: 'Account'}, + {fieldtype: 'Link', target: 'Party', label: 'Party'}, + {fieldtype: 'Date', label: 'From Date'}, + {fieldtype: 'Date', label: 'To Date'} + ] + }); this.method = 'general-ledger'; } @@ -57879,13 +57954,36 @@ module.exports = { var Party_1 = Party.links; +var utils$6 = createCommonjsModule(function (module) { +module.exports = { + ledgerLink: { + label: 'Ledger Entries', + condition: form => form.doc.submitted, + action: form => { + return { + route: ['report', 'general-ledger'], + params: { + filters: { + referenceType: form.doc.doctype, + referenceName: form.doc.name + } + } + }; + } + }, +}; +}); + +var utils_1$2 = utils$6.ledgerLink; + var Payment = { name: "Payment", label: "Payment", - naming: "name", // {random|autoincrement} isSingle: 0, isChild: 0, + isSubmittable: 1, keywordFields: [], + settings: "PaymentSettings", fields: [ { "fieldname": "date", @@ -57948,6 +58046,10 @@ var Payment = { { fields: ['amount', 'writeoff'] } + ], + + links: [ + utils$6.ledgerLink ] }; @@ -57980,6 +58082,24 @@ var PaymentFor = { ] }; +var PaymentSettings = { + name: "PaymentSettings", + label: "Payment Settings", + isSingle: 1, + isChild: 0, + keywordFields: [], + "fields": [ + { + "fieldname": "numberSeries", + "label": "Number Series", + "fieldtype": "Link", + "target": "NumberSeries", + "required": 1, + "default": "PAY" + } + ] +}; + var Item = { name: "Item", doctype: "DocType", @@ -58231,21 +58351,7 @@ module.exports = { ], links: [ - { - label: 'Ledger Entries', - condition: form => form.doc.submitted, - action: form => { - return { - route: ['table', 'AccountingLedgerEntry'], - params: { - filters: { - referenceType: 'Invoice', - referenceName: form.doc.name - } - } - }; - } - }, + utils$6.ledgerLink, { label: 'Make Payment', condition: form => form.doc.submitted, @@ -58356,7 +58462,8 @@ var InvoiceSettings = { "label": "Number Series", "fieldtype": "Link", "target": "NumberSeries", - "required": 1 + "required": 1, + "default": "INV" } ] }; @@ -58442,12 +58549,17 @@ var models$2 = { Account: Account, AccountingLedgerEntry: AccountingLedgerEntry, Party: Party, + Payment: Payment, PaymentFor: PaymentFor, + PaymentSettings: PaymentSettings, + Item: Item, + Invoice: Invoice, InvoiceItem: InvoiceItem, InvoiceSettings: InvoiceSettings, + Tax: Tax, TaxDetail: TaxDetail, TaxSummary: TaxSummary