From 76bf6cfda5ab93700f8130500eabdaa02fb84153 Mon Sep 17 00:00:00 2001 From: 18alantom <2.alan.tom@gmail.com> Date: Mon, 18 Apr 2022 16:59:20 +0530 Subject: [PATCH] incr: rem singleton from index.ts cause can't test - update models to not use singleton export --- accounting/exchangeRate.ts | 4 +- accounting/ledgerPosting.ts | 41 +++-- backend/database/core.ts | 9 +- backend/database/manager.ts | 3 +- backend/database/types.ts | 7 +- frappe/README.md | 14 +- frappe/core/authHandler.ts | 16 +- frappe/core/converter.ts | 15 +- frappe/core/dbHandler.ts | 6 +- frappe/core/docHandler.ts | 2 +- frappe/core/types.ts | 38 ++++- frappe/demux/auth.ts | 22 +++ frappe/demux/config.ts | 55 ++++++ frappe/index.ts | 51 ++++-- frappe/model/doc.ts | 94 +++++++---- frappe/model/helpers.ts | 7 +- frappe/model/naming.ts | 28 ++-- frappe/model/types.ts | 7 +- frappe/models/NumberSeries.ts | 3 +- frappe/telemetry/helpers.ts | 116 +++++++++++++ {src => frappe}/telemetry/telemetry.ts | 50 ++++-- {src => frappe}/telemetry/types.ts | 12 +- frappe/tests/helpers.ts | 7 + frappe/tests/testFrappe.spec.ts | 39 ++++- frappe/utils/consts.ts | 12 +- frappe/utils/format.ts | 32 ++-- models/README.md | 3 + models/baseModels/Account/Account.ts | 41 +++-- models/baseModels/Account/types.ts | 27 +++ .../AccountingLedgerEntry.ts | 8 +- models/baseModels/Address/Address.ts | 3 +- models/baseModels/Invoice/Invoice.ts | 23 +-- models/baseModels/InvoiceItem/InvoiceItem.ts | 8 +- models/baseModels/Item/Item.ts | 73 ++++---- .../baseModels/JournalEntry/JournalEntry.ts | 82 ++++----- .../JournalEntryAccount.ts | 3 +- models/baseModels/Party/Party.ts | 153 +++++++++-------- models/baseModels/Payment/Payment.ts | 157 ++++++++++-------- models/baseModels/PaymentFor/PaymentFor.ts | 3 +- .../PurchaseInvoice/PurchaseInvoice.ts | 40 +++-- .../baseModels/SalesInvoice/SalesInvoice.ts | 40 +++-- models/baseModels/SetupWizard/SetupWizard.ts | 4 +- models/baseModels/Tax/Tax.ts | 4 +- models/helpers.ts | 15 +- models/index.ts | 7 +- models/regionalModels/in/Party.ts | 2 +- models/types.ts | 30 ++++ schemas/app/Account.json | 5 +- schemas/index.ts | 8 +- schemas/types.ts | 2 - src/config.ts | 26 --- src/dataImport.ts | 4 +- src/errorHandling.ts | 6 +- src/initFyo.ts | 3 + src/postStart.ts | 24 ++- src/renderer/helpers.ts | 7 +- src/renderer/registerIpcRendererListeners.ts | 2 +- src/router.js | 4 +- src/telemetry/helpers.ts | 129 -------------- tsconfig.json | 2 +- utils/auth/types.ts | 5 + utils/config.ts | 4 + 62 files changed, 1009 insertions(+), 638 deletions(-) create mode 100644 frappe/demux/auth.ts create mode 100644 frappe/demux/config.ts create mode 100644 frappe/telemetry/helpers.ts rename {src => frappe}/telemetry/telemetry.ts (79%) rename {src => frappe}/telemetry/types.ts (83%) create mode 100644 frappe/tests/helpers.ts create mode 100644 models/baseModels/Account/types.ts delete mode 100644 src/config.ts create mode 100644 src/initFyo.ts delete mode 100644 src/telemetry/helpers.ts create mode 100644 utils/auth/types.ts create mode 100644 utils/config.ts diff --git a/accounting/exchangeRate.ts b/accounting/exchangeRate.ts index bdbdf78a..e40ca95c 100644 --- a/accounting/exchangeRate.ts +++ b/accounting/exchangeRate.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { NotFoundError } from 'frappe/utils/errors'; import { DateTime } from 'luxon'; export async function getExchangeRate({ @@ -15,7 +15,7 @@ export async function getExchangeRate({ } if (!fromCurrency || !toCurrency) { - throw new frappe.errors.NotFoundError( + throw new NotFoundError( 'Please provide `fromCurrency` and `toCurrency` to get exchange rate.' ); } diff --git a/accounting/ledgerPosting.ts b/accounting/ledgerPosting.ts index ddb7f383..5868620b 100644 --- a/accounting/ledgerPosting.ts +++ b/accounting/ledgerPosting.ts @@ -1,5 +1,6 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; +import { ValidationError } from 'frappe/utils/errors'; import Money from 'pesa/dist/types/src/money'; import { AccountEntry, @@ -18,7 +19,12 @@ export class LedgerPosting { reverted: boolean; accountEntries: AccountEntry[]; - constructor({ reference, party, date, description }: LedgerPostingOptions) { + frappe: Frappe; + + constructor( + { reference, party, date, description }: LedgerPostingOptions, + frappe: Frappe + ) { this.reference = reference; this.party = party; this.date = date; @@ -28,6 +34,8 @@ export class LedgerPosting { this.reverted = false; // To change balance while entering ledger entries this.accountEntries = []; + + this.frappe = frappe; } async debit( @@ -58,7 +66,7 @@ export class LedgerPosting { amount: Money ) { const debitAccounts = ['Asset', 'Expense']; - const accountDoc = await frappe.doc.getDoc('Account', accountName); + const accountDoc = await this.frappe.doc.getDoc('Account', accountName); const rootType = accountDoc.rootType as string; if (debitAccounts.indexOf(rootType) === -1) { @@ -86,8 +94,8 @@ export class LedgerPosting { referenceName: referenceName ?? this.reference.name!, description: this.description, reverted: this.reverted, - debit: frappe.pesa(0), - credit: frappe.pesa(0), + debit: this.frappe.pesa(0), + credit: this.frappe.pesa(0), }; this.entries.push(entry); @@ -105,7 +113,7 @@ export class LedgerPosting { async postReverse() { this.validateEntries(); - const data = await frappe.db.getAll('AccountingLedgerEntry', { + const data = await this.frappe.db.getAll('AccountingLedgerEntry', { fields: ['name'], filters: { referenceName: this.reference.name!, @@ -114,7 +122,7 @@ export class LedgerPosting { }); for (const entry of data) { - const entryDoc = await frappe.doc.getDoc( + const entryDoc = await this.frappe.doc.getDoc( 'AccountingLedgerEntry', entry.name as string ); @@ -157,18 +165,21 @@ export class LedgerPosting { validateEntries() { const { debit, credit } = this.getTotalDebitAndCredit(); if (debit.neq(credit)) { - throw new frappe.errors.ValidationError( - `Total Debit: ${frappe.format( + throw new ValidationError( + `Total Debit: ${this.frappe.format( debit, 'Currency' - )} must be equal to Total Credit: ${frappe.format(credit, 'Currency')}` + )} must be equal to Total Credit: ${this.frappe.format( + credit, + 'Currency' + )}` ); } } getTotalDebitAndCredit() { - let debit = frappe.pesa(0); - let credit = frappe.pesa(0); + let debit = this.frappe.pesa(0); + let credit = this.frappe.pesa(0); for (const entry of this.entries) { debit = debit.add(entry.debit); @@ -180,12 +191,12 @@ export class LedgerPosting { async insertEntries() { for (const entry of this.entries) { - const entryDoc = frappe.doc.getNewDoc('AccountingLedgerEntry'); + const entryDoc = this.frappe.doc.getNewDoc('AccountingLedgerEntry'); Object.assign(entryDoc, entry); await entryDoc.insert(); } for (const entry of this.accountEntries) { - const entryDoc = await frappe.doc.getDoc('Account', entry.name); + const entryDoc = await this.frappe.doc.getDoc('Account', entry.name); const balance = entryDoc.get('balance') as Money; entryDoc.balance = balance.add(entry.balanceChange); await entryDoc.update(); @@ -193,6 +204,6 @@ export class LedgerPosting { } getRoundOffAccount() { - return frappe.singles.AccountingSettings!.roundOffAccount as string; + return this.frappe.singles.AccountingSettings!.roundOffAccount as string; } } diff --git a/backend/database/core.ts b/backend/database/core.ts index b2630801..14dab13f 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -18,7 +18,12 @@ import { import { getRandomString, getValueMapFromList } from '../../utils'; import { DatabaseBase, GetAllOptions, QueryFilter } from '../../utils/db/types'; import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../helpers'; -import { ColumnDiff, FieldValueMap, GetQueryBuilderOptions } from './types'; +import { + ColumnDiff, + FieldValueMap, + GetQueryBuilderOptions, + SingleValue, +} from './types'; /** * # DatabaseCore @@ -256,7 +261,7 @@ export default class DatabaseCore extends DatabaseBase { async getSingleValues( ...fieldnames: ({ fieldname: string; parent?: string } | string)[] - ): Promise<{ fieldname: string; parent: string; value: RawValue }[]> { + ): Promise> { const fieldnameList = fieldnames.map((fieldname) => { if (typeof fieldname === 'string') { return { fieldname }; diff --git a/backend/database/manager.ts b/backend/database/manager.ts index b08677b9..2555a99a 100644 --- a/backend/database/manager.ts +++ b/backend/database/manager.ts @@ -21,8 +21,7 @@ export class DatabaseManager extends DatabaseDemuxBase { async createNewDatabase(dbPath: string, countryCode: string) { await this.#unlinkIfExists(dbPath); - await this.connectToDatabase(dbPath, countryCode); - return countryCode; + return await this.connectToDatabase(dbPath, countryCode); } async connectToDatabase(dbPath: string, countryCode?: string) { diff --git a/backend/database/types.ts b/backend/database/types.ts index 6dcf0ab8..6ccd0cce 100644 --- a/backend/database/types.ts +++ b/backend/database/types.ts @@ -45,4 +45,9 @@ export interface SqliteTableInfo { dflt_value: string | null; } -export type BespokeFunction = (db:DatabaseCore, ...args: unknown[]) => Promise \ No newline at end of file +export type BespokeFunction = (db:DatabaseCore, ...args: unknown[]) => Promise +export type SingleValue = { + fieldname: string; + parent: string; + value: T; +}[]; \ No newline at end of file diff --git a/frappe/README.md b/frappe/README.md index f38d2f6c..5271c738 100644 --- a/frappe/README.md +++ b/frappe/README.md @@ -5,7 +5,7 @@ removed into a separate repo, but as of now it's in gestation. The reason for maintaining a framework is to allow for varied backends. Currently Books runs on the electron renderer process and all db stuff happens -on the electron main process which has access to node libs. As the development +on the electron main process which has access to nodelibs. As the development of `Fyo` progresses it will allow for a browser frontend and a node server backend. @@ -70,6 +70,9 @@ other things. - Get models and `regionalModels` using `countryCode` from `models/index.ts/getRegionalModels`. - Call `fyo.initializeAndRegister` with the all models. +_Note: since **SystemSettings** are initialized on `fyo.initializeAndRegister` +db needs to be set first else an error will be thrown_ + ## Testing For testing the `fyo` class, `mocha` is used (`node` side). So for this the @@ -77,15 +80,18 @@ demux classes are directly replaced by `node` side managers such as `DatabaseManager`. For this to work the class signatures of the demux class and the manager have to -be the same. +be the same which is maintained by abstract demux classes. + +`DatabaseManager` is used as the `DatabaseDemux` for testing without API or IPC +calls. For `AuthDemux` the `DummyAuthDemux` class is used. ## Translations All translations take place during runtime, for translations to work, a -`LanguageMap` (for def check `utils/types.ts`) has to be set. +`LanguageMap` (for def check `utils/types.ts`) has to be set. This can be done using `fyo/utils/translation.ts/setLanguageMapOnTranslationString`. Since translations are runtime, if the code is evaluated before the language map is loaded, translations won't work. To prevent this, don't maintain translation -strings globally. \ No newline at end of file +strings globally. diff --git a/frappe/core/authHandler.ts b/frappe/core/authHandler.ts index d1f727c0..5bd6f1fb 100644 --- a/frappe/core/authHandler.ts +++ b/frappe/core/authHandler.ts @@ -1,4 +1,7 @@ import { Frappe } from 'frappe'; +import { AuthDemux } from 'frappe/demux/auth'; +import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types'; +import { AuthDemuxConstructor } from './types'; interface AuthConfig { serverURL: string; @@ -15,8 +18,9 @@ export class AuthHandler { #config: AuthConfig; #session: Session; frappe: Frappe; + #demux: AuthDemuxBase; - constructor(frappe: Frappe) { + constructor(frappe: Frappe, Demux?: AuthDemuxConstructor) { this.frappe = frappe; this.#config = { serverURL: '', @@ -28,6 +32,12 @@ export class AuthHandler { user: '', token: '', }; + + if (Demux !== undefined) { + this.#demux = new Demux(frappe.isElectron); + } else { + this.#demux = new AuthDemux(frappe.isElectron); + } } get session(): Readonly { @@ -90,4 +100,8 @@ export class AuthHandler { #getServerURL() { return this.#config.serverURL || ''; } + + async getTelemetryCreds(): Promise { + return await this.#demux.getTelemetryCreds(); + } } diff --git a/frappe/core/converter.ts b/frappe/core/converter.ts index 60eb5b9e..0d70c042 100644 --- a/frappe/core/converter.ts +++ b/frappe/core/converter.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; import Money from 'pesa/dist/types/src/money'; import { FieldType, FieldTypeEnum, RawValue } from 'schemas/types'; @@ -22,9 +22,11 @@ import { DocValue, DocValueMap, RawValueMap } from './types'; export class Converter { db: DatabaseHandler; + frappe: Frappe; - constructor(db: DatabaseHandler) { + constructor(db: DatabaseHandler, frappe: Frappe) { this.db = db; + this.frappe = frappe; } toDocValueMap( @@ -49,7 +51,11 @@ export class Converter { } } - static toDocValue(value: RawValue, fieldtype: FieldType): DocValue { + static toDocValue( + value: RawValue, + fieldtype: FieldType, + frappe: Frappe + ): DocValue { switch (fieldtype) { case FieldTypeEnum.Currency: return frappe.pesa((value ?? 0) as string | number); @@ -112,7 +118,8 @@ export class Converter { } else { docValueMap[fieldname] = Converter.toDocValue( rawValue, - field.fieldtype + field.fieldtype, + this.frappe ); } } diff --git a/frappe/core/dbHandler.ts b/frappe/core/dbHandler.ts index ff6edad3..7014e486 100644 --- a/frappe/core/dbHandler.ts +++ b/frappe/core/dbHandler.ts @@ -1,3 +1,4 @@ +import { SingleValue } from 'backend/database/types'; import { Frappe } from 'frappe'; import { DatabaseDemux } from 'frappe/demux/db'; import { Field, RawValue, SchemaMap } from 'schemas/types'; @@ -9,7 +10,6 @@ import { DocValue, DocValueMap, RawValueMap, - SingleValue, } from './types'; // Return types of Bespoke Queries @@ -27,7 +27,7 @@ export class DatabaseHandler extends DatabaseBase { constructor(frappe: Frappe, Demux?: DatabaseDemuxConstructor) { super(); this.#frappe = frappe; - this.converter = new Converter(this); + this.converter = new Converter(this, this.#frappe); if (Demux !== undefined) { this.#demux = new Demux(frappe.isElectron); @@ -117,7 +117,7 @@ export class DatabaseHandler extends DatabaseBase { const docSingleValue: SingleValue = []; for (const sv of rawSingleValue) { const fieldtype = this.fieldValueMap[sv.parent][sv.fieldname].fieldtype; - const value = Converter.toDocValue(sv.value, fieldtype); + const value = Converter.toDocValue(sv.value, fieldtype, this.#frappe); docSingleValue.push({ value, diff --git a/frappe/core/docHandler.ts b/frappe/core/docHandler.ts index bdf40063..8db4e1e2 100644 --- a/frappe/core/docHandler.ts +++ b/frappe/core/docHandler.ts @@ -164,7 +164,7 @@ export class DocHandler { throw new Error(`Schema not found for ${schemaName}`); } - const doc = new Model(schema, data); + const doc = new Model(schema, data, this.frappe); doc.setDefaults(); return doc; } diff --git a/frappe/core/types.ts b/frappe/core/types.ts index 4a8afa49..8b7ae7ba 100644 --- a/frappe/core/types.ts +++ b/frappe/core/types.ts @@ -1,21 +1,45 @@ import Doc from 'frappe/model/doc'; import Money from 'pesa/dist/types/src/money'; import { RawValue } from 'schemas/types'; +import { AuthDemuxBase } from 'utils/auth/types'; import { DatabaseDemuxBase } from 'utils/db/types'; export type DocValue = string | number | boolean | Date | Money | null; export type DocValueMap = Record; export type RawValueMap = Record; -export type SingleValue = { - fieldname: string; - parent: string; - value: T; -}[]; - /** * DatabaseDemuxConstructor: type for a constructor that returns a DatabaseDemuxBase * it's typed this way because `typeof AbstractClass` is invalid as abstract classes * can't be initialized using `new`. + * + * AuthDemuxConstructor: same as the above but for AuthDemuxBase */ -export type DatabaseDemuxConstructor = new (isElectron?: boolean)=> DatabaseDemuxBase \ No newline at end of file + +export type DatabaseDemuxConstructor = new ( + isElectron?: boolean +) => DatabaseDemuxBase; + +export type AuthDemuxConstructor = new (isElectron?: boolean) => AuthDemuxBase; + +export enum ConfigKeys { + Files = 'files', + LastSelectedFilePath = 'lastSelectedFilePath', + Language = 'language', + DeviceId = 'deviceId', + Telemetry = 'telemetry', + OpenCount = 'openCount', +} + +export interface ConfigFile { + id: string; + companyName: string; + filePath: string; +} + +export interface FyoConfig { + DatabaseDemux?: DatabaseDemuxConstructor; + AuthDemux?: AuthDemuxConstructor; + isElectron?: boolean; + isTest?: boolean; +} diff --git a/frappe/demux/auth.ts b/frappe/demux/auth.ts new file mode 100644 index 00000000..e9ea5136 --- /dev/null +++ b/frappe/demux/auth.ts @@ -0,0 +1,22 @@ +import { ipcRenderer } from 'electron'; +import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types'; +import { IPC_ACTIONS } from 'utils/messages'; + +export class AuthDemux extends AuthDemuxBase { + #isElectron: boolean = false; + constructor(isElectron: boolean) { + super(); + this.#isElectron = isElectron; + } + + async getTelemetryCreds(): Promise { + if (this.#isElectron) { + const creds = await ipcRenderer.invoke(IPC_ACTIONS.GET_CREDS); + const url: string = creds?.telemetryUrl ?? ''; + const token: string = creds?.tokenString ?? ''; + return { url, token }; + } else { + return { url: '', token: '' }; + } + } +} diff --git a/frappe/demux/config.ts b/frappe/demux/config.ts new file mode 100644 index 00000000..2126c631 --- /dev/null +++ b/frappe/demux/config.ts @@ -0,0 +1,55 @@ +import config from 'utils/config'; + +export class Config { + #isElectron: boolean; + fallback: Map = new Map(); + constructor(isElectron: boolean) { + this.#isElectron = isElectron; + } + + get store(): Record { + if (this.#isElectron) { + return config.store; + } else { + const store: Record = {}; + + for (const key of this.fallback.keys()) { + store[key] = this.fallback.get(key); + } + + return store; + } + } + + get(key: string, defaultValue?: unknown): unknown { + if (this.#isElectron) { + return config.get(key, defaultValue); + } else { + return this.fallback.get(key) ?? defaultValue; + } + } + + set(key: string, value: unknown) { + if (this.#isElectron) { + config.set(key, value); + } else { + this.fallback.set(key, value); + } + } + + delete(key: string) { + if (this.#isElectron) { + config.delete(key); + } else { + this.fallback.delete(key); + } + } + + clear() { + if (this.#isElectron) { + config.clear(); + } else { + this.fallback.clear(); + } + } +} diff --git a/frappe/index.ts b/frappe/index.ts index 398ed0a4..c14d705f 100644 --- a/frappe/index.ts +++ b/frappe/index.ts @@ -1,10 +1,14 @@ import { getMoneyMaker, MoneyMaker } from 'pesa'; +import { Field } from 'schemas/types'; import { markRaw } from 'vue'; import { AuthHandler } from './core/authHandler'; import { DatabaseHandler } from './core/dbHandler'; import { DocHandler } from './core/docHandler'; -import { DatabaseDemuxConstructor } from './core/types'; +import { DocValue, FyoConfig } from './core/types'; +import { Config } from './demux/config'; +import Doc from './model/doc'; import { ModelMap } from './model/types'; +import { TelemetryManager } from './telemetry/telemetry'; import { DEFAULT_CURRENCY, DEFAULT_DISPLAY_PRECISION, @@ -18,10 +22,9 @@ import { ErrorLog } from './utils/types'; export class Frappe { t = t; T = T; - format = format; errors = errors; - isElectron = false; + isElectron: boolean; pesa: MoneyMaker; @@ -38,21 +41,27 @@ export class Frappe { currencyFormatter?: Intl.NumberFormat; currencySymbols: Record = {}; - constructor(DatabaseDemux?: DatabaseDemuxConstructor) { - /** - * `DatabaseManager` can be passed as the `DatabaseDemux` for - * testing this class without API or IPC calls. - */ - this.auth = new AuthHandler(this); - this.db = new DatabaseHandler(this, DatabaseDemux); + isTest: boolean; + telemetry: TelemetryManager; + config: Config; + + constructor(conf: FyoConfig = {}) { + this.isTest = conf.isTest ?? false; + this.isElectron = conf.isElectron ?? true; + + this.auth = new AuthHandler(this, conf.AuthDemux); + this.db = new DatabaseHandler(this, conf.DatabaseDemux); this.doc = new DocHandler(this); this.pesa = getMoneyMaker({ - currency: 'XXX', + currency: DEFAULT_CURRENCY, precision: DEFAULT_INTERNAL_PRECISION, display: DEFAULT_DISPLAY_PRECISION, wrapper: markRaw, }); + + this.telemetry = new TelemetryManager(this); + this.config = new Config(this.isElectron); } get initialized() { @@ -75,6 +84,19 @@ export class Frappe { return this.db.schemaMap; } + format(value: DocValue, field: string | Field, doc?: Doc) { + return format(value, field, doc ?? null, this); + } + + async setIsElectron() { + try { + const { ipcRenderer } = await import('electron'); + this.isElectron = Boolean(ipcRenderer); + } catch { + this.isElectron = false; + } + } + async initializeAndRegister( models: ModelMap = {}, regionalModels: ModelMap = {}, @@ -138,9 +160,9 @@ export class Frappe { }); } - close() { - this.db.close(); - this.auth.logout(); + async close() { + await this.db.close(); + await this.auth.logout(); } store = { @@ -150,4 +172,3 @@ export class Frappe { } export { T, t }; -export default new Frappe(); diff --git a/frappe/model/doc.ts b/frappe/model/doc.ts index dd09ae8c..1451c4ca 100644 --- a/frappe/model/doc.ts +++ b/frappe/model/doc.ts @@ -1,6 +1,6 @@ -import telemetry from '@/telemetry/telemetry'; -import { Verb } from '@/telemetry/types'; +import { Frappe } from 'frappe'; import { DocValue, DocValueMap } from 'frappe/core/types'; +import { Verb } from 'frappe/telemetry/types'; import { Conflict, MandatoryError, @@ -17,7 +17,6 @@ import { TargetField, } from 'schemas/types'; import { getIsNullOrUndef, getMapFromList } from 'utils'; -import frappe from '..'; import { getRandomString, isPesa } from '../utils/index'; import { areDocValuesEqual, @@ -47,6 +46,7 @@ import { validateSelect } from './validationFunction'; export default class Doc extends Observable { name?: string; schema: Readonly; + frappe: Frappe; fieldMap: Record; /** @@ -66,11 +66,16 @@ export default class Doc extends Observable { revertAction: false, }; - constructor(schema: Schema, data: DocValueMap) { + constructor(schema: Schema, data: DocValueMap, frappe: Frappe) { super(); + this.frappe = frappe; this.schema = schema; this._setInitialValues(data); this.fieldMap = getMapFromList(schema.fields, 'fieldname'); + + if (this.schema.isSingle) { + this.name = this.schemaName; + } } get schemaName(): string { @@ -183,7 +188,10 @@ export default class Doc extends Observable { continue; } - let defaultValue: DocValue | Doc[] = getPreDefaultValues(field.fieldtype); + let defaultValue: DocValue | Doc[] = getPreDefaultValues( + field.fieldtype, + this.frappe + ); const defaultFunction = this.defaults[field.fieldname]; if (defaultFunction !== undefined) { @@ -193,7 +201,7 @@ export default class Doc extends Observable { } if (field.fieldtype === 'Currency' && !isPesa(defaultValue)) { - defaultValue = frappe.pesa!(defaultValue as string | number); + defaultValue = this.frappe.pesa!(defaultValue as string | number); } this[field.fieldname] = defaultValue; @@ -235,8 +243,8 @@ export default class Doc extends Observable { } const childSchemaName = this.fieldMap[fieldname] as TargetField; - const schema = frappe.db.schemaMap[childSchemaName.target] as Schema; - const childDoc = new Doc(schema, data as DocValueMap); + const schema = this.frappe.db.schemaMap[childSchemaName.target] as Schema; + const childDoc = new Doc(schema, data as DocValueMap, this.frappe); childDoc.setDefaults(); return childDoc; } @@ -263,7 +271,7 @@ export default class Doc extends Observable { if (missingMandatoryMessage.length > 0) { const fields = missingMandatoryMessage.join('\n'); - const message = frappe.t`Value missing for ${fields}`; + const message = this.frappe.t`Value missing for ${fields}`; throw new MandatoryError(message); } } @@ -322,7 +330,7 @@ export default class Doc extends Observable { } if (!this.createdBy) { - this.createdBy = frappe.auth.session.user; + this.createdBy = this.frappe.auth.session.user; } if (!this.created) { @@ -333,7 +341,7 @@ export default class Doc extends Observable { } updateModified() { - this.modifiedBy = frappe.auth.session.user; + this.modifiedBy = this.frappe.auth.session.user; this.modified = new Date(); } @@ -342,7 +350,11 @@ export default class Doc extends Observable { return; } - const data = await frappe.db.get(this.schemaName, this.name); + const data = await this.frappe.db.get(this.schemaName, this.name); + if (this.schema.isSingle && !data?.name) { + data.name = this.name!; + } + if (data && data.name) { this.syncValues(data); if (this.schema.isSingle) { @@ -375,7 +387,7 @@ export default class Doc extends Observable { return; } - this._links[fieldname] = await frappe.doc.getDoc( + this._links[fieldname] = await this.frappe.doc.getDoc( field.target, value as string ); @@ -422,7 +434,7 @@ export default class Doc extends Observable { return; } - const currentDoc = await frappe.db.get(this.schemaName, this.name); + const currentDoc = await this.frappe.db.get(this.schemaName, this.name); // check for conflict if ( @@ -430,13 +442,14 @@ export default class Doc extends Observable { (this.modified as Date) !== (currentDoc.modified as Date) ) { throw new Conflict( - frappe.t`Document ${this.schemaName} ${this.name} has been modified after loading` + this.frappe + .t`Document ${this.schemaName} ${this.name} has been modified after loading` ); } if (this.submitted && !this.schema.isSubmittable) { throw new ValidationError( - frappe.t`Document type ${this.schemaName} is not submittable` + this.frappe.t`Document type ${this.schemaName} is not submittable` ); } @@ -532,24 +545,27 @@ export default class Doc extends Observable { } async insert() { - await setName(this); + await setName(this, this.frappe); this.setBaseMetaValues(); await this.commit(); await this.validateInsert(); await this.trigger('beforeInsert', null); const oldName = this.name!; - const data = await frappe.db.insert(this.schemaName, this.getValidDict()); + const data = await this.frappe.db.insert( + this.schemaName, + this.getValidDict() + ); this.syncValues(data); if (oldName !== this.name) { - frappe.doc.removeFromCache(this.schemaName, oldName); + this.frappe.doc.removeFromCache(this.schemaName, oldName); } await this.trigger('afterInsert', null); await this.trigger('afterSave', null); - telemetry.log(Verb.Created, this.schemaName); + this.frappe.telemetry.log(Verb.Created, this.schemaName); return this; } @@ -566,7 +582,7 @@ export default class Doc extends Observable { this.updateModified(); const data = this.getValidDict(); - await frappe.db.update(this.schemaName, data); + await this.frappe.db.update(this.schemaName, data); this.syncValues(data); await this.trigger('afterUpdate'); @@ -589,10 +605,10 @@ export default class Doc extends Observable { async delete() { await this.trigger('beforeDelete'); - await frappe.db.delete(this.schemaName, this.name!); + await this.frappe.db.delete(this.schemaName, this.name!); await this.trigger('afterDelete'); - telemetry.log(Verb.Deleted, this.schemaName); + this.frappe.telemetry.log(Verb.Deleted, this.schemaName); } async submitOrRevert(isSubmit: boolean) { @@ -617,7 +633,7 @@ export default class Doc extends Observable { async rename(newName: string) { await this.trigger('beforeRename'); - await frappe.db.rename(this.schemaName, this.name!, newName); + await this.frappe.db.rename(this.schemaName, this.name!, newName); this.name = newName; await this.trigger('afterRename'); } @@ -637,7 +653,7 @@ export default class Doc extends Observable { const value = d.get(childfield) ?? 0; if (!isPesa(value)) { try { - return frappe.pesa(value as string | number); + return this.frappe.pesa(value as string | number); } catch (err) { ( err as Error @@ -647,7 +663,7 @@ export default class Doc extends Observable { } return value as Money; }) - .reduce((a, b) => a.add(b), frappe.pesa(0)); + .reduce((a, b) => a.add(b), this.frappe.pesa(0)); if (convertToFloat) { return sum.float; @@ -660,7 +676,7 @@ export default class Doc extends Observable { return ''; } - return frappe.doc.getCachedValue(schemaName, name, fieldname); + return this.frappe.doc.getCachedValue(schemaName, name, fieldname); } async duplicate(shouldInsert: boolean = true): Promise { @@ -690,7 +706,7 @@ export default class Doc extends Observable { updateMap.name = updateMap.name + ' CPY'; } - const doc = frappe.doc.getEmptyDoc(this.schemaName, false); + const doc = this.frappe.doc.getEmptyDoc(this.schemaName, false); await doc.setMultiple(updateMap); if (shouldInsert) { @@ -700,6 +716,16 @@ export default class Doc extends Observable { return doc; } + async beforeInsert() {} + async afterInsert() {} + async beforeUpdate() {} + async afterUpdate() {} + async afterSave() {} + async beforeDelete() {} + async afterDelete() {} + async beforeRevert() {} + async afterRevert() {} + formulas: FormulaMap = {}; defaults: DefaultMap = {}; validations: ValidationMap = {}; @@ -711,8 +737,14 @@ export default class Doc extends Observable { static lists: ListsMap = {}; static filters: FiltersMap = {}; static emptyMessages: EmptyMessageMap = {}; - static listSettings: ListViewSettings = {}; - static treeSettings?: TreeViewSettings; - static actions: Action[] = []; + static getListViewSettings(frappe: Frappe): ListViewSettings { + return {}; + } + + static getTreeSettings(frappe: Frappe): TreeViewSettings | void {} + + static getActions(frappe: Frappe): Action[] { + return []; + } } diff --git a/frappe/model/helpers.ts b/frappe/model/helpers.ts index 7a2c7088..4cb73afd 100644 --- a/frappe/model/helpers.ts +++ b/frappe/model/helpers.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import { DocValue } from 'frappe/core/types'; import { isPesa } from 'frappe/utils'; import { isEqual } from 'lodash'; @@ -26,7 +26,10 @@ export function areDocValuesEqual( return isEqual(dvOne, dvTwo); } -export function getPreDefaultValues(fieldtype: FieldType): DocValue | Doc[] { +export function getPreDefaultValues( + fieldtype: FieldType, + frappe: Frappe +): DocValue | Doc[] { switch (fieldtype) { case FieldTypeEnum.Table: return [] as Doc[]; diff --git a/frappe/model/naming.ts b/frappe/model/naming.ts index 5a7b0ee6..9e3f399e 100644 --- a/frappe/model/naming.ts +++ b/frappe/model/naming.ts @@ -1,6 +1,7 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import NumberSeries from 'frappe/models/NumberSeries'; import { getRandomString } from 'frappe/utils'; +import { DEFAULT_SERIES_START } from 'frappe/utils/consts'; import { BaseError } from 'frappe/utils/errors'; import { Field, Schema } from 'schemas/types'; import Doc from './doc'; @@ -12,7 +13,7 @@ export function getNumberSeries(schema: Schema): Field | undefined { return numberSeries; } -export function isNameAutoSet(schemaName: string): boolean { +export function isNameAutoSet(schemaName: string, frappe: Frappe): boolean { const schema = frappe.schemaMap[schemaName]!; if (schema.naming === 'autoincrement') { return true; @@ -26,17 +27,17 @@ export function isNameAutoSet(schemaName: string): boolean { return false; } -export async function setName(doc: Doc) { +export async function setName(doc: Doc, frappe: Frappe) { // if is server, always name again if autoincrement or other if (doc.schema.naming === 'autoincrement') { - doc.name = await getNextId(doc.schemaName); + doc.name = await getNextId(doc.schemaName, frappe); return; } // Current, per doc number series const numberSeries = doc.numberSeries as string | undefined; if (numberSeries !== undefined) { - doc.name = await getSeriesNext(numberSeries, doc.schemaName); + doc.name = await getSeriesNext(numberSeries, doc.schemaName, frappe); return; } @@ -57,9 +58,9 @@ export async function setName(doc: Doc) { } } -export async function getNextId(schemaName: string) { +export async function getNextId(schemaName: string, frappe: Frappe) { // get the last inserted row - const lastInserted = await getLastInserted(schemaName); + const lastInserted = await getLastInserted(schemaName, frappe); let name = 1; if (lastInserted) { let lastNumber = parseInt(lastInserted.name as string); @@ -69,7 +70,7 @@ export async function getNextId(schemaName: string) { return (name + '').padStart(9, '0'); } -export async function getLastInserted(schemaName: string) { +export async function getLastInserted(schemaName: string, frappe: Frappe) { const lastInserted = await frappe.db.getAll(schemaName, { fields: ['name'], limit: 1, @@ -79,7 +80,11 @@ export async function getLastInserted(schemaName: string) { return lastInserted && lastInserted.length ? lastInserted[0] : null; } -export async function getSeriesNext(prefix: string, schemaName: string) { +export async function getSeriesNext( + prefix: string, + schemaName: string, + frappe: Frappe +) { let series: NumberSeries; try { @@ -90,7 +95,7 @@ export async function getSeriesNext(prefix: string, schemaName: string) { throw e; } - await createNumberSeries(prefix, schemaName); + await createNumberSeries(prefix, schemaName, DEFAULT_SERIES_START, frappe); series = (await frappe.doc.getDoc('NumberSeries', prefix)) as NumberSeries; } @@ -100,7 +105,8 @@ export async function getSeriesNext(prefix: string, schemaName: string) { export async function createNumberSeries( prefix: string, referenceType: string, - start = 1001 + start: number, + frappe: Frappe ) { const exists = await frappe.db.exists('NumberSeries', prefix); if (exists) { diff --git a/frappe/model/types.ts b/frappe/model/types.ts index 215bf0d3..33e5032a 100644 --- a/frappe/model/types.ts +++ b/frappe/model/types.ts @@ -1,3 +1,4 @@ +import { Frappe } from 'frappe'; import { DocValue, DocValueMap } from 'frappe/core/types'; import SystemSettings from 'frappe/models/SystemSettings'; import { FieldType } from 'schemas/types'; @@ -41,8 +42,8 @@ export type ModelMap = Record; export type DocMap = Record; export interface SinglesMap { - SystemSettings?: SystemSettings - [key: string]: Doc | undefined + SystemSettings?: SystemSettings; + [key: string]: Doc | undefined; } // Static Config properties @@ -50,7 +51,7 @@ export interface SinglesMap { export type FilterFunction = (doc: Doc) => QueryFilter; export type FiltersMap = Record; -export type EmptyMessageFunction = (doc: Doc) => string; +export type EmptyMessageFunction = (doc: Doc, frappe: Frappe) => string; export type EmptyMessageMap = Record; export type ListFunction = (doc?: Doc) => string[]; diff --git a/frappe/models/NumberSeries.ts b/frappe/models/NumberSeries.ts index 96fbcc50..351ed8b1 100644 --- a/frappe/models/NumberSeries.ts +++ b/frappe/models/NumberSeries.ts @@ -1,4 +1,3 @@ -import frappe from 'frappe'; import Doc from 'frappe/model/doc'; function getPaddedName(prefix: string, next: number, padZeros: number): string { @@ -32,7 +31,7 @@ export default class NumberSeries extends Doc { } const name = this.getPaddedName(this.current as number); - return await frappe.db.exists(schemaName, name); + return await this.frappe.db.exists(schemaName, name); } getPaddedName(next: number): string { diff --git a/frappe/telemetry/helpers.ts b/frappe/telemetry/helpers.ts new file mode 100644 index 00000000..6c0fa15f --- /dev/null +++ b/frappe/telemetry/helpers.ts @@ -0,0 +1,116 @@ +import { Frappe } from 'frappe'; +import { ConfigFile, ConfigKeys } from 'frappe/core/types'; +import { DEFAULT_COUNTRY_CODE } from 'frappe/utils/consts'; +import { Count, TelemetrySetting, UniqueId } from './types'; + +export function getId(): string { + let id: string = ''; + + for (let i = 0; i < 4; i++) { + id += Math.random().toString(36).slice(2, 9); + } + + return id; +} + +export function getCountry(): string { + // @ts-ignore + return frappe.singles.SystemSettings?.countryCode ?? DEFAULT_COUNTRY_CODE; +} + +export function getLanguage(frappe: Frappe): string { + return frappe.config.get('language') as string; +} + +export async function getCounts( + interestingDocs: string[], + frappe: Frappe +): Promise { + const countMap: Count = {}; + // @ts-ignore + if (frappe.db === undefined) { + return countMap; + } + + for (const name of interestingDocs) { + const count: number = (await frappe.db.getAll(name)).length; + countMap[name] = count; + } + + return countMap; +} + +export function getDeviceId(frappe: Frappe): UniqueId { + let deviceId = frappe.config.get(ConfigKeys.DeviceId) as string | undefined; + if (deviceId === undefined) { + deviceId = getId(); + frappe.config.set(ConfigKeys.DeviceId, deviceId); + } + + return deviceId; +} + +export function getInstanceId(frappe: Frappe): UniqueId { + const files = frappe.config.get(ConfigKeys.Files) as ConfigFile[]; + + // @ts-ignore + const companyName = frappe.AccountingSettings?.companyName; + if (companyName === undefined) { + return ''; + } + + const file = files.find((f) => f.companyName === companyName); + + if (file === undefined) { + return addNewFile(companyName, files, frappe); + } + + if (file.id === undefined) { + return setInstanceId(companyName, files, frappe); + } + + return file.id; +} + +function addNewFile( + companyName: string, + files: ConfigFile[], + frappe: Frappe +): UniqueId { + const newFile: ConfigFile = { + companyName, + filePath: frappe.config.get(ConfigKeys.LastSelectedFilePath, '') as string, + id: getId(), + }; + + files.push(newFile); + frappe.config.set(ConfigKeys.Files, files); + return newFile.id; +} + +function setInstanceId( + companyName: string, + files: ConfigFile[], + frappe: Frappe +): UniqueId { + let id = ''; + for (const file of files) { + if (file.id) { + continue; + } + + file.id = getId(); + if (file.companyName === companyName) { + id = file.id; + } + } + + frappe.config.set(ConfigKeys.Files, files); + return id; +} + +export const getTelemetryOptions = (frappe: Frappe) => ({ + [TelemetrySetting.allow]: frappe.t`Allow Telemetry`, + [TelemetrySetting.dontLogUsage]: frappe.t`Don't Log Usage`, + [TelemetrySetting.dontLogAnything]: frappe.t`Don't Log Anything`, +}); diff --git a/src/telemetry/telemetry.ts b/frappe/telemetry/telemetry.ts similarity index 79% rename from src/telemetry/telemetry.ts rename to frappe/telemetry/telemetry.ts index e2f286e4..7fe517c4 100644 --- a/src/telemetry/telemetry.ts +++ b/frappe/telemetry/telemetry.ts @@ -1,15 +1,21 @@ -import config, { ConfigKeys, TelemetrySetting } from '@/config'; -import frappe from 'frappe'; +import { Frappe } from 'frappe'; +import { ConfigKeys } from 'frappe/core/types'; import { cloneDeep } from 'lodash'; import { getCountry, getCounts, - getCreds, getDeviceId, getInstanceId, getLanguage, } from './helpers'; -import { Noun, NounEnum, Platform, Telemetry, Verb } from './types'; +import { + Noun, + NounEnum, + Platform, + Telemetry, + TelemetrySetting, + Verb, +} from './types'; /** * # Telemetry @@ -44,16 +50,26 @@ import { Noun, NounEnum, Platform, Telemetry, Verb } from './types'; * telemetry and not app usage. */ -class TelemetryManager { +export class TelemetryManager { #url: string = ''; #token: string = ''; #started = false; #telemetryObject: Partial = {}; + #interestingDocs: string[] = []; + frappe: Frappe; + + constructor(frappe: Frappe) { + this.frappe = frappe; + } set platform(value: Platform) { this.#telemetryObject.platform ||= value; } + set interestingDocs(schemaNames: string[]) { + this.#interestingDocs = schemaNames; + } + get hasCreds() { return !!this.#url && !!this.#token; } @@ -68,9 +84,9 @@ class TelemetryManager { async start() { this.#telemetryObject.country ||= getCountry(); - this.#telemetryObject.language ??= getLanguage(); - this.#telemetryObject.deviceId ||= getDeviceId(); - this.#telemetryObject.instanceId ||= getInstanceId(); + this.#telemetryObject.language ??= getLanguage(this.frappe); + this.#telemetryObject.deviceId ||= getDeviceId(this.frappe); + this.#telemetryObject.instanceId ||= getInstanceId(this.frappe); this.#telemetryObject.openTime ||= new Date().valueOf(); this.#telemetryObject.timeline ??= []; this.#telemetryObject.errors ??= {}; @@ -120,7 +136,10 @@ class TelemetryManager { this.#clear(); - if (config.get(ConfigKeys.Telemetry) === TelemetrySetting.dontLogAnything) { + if ( + this.frappe.config.get(ConfigKeys.Telemetry) === + TelemetrySetting.dontLogAnything + ) { return; } navigator.sendBeacon(this.#url, data); @@ -141,7 +160,10 @@ class TelemetryManager { return; } - this.#telemetryObject.counts = await getCounts(); + this.#telemetryObject.counts = await getCounts( + this.#interestingDocs, + this.frappe + ); } async #setCreds() { @@ -149,13 +171,15 @@ class TelemetryManager { return; } - const { url, token } = await getCreds(); + const { url, token } = await this.frappe.auth.getTelemetryCreds(); this.#url = url; this.#token = token; } #getCanLog(): boolean { - const telemetrySetting = config.get(ConfigKeys.Telemetry) as string; + const telemetrySetting = this.frappe.config.get( + ConfigKeys.Telemetry + ) as string; return telemetrySetting === TelemetrySetting.allow; } @@ -170,5 +194,3 @@ class TelemetryManager { delete this.#telemetryObject.country; } } - -export default new TelemetryManager(); diff --git a/src/telemetry/types.ts b/frappe/telemetry/types.ts similarity index 83% rename from src/telemetry/types.ts rename to frappe/telemetry/types.ts index 804232e0..16eded28 100644 --- a/src/telemetry/types.ts +++ b/frappe/telemetry/types.ts @@ -1,5 +1,3 @@ -import { DoctypeName } from 'models/types'; - export type AppVersion = string; export type UniqueId = string; export type Timestamp = number; @@ -11,9 +9,7 @@ export interface InteractionEvent { more?: Record; } -export type Count = Partial<{ - [key in DoctypeName]: number; -}>; +export type Count = Record; export type Platform = 'Windows' | 'Mac' | 'Linux'; export interface Telemetry { @@ -46,3 +42,9 @@ export enum NounEnum { } export type Noun = string | NounEnum; + +export enum TelemetrySetting { + allow = 'allow', + dontLogUsage = 'dontLogUsage', + dontLogAnything = 'dontLogAnything', +} diff --git a/frappe/tests/helpers.ts b/frappe/tests/helpers.ts new file mode 100644 index 00000000..d91f9fbd --- /dev/null +++ b/frappe/tests/helpers.ts @@ -0,0 +1,7 @@ +import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types'; + +export class DummyAuthDemux extends AuthDemuxBase { + async getTelemetryCreds(): Promise { + return { url: '', token: '' }; + } +} diff --git a/frappe/tests/testFrappe.spec.ts b/frappe/tests/testFrappe.spec.ts index 1b3595b1..156035a2 100644 --- a/frappe/tests/testFrappe.spec.ts +++ b/frappe/tests/testFrappe.spec.ts @@ -1,10 +1,18 @@ import * as assert from 'assert'; import 'mocha'; +import models, { getRegionalModels } from 'models'; +import { getSchemas } from 'schemas'; import { Frappe } from '..'; import { DatabaseManager } from '../../backend/database/manager'; +import { DummyAuthDemux } from './helpers'; describe('Frappe', function () { - const frappe = new Frappe(DatabaseManager); + const frappe = new Frappe({ + DatabaseDemux: DatabaseManager, + AuthDemux: DummyAuthDemux, + isTest: true, + isElectron: false, + }); specify('Init', async function () { assert.strictEqual( @@ -12,7 +20,7 @@ describe('Frappe', function () { 0, 'zero schemas one' ); - await frappe.initializeAndRegister(); + assert.strictEqual( Object.keys(frappe.schemaMap).length, 0, @@ -20,7 +28,7 @@ describe('Frappe', function () { ); await frappe.db.createNewDatabase(':memory:', 'in'); - await frappe.initializeAndRegister({}, {}, true); + await frappe.initializeAndRegister({}, {}); assert.strictEqual( Object.keys(frappe.schemaMap).length > 0, true, @@ -29,3 +37,28 @@ describe('Frappe', function () { await frappe.db.close(); }); }); + +describe('Frappe', function () { + const countryCode = 'in'; + let frappe: Frappe; + const schemas = getSchemas(countryCode); + this.beforeEach(async function () { + frappe = new Frappe({ + DatabaseDemux: DatabaseManager, + isTest: true, + isElectron: false, + }); + + const regionalModels = await getRegionalModels(countryCode); + await frappe.db.createNewDatabase(':memory:', countryCode); + await frappe.initializeAndRegister(models, regionalModels); + }); + + this.afterEach(async function () { + await frappe.close(); + }); + + specify('temp', async function () { + frappe.db.schemaMap; + }); +}); diff --git a/frappe/utils/consts.ts b/frappe/utils/consts.ts index c9ccba20..1f35baf4 100644 --- a/frappe/utils/consts.ts +++ b/frappe/utils/consts.ts @@ -5,14 +5,4 @@ export const DEFAULT_LOCALE = 'en-IN'; export const DEFAULT_COUNTRY_CODE = 'in'; export const DEFAULT_CURRENCY = 'INR'; export const DEFAULT_LANGUAGE = 'English'; -export const DEFAULT_NUMBER_SERIES = { - SalesInvoice: 'SINV-', - PurchaseInvoice: 'PINV-', - Payment: 'PAY-', - JournalEntry: 'JV-', - Quotation: 'QTN-', - SalesOrder: 'SO-', - Fulfillment: 'OF-', - PurchaseOrder: 'PO-', - PurchaseReceipt: 'PREC-', -}; +export const DEFAULT_SERIES_START = 1001; \ No newline at end of file diff --git a/frappe/utils/format.ts b/frappe/utils/format.ts index 2a324647..6fefcfc7 100644 --- a/frappe/utils/format.ts +++ b/frappe/utils/format.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import { DocValue } from 'frappe/core/types'; import Doc from 'frappe/model/doc'; import { DateTime } from 'luxon'; @@ -14,8 +14,9 @@ import { export function format( value: DocValue, - df?: string | Field, - doc?: Doc + df: string | Field | null, + doc: Doc | null, + frappe: Frappe ): string { if (!df) { return String(value); @@ -24,11 +25,11 @@ export function format( const field: Field = getField(df); if (field.fieldtype === FieldTypeEnum.Currency) { - return formatCurrency(value, field, doc); + return formatCurrency(value, field, doc, frappe); } if (field.fieldtype === FieldTypeEnum.Date) { - return formatDate(value); + return formatDate(value, frappe); } if (field.fieldtype === FieldTypeEnum.Check) { @@ -42,7 +43,7 @@ export function format( return String(value); } -function formatDate(value: DocValue): string { +function formatDate(value: DocValue, frappe: Frappe): string { const dateFormat = (frappe.singles.SystemSettings?.dateFormat as string) ?? DEFAULT_DATE_FORMAT; @@ -64,12 +65,17 @@ function formatDate(value: DocValue): string { return formattedDate; } -function formatCurrency(value: DocValue, field: Field, doc?: Doc): string { - const currency = getCurrency(field, doc); +function formatCurrency( + value: DocValue, + field: Field, + doc: Doc | null, + frappe: Frappe +): string { + const currency = getCurrency(field, doc, frappe); let valueString; try { - valueString = formatNumber(value); + valueString = formatNumber(value, frappe); } catch (err) { (err as Error).message += ` value: '${value}', type: ${typeof value}`; throw err; @@ -83,8 +89,8 @@ function formatCurrency(value: DocValue, field: Field, doc?: Doc): string { return valueString; } -function formatNumber(value: DocValue): string { - const numberFormatter = getNumberFormatter(); +function formatNumber(value: DocValue, frappe: Frappe): string { + const numberFormatter = getNumberFormatter(frappe); if (typeof value === 'number') { return numberFormatter.format(value); } @@ -106,7 +112,7 @@ function formatNumber(value: DocValue): string { return formattedNumber; } -function getNumberFormatter() { +function getNumberFormatter(frappe: Frappe) { if (frappe.currencyFormatter) { return frappe.currencyFormatter; } @@ -123,7 +129,7 @@ function getNumberFormatter() { })); } -function getCurrency(field: Field, doc?: Doc): string { +function getCurrency(field: Field, doc: Doc | null, frappe: Frappe): string { if (doc && doc.getCurrencies[field.fieldname]) { return doc.getCurrencies[field.fieldname](); } diff --git a/models/README.md b/models/README.md index 39250989..f7bc95b1 100644 --- a/models/README.md +++ b/models/README.md @@ -36,6 +36,9 @@ 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. +It should also not import the `frappe` object (singleton) from `src`, where ever +frappe is required in models it should be passed to it. + _Note: Frontend specific code can be imported but they should be done so, only using dynamic imports i.e. `await import('...')`._ diff --git a/models/baseModels/Account/Account.ts b/models/baseModels/Account/Account.ts index 109e11d8..49fb9366 100644 --- a/models/baseModels/Account/Account.ts +++ b/models/baseModels/Account/Account.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; import { FiltersMap, @@ -6,36 +6,45 @@ import { TreeViewSettings, } from 'frappe/model/types'; import { QueryFilter } from 'utils/db/types'; +import { AccountRootType, AccountType } from './types'; export class Account extends Doc { + rootType?: AccountRootType; + accountType?: AccountType; + parentAccount?: string; + async beforeInsert() { if (this.accountType || !this.parentAccount) { return; } - const account = await frappe.db.get( + const account = await this.frappe.db.get( 'Account', this.parentAccount as string ); - this.accountType = account.accountType as string; + this.accountType = account.accountType as AccountType; } - static listSettings: ListViewSettings = { - columns: ['name', 'parentAccount', 'rootType'], - }; + static getListViewSettings(): ListViewSettings { + return { + columns: ['name', 'parentAccount', 'rootType'], + }; + } - static treeSettings: TreeViewSettings = { - parentField: 'parentAccount', - async getRootLabel(): Promise { - const accountingSettings = await frappe.doc.getSingle( - 'AccountingSettings' - ); - return accountingSettings.companyName as string; - }, - }; + static getTreeSettings(frappe: Frappe): void | TreeViewSettings { + return { + parentField: 'parentAccount', + async getRootLabel(): Promise { + const accountingSettings = await frappe.doc.getSingle( + 'AccountingSettings' + ); + return accountingSettings.companyName as string; + }, + }; + } static filters: FiltersMap = { - parentAccount: (doc: Doc) => { + parentAccount: (doc: Account) => { const filter: QueryFilter = { isGroup: true, }; diff --git a/models/baseModels/Account/types.ts b/models/baseModels/Account/types.ts new file mode 100644 index 00000000..8b9c4b33 --- /dev/null +++ b/models/baseModels/Account/types.ts @@ -0,0 +1,27 @@ +export type AccountType = + | 'Accumulated Depreciation' + | 'Bank' + | 'Cash' + | 'Chargeable' + | 'Cost of Goods Sold' + | 'Depreciation' + | 'Equity' + | 'Expense Account' + | 'Expenses Included In Valuation' + | 'Fixed Asset' + | 'Income Account' + | 'Payable' + | 'Receivable' + | 'Round Off' + | 'Stock' + | 'Stock Adjustment' + | 'Stock Received But Not Billed' + | 'Tax' + | 'Temporary'; + +export type AccountRootType = + | 'Asset' + | 'Liability' + | 'Equity' + | 'Income' + | 'Expense'; diff --git a/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts b/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts index b14e36e0..00851407 100644 --- a/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts +++ b/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts @@ -2,7 +2,9 @@ import Doc from 'frappe/model/doc'; import { ListViewSettings } from 'frappe/model/types'; export class AccountingLedgerEntry extends Doc { - static listSettings: ListViewSettings = { - columns: ['account', 'party', 'debit', 'credit', 'balance'], - }; + static getListViewSettings(): ListViewSettings { + return { + columns: ['account', 'party', 'debit', 'credit', 'balance'], + }; + } } diff --git a/models/baseModels/Address/Address.ts b/models/baseModels/Address/Address.ts index 2acef2bf..6c26d116 100644 --- a/models/baseModels/Address/Address.ts +++ b/models/baseModels/Address/Address.ts @@ -1,4 +1,3 @@ -import frappe from 'frappe'; import Doc from 'frappe/model/doc'; import { EmptyMessageMap, FormulaMap, ListsMap } from 'frappe/model/types'; import { stateCodeMap } from 'regional/in'; @@ -37,7 +36,7 @@ export class Address extends Doc { }; static emptyMessages: EmptyMessageMap = { - state: (doc: Doc) => { + state: (doc: Doc, frappe) => { if (doc.country) { return frappe.t`Enter State`; } diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index 3169b776..30ef65a7 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -1,5 +1,4 @@ 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'; @@ -28,7 +27,7 @@ export abstract class Invoice extends Doc { } async getPayments() { - const payments = await frappe.db.getAll('PaymentFor', { + const payments = await this.frappe.db.getAll('PaymentFor', { fields: ['parent'], filters: { referenceName: this.name! }, orderBy: 'name', @@ -56,12 +55,12 @@ export abstract class Invoice extends Doc { await entries.post(); // update outstanding amounts - await frappe.db.update(this.schemaName, { + await this.frappe.db.update(this.schemaName, { name: this.name as string, outstandingAmount: this.baseGrandTotal!, }); - const party = (await frappe.doc.getDoc('Party', this.party!)) as Party; + const party = (await this.frappe.doc.getDoc('Party', this.party!)) as Party; await party.updateOutstandingAmount(); } @@ -69,7 +68,7 @@ export abstract class Invoice extends Doc { const paymentRefList = await this.getPayments(); for (const paymentFor of paymentRefList) { const paymentReference = paymentFor.parent; - const payment = (await frappe.doc.getDoc( + const payment = (await this.frappe.doc.getDoc( 'Payment', paymentReference as string )) as Payment; @@ -80,7 +79,7 @@ export abstract class Invoice extends Doc { } // To set the payment status as unsubmitted. - await frappe.db.update('Payment', { + await this.frappe.db.update('Payment', { name: paymentReference, submitted: false, cancelled: true, @@ -93,7 +92,9 @@ export abstract class Invoice extends Doc { async getExchangeRate() { if (!this.currency) return 1.0; - const accountingSettings = await frappe.doc.getSingle('AccountingSettings'); + const accountingSettings = await this.frappe.doc.getSingle( + 'AccountingSettings' + ); const companyCurrency = accountingSettings.currency; if (this.currency === companyCurrency) { return 1.0; @@ -129,8 +130,8 @@ export abstract class Invoice extends Doc { taxes[account] = taxes[account] || { account, rate, - amount: frappe.pesa(0), - baseAmount: frappe.pesa(0), + amount: this.frappe.pesa(0), + baseAmount: this.frappe.pesa(0), }; const amount = (row.amount as Money).mul(rate).div(100); @@ -149,7 +150,7 @@ export abstract class Invoice extends Doc { async getTax(tax: string) { if (!this._taxes![tax]) { - this._taxes[tax] = await frappe.doc.getDoc('Tax', tax); + this._taxes[tax] = await this.frappe.doc.getDoc('Tax', tax); } return this._taxes[tax]; @@ -166,7 +167,7 @@ export abstract class Invoice extends Doc { this.getFrom('Party', this.party!, 'defaultAccount') as string, currency: async () => (this.getFrom('Party', this.party!, 'currency') as string) || - (frappe.singles.AccountingSettings!.currency as string), + (this.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!), diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index d5fbe68b..ceee496f 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -1,4 +1,3 @@ -import frappe from 'frappe'; import { DocValue } from 'frappe/core/types'; import Doc from 'frappe/model/doc'; import { @@ -7,6 +6,7 @@ import { FormulaMap, ValidationMap, } from 'frappe/model/types'; +import { ValidationError } from 'frappe/utils/errors'; import Money from 'pesa/dist/types/src/money'; import { Invoice } from '../Invoice/Invoice'; @@ -32,7 +32,7 @@ export abstract class InvoiceItem extends Doc { 'Item', this.item as string, 'rate' - )) || frappe.pesa(0)) as Money; + )) || this.frappe.pesa(0)) as Money; return baseRate.div(this.exchangeRate!); }, @@ -73,8 +73,8 @@ export abstract class InvoiceItem extends Doc { return; } - throw new frappe.errors.ValidationError( - frappe.t`Rate (${frappe.format( + throw new ValidationError( + this.frappe.t`Rate (${this.frappe.format( value, 'Currency' )}) cannot be less zero.` diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index 3bef01d7..02d899f0 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import { DocValue } from 'frappe/core/types'; import Doc from 'frappe/model/doc'; import { @@ -9,6 +9,7 @@ import { ListViewSettings, ValidationMap, } from 'frappe/model/types'; +import { ValidationError } from 'frappe/utils/errors'; import Money from 'pesa/dist/types/src/money'; export class Item extends Doc { @@ -19,11 +20,11 @@ export class Item extends Doc { accountName = 'Sales'; } - const accountExists = await frappe.db.exists('Account', accountName); + const accountExists = await this.frappe.db.exists('Account', accountName); return accountExists ? accountName : ''; }, expenseAccount: async () => { - const cogs = await frappe.db.getAllRaw('Account', { + const cogs = await this.frappe.db.getAllRaw('Account', { filters: { accountType: 'Cost of Goods Sold', }, @@ -56,43 +57,45 @@ export class Item extends Doc { validations: ValidationMap = { rate: async (value: DocValue) => { if ((value as Money).isNegative()) { - throw new frappe.errors.ValidationError( - frappe.t`Rate can't be negative.` - ); + throw new ValidationError(this.frappe.t`Rate can't be negative.`); } }, }; - static actions: Action[] = [ - { - label: frappe.t`New Invoice`, - condition: (doc) => !doc.isNew, - action: async (doc, router) => { - const invoice = await frappe.doc.getEmptyDoc('SalesInvoice'); - invoice.append('items', { - item: doc.name as string, - rate: doc.rate as Money, - tax: doc.tax as string, - }); - router.push(`/edit/SalesInvoice/${invoice.name}`); + static getActions(frappe: Frappe): Action[] { + return [ + { + label: frappe.t`New Invoice`, + condition: (doc) => !doc.isNew, + action: async (doc, router) => { + const invoice = await frappe.doc.getEmptyDoc('SalesInvoice'); + invoice.append('items', { + item: doc.name as string, + rate: doc.rate as Money, + tax: doc.tax as string, + }); + router.push(`/edit/SalesInvoice/${invoice.name}`); + }, }, - }, - { - label: frappe.t`New Bill`, - condition: (doc) => !doc.isNew, - action: async (doc, router) => { - const invoice = await frappe.doc.getEmptyDoc('PurchaseInvoice'); - invoice.append('items', { - item: doc.name as string, - rate: doc.rate as Money, - tax: doc.tax as string, - }); - router.push(`/edit/PurchaseInvoice/${invoice.name}`); + { + label: frappe.t`New Bill`, + condition: (doc) => !doc.isNew, + action: async (doc, router) => { + const invoice = await frappe.doc.getEmptyDoc('PurchaseInvoice'); + invoice.append('items', { + item: doc.name as string, + rate: doc.rate as Money, + tax: doc.tax as string, + }); + router.push(`/edit/PurchaseInvoice/${invoice.name}`); + }, }, - }, - ]; + ]; + } - listSettings: ListViewSettings = { - columns: ['name', 'unit', 'tax', 'rate'], - }; + static getListViewSettings(): ListViewSettings { + return { + columns: ['name', 'unit', 'tax', 'rate'], + }; + } } diff --git a/models/baseModels/JournalEntry/JournalEntry.ts b/models/baseModels/JournalEntry/JournalEntry.ts index f0f38c25..be27b387 100644 --- a/models/baseModels/JournalEntry/JournalEntry.ts +++ b/models/baseModels/JournalEntry/JournalEntry.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; import { Action, @@ -15,7 +15,7 @@ export class JournalEntry extends Doc { accounts: Doc[] = []; getPosting() { - const entries = new LedgerPosting({ reference: this }); + const entries = new LedgerPosting({ reference: this }, this.frappe); for (const row of this.accounts) { const debit = row.debit as Money; @@ -32,11 +32,11 @@ export class JournalEntry extends Doc { return entries; } - beforeUpdate() { + async beforeUpdate() { this.getPosting().validateEntries(); } - beforeInsert() { + async beforeInsert() { this.getPosting().validateEntries(); } @@ -56,44 +56,48 @@ export class JournalEntry extends Doc { numberSeries: () => ({ referenceType: 'JournalEntry' }), }; - static actions: Action[] = [getLedgerLinkAction()]; + static getActions(frappe: Frappe): Action[] { + return [getLedgerLinkAction(frappe)]; + } - static listSettings: ListViewSettings = { - formRoute: (name) => `/edit/JournalEntry/${name}`, - columns: [ - 'date', - { - label: frappe.t`Status`, - fieldtype: 'Select', - size: 'small', - render(doc) { - let status = 'Draft'; - let color = 'gray'; - if (doc.submitted) { - color = 'green'; - status = 'Submitted'; - } + static getListViewSettings(frappe: Frappe): ListViewSettings { + return { + formRoute: (name) => `/edit/JournalEntry/${name}`, + columns: [ + 'date', + { + label: frappe.t`Status`, + fieldtype: 'Select', + size: 'small', + render(doc) { + let status = 'Draft'; + let color = 'gray'; + if (doc.submitted) { + color = 'green'; + status = 'Submitted'; + } - if (doc.cancelled) { - color = 'red'; - status = 'Cancelled'; - } + if (doc.cancelled) { + color = 'red'; + status = 'Cancelled'; + } - return { - template: `${status}`, - }; + return { + template: `${status}`, + }; + }, }, - }, - { - label: frappe.t`Entry ID`, - fieldtype: 'Data', - fieldname: 'name', - getValue(doc) { - return doc.name as string; + { + label: frappe.t`Entry ID`, + fieldtype: 'Data', + fieldname: 'name', + getValue(doc) { + return doc.name as string; + }, }, - }, - 'entryType', - 'referenceNumber', - ], - }; + 'entryType', + 'referenceNumber', + ], + }; + } } diff --git a/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts b/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts index 99a5fe76..dd947885 100644 --- a/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts +++ b/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts @@ -1,4 +1,3 @@ -import frappe from 'frappe'; import Doc from 'frappe/model/doc'; import { FiltersMap, FormulaMap } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; @@ -9,7 +8,7 @@ export class JournalEntryAccount extends Doc { const otherTypeValue = this.get(otherType) as Money; if (!otherTypeValue.isZero()) { - return frappe.pesa(0); + return this.frappe.pesa(0); } const totalType = this.parentdoc!.getSum('accounts', type, false) as Money; diff --git a/models/baseModels/Party/Party.ts b/models/baseModels/Party/Party.ts index ddec170f..2c89d3c2 100644 --- a/models/baseModels/Party/Party.ts +++ b/models/baseModels/Party/Party.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; import { Action, @@ -29,15 +29,17 @@ export class Party extends Doc { schemaName: 'SalesInvoice' | 'PurchaseInvoice' ) { const outstandingAmounts = ( - await frappe.db.getAllRaw(schemaName, { + await this.frappe.db.getAllRaw(schemaName, { fields: ['outstandingAmount', 'party'], filters: { submitted: true }, }) ).filter(({ party }) => party === this.name); const totalOutstanding = outstandingAmounts - .map(({ outstandingAmount }) => frappe.pesa(outstandingAmount as number)) - .reduce((a, b) => a.add(b), frappe.pesa(0)); + .map(({ outstandingAmount }) => + this.frappe.pesa(outstandingAmount as number) + ) + .reduce((a, b) => a.add(b), this.frappe.pesa(0)); await this.set('outstandingAmount', totalOutstanding); await this.update(); @@ -55,10 +57,11 @@ export class Party extends Doc { accountName = 'Creditors'; } - const accountExists = await frappe.db.exists('Account', accountName); + const accountExists = await this.frappe.db.exists('Account', accountName); return accountExists ? accountName : ''; }, - currency: async () => frappe.singles.AccountingSettings!.currency as string, + currency: async () => + this.frappe.singles.AccountingSettings!.currency as string, addressDisplay: async () => { const address = this.address as string | undefined; if (address) { @@ -85,80 +88,84 @@ export class Party extends Doc { }, }; - static listSettings: ListViewSettings = { - columns: ['name', 'phone', 'outstandingAmount'], - }; + static getListViewSettings(): ListViewSettings { + return { + columns: ['name', 'phone', 'outstandingAmount'], + }; + } - static actions: Action[] = [ - { - label: frappe.t`Create Bill`, - condition: (doc: Doc) => - !doc.isNew && (doc.role as PartyRole) !== 'Customer', - action: async (partyDoc, router) => { - const doc = await frappe.doc.getEmptyDoc('PurchaseInvoice'); - router.push({ - path: `/edit/PurchaseInvoice/${doc.name}`, - query: { - doctype: 'PurchaseInvoice', - values: { - // @ts-ignore - party: partyDoc.name!, + static getActions(frappe: Frappe): Action[] { + return [ + { + label: frappe.t`Create Bill`, + condition: (doc: Doc) => + !doc.isNew && (doc.role as PartyRole) !== 'Customer', + action: async (partyDoc, router) => { + const doc = await frappe.doc.getEmptyDoc('PurchaseInvoice'); + router.push({ + path: `/edit/PurchaseInvoice/${doc.name}`, + query: { + doctype: 'PurchaseInvoice', + values: { + // @ts-ignore + party: partyDoc.name!, + }, }, - }, - }); + }); + }, }, - }, - { - label: frappe.t`View Bills`, - condition: (doc: Doc) => - !doc.isNew && (doc.role as PartyRole) !== 'Customer', - action: async (partyDoc, router) => { - router.push({ - name: 'ListView', - params: { - doctype: 'PurchaseInvoice', - filters: { - // @ts-ignore - party: partyDoc.name!, + { + label: frappe.t`View Bills`, + condition: (doc: Doc) => + !doc.isNew && (doc.role as PartyRole) !== 'Customer', + action: async (partyDoc, router) => { + router.push({ + name: 'ListView', + params: { + doctype: 'PurchaseInvoice', + filters: { + // @ts-ignore + party: partyDoc.name!, + }, }, - }, - }); + }); + }, }, - }, - { - label: frappe.t`Create Invoice`, - condition: (doc: Doc) => - !doc.isNew && (doc.role as PartyRole) !== 'Supplier', - action: async (partyDoc, router) => { - const doc = await frappe.doc.getEmptyDoc('SalesInvoice'); - router.push({ - path: `/edit/SalesInvoice/${doc.name}`, - query: { - doctype: 'SalesInvoice', - values: { - // @ts-ignore - party: partyDoc.name!, + { + label: frappe.t`Create Invoice`, + condition: (doc: Doc) => + !doc.isNew && (doc.role as PartyRole) !== 'Supplier', + action: async (partyDoc, router) => { + const doc = await frappe.doc.getEmptyDoc('SalesInvoice'); + router.push({ + path: `/edit/SalesInvoice/${doc.name}`, + query: { + doctype: 'SalesInvoice', + values: { + // @ts-ignore + party: partyDoc.name!, + }, }, - }, - }); + }); + }, }, - }, - { - label: frappe.t`View Invoices`, - condition: (doc: Doc) => - !doc.isNew && (doc.role as PartyRole) !== 'Supplier', - action: async (partyDoc, router) => { - router.push({ - name: 'ListView', - params: { - doctype: 'SalesInvoice', - filters: { - // @ts-ignore - party: partyDoc.name!, + { + label: frappe.t`View Invoices`, + condition: (doc: Doc) => + !doc.isNew && (doc.role as PartyRole) !== 'Supplier', + action: async (partyDoc, router) => { + router.push({ + name: 'ListView', + params: { + doctype: 'SalesInvoice', + filters: { + // @ts-ignore + party: partyDoc.name!, + }, }, - }, - }); + }); + }, }, - }, - ]; + ]; + } } diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index 7a29bf65..fd9412f3 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -1,5 +1,5 @@ import { LedgerPosting } from 'accounting/ledgerPosting'; -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import { DocValue } from 'frappe/core/types'; import Doc from 'frappe/model/doc'; import { @@ -21,6 +21,8 @@ import { PaymentMethod, PaymentType } from './types'; export class Payment extends Doc { party?: string; + amount?: Money; + writeoff?: Money; async change({ changed }: { changed: string }) { switch (changed) { @@ -48,7 +50,10 @@ export class Payment extends Doc { } const schemaName = referenceType as string; - const doc = await frappe.doc.getDoc(schemaName, referenceName as string); + const doc = await this.frappe.doc.getDoc( + schemaName, + referenceName as string + ); let party; let paymentType: PaymentType; @@ -66,7 +71,7 @@ export class Payment extends Doc { } updateAmountOnReferenceUpdate() { - this.amount = frappe.pesa(0); + this.amount = this.frappe.pesa(0); for (const paymentReference of this.for as Doc[]) { this.amount = (this.amount as Money).add( paymentReference.amount as Money @@ -93,7 +98,7 @@ export class Payment extends Doc { if (this.paymentAccount !== this.account || !this.account) { return; } - throw new frappe.errors.ValidationError( + throw new this.frappe.errors.ValidationError( `To Account and From Account can't be the same: ${this.account}` ); } @@ -106,7 +111,7 @@ export class Payment extends Doc { const referenceAmountTotal = forReferences .map(({ amount }) => amount as Money) - .reduce((a, b) => a.add(b), frappe.pesa(0)); + .reduce((a, b) => a.add(b), this.frappe.pesa(0)); if ( (this.amount as Money) @@ -116,20 +121,20 @@ export class Payment extends Doc { return; } - const writeoff = frappe.format(this.writeoff, 'Currency'); - const payment = frappe.format(this.amount, 'Currency'); - const refAmount = frappe.format(referenceAmountTotal, 'Currency'); + const writeoff = this.frappe.format(this.writeoff!, 'Currency'); + const payment = this.frappe.format(this.amount!, 'Currency'); + const refAmount = this.frappe.format(referenceAmountTotal, 'Currency'); if ((this.writeoff as Money).gt(0)) { - throw new frappe.errors.ValidationError( - frappe.t`Amount: ${payment} and writeoff: ${writeoff} + throw new ValidationError( + this.frappe.t`Amount: ${payment} and writeoff: ${writeoff} is less than the total amount allocated to references: ${refAmount}.` ); } - throw new frappe.errors.ValidationError( - frappe.t`Amount: ${payment} is less than the total + throw new ValidationError( + this.frappe.t`Amount: ${payment} is less than the total amount allocated to references: ${refAmount}.` ); } @@ -139,9 +144,9 @@ export class Payment extends Doc { return; } - if (!frappe.singles.AccountingSettings!.writeOffAccount) { - throw new frappe.errors.ValidationError( - frappe.t`Write Off Account not set. + if (!this.frappe.singles.AccountingSettings!.writeOffAccount) { + throw new ValidationError( + this.frappe.t`Write Off Account not set. Please set Write Off Account in General Settings` ); } @@ -152,10 +157,13 @@ export class Payment extends Doc { const paymentAccount = this.paymentAccount as string; const amount = this.amount as Money; const writeoff = this.writeoff as Money; - const entries = new LedgerPosting({ - reference: this, - party: this.party!, - }); + const entries = new LedgerPosting( + { + reference: this, + party: this.party!, + }, + this.frappe + ); await entries.debit(paymentAccount as string, amount.sub(writeoff)); await entries.credit(account as string, amount.sub(writeoff)); @@ -164,11 +172,14 @@ export class Payment extends Doc { return [entries]; } - const writeoffEntry = new LedgerPosting({ - reference: this, - party: this.party!, - }); - const writeOffAccount = frappe.singles.AccountingSettings! + const writeoffEntry = new LedgerPosting( + { + reference: this, + party: this.party!, + }, + this.frappe + ); + const writeOffAccount = this.frappe.singles.AccountingSettings! .writeOffAccount as string; if (this.paymentType === 'Pay') { @@ -196,7 +207,7 @@ export class Payment extends Doc { ) { continue; } - const referenceDoc = await frappe.doc.getDoc( + const referenceDoc = await this.frappe.doc.getDoc( row.referenceType as string, row.referenceName as string ); @@ -210,26 +221,30 @@ export class Payment extends Doc { } if (amount.lte(0) || amount.gt(outstandingAmount)) { - let message = frappe.t`Payment amount: ${frappe.format( - this.amount, + let message = this.frappe.t`Payment amount: ${this.frappe.format( + this.amount!, 'Currency' - )} should be less than Outstanding amount: ${frappe.format( + )} should be less than Outstanding amount: ${this.frappe.format( outstandingAmount, 'Currency' )}.`; if (amount.lte(0)) { - const amt = frappe.format(this.amount, 'Currency'); - message = frappe.t`Payment amount: ${amt} should be greater than 0.`; + const amt = this.frappe.format(this.amount!, 'Currency'); + message = this.frappe + .t`Payment amount: ${amt} should be greater than 0.`; } - throw new frappe.errors.ValidationError(message); + throw new ValidationError(message); } else { // update outstanding amounts in invoice and party const newOutstanding = outstandingAmount.sub(amount); await referenceDoc.set('outstandingAmount', newOutstanding); await referenceDoc.update(); - const party = (await frappe.doc.getDoc('Party', this.party!)) as Party; + const party = (await this.frappe.doc.getDoc( + 'Party', + this.party! + )) as Party; await party.updateOutstandingAmount(); } @@ -254,7 +269,7 @@ export class Payment extends Doc { async updateReferenceOutstandingAmount() { await (this.for as Doc[]).forEach( async ({ amount, referenceType, referenceName }) => { - const refDoc = await frappe.doc.getDoc( + const refDoc = await this.frappe.doc.getDoc( referenceType as string, referenceName as string ); @@ -289,7 +304,7 @@ export class Payment extends Doc { amount: async (value: DocValue) => { if ((value as Money).isNegative()) { throw new ValidationError( - frappe.t`Payment amount cannot be less than zero.` + this.frappe.t`Payment amount cannot be less than zero.` ); } @@ -297,14 +312,14 @@ export class Payment extends Doc { const amount = this.getSum('for', 'amount', false); if ((value as Money).gt(amount)) { - throw new frappe.errors.ValidationError( - frappe.t`Payment amount cannot - exceed ${frappe.format(amount, 'Currency')}.` + throw new ValidationError( + this.frappe.t`Payment amount cannot + exceed ${this.frappe.format(amount, 'Currency')}.` ); } else if ((value as Money).isZero()) { - throw new frappe.errors.ValidationError( - frappe.t`Payment amount cannot - be ${frappe.format(value, 'Currency')}.` + throw new ValidationError( + this.frappe.t`Payment amount cannot + be ${this.frappe.format(value, 'Currency')}.` ); } }, @@ -353,36 +368,40 @@ export class Payment extends Doc { }, }; - static actions: Action[] = [getLedgerLinkAction()]; + static getActions(frappe: Frappe): Action[] { + return [getLedgerLinkAction(frappe)]; + } - static listSettings: ListViewSettings = { - columns: [ - 'party', - { - label: frappe.t`Status`, - fieldname: 'status', - fieldtype: 'Select', - size: 'small', - render(doc) { - let status = 'Draft'; - let color = 'gray'; - if (doc.submitted === 1) { - color = 'green'; - status = 'Submitted'; - } - if (doc.cancelled === 1) { - color = 'red'; - status = 'Cancelled'; - } + static getListViewSettings(frappe: Frappe): ListViewSettings { + return { + columns: [ + 'party', + { + label: frappe.t`Status`, + fieldname: 'status', + fieldtype: 'Select', + size: 'small', + render(doc) { + let status = 'Draft'; + let color = 'gray'; + if (doc.submitted === 1) { + color = 'green'; + status = 'Submitted'; + } + if (doc.cancelled === 1) { + color = 'red'; + status = 'Cancelled'; + } - return { - template: `${status}`, - }; + return { + template: `${status}`, + }; + }, }, - }, - 'paymentType', - 'date', - 'amount', - ], - }; + 'paymentType', + 'date', + 'amount', + ], + }; + } } diff --git a/models/baseModels/PaymentFor/PaymentFor.ts b/models/baseModels/PaymentFor/PaymentFor.ts index 608389fe..01ebc445 100644 --- a/models/baseModels/PaymentFor/PaymentFor.ts +++ b/models/baseModels/PaymentFor/PaymentFor.ts @@ -1,4 +1,3 @@ -import frappe from 'frappe'; import Doc from 'frappe/model/doc'; import { FiltersMap, FormulaMap } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; @@ -16,7 +15,7 @@ export class PaymentFor extends Doc { return outstandingAmount; } - return frappe.pesa(0); + return this.frappe.pesa(0); }, }; diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts index 615de850..1a68794b 100644 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts +++ b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts @@ -1,4 +1,5 @@ import { LedgerPosting } from 'accounting/ledgerPosting'; +import { Frappe } from 'frappe'; import { Action, ListViewSettings } from 'frappe/model/types'; import { getTransactionActions, @@ -11,10 +12,13 @@ export class PurchaseInvoice extends Invoice { items?: PurchaseInvoiceItem[]; async getPosting() { - const entries: LedgerPosting = new LedgerPosting({ - reference: this, - party: this.party, - }); + const entries: LedgerPosting = new LedgerPosting( + { + reference: this, + party: this.party, + }, + this.frappe + ); await entries.credit(this.account!, this.baseGrandTotal!); @@ -32,17 +36,21 @@ export class PurchaseInvoice extends Invoice { return entries; } - static actions: Action[] = getTransactionActions('PurchaseInvoice'); + static getActions(frappe: Frappe): Action[] { + return getTransactionActions('PurchaseInvoice', frappe); + } - static listSettings: ListViewSettings = { - formRoute: (name) => `/edit/PurchaseInvoice/${name}`, - columns: [ - 'party', - 'name', - getTransactionStatusColumn(), - 'date', - 'grandTotal', - 'outstandingAmount', - ], - }; + static getListViewSettings(frappe: Frappe): ListViewSettings { + return { + formRoute: (name) => `/edit/PurchaseInvoice/${name}`, + columns: [ + 'party', + 'name', + getTransactionStatusColumn(frappe), + 'date', + 'grandTotal', + 'outstandingAmount', + ], + }; + } } diff --git a/models/baseModels/SalesInvoice/SalesInvoice.ts b/models/baseModels/SalesInvoice/SalesInvoice.ts index bc66337a..260150ff 100644 --- a/models/baseModels/SalesInvoice/SalesInvoice.ts +++ b/models/baseModels/SalesInvoice/SalesInvoice.ts @@ -1,4 +1,5 @@ import { LedgerPosting } from 'accounting/ledgerPosting'; +import { Frappe } from 'frappe'; import { Action, ListViewSettings } from 'frappe/model/types'; import { getTransactionActions, @@ -11,10 +12,13 @@ export class SalesInvoice extends Invoice { items?: SalesInvoiceItem[]; async getPosting() { - const entries: LedgerPosting = new LedgerPosting({ - reference: this, - party: this.party, - }); + const entries: LedgerPosting = new LedgerPosting( + { + reference: this, + party: this.party, + }, + this.frappe + ); await entries.debit(this.account!, this.baseGrandTotal!); for (const item of this.items!) { @@ -30,17 +34,21 @@ export class SalesInvoice extends Invoice { return entries; } - static actions: Action[] = getTransactionActions('SalesInvoice'); + static getActions(frappe: Frappe): Action[] { + return getTransactionActions('SalesInvoice', frappe); + } - static listSettings: ListViewSettings = { - formRoute: (name) => `/edit/SalesInvoice/${name}`, - columns: [ - 'party', - 'name', - getTransactionStatusColumn(), - 'date', - 'grandTotal', - 'outstandingAmount', - ], - }; + static getListViewSettings(frappe: Frappe): ListViewSettings { + return { + formRoute: (name) => `/edit/SalesInvoice/${name}`, + columns: [ + 'party', + 'name', + getTransactionStatusColumn(frappe), + 'date', + 'grandTotal', + 'outstandingAmount', + ], + }; + } } diff --git a/models/baseModels/SetupWizard/SetupWizard.ts b/models/baseModels/SetupWizard/SetupWizard.ts index 3728b2f3..baf1ef70 100644 --- a/models/baseModels/SetupWizard/SetupWizard.ts +++ b/models/baseModels/SetupWizard/SetupWizard.ts @@ -1,4 +1,4 @@ -import frappe from 'frappe'; +import { t } from 'frappe'; import Doc from 'frappe/model/doc'; import { FormulaMap, ListsMap } from 'frappe/model/types'; import { DateTime } from 'luxon'; @@ -6,7 +6,7 @@ import countryInfo from '../../../fixtures/countryInfo.json'; export function getCOAList() { return [ - { name: frappe.t`Standard Chart of Accounts`, countryCode: '' }, + { name: t`Standard Chart of Accounts`, countryCode: '' }, { countryCode: 'ae', name: 'U.A.E - Chart of Accounts' }, { diff --git a/models/baseModels/Tax/Tax.ts b/models/baseModels/Tax/Tax.ts index 8c148252..40dad8bb 100644 --- a/models/baseModels/Tax/Tax.ts +++ b/models/baseModels/Tax/Tax.ts @@ -2,5 +2,7 @@ import Doc from 'frappe/model/doc'; import { ListViewSettings } from 'frappe/model/types'; export class Tax extends Doc { - static listSettings: ListViewSettings = { columns: ['name'] }; + static getListViewSettings(): ListViewSettings { + return { columns: ['name'] }; + } } diff --git a/models/helpers.ts b/models/helpers.ts index de48bc70..5f226b1d 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -1,12 +1,11 @@ -import { openQuickEdit } from '@/utils'; -import frappe from 'frappe'; +import { Frappe } from 'frappe'; import Doc from 'frappe/model/doc'; import { Action, ColumnConfig } from 'frappe/model/types'; import Money from 'pesa/dist/types/src/money'; import { Router } from 'vue-router'; import { InvoiceStatus } from './types'; -export function getLedgerLinkAction(): Action { +export function getLedgerLinkAction(frappe: Frappe): Action { return { label: frappe.t`Ledger Entries`, condition: (doc: Doc) => !!doc.submitted, @@ -26,7 +25,10 @@ export function getLedgerLinkAction(): Action { }; } -export function getTransactionActions(schemaName: string): Action[] { +export function getTransactionActions( + schemaName: string, + frappe: Frappe +): Action[] { return [ { label: frappe.t`Make Payment`, @@ -42,6 +44,7 @@ export function getTransactionActions(schemaName: string): Action[] { const paymentType = isSales ? 'Receive' : 'Pay'; const hideAccountField = isSales ? 'account' : 'paymentAccount'; + const { openQuickEdit } = await import('../src/utils'); await openQuickEdit({ schemaName: 'Payment', name: payment.name as string, @@ -69,11 +72,11 @@ export function getTransactionActions(schemaName: string): Action[] { router.push({ path: `/print/${doc.doctype}/${doc.name}` }); }, }, - getLedgerLinkAction(), + getLedgerLinkAction(frappe), ]; } -export function getTransactionStatusColumn(): ColumnConfig { +export function getTransactionStatusColumn(frappe: Frappe): ColumnConfig { const statusMap = { Unpaid: frappe.t`Unpaid`, Paid: frappe.t`Paid`, diff --git a/models/index.ts b/models/index.ts index f7573e4c..e129ebad 100644 --- a/models/index.ts +++ b/models/index.ts @@ -1,3 +1,4 @@ +import { ModelMap } from 'frappe/model/types'; import { Account } from './baseModels/Account/Account'; import { AccountingLedgerEntry } from './baseModels/AccountingLedgerEntry/AccountingLedgerEntry'; import { AccountingSettings } from './baseModels/AccountingSettings/AccountingSettings'; @@ -34,9 +35,11 @@ export default { SetupWizard, Tax, TaxSummary, -}; +} as ModelMap; -export async function getRegionalModels(countryCode: string) { +export async function getRegionalModels( + countryCode: string +): Promise { if (countryCode !== 'in') { return {}; } diff --git a/models/regionalModels/in/Party.ts b/models/regionalModels/in/Party.ts index 6f2aa125..ac49b067 100644 --- a/models/regionalModels/in/Party.ts +++ b/models/regionalModels/in/Party.ts @@ -3,7 +3,7 @@ import { Party as BaseParty } from 'models/baseModels/Party/Party'; import { GSTType } from './types'; export class Party extends BaseParty { - beforeInsert() { + async beforeInsert() { const gstin = this.get('gstin') as string | undefined; const gstType = this.get('gstType') as GSTType; diff --git a/models/types.ts b/models/types.ts index 6d62634a..6d7bb13a 100644 --- a/models/types.ts +++ b/models/types.ts @@ -1 +1,31 @@ export type InvoiceStatus = 'Draft' | 'Unpaid' | 'Cancelled' | 'Paid'; +export enum ModelNameEnum { + Account = 'Account', + AccountingLedgerEntry = 'AccountingLedgerEntry', + AccountingSettings = 'AccountingSettings', + Address = 'Address', + Color = 'Color', + CompanySettings = 'CompanySettings', + Currency = 'Currency', + GetStarted = 'GetStarted', + Item = 'Item', + JournalEntry = 'JournalEntry', + JournalEntryAccount = 'JournalEntryAccount', + NumberSeries = 'NumberSeries', + Party = 'Party', + Payment = 'Payment', + PaymentFor = 'PaymentFor', + PrintSettings = 'PrintSettings', + PurchaseInvoice = 'PurchaseInvoice', + PurchaseInvoiceItem = 'PurchaseInvoiceItem', + SalesInvoice = 'SalesInvoice', + SalesInvoiceItem = 'SalesInvoiceItem', + SalesInvoiceSettings = 'SalesInvoiceSettings', + SetupWizard = 'SetupWizard', + Tax = 'Tax', + TaxDetail = 'TaxDetail', + TaxSummary = 'TaxSummary', + PatchRun = 'PatchRun', + SingleValue = 'SingleValue', + SystemSettings = 'SystemSettings', +} diff --git a/schemas/app/Account.json b/schemas/app/Account.json index 75a03c30..2c59b8af 100644 --- a/schemas/app/Account.json +++ b/schemas/app/Account.json @@ -149,8 +149,5 @@ "accountType", "isGroup" ], - "keywordFields": ["name", "rootType", "accountType"], - "treeSettings": { - "parentField": "parentAccount" - } + "keywordFields": ["name", "rootType", "accountType"] } diff --git a/schemas/index.ts b/schemas/index.ts index 65e9cbea..b807bd99 100644 --- a/schemas/index.ts +++ b/schemas/index.ts @@ -67,7 +67,7 @@ function deepFreeze(schemaMap: SchemaMap) { } export function addMetaFields(schemaMap: SchemaMap): SchemaMap { - const metaSchemaMap = getMapFromList(metaSchemas, 'name'); + const metaSchemaMap = getMapFromList(cloneDeep(metaSchemas), 'name'); const base = metaSchemaMap.base; const tree = getCombined(metaSchemaMap.tree, base); @@ -115,13 +115,13 @@ function addNameField(schemaMap: SchemaMap) { } function getCoreSchemas(): SchemaMap { - const rawSchemaMap = getMapFromList(coreSchemas, 'name'); + const rawSchemaMap = getMapFromList(cloneDeep(coreSchemas), 'name'); const coreSchemaMap = getAbstractCombinedSchemas(rawSchemaMap); return cleanSchemas(coreSchemaMap); } function getAppSchemas(countryCode: string): SchemaMap { - const appSchemaMap = getMapFromList(appSchemas, 'name'); + const appSchemaMap = getMapFromList(cloneDeep(appSchemas), 'name'); const regionalSchemaMap = getRegionalSchemaMap(countryCode); const combinedSchemas = getRegionalCombinedSchemas( appSchemaMap, @@ -225,7 +225,7 @@ export function getRegionalCombinedSchemas( } function getRegionalSchemaMap(countryCode: string): SchemaStubMap { - const countrySchemas = regionalSchemas[countryCode] as + const countrySchemas = cloneDeep(regionalSchemas[countryCode]) as | SchemaStub[] | undefined; if (countrySchemas === undefined) { diff --git a/schemas/types.ts b/schemas/types.ts index c76ee860..c00ac1e3 100644 --- a/schemas/types.ts +++ b/schemas/types.ts @@ -68,7 +68,6 @@ export type Field = | DynamicLinkField | NumberField; -export type TreeSettings = { parentField: string }; export type Naming = 'autoincrement' | 'random' | 'numberSeries' export interface Schema { @@ -84,7 +83,6 @@ export interface Schema { isSubmittable?: boolean; // For transactional types, values considered only after submit keywordFields?: string[]; // Used to get fields that are to be used for search. quickEditFields?: string[]; // Used to get fields for the quickEditForm - treeSettings?: TreeSettings; // Used to determine root nodes inlineEditDisplayField?:string;// Display field if inline editable naming?: Naming; // Used for assigning name, default is 'random' else 'numberSeries' if present removeFields?: string[]; // Used by the builder to remove fields. diff --git a/src/config.ts b/src/config.ts deleted file mode 100644 index a5be5297..00000000 --- a/src/config.ts +++ /dev/null @@ -1,26 +0,0 @@ -import Store from 'electron-store'; -import frappe from 'frappe'; - -const config = new Store(); -export default config; - -export enum ConfigKeys { - Files = 'files', - LastSelectedFilePath = 'lastSelectedFilePath', - Language = 'language', - DeviceId = 'deviceId', - Telemetry = 'telemetry', - OpenCount = 'openCount', -} - -export enum TelemetrySetting { - allow = 'allow', - dontLogUsage = 'dontLogUsage', - dontLogAnything = 'dontLogAnything', -} - -export interface ConfigFile { - id: string; - companyName: string; - filePath: string; -} diff --git a/src/dataImport.ts b/src/dataImport.ts index ab18f3b7..f1cd86fd 100644 --- a/src/dataImport.ts +++ b/src/dataImport.ts @@ -4,8 +4,8 @@ import Doc from 'frappe/model/doc'; import { isNameAutoSet } from 'frappe/model/naming'; import { FieldType, FieldTypeEnum } from 'schemas/types'; import { parseCSV } from '../utils/csvParser'; -import telemetry from './telemetry/telemetry'; -import { Noun, Verb } from './telemetry/types'; +import telemetry from '../frappe/telemetry/telemetry'; +import { Noun, Verb } from '../frappe/telemetry/types'; export const importable = [ 'SalesInvoice', diff --git a/src/errorHandling.ts b/src/errorHandling.ts index 476ee615..a463f40e 100644 --- a/src/errorHandling.ts +++ b/src/errorHandling.ts @@ -9,9 +9,9 @@ import { } from 'frappe/utils/errors'; import { ErrorLog } from 'frappe/utils/types'; import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; -import config, { ConfigKeys, TelemetrySetting } from './config'; -import telemetry from './telemetry/telemetry'; -import { showMessageDialog, showToast } from './utils'; +import telemetry from '../frappe/telemetry/telemetry'; +import config, { ConfigKeys, TelemetrySetting } from '../utils/config'; +import { showMessageDialog, showToast } from './utils.js'; function getCanLog(): boolean { const telemetrySetting = config.get(ConfigKeys.Telemetry); diff --git a/src/initFyo.ts b/src/initFyo.ts new file mode 100644 index 00000000..917b157f --- /dev/null +++ b/src/initFyo.ts @@ -0,0 +1,3 @@ +import { Frappe } from 'frappe'; + +export const fyo = new Frappe({ isTest: false, isElectron: true }); diff --git a/src/postStart.ts b/src/postStart.ts index 0196b22f..03a67eaa 100644 --- a/src/postStart.ts +++ b/src/postStart.ts @@ -1,6 +1,7 @@ -import frappe from 'frappe'; import { createNumberSeries } from 'frappe/model/naming'; +import { DEFAULT_SERIES_START } from 'frappe/utils/consts'; import { getValueMapFromList } from 'utils'; +import { fyo } from './initFyo'; export default async function postStart() { await createDefaultNumberSeries(); @@ -9,23 +10,28 @@ export default async function postStart() { } async function createDefaultNumberSeries() { - await createNumberSeries('SINV-', 'SalesInvoice'); - await createNumberSeries('PINV-', 'PurchaseInvoice'); - await createNumberSeries('PAY-', 'Payment'); - await createNumberSeries('JV-', 'JournalEntry'); + await createNumberSeries('SINV-', 'SalesInvoice', DEFAULT_SERIES_START, fyo); + await createNumberSeries( + 'PINV-', + 'PurchaseInvoice', + DEFAULT_SERIES_START, + fyo + ); + await createNumberSeries('PAY-', 'Payment', DEFAULT_SERIES_START, fyo); + await createNumberSeries('JV-', 'JournalEntry', DEFAULT_SERIES_START, fyo); } async function setSingles() { - await frappe.doc.getSingle('AccountingSettings'); - await frappe.doc.getSingle('GetStarted'); + await fyo.doc.getSingle('AccountingSettings'); + await fyo.doc.getSingle('GetStarted'); } async function setCurrencySymbols() { - const currencies = (await frappe.db.getAll('Currency', { + const currencies = (await fyo.db.getAll('Currency', { fields: ['name', 'symbol'], })) as { name: string; symbol: string }[]; - frappe.currencySymbols = getValueMapFromList( + fyo.currencySymbols = getValueMapFromList( currencies, 'name', 'symbol' diff --git a/src/renderer/helpers.ts b/src/renderer/helpers.ts index 6dd74ec7..6cbfc2d9 100644 --- a/src/renderer/helpers.ts +++ b/src/renderer/helpers.ts @@ -1,12 +1,13 @@ -import config, { ConfigKeys } from '@/config'; +import frappe from 'frappe'; +import { ConfigKeys } from 'frappe/core/types'; export function incrementOpenCount() { - let openCount = config.get(ConfigKeys.OpenCount); + let openCount = frappe.config.get(ConfigKeys.OpenCount); if (typeof openCount !== 'number') { openCount = 1; } else { openCount += 1; } - config.set(ConfigKeys.OpenCount, openCount); + frappe.config.set(ConfigKeys.OpenCount, openCount); } diff --git a/src/renderer/registerIpcRendererListeners.ts b/src/renderer/registerIpcRendererListeners.ts index 10171ad0..69ebf2cb 100644 --- a/src/renderer/registerIpcRendererListeners.ts +++ b/src/renderer/registerIpcRendererListeners.ts @@ -1,6 +1,6 @@ import { handleError } from '@/errorHandling'; import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages'; -import telemetry from '@/telemetry/telemetry'; +import telemetry from 'frappe/telemetry/telemetry'; import { showToast } from '@/utils'; import { ipcRenderer } from 'electron'; import frappe from 'frappe'; diff --git a/src/router.js b/src/router.js index e97f6c51..d757958a 100644 --- a/src/router.js +++ b/src/router.js @@ -10,8 +10,8 @@ import QuickEditForm from '@/pages/QuickEditForm.vue'; import Report from '@/pages/Report.vue'; import Settings from '@/pages/Settings/Settings.vue'; import { createRouter, createWebHistory } from 'vue-router'; -import telemetry from './telemetry/telemetry'; -import { NounEnum, Verb } from './telemetry/types'; +import telemetry from '../frappe/telemetry/telemetry'; +import { NounEnum, Verb } from '../frappe/telemetry/types'; const routes = [ { diff --git a/src/telemetry/helpers.ts b/src/telemetry/helpers.ts deleted file mode 100644 index 5f80c425..00000000 --- a/src/telemetry/helpers.ts +++ /dev/null @@ -1,129 +0,0 @@ -import config, { ConfigFile, ConfigKeys, TelemetrySetting } from '@/config'; -import { ipcRenderer } from 'electron'; -import frappe, { t } from 'frappe'; -import { IPC_ACTIONS } from 'utils/messages'; -import { DoctypeName } from '../../models/types'; -import { Count, UniqueId } from './types'; - -export function getId(): string { - let id: string = ''; - - for (let i = 0; i < 4; i++) { - id += Math.random().toString(36).slice(2, 9); - } - - return id; -} - -export function getCountry(): string { - // @ts-ignore - return frappe.AccountingSettings?.country ?? ''; -} - -export function getLanguage(): string { - return config.get('language') as string; -} - -export async function getCounts(): Promise { - const interestingDocs = [ - DoctypeName.Payment, - DoctypeName.PaymentFor, - DoctypeName.SalesInvoice, - DoctypeName.SalesInvoiceItem, - DoctypeName.PurchaseInvoice, - DoctypeName.PurchaseInvoiceItem, - DoctypeName.JournalEntry, - DoctypeName.JournalEntryAccount, - DoctypeName.Party, - DoctypeName.Account, - DoctypeName.Tax, - ]; - - const countMap: Count = {}; - // @ts-ignore - if (frappe.db === undefined) { - return countMap; - } - - type CountResponse = { 'count(*)': number }[]; - for (const name of interestingDocs) { - const count: number = (await frappe.db.getAll(name)).length; - countMap[name] = count; - } - - return countMap; -} - -export function getDeviceId(): UniqueId { - let deviceId = config.get(ConfigKeys.DeviceId) as string | undefined; - if (deviceId === undefined) { - deviceId = getId(); - config.set(ConfigKeys.DeviceId, deviceId); - } - - return deviceId; -} - -export function getInstanceId(): UniqueId { - const files = config.get(ConfigKeys.Files) as ConfigFile[]; - - // @ts-ignore - const companyName = frappe.AccountingSettings?.companyName; - if (companyName === undefined) { - return ''; - } - - const file = files.find((f) => f.companyName === companyName); - - if (file === undefined) { - return addNewFile(companyName, files); - } - - if (file.id === undefined) { - return setInstanceId(companyName, files); - } - - return file.id; -} - -function addNewFile(companyName: string, files: ConfigFile[]): UniqueId { - const newFile: ConfigFile = { - companyName, - filePath: config.get(ConfigKeys.LastSelectedFilePath, '') as string, - id: getId(), - }; - - files.push(newFile); - config.set(ConfigKeys.Files, files); - return newFile.id; -} - -function setInstanceId(companyName: string, files: ConfigFile[]): UniqueId { - let id = ''; - for (const file of files) { - if (file.id) { - continue; - } - - file.id = getId(); - if (file.companyName === companyName) { - id = file.id; - } - } - - config.set(ConfigKeys.Files, files); - return id; -} - -export async function getCreds() { - const creds = await ipcRenderer.invoke(IPC_ACTIONS.GET_CREDS); - const url: string = creds?.telemetryUrl ?? ''; - const token: string = creds?.tokenString ?? ''; - return { url, token }; -} - -export const getTelemetryOptions = () => ({ - [TelemetrySetting.allow]: t`Allow Telemetry`, - [TelemetrySetting.dontLogUsage]: t`Don't Log Usage`, - [TelemetrySetting.dontLogAnything]: t`Don't Log Anything`, -}); diff --git a/tsconfig.json b/tsconfig.json index eb9a7256..f16aa405 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -44,6 +44,6 @@ "scripts/**/*.ts", "utils/csvParser.ts" - ], +, "utils/config.ts" ], "exclude": ["node_modules"] } diff --git a/utils/auth/types.ts b/utils/auth/types.ts new file mode 100644 index 00000000..d730aaea --- /dev/null +++ b/utils/auth/types.ts @@ -0,0 +1,5 @@ +export type TelemetryCreds = { url: string; token: string }; + +export abstract class AuthDemuxBase { + abstract getTelemetryCreds(): Promise +} diff --git a/utils/config.ts b/utils/config.ts new file mode 100644 index 00000000..f30346e7 --- /dev/null +++ b/utils/config.ts @@ -0,0 +1,4 @@ +import Store from 'electron-store'; + +const config = new Store(); +export default config;