diff --git a/frappe/model/doc.ts b/frappe/model/doc.ts index 97000a59..37b4f116 100644 --- a/frappe/model/doc.ts +++ b/frappe/model/doc.ts @@ -33,6 +33,7 @@ import { EmptyMessageMap, FiltersMap, FormulaMap, + FormulaReturn, HiddenMap, ListsMap, ListViewSettings, @@ -507,7 +508,7 @@ export default class Doc extends Observable { } async getValueFromFormula(field: Field, doc: Doc) { - let value: Doc[] | DocValue | undefined; + let value: FormulaReturn; const formula = doc.formulas[field.fieldtype]; if (formula === undefined) { @@ -515,7 +516,6 @@ export default class Doc extends Observable { } value = await formula(); - if (Array.isArray(value) && field.fieldtype === FieldTypeEnum.Table) { value = value.map((row) => this._initChild(row, field.fieldname)); } diff --git a/frappe/model/types.ts b/frappe/model/types.ts index 581e823d..9b0a8c78 100644 --- a/frappe/model/types.ts +++ b/frappe/model/types.ts @@ -1,4 +1,4 @@ -import { DocValue } from 'frappe/core/types'; +import { DocValue, DocValueMap } from 'frappe/core/types'; import { FieldType } from 'schemas/types'; import { QueryFilter } from 'utils/db/types'; import { Router } from 'vue-router'; @@ -16,7 +16,8 @@ import Doc from './doc'; * - `Validation`: Async function that throw an error if the value is invalid. * - `Required`: Regular function used to decide if a value is mandatory (there are !notnul in the db). */ -export type Formula = () => Promise; +export type FormulaReturn = DocValue | DocValueMap[] | undefined | Doc[]; +export type Formula = () => Promise | FormulaReturn; export type Default = () => DocValue; export type Validation = (value: DocValue) => Promise; export type Required = () => boolean; diff --git a/models/README.md b/models/README.md new file mode 100644 index 00000000..054ac915 --- /dev/null +++ b/models/README.md @@ -0,0 +1,32 @@ +# Models + +The `models` root folder contains all the model files, i.e. files containing all the models. **Models** here, refers to the classes that handle the data, its validation and updation and a bunch of other stuff. + +Each model directly or indirectly extends the `Doc` class from `frappe/model/doc.ts` so for more info check that file and the associated types in `frappe/model/types.ts`. + +A model class can used even if the class body has no content, for example `PurchaseInvoiceItem`. Else the model used will default to using `Doc`. The class can also be used to provide type information for the field types else they default to the catch all `DocValue` example: + +```typescript +class Todo extends Doc { + title?: string; + date?: Date; + completed?: boolean; +} +``` + +While this has obvious advantages, the drawback is if the underlying fieldtype changes this too will have to be changed. + +## Adding Stuff + +When adding stuff to `models/**` make sure that it isn't importing any Vue code or other frontend specific code globally. This is cause model file tests will directly use the the `Frappe` class and will be run using `mocha` on `node`. + +Importing frontend code will break all the tests. This also implies that one should be wary about transitive dependencies. + +_Note: Frontend specific code can be imported but they should be done so, only using dynamic imports i.e. `await import('...')`._ + +## Regional Models + +Regional models should as far as possible extend the base model and override what's required. + +They should then be imported dynamicall and returned from `getRegionalModels` in `models/index.ts` on the basis of `countryCode`. + diff --git a/models/baseModels/Account/Account.ts b/models/baseModels/Account/Account.ts index e296306e..109e11d8 100644 --- a/models/baseModels/Account/Account.ts +++ b/models/baseModels/Account/Account.ts @@ -7,7 +7,7 @@ import { } from 'frappe/model/types'; import { QueryFilter } from 'utils/db/types'; -export default class Account extends Doc { +export class Account extends Doc { async beforeInsert() { if (this.accountType || !this.parentAccount) { return; diff --git a/models/baseModels/Transaction/Transaction.ts b/models/baseModels/Invoice/Invoice.ts similarity index 60% rename from models/baseModels/Transaction/Transaction.ts rename to models/baseModels/Invoice/Invoice.ts index e825921f..3169b776 100644 --- a/models/baseModels/Transaction/Transaction.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -1,21 +1,36 @@ import { LedgerPosting } from 'accounting/ledgerPosting'; import frappe from 'frappe'; +import { DocValue } from 'frappe/core/types'; import Doc from 'frappe/model/doc'; +import { DefaultMap, FiltersMap, FormulaMap } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; import { getExchangeRate } from '../../../accounting/exchangeRate'; import { Party } from '../Party/Party'; import { Payment } from '../Payment/Payment'; import { Tax } from '../Tax/Tax'; +import { TaxSummary } from '../TaxSummary/TaxSummary'; -export abstract class Transaction extends Doc { +export abstract class Invoice extends Doc { _taxes: Record = {}; + taxes?: TaxSummary[]; - abstract getPosting(): LedgerPosting; + party?: string; + account?: string; + currency?: string; + netTotal?: Money; + baseGrandTotal?: Money; + exchangeRate?: number; + + abstract getPosting(): Promise; + + get isSales() { + return this.schemaName === 'SalesInvoice'; + } async getPayments() { const payments = await frappe.db.getAll('PaymentFor', { fields: ['parent'], - filters: { referenceName: this.name as string }, + filters: { referenceName: this.name! }, orderBy: 'name', }); @@ -43,13 +58,10 @@ export abstract class Transaction extends Doc { // update outstanding amounts await frappe.db.update(this.schemaName, { name: this.name as string, - outstandingAmount: this.baseGrandTotal as Money, + outstandingAmount: this.baseGrandTotal!, }); - const party = (await frappe.doc.getDoc( - 'Party', - this.party as string - )) as Party; + const party = (await frappe.doc.getDoc('Party', this.party!)) as Party; await party.updateOutstandingAmount(); } @@ -87,7 +99,7 @@ export abstract class Transaction extends Doc { return 1.0; } return await getExchangeRate({ - fromCurrency: this.currency as string, + fromCurrency: this.currency!, toCurrency: companyCurrency as string, }); } @@ -95,7 +107,13 @@ export abstract class Transaction extends Doc { async getTaxSummary() { const taxes: Record< string, - { account: string; rate: number; amount: Money; baseAmount?: Money } + { + account: string; + rate: number; + amount: Money; + baseAmount: Money; + [key: string]: DocValue; + } > = {}; for (const row of this.items as Doc[]) { @@ -112,6 +130,7 @@ export abstract class Transaction extends Doc { account, rate, amount: frappe.pesa(0), + baseAmount: frappe.pesa(0), }; const amount = (row.amount as Money).mul(rate).div(100); @@ -122,7 +141,7 @@ export abstract class Transaction extends Doc { return Object.keys(taxes) .map((account) => { const tax = taxes[account]; - tax.baseAmount = tax.amount.mul(this.exchangeRate as number); + tax.baseAmount = tax.amount.mul(this.exchangeRate!); return tax; }) .filter((tax) => !tax.amount.isZero()); @@ -139,6 +158,40 @@ export abstract class Transaction extends Doc { async getGrandTotal() { return ((this.taxes ?? []) as Doc[]) .map((doc) => doc.amount as Money) - .reduce((a, b) => a.add(b), this.netTotal as Money); + .reduce((a, b) => a.add(b), this.netTotal!); } + + formulas: FormulaMap = { + account: async () => + this.getFrom('Party', this.party!, 'defaultAccount') as string, + currency: async () => + (this.getFrom('Party', this.party!, 'currency') as string) || + (frappe.singles.AccountingSettings!.currency as string), + exchangeRate: async () => await this.getExchangeRate(), + netTotal: async () => this.getSum('items', 'amount', false), + baseNetTotal: async () => this.netTotal!.mul(this.exchangeRate!), + taxes: async () => await this.getTaxSummary(), + grandTotal: async () => await this.getGrandTotal(), + baseGrandTotal: async () => + (this.grandTotal as Money).mul(this.exchangeRate!), + outstandingAmount: async () => { + if (this.submitted) { + return; + } + + return this.baseGrandTotal!; + }, + }; + + defaults: DefaultMap = { + date: () => new Date().toISOString().slice(0, 10), + }; + + static filters: FiltersMap = { + account: (doc: Doc) => ({ + isGroup: false, + accountType: doc.isSales ? 'Receivable' : 'Payable', + }), + numberSeries: (doc: Doc) => ({ referenceType: doc.schemaName }), + }; } diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts new file mode 100644 index 00000000..d5fbe68b --- /dev/null +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -0,0 +1,106 @@ +import frappe from 'frappe'; +import { DocValue } from 'frappe/core/types'; +import Doc from 'frappe/model/doc'; +import { + DependsOnMap, + FiltersMap, + FormulaMap, + ValidationMap, +} from 'frappe/model/types'; +import Money from 'pesa/dist/types/src/money'; +import { Invoice } from '../Invoice/Invoice'; + +export abstract class InvoiceItem extends Doc { + account?: string; + baseAmount?: Money; + exchangeRate?: number; + parentdoc?: Invoice; + + get isSales() { + return this.schemaName === 'SalesInvoiceItem'; + } + + formulas: FormulaMap = { + description: () => + this.parentdoc!.getFrom( + 'Item', + this.item as string, + 'description' + ) as string, + rate: async () => { + const baseRate = ((await this.parentdoc!.getFrom( + 'Item', + this.item as string, + 'rate' + )) || frappe.pesa(0)) as Money; + + return baseRate.div(this.exchangeRate!); + }, + baseRate: () => + (this.rate as Money).mul(this.parentdoc!.exchangeRate as number), + account: () => { + let accountType = 'expenseAccount'; + if (this.isSales) { + accountType = 'incomeAccount'; + } + return this.parentdoc!.getFrom('Item', this.item as string, accountType); + }, + tax: () => { + if (this.tax) { + return this.tax as string; + } + + return this.parentdoc!.getFrom( + 'Item', + this.item as string, + 'tax' + ) as string; + }, + amount: () => (this.rate as Money).mul(this.quantity as number), + baseAmount: () => + (this.amount as Money).mul(this.parentdoc!.exchangeRate as number), + hsnCode: () => + this.parentdoc!.getFrom('Item', this.item as string, 'hsnCode'), + }; + + dependsOn: DependsOnMap = { + hsnCode: ['item'], + }; + + validations: ValidationMap = { + rate: async (value: DocValue) => { + if ((value as Money).gte(0)) { + return; + } + + throw new frappe.errors.ValidationError( + frappe.t`Rate (${frappe.format( + value, + 'Currency' + )}) cannot be less zero.` + ); + }, + }; + + static filters: FiltersMap = { + item: (doc: Doc) => { + const itemList = doc.parentdoc!.items as Doc[]; + const items = itemList.map((d) => d.item as string).filter(Boolean); + + let itemNotFor = 'sales'; + if (doc.isSales) { + itemNotFor = 'purchases'; + } + + const baseFilter = { for: ['not in', [itemNotFor]] }; + if (items.length <= 0) { + return baseFilter; + } + + return { + name: ['not in', items], + ...baseFilter, + }; + }, + }; +} diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index ff7c890f..7a29bf65 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -20,6 +20,8 @@ import { Party } from '../Party/Party'; import { PaymentMethod, PaymentType } from './types'; export class Payment extends Doc { + party?: string; + async change({ changed }: { changed: string }) { switch (changed) { case 'for': { @@ -59,7 +61,7 @@ export class Payment extends Doc { paymentType = 'Pay'; } - this.party = party; + this.party = party as string; this.paymentType = paymentType; } @@ -152,7 +154,7 @@ export class Payment extends Doc { const writeoff = this.writeoff as Money; const entries = new LedgerPosting({ reference: this, - party: this.party as string, + party: this.party!, }); await entries.debit(paymentAccount as string, amount.sub(writeoff)); @@ -164,7 +166,7 @@ export class Payment extends Doc { const writeoffEntry = new LedgerPosting({ reference: this, - party: this.party as string, + party: this.party!, }); const writeOffAccount = frappe.singles.AccountingSettings! .writeOffAccount as string; @@ -227,10 +229,7 @@ export class Payment extends Doc { const newOutstanding = outstandingAmount.sub(amount); await referenceDoc.set('outstandingAmount', newOutstanding); await referenceDoc.update(); - const party = (await frappe.doc.getDoc( - 'Party', - this.party as string - )) as Party; + const party = (await frappe.doc.getDoc('Party', this.party!)) as Party; await party.updateOutstandingAmount(); } diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoice.js b/models/baseModels/PurchaseInvoice/PurchaseInvoice.js deleted file mode 100644 index c67832e7..00000000 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoice.js +++ /dev/null @@ -1,152 +0,0 @@ -import { t } from 'frappe'; -import { DEFAULT_NUMBER_SERIES } from '../../../frappe/utils/consts'; -import InvoiceTemplate from '../SalesInvoice/InvoiceTemplate.vue'; -import { getActions } from '../Transaction/Transaction'; -import PurchaseInvoice from './PurchaseInvoiceDocument'; - -export default { - name: 'PurchaseInvoice', - doctype: 'DocType', - label: t`Bill`, - documentClass: PurchaseInvoice, - printTemplate: InvoiceTemplate, - isSingle: 0, - isChild: 0, - isSubmittable: 1, - keywordFields: ['name', 'supplier'], - settings: 'PurchaseInvoiceSettings', - showTitle: true, - fields: [ - { - label: t`Bill No`, - fieldname: 'name', - fieldtype: 'Data', - required: 1, - readOnly: 1, - }, - { - fieldname: 'date', - label: t`Date`, - fieldtype: 'Date', - default: () => new Date().toISOString().slice(0, 10), - }, - { - fieldname: 'supplier', - label: t`Supplier`, - fieldtype: 'Link', - target: 'Supplier', - required: 1, - }, - { - fieldname: 'account', - label: t`Account`, - fieldtype: 'Link', - target: 'Account', - formula: (doc) => doc.getFrom('Party', doc.supplier, 'defaultAccount'), - getFilters: () => { - return { - isGroup: 0, - accountType: 'Payable', - }; - }, - }, - { - fieldname: 'currency', - label: t`Supplier Currency`, - fieldtype: 'Link', - target: 'Currency', - hidden: 1, - formula: (doc) => - doc.getFrom('Party', doc.supplier, 'currency') || - frappe.AccountingSettings.currency, - formulaDependsOn: ['supplier'], - }, - { - fieldname: 'exchangeRate', - label: t`Exchange Rate`, - fieldtype: 'Float', - default: 1, - formula: async (doc) => await doc.getExchangeRate(), - }, - { - fieldname: 'items', - label: t`Items`, - fieldtype: 'Table', - childtype: 'PurchaseInvoiceItem', - required: true, - }, - { - fieldname: 'netTotal', - label: t`Net Total`, - fieldtype: 'Currency', - formula: (doc) => doc.getSum('items', 'amount', false), - readOnly: 1, - getCurrency: (doc) => doc.currency, - }, - { - fieldname: 'baseNetTotal', - label: t`Net Total (Company Currency)`, - fieldtype: 'Currency', - formula: (doc) => doc.netTotal.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'taxes', - label: t`Taxes`, - fieldtype: 'Table', - childtype: 'TaxSummary', - formula: (doc) => doc.getTaxSummary(), - readOnly: 1, - }, - { - fieldname: 'grandTotal', - label: t`Grand Total`, - fieldtype: 'Currency', - formula: (doc) => doc.getGrandTotal(), - readOnly: 1, - getCurrency: (doc) => doc.currency, - }, - { - fieldname: 'baseGrandTotal', - label: t`Grand Total (Company Currency)`, - fieldtype: 'Currency', - formula: (doc) => doc.grandTotal.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'outstandingAmount', - label: t`Outstanding Amount`, - fieldtype: 'Currency', - formula: (doc) => { - if (doc.submitted) return; - return doc.baseGrandTotal; - }, - readOnly: 1, - }, - { - fieldname: 'terms', - label: t`Terms`, - fieldtype: 'Text', - }, - { - fieldname: 'cancelled', - label: t`Cancelled`, - fieldtype: 'Check', - default: 0, - readOnly: 1, - }, - { - fieldname: 'numberSeries', - label: t`Number Series`, - fieldtype: 'Link', - target: 'NumberSeries', - required: 1, - getFilters: () => { - return { referenceType: 'PurchaseInvoice' }; - }, - default: DEFAULT_NUMBER_SERIES['PurchaseInvoice'], - }, - ], - - actions: getActions('PurchaseInvoice'), -}; diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts index e69de29b..615de850 100644 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts +++ b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts @@ -0,0 +1,48 @@ +import { LedgerPosting } from 'accounting/ledgerPosting'; +import { Action, ListViewSettings } from 'frappe/model/types'; +import { + getTransactionActions, + getTransactionStatusColumn, +} from '../../helpers'; +import { Invoice } from '../Invoice/Invoice'; +import { PurchaseInvoiceItem } from '../PurchaseInvoiceItem/PurchaseInvoiceItem'; + +export class PurchaseInvoice extends Invoice { + items?: PurchaseInvoiceItem[]; + + async getPosting() { + const entries: LedgerPosting = new LedgerPosting({ + reference: this, + party: this.party, + }); + + await entries.credit(this.account!, this.baseGrandTotal!); + + for (const item of this.items!) { + await entries.debit(item.account!, item.baseAmount!); + } + + if (this.taxes) { + for (const tax of this.taxes) { + await entries.debit(tax.account!, tax.baseAmount!); + } + } + + entries.makeRoundOffEntry(); + return entries; + } + + static actions: Action[] = getTransactionActions('PurchaseInvoice'); + + static listSettings: ListViewSettings = { + formRoute: (name) => `/edit/PurchaseInvoice/${name}`, + columns: [ + 'party', + 'name', + getTransactionStatusColumn(), + 'date', + 'grandTotal', + 'outstandingAmount', + ], + }; +} diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoiceDocument.js b/models/baseModels/PurchaseInvoice/PurchaseInvoiceDocument.js deleted file mode 100644 index 22ae8f33..00000000 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoiceDocument.js +++ /dev/null @@ -1,3 +0,0 @@ -import TransactionDocument from '../Transaction/TransactionDocument'; - -export default class PurchaseInvoice extends TransactionDocument {}; diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoiceList.js b/models/baseModels/PurchaseInvoice/PurchaseInvoiceList.js deleted file mode 100644 index c28994df..00000000 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoiceList.js +++ /dev/null @@ -1,16 +0,0 @@ -import { t } from 'frappe'; -import { getStatusColumn } from '../Transaction/Transaction'; - -export default { - doctype: 'PurchaseInvoice', - title: t`Bills`, - formRoute: (name) => `/edit/PurchaseInvoice/${name}`, - columns: [ - 'supplier', - 'name', - getStatusColumn('PurchaseInvoice'), - 'date', - 'grandTotal', - 'outstandingAmount', - ], -}; diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoiceServer.js b/models/baseModels/PurchaseInvoice/PurchaseInvoiceServer.js deleted file mode 100644 index 1e95f6aa..00000000 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoiceServer.js +++ /dev/null @@ -1,27 +0,0 @@ -import TransactionServer from '../Transaction/TransactionServer'; -import PurchaseInvoice from './PurchaseInvoiceDocument'; -import LedgerPosting from '../../../accounting/ledgerPosting'; - -class PurchaseInvoiceServer extends PurchaseInvoice { - async getPosting() { - let entries = new LedgerPosting({ reference: this, party: this.supplier }); - await entries.credit(this.account, this.baseGrandTotal); - - for (let item of this.items) { - await entries.debit(item.account, item.baseAmount); - } - - if (this.taxes) { - for (let tax of this.taxes) { - await entries.debit(tax.account, tax.baseAmount); - } - } - entries.makeRoundOffEntry(); - return entries; - } -} - -// apply common methods from TransactionServer -Object.assign(PurchaseInvoiceServer.prototype, TransactionServer); - -export default PurchaseInvoiceServer; diff --git a/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.js b/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.js deleted file mode 100644 index ef8d5da1..00000000 --- a/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.js +++ /dev/null @@ -1,109 +0,0 @@ -import { t } from 'frappe'; -export default { - name: 'PurchaseInvoiceItem', - doctype: 'DocType', - isChild: 1, - keywordFields: [], - tableFields: ['item', 'tax', 'quantity', 'rate', 'amount'], - fields: [ - { - fieldname: 'item', - label: t`Item`, - fieldtype: 'Link', - target: 'Item', - required: 1, - getFilters(_, doc) { - let items = doc.parentdoc.items.map((d) => d.item).filter(Boolean); - - const baseFilter = { for: ['not in', ['sales']] }; - if (items.length <= 0) { - return baseFilter; - } - - return { - name: ['not in', items], - ...baseFilter, - }; - }, - }, - { - fieldname: 'description', - label: t`Description`, - fieldtype: 'Text', - formula: (row, doc) => doc.getFrom('Item', row.item, 'description'), - hidden: 1, - }, - { - fieldname: 'quantity', - label: t`Quantity`, - fieldtype: 'Float', - required: 1, - default: 1, - }, - { - fieldname: 'rate', - label: t`Rate`, - fieldtype: 'Currency', - required: 1, - formula: async (row, doc) => { - const baseRate = - (await doc.getFrom('Item', row.item, 'rate')) || frappe.pesa(0); - return baseRate.div(doc.exchangeRate); - }, - getCurrency: (row, doc) => doc.currency, - validate(value) { - if (value.gte(0)) { - return; - } - - throw new frappe.errors.ValidationError( - frappe.t`Rate (${frappe.format( - value, - 'Currency' - )}) cannot be less zero.` - ); - }, - }, - { - fieldname: 'baseRate', - label: t`Rate (Company Currency)`, - fieldtype: 'Currency', - formula: (row, doc) => row.rate.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'account', - label: t`Account`, - fieldtype: 'Link', - target: 'Account', - required: 1, - readOnly: 1, - formula: (row, doc) => doc.getFrom('Item', row.item, 'expenseAccount'), - }, - { - fieldname: 'tax', - label: t`Tax`, - fieldtype: 'Link', - target: 'Tax', - formula: (row, doc) => { - if (row.tax) return row.tax; - return doc.getFrom('Item', row.item, 'tax'); - }, - }, - { - fieldname: 'amount', - label: t`Amount`, - fieldtype: 'Currency', - readOnly: 1, - formula: (row) => row.rate.mul(row.quantity), - getCurrency: (row, doc) => doc.currency, - }, - { - fieldname: 'baseAmount', - label: t`Amount (Company Currency)`, - fieldtype: 'Currency', - readOnly: 1, - formula: (row, doc) => row.amount.mul(doc.exchangeRate), - }, - ], -}; diff --git a/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.ts b/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.ts new file mode 100644 index 00000000..ec2dd70c --- /dev/null +++ b/models/baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem.ts @@ -0,0 +1,3 @@ +import { InvoiceItem } from '../InvoiceItem/InvoiceItem'; + +export class PurchaseInvoiceItem extends InvoiceItem {} diff --git a/models/baseModels/PurchaseInvoiceItem/RegionalChanges.js b/models/baseModels/PurchaseInvoiceItem/RegionalChanges.js deleted file mode 100644 index 0caaad05..00000000 --- a/models/baseModels/PurchaseInvoiceItem/RegionalChanges.js +++ /dev/null @@ -1,25 +0,0 @@ -import { t } from 'frappe'; -import { cloneDeep } from 'lodash'; -import PurchaseInvoiceItemOriginal from './PurchaseInvoiceItem'; - -export default function getAugmentedPurchaseInvoiceItem({ country }) { - const PurchaseInvoiceItem = cloneDeep(PurchaseInvoiceItemOriginal); - if (!country) { - return PurchaseInvoiceItem; - } - - if (country === 'India') { - PurchaseInvoiceItem.fields = [ - ...PurchaseInvoiceItem.fields, - { - fieldname: 'hsnCode', - label: t`HSN/SAC`, - fieldtype: 'Int', - formula: (row, doc) => doc.getFrom('Item', row.item, 'hsnCode'), - formulaDependsOn: ['item'], - }, - ]; - } - - return PurchaseInvoiceItem; -} diff --git a/models/baseModels/SalesInvoice/SalesInvoice.js b/models/baseModels/SalesInvoice/SalesInvoice.js deleted file mode 100644 index 91318cbf..00000000 --- a/models/baseModels/SalesInvoice/SalesInvoice.js +++ /dev/null @@ -1,152 +0,0 @@ -import { t } from 'frappe'; -import { DEFAULT_NUMBER_SERIES } from '../../../frappe/utils/consts'; -import { getActions } from '../Transaction/Transaction'; -import InvoiceTemplate from './InvoiceTemplate.vue'; -import SalesInvoice from './SalesInvoiceDocument'; - -export default { - name: 'SalesInvoice', - label: t`Invoice`, - doctype: 'DocType', - documentClass: SalesInvoice, - printTemplate: InvoiceTemplate, - isSingle: 0, - isChild: 0, - isSubmittable: 1, - keywordFields: ['name', 'customer'], - settings: 'SalesInvoiceSettings', - fields: [ - { - label: t`Invoice No`, - fieldname: 'name', - fieldtype: 'Data', - required: 1, - readOnly: 1, - }, - { - fieldname: 'date', - label: t`Date`, - fieldtype: 'Date', - default: () => new Date().toISOString().slice(0, 10), - }, - { - fieldname: 'customer', - label: t`Customer`, - fieldtype: 'Link', - target: 'Customer', - required: 1, - }, - { - fieldname: 'account', - label: t`Account`, - fieldtype: 'Link', - target: 'Account', - disableCreation: true, - formula: (doc) => doc.getFrom('Party', doc.customer, 'defaultAccount'), - getFilters: () => { - return { - isGroup: 0, - accountType: 'Receivable', - }; - }, - }, - { - fieldname: 'currency', - label: t`Customer Currency`, - fieldtype: 'Link', - target: 'Currency', - formula: (doc) => - doc.getFrom('Party', doc.customer, 'currency') || - frappe.AccountingSettings.currency, - formulaDependsOn: ['customer'], - }, - { - fieldname: 'exchangeRate', - label: t`Exchange Rate`, - fieldtype: 'Float', - default: 1, - formula: (doc) => doc.getExchangeRate(), - readOnly: true, - }, - { - fieldname: 'items', - label: t`Items`, - fieldtype: 'Table', - childtype: 'SalesInvoiceItem', - required: true, - }, - { - fieldname: 'netTotal', - label: t`Net Total`, - fieldtype: 'Currency', - formula: (doc) => doc.getSum('items', 'amount', false), - readOnly: 1, - getCurrency: (doc) => doc.currency, - }, - { - fieldname: 'baseNetTotal', - label: t`Net Total (Company Currency)`, - fieldtype: 'Currency', - formula: (doc) => doc.netTotal.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'taxes', - label: t`Taxes`, - fieldtype: 'Table', - childtype: 'TaxSummary', - formula: (doc) => doc.getTaxSummary(), - readOnly: 1, - }, - { - fieldname: 'grandTotal', - label: t`Grand Total`, - fieldtype: 'Currency', - formula: (doc) => doc.getGrandTotal(), - readOnly: 1, - getCurrency: (doc) => doc.currency, - }, - { - fieldname: 'baseGrandTotal', - label: t`Grand Total (Company Currency)`, - fieldtype: 'Currency', - formula: (doc) => doc.grandTotal.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'outstandingAmount', - label: t`Outstanding Amount`, - fieldtype: 'Currency', - formula: (doc) => { - if (doc.submitted) return; - return doc.baseGrandTotal; - }, - readOnly: 1, - }, - { - fieldname: 'terms', - label: t`Notes`, - fieldtype: 'Text', - }, - { - fieldname: 'cancelled', - label: t`Cancelled`, - fieldtype: 'Check', - default: 0, - readOnly: 1, - }, - { - fieldname: 'numberSeries', - label: t`Number Series`, - fieldtype: 'Link', - target: 'NumberSeries', - required: 1, - getFilters: () => { - return { referenceType: 'SalesInvoice' }; - }, - default: DEFAULT_NUMBER_SERIES['SalesInvoice'], - }, - ], - - actions: getActions('SalesInvoice'), -}; diff --git a/models/baseModels/SalesInvoice/SalesInvoice.ts b/models/baseModels/SalesInvoice/SalesInvoice.ts new file mode 100644 index 00000000..bc66337a --- /dev/null +++ b/models/baseModels/SalesInvoice/SalesInvoice.ts @@ -0,0 +1,46 @@ +import { LedgerPosting } from 'accounting/ledgerPosting'; +import { Action, ListViewSettings } from 'frappe/model/types'; +import { + getTransactionActions, + getTransactionStatusColumn, +} from '../../helpers'; +import { Invoice } from '../Invoice/Invoice'; +import { SalesInvoiceItem } from '../SalesInvoiceItem/SalesInvoiceItem'; + +export class SalesInvoice extends Invoice { + items?: SalesInvoiceItem[]; + + async getPosting() { + const entries: LedgerPosting = new LedgerPosting({ + reference: this, + party: this.party, + }); + await entries.debit(this.account!, this.baseGrandTotal!); + + for (const item of this.items!) { + await entries.credit(item.account!, item.baseAmount!); + } + + if (this.taxes) { + for (const tax of this.taxes!) { + await entries.credit(tax.account!, tax.baseAmount!); + } + } + entries.makeRoundOffEntry(); + return entries; + } + + static actions: Action[] = getTransactionActions('SalesInvoice'); + + static listSettings: ListViewSettings = { + formRoute: (name) => `/edit/SalesInvoice/${name}`, + columns: [ + 'party', + 'name', + getTransactionStatusColumn(), + 'date', + 'grandTotal', + 'outstandingAmount', + ], + }; +} diff --git a/models/baseModels/SalesInvoice/SalesInvoiceDocument.js b/models/baseModels/SalesInvoice/SalesInvoiceDocument.js deleted file mode 100644 index 8631ac5a..00000000 --- a/models/baseModels/SalesInvoice/SalesInvoiceDocument.js +++ /dev/null @@ -1,3 +0,0 @@ -import TransactionDocument from '../Transaction/TransactionDocument'; - -export default class SalesInvoice extends TransactionDocument {}; diff --git a/models/baseModels/SalesInvoice/SalesInvoiceList.js b/models/baseModels/SalesInvoice/SalesInvoiceList.js deleted file mode 100644 index e9eae021..00000000 --- a/models/baseModels/SalesInvoice/SalesInvoiceList.js +++ /dev/null @@ -1,16 +0,0 @@ -import { t } from 'frappe'; -import { getStatusColumn } from '../Transaction/Transaction'; - -export default { - doctype: 'SalesInvoice', - title: t`Invoices`, - formRoute: (name) => `/edit/SalesInvoice/${name}`, - columns: [ - 'customer', - 'name', - getStatusColumn('SalesInvoice'), - 'date', - 'grandTotal', - 'outstandingAmount', - ], -}; diff --git a/models/baseModels/SalesInvoice/SalesInvoiceServer.js b/models/baseModels/SalesInvoice/SalesInvoiceServer.js deleted file mode 100644 index 46769aa0..00000000 --- a/models/baseModels/SalesInvoice/SalesInvoiceServer.js +++ /dev/null @@ -1,27 +0,0 @@ -import TransactionServer from '../Transaction/TransactionServer'; -import SalesInvoice from './SalesInvoiceDocument'; -import LedgerPosting from '../../../accounting/ledgerPosting'; - -class SalesInvoiceServer extends SalesInvoice { - async getPosting() { - let entries = new LedgerPosting({ reference: this, party: this.customer }); - await entries.debit(this.account, this.baseGrandTotal); - - for (let item of this.items) { - await entries.credit(item.account, item.baseAmount); - } - - if (this.taxes) { - for (let tax of this.taxes) { - await entries.credit(tax.account, tax.baseAmount); - } - } - entries.makeRoundOffEntry(); - return entries; - } -} - -// apply common methods from TransactionServer -Object.assign(SalesInvoiceServer.prototype, TransactionServer); - -export default SalesInvoiceServer; diff --git a/models/baseModels/SalesInvoiceItem/RegionalChanges.js b/models/baseModels/SalesInvoiceItem/RegionalChanges.js deleted file mode 100644 index 4a6eba31..00000000 --- a/models/baseModels/SalesInvoiceItem/RegionalChanges.js +++ /dev/null @@ -1,25 +0,0 @@ -import { t } from 'frappe'; -import { cloneDeep } from 'lodash'; -import SalesInvoiceItemOriginal from './SalesInvoiceItem'; - -export default function getAugmentedSalesInvoiceItem({ country }) { - const SalesInvoiceItem = cloneDeep(SalesInvoiceItemOriginal); - if (!country) { - return SalesInvoiceItem; - } - - if (country === 'India') { - SalesInvoiceItem.fields = [ - ...SalesInvoiceItem.fields, - { - fieldname: 'hsnCode', - label: t`HSN/SAC`, - fieldtype: 'Int', - formula: (row, doc) => doc.getFrom('Item', row.item, 'hsnCode'), - formulaDependsOn: ['item'], - }, - ]; - } - - return SalesInvoiceItem; -} diff --git a/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.js b/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.js deleted file mode 100644 index bc548f9d..00000000 --- a/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.js +++ /dev/null @@ -1,120 +0,0 @@ -import { t } from 'frappe'; -export default { - name: 'SalesInvoiceItem', - doctype: 'DocType', - isChild: 1, - regional: 1, - keywordFields: [], - tableFields: ['item', 'tax', 'quantity', 'rate', 'amount'], - fields: [ - { - fieldname: 'item', - label: t`Item`, - fieldtype: 'Link', - target: 'Item', - required: 1, - getFilters(_, doc) { - let items = doc.parentdoc.items.map((d) => d.item).filter(Boolean); - - const baseFilter = { for: ['not in', ['purchases']] }; - if (items.length <= 0) { - return baseFilter; - } - - return { - name: ['not in', items], - ...baseFilter, - }; - }, - }, - { - fieldname: 'description', - label: t`Description`, - fieldtype: 'Text', - formula: (row, doc) => doc.getFrom('Item', row.item, 'description'), - hidden: 1, - formulaDependsOn: ['item'], - }, - { - fieldname: 'quantity', - label: t`Quantity`, - fieldtype: 'Float', - required: 1, - default: 1, - validate(value, doc) { - if (value >= 0) { - return; - } - - throw new frappe.errors.ValidationError( - frappe.t`Quantity (${value}) cannot be less than zero.` - ); - }, - }, - { - fieldname: 'rate', - label: t`Rate`, - fieldtype: 'Currency', - required: 1, - formula: async (row, doc) => { - const baseRate = - (await doc.getFrom('Item', row.item, 'rate')) || frappe.pesa(0); - return baseRate.div(doc.exchangeRate); - }, - getCurrency: (row, doc) => doc.currency, - formulaDependsOn: ['item'], - validate(value, doc) { - if (value.gte(0)) { - return; - } - - throw new frappe.errors.ValidationError( - frappe.t`Rate (${frappe.format( - value, - 'Currency' - )}) cannot be less zero.` - ); - }, - }, - { - fieldname: 'baseRate', - label: t`Rate (Company Currency)`, - fieldtype: 'Currency', - formula: (row, doc) => row.rate.mul(doc.exchangeRate), - readOnly: 1, - }, - { - fieldname: 'account', - label: t`Account`, - hidden: 1, - fieldtype: 'Link', - target: 'Account', - required: 1, - readOnly: 1, - formula: (row, doc) => doc.getFrom('Item', row.item, 'incomeAccount'), - }, - { - fieldname: 'tax', - label: t`Tax`, - fieldtype: 'Link', - target: 'Tax', - formula: (row, doc) => doc.getFrom('Item', row.item, 'tax'), - formulaDependsOn: ['item'], - }, - { - fieldname: 'amount', - label: t`Amount`, - fieldtype: 'Currency', - readOnly: 1, - formula: (row) => row.rate.mul(row.quantity), - getCurrency: (row, doc) => doc.currency, - }, - { - fieldname: 'baseAmount', - label: t`Amount (Company Currency)`, - fieldtype: 'Currency', - readOnly: 1, - formula: (row, doc) => row.amount.mul(doc.exchangeRate), - }, - ], -}; diff --git a/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.ts b/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.ts new file mode 100644 index 00000000..d2c0960b --- /dev/null +++ b/models/baseModels/SalesInvoiceItem/SalesInvoiceItem.ts @@ -0,0 +1,3 @@ +import { InvoiceItem } from '../InvoiceItem/InvoiceItem'; + +export class SalesInvoiceItem extends InvoiceItem {} diff --git a/models/baseModels/TaxSummary/TaxSummary.ts b/models/baseModels/TaxSummary/TaxSummary.ts index 3f5c4b0f..7e0cf01d 100644 --- a/models/baseModels/TaxSummary/TaxSummary.ts +++ b/models/baseModels/TaxSummary/TaxSummary.ts @@ -3,6 +3,11 @@ import { FormulaMap } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; export class TaxSummary extends Doc { + account?: string; + rate?: number; + amount?: Money; + baseAmount?: Money; + formulas: FormulaMap = { baseAmount: async () => { const amount = this.amount as Money; diff --git a/models/helpers.ts b/models/helpers.ts index 82c77315..de48bc70 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -1,7 +1,7 @@ import { openQuickEdit } from '@/utils'; import frappe from 'frappe'; import Doc from 'frappe/model/doc'; -import { Action } from 'frappe/model/types'; +import { Action, ColumnConfig } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; import { Router } from 'vue-router'; import { InvoiceStatus } from './types'; @@ -26,12 +26,12 @@ export function getLedgerLinkAction(): Action { }; } -export function getTransactionActions(schemaName: string) { +export function getTransactionActions(schemaName: string): Action[] { return [ { label: frappe.t`Make Payment`, condition: (doc: Doc) => - doc.submitted && (doc.outstandingAmount as Money).gt(0), + (doc.submitted as boolean) && (doc.outstandingAmount as Money).gt(0), action: async function makePayment(doc: Doc) { const payment = await frappe.doc.getEmptyDoc('Payment'); payment.once('afterInsert', async () => { @@ -64,8 +64,8 @@ export function getTransactionActions(schemaName: string) { }, { label: frappe.t`Print`, - condition: (doc: Doc) => doc.submitted, - action(doc: Doc, router: Router) { + condition: (doc: Doc) => doc.submitted as boolean, + action: async (doc: Doc, router: Router) => { router.push({ path: `/print/${doc.doctype}/${doc.name}` }); }, }, @@ -73,7 +73,7 @@ export function getTransactionActions(schemaName: string) { ]; } -export function getTransactionStatusColumn() { +export function getTransactionStatusColumn(): ColumnConfig { const statusMap = { Unpaid: frappe.t`Unpaid`, Paid: frappe.t`Paid`, diff --git a/models/index.js b/models/index.js deleted file mode 100644 index 3ddd0c4f..00000000 --- a/models/index.js +++ /dev/null @@ -1,63 +0,0 @@ -import Account from './doctype/Account/Account.js'; -import AccountingLedgerEntry from './doctype/AccountingLedgerEntry/AccountingLedgerEntry.js'; -import AccountingSettings from './doctype/AccountingSettings/AccountingSettings.js'; -import Address from './doctype/Address/Address.js'; -import Color from './doctype/Color/Color.js'; -import CompanySettings from './doctype/CompanySettings/CompanySettings.js'; -import Contact from './doctype/Contact/Contact.js'; -import Currency from './doctype/Currency/Currency.js'; -import GetStarted from './doctype/GetStarted/GetStarted.js'; -import Item from './doctype/Item/Item.js'; -import JournalEntry from './doctype/JournalEntry/JournalEntry.js'; -import JournalEntryAccount from './doctype/JournalEntryAccount/JournalEntryAccount.js'; -import JournalEntrySettings from './doctype/JournalEntrySettings/JournalEntrySettings.js'; -import Customer from './doctype/Party/Customer.js'; -import Party from './doctype/Party/Party.js'; -import Supplier from './doctype/Party/Supplier.js'; -import Payment from './doctype/Payment/Payment.js'; -import PaymentFor from './doctype/PaymentFor/PaymentFor.js'; -import PaymentSettings from './doctype/PaymentSettings/PaymentSettings.js'; -import PrintSettings from './doctype/PrintSettings/PrintSettings.js'; -import PurchaseInvoice from './doctype/PurchaseInvoice/PurchaseInvoice.js'; -import PurchaseInvoiceItem from './doctype/PurchaseInvoiceItem/PurchaseInvoiceItem.js'; -import PurchaseInvoiceSettings from './doctype/PurchaseInvoiceSettings/PurchaseInvoiceSettings.js'; -import SalesInvoice from './doctype/SalesInvoice/SalesInvoice.js'; -import SalesInvoiceItem from './doctype/SalesInvoiceItem/SalesInvoiceItem.js'; -import SalesInvoiceSettings from './doctype/SalesInvoiceSettings/SalesInvoiceSettings.js'; -import SetupWizard from './doctype/SetupWizard/SetupWizard.js'; -import Tax from './doctype/Tax/Tax.js'; -import TaxDetail from './doctype/TaxDetail/TaxDetail.js'; -import TaxSummary from './doctype/TaxSummary/TaxSummary.js'; - -export default { - SetupWizard, - Currency, - Color, - Account, - AccountingSettings, - CompanySettings, - AccountingLedgerEntry, - Party, - Customer, - Supplier, - Payment, - PaymentFor, - PaymentSettings, - Item, - SalesInvoice, - SalesInvoiceItem, - SalesInvoiceSettings, - PurchaseInvoice, - PurchaseInvoiceItem, - PurchaseInvoiceSettings, - Tax, - TaxDetail, - TaxSummary, - Address, - Contact, - JournalEntry, - JournalEntryAccount, - JournalEntrySettings, - PrintSettings, - GetStarted, -}; diff --git a/models/index.ts b/models/index.ts new file mode 100644 index 00000000..f7573e4c --- /dev/null +++ b/models/index.ts @@ -0,0 +1,47 @@ +import { Account } from './baseModels/Account/Account'; +import { AccountingLedgerEntry } from './baseModels/AccountingLedgerEntry/AccountingLedgerEntry'; +import { AccountingSettings } from './baseModels/AccountingSettings/AccountingSettings'; +import { Address } from './baseModels/Address/Address'; +import { Item } from './baseModels/Item/Item'; +import { JournalEntry } from './baseModels/JournalEntry/JournalEntry'; +import { JournalEntryAccount } from './baseModels/JournalEntryAccount/JournalEntryAccount'; +import { Party } from './baseModels/Party/Party'; +import { Payment } from './baseModels/Payment/Payment'; +import { PaymentFor } from './baseModels/PaymentFor/PaymentFor'; +import { PurchaseInvoice } from './baseModels/PurchaseInvoice/PurchaseInvoice'; +import { PurchaseInvoiceItem } from './baseModels/PurchaseInvoiceItem/PurchaseInvoiceItem'; +import { SalesInvoice } from './baseModels/SalesInvoice/SalesInvoice'; +import { SalesInvoiceItem } from './baseModels/SalesInvoiceItem/SalesInvoiceItem'; +import { SetupWizard } from './baseModels/SetupWizard/SetupWizard'; +import { Tax } from './baseModels/Tax/Tax'; +import { TaxSummary } from './baseModels/TaxSummary/TaxSummary'; + +export default { + Account, + AccountingLedgerEntry, + AccountingSettings, + Address, + Item, + JournalEntry, + JournalEntryAccount, + Party, + Payment, + PaymentFor, + PurchaseInvoice, + PurchaseInvoiceItem, + SalesInvoice, + SalesInvoiceItem, + SetupWizard, + Tax, + TaxSummary, +}; + +export async function getRegionalModels(countryCode: string) { + if (countryCode !== 'in') { + return {}; + } + + const { Address } = await import('./regionalModels/in/Address'); + const { Party } = await import('./regionalModels/in/Party'); + return { Address, Party }; +} diff --git a/models/regionalModelUpdates.js b/models/regionalModelUpdates.js deleted file mode 100644 index 6e261ff3..00000000 --- a/models/regionalModelUpdates.js +++ /dev/null @@ -1,20 +0,0 @@ -import frappe from 'frappe'; - -async function setAugmentedModel(model, regionalInfo) { - const getAugmentedModel = ( - await import('./doctype/' + model + '/RegionalChanges') - ).default; - const augmentedModel = getAugmentedModel(regionalInfo); - frappe.models[model] = augmentedModel; - frappe.models[model].augmented = 1; -} - -export default async function regionalModelUpdates(regionalInfo) { - for (let model in frappe.models) { - const { regional, basedOn, augmented } = frappe.models[model]; - if (!regional || basedOn || augmented) { - continue; - } - await setAugmentedModel(model, regionalInfo); - } -} diff --git a/models/types.ts b/models/types.ts index 2e4fcf14..6d62634a 100644 --- a/models/types.ts +++ b/models/types.ts @@ -1,54 +1 @@ -export enum DoctypeName { - SetupWizard = 'SetupWizard', - Currency = 'Currency', - Color = 'Color', - Account = 'Account', - AccountingSettings = 'AccountingSettings', - CompanySettings = 'CompanySettings', - AccountingLedgerEntry = 'AccountingLedgerEntry', - Party = 'Party', - Customer = 'Customer', - Supplier = 'Supplier', - Payment = 'Payment', - PaymentFor = 'PaymentFor', - PaymentSettings = 'PaymentSettings', - Item = 'Item', - SalesInvoice = 'SalesInvoice', - SalesInvoiceItem = 'SalesInvoiceItem', - SalesInvoiceSettings = 'SalesInvoiceSettings', - PurchaseInvoice = 'PurchaseInvoice', - PurchaseInvoiceItem = 'PurchaseInvoiceItem', - PurchaseInvoiceSettings = 'PurchaseInvoiceSettings', - Tax = 'Tax', - TaxDetail = 'TaxDetail', - TaxSummary = 'TaxSummary', - Address = 'Address', - Contact = 'Contact', - JournalEntry = 'JournalEntry', - JournalEntryAccount = 'JournalEntryAccount', - JournalEntrySettings = 'JournalEntrySettings', - Quotation = 'Quotation', - QuotationItem = 'QuotationItem', - QuotationSettings = 'QuotationSettings', - SalesOrder = 'SalesOrder', - SalesOrderItem = 'SalesOrderItem', - SalesOrderSettings = 'SalesOrderSettings', - Fulfillment = 'Fulfillment', - FulfillmentItem = 'FulfillmentItem', - FulfillmentSettings = 'FulfillmentSettings', - PurchaseOrder = 'PurchaseOrder', - PurchaseOrderItem = 'PurchaseOrderItem', - PurchaseOrderSettings = 'PurchaseOrderSettings', - PurchaseReceipt = 'PurchaseReceipt', - PurchaseReceiptItem = 'PurchaseReceiptItem', - PurchaseReceiptSettings = 'PurchaseReceiptSettings', - Event = 'Event', - EventSchedule = 'EventSchedule', - EventSettings = 'EventSettings', - Email = 'Email', - EmailAccount = 'EmailAccount', - PrintSettings = 'PrintSettings', - GetStarted = 'GetStarted', -} - export type InvoiceStatus = 'Draft' | 'Unpaid' | 'Cancelled' | 'Paid'; diff --git a/schemas/app/PurchaseInvoiceItem.json b/schemas/app/PurchaseInvoiceItem.json index c109ddbe..c85213da 100644 --- a/schemas/app/PurchaseInvoiceItem.json +++ b/schemas/app/PurchaseInvoiceItem.json @@ -2,7 +2,6 @@ "name": "PurchaseInvoiceItem", "label": "Purchase Invoice Item", "isChild": true, - "tableFields": ["item", "tax", "quantity", "rate", "amount"], "fields": [ { "fieldname": "item", @@ -64,6 +63,13 @@ "fieldtype": "Currency", "computed": true, "readOnly": true + }, + { + "fieldname": "hsnCode", + "label": "HSN/SAC", + "fieldtype": "Int", + "placeholder": "HSN/SAC Code" } - ] -} \ No newline at end of file + ], + "tableFields": ["item", "tax", "quantity", "rate", "amount"] +} diff --git a/schemas/app/SalesInvoiceItem.json b/schemas/app/SalesInvoiceItem.json index 2be59cd5..805d25b8 100644 --- a/schemas/app/SalesInvoiceItem.json +++ b/schemas/app/SalesInvoiceItem.json @@ -64,7 +64,13 @@ "fieldtype": "Currency", "computed": true, "readOnly": true + }, + { + "fieldname": "hsnCode", + "label": "HSN/SAC", + "fieldtype": "Int", + "placeholder": "HSN/SAC Code" } ], "tableFields": ["item", "tax", "quantity", "rate", "amount"] -} \ No newline at end of file +} diff --git a/models/baseModels/SalesInvoice/InvoiceTemplate.vue b/src/components/SalesInvoice/InvoiceTemplate.vue similarity index 100% rename from models/baseModels/SalesInvoice/InvoiceTemplate.vue rename to src/components/SalesInvoice/InvoiceTemplate.vue diff --git a/models/baseModels/SalesInvoice/SalesInvoicePrint.vue b/src/components/SalesInvoice/SalesInvoicePrint.vue similarity index 100% rename from models/baseModels/SalesInvoice/SalesInvoicePrint.vue rename to src/components/SalesInvoice/SalesInvoicePrint.vue diff --git a/models/baseModels/SalesInvoice/Templates/Base.vue b/src/components/SalesInvoice/Templates/Base.vue similarity index 100% rename from models/baseModels/SalesInvoice/Templates/Base.vue rename to src/components/SalesInvoice/Templates/Base.vue diff --git a/models/baseModels/SalesInvoice/Templates/Basic.vue b/src/components/SalesInvoice/Templates/Basic.vue similarity index 100% rename from models/baseModels/SalesInvoice/Templates/Basic.vue rename to src/components/SalesInvoice/Templates/Basic.vue diff --git a/models/baseModels/SalesInvoice/Templates/Business.vue b/src/components/SalesInvoice/Templates/Business.vue similarity index 100% rename from models/baseModels/SalesInvoice/Templates/Business.vue rename to src/components/SalesInvoice/Templates/Business.vue diff --git a/models/baseModels/SalesInvoice/Templates/Minimal.vue b/src/components/SalesInvoice/Templates/Minimal.vue similarity index 100% rename from models/baseModels/SalesInvoice/Templates/Minimal.vue rename to src/components/SalesInvoice/Templates/Minimal.vue