diff --git a/.eslintrc.js b/.eslintrc.js index 2b09c43c4..7b221393e 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,23 +1,62 @@ module.exports = { root: true, - env: { node: true, + browser: true, + es2018: true, }, - rules: { - 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off', - 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off', + 'no-console': 'warn', + 'no-debugger': 'warn', 'arrow-body-style': 'off', - 'prefer-arrow-callback': 'off', + 'prettier/prettier': 'warn', + 'prefer-arrow-callback': 'warn', 'vue/no-mutating-props': 'off', 'vue/multi-word-component-names': 'off', 'vue/no-useless-template-attributes': 'off', + 'vue/one-component-per-file': 'off', + 'vue/no-reserved-component-names': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-misused-promises': 'warn', }, - + parser: 'vue-eslint-parser', parserOptions: { parser: '@typescript-eslint/parser', + project: true, + tsconfigRootDir: __dirname, + sourceType: 'module', + extraFileExtensions: ['.vue'], }, - - extends: ['plugin:vue/vue3-essential', '@vue/typescript'], + plugins: ['@typescript-eslint', 'prettier'], + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'plugin:@typescript-eslint/recommended-requiring-type-checking', + 'plugin:prettier/recommended', + ], + overrides: [ + { + files: ['*.vue'], + rules: { + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + }, + }, + ], + ignorePatterns: [ + '*.mjs', + '.eslintrc.js', + 'tailwind.config.js', + 'node_modules', + 'dist_electron', + '*.spec.ts', + 'vite.config.ts', + 'postcss.config.js', + 'src/components/**/*.vue', // Incrementally fix these + ], }; diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d49750f27..844e77275 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -13,7 +13,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16.13.1' + node-version: '16.14.0' - name: Set yarn version run: yarn set version 1.22.18 @@ -28,4 +28,4 @@ jobs: env: CSC_IDENTITY_AUTO_DISCOVERY: false APPLE_NOTARIZE: 0 - run: yarn electron:build -mw --publish never + run: yarn build -mw --publish never diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..32f0b7fd2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,32 @@ +name: Lint + +on: + push: + branches: [$default-branch] + paths-ignore: + - '**.md' + pull_request: + paths-ignore: + - '**.md' + workflow_dispatch: + +jobs: + setup_and_lint: + runs-on: macos-latest + steps: + - name: Setup node + uses: actions/setup-node@v2 + with: + node-version: '16.14.0' + + - name: Set yarn version + run: yarn set version 1.22.18 + + - name: Checkout Books + uses: actions/checkout@v2 + + - name: Install Dependencies + run: yarn + + - name: Lint + run: yarn lint \ No newline at end of file diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index b1676bdf4..4f1a5ec2e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,7 +9,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16.13.1' + node-version: '16.14.0' - name: Checkout Books uses: actions/checkout@v2 @@ -42,7 +42,7 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | yarn set version 1.22.18 - yarn electron:build --mac --publish always + yarn build --mac --publish always - name: Tar files run: tar -cvf dist-macOS.tar dist_electron @@ -59,7 +59,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16.13.1' + node-version: '16.14.0' - name: Checkout Books uses: actions/checkout@v2 @@ -86,7 +86,7 @@ jobs: GH_TOKEN: ${{ secrets.GH_TOKEN }} run: | yarn set version 1.22.18 - yarn electron:build --linux --publish always + yarn build --linux --publish always - name: Tar files run: tar -cvf dist-linux.tar dist_electron @@ -107,7 +107,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16.13.1' + node-version: '16.14.0' - name: Checkout Books uses: actions/checkout@v2 @@ -132,7 +132,7 @@ jobs: WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} GH_TOKEN: ${{ secrets.GH_TOKEN }} - run: yarn electron:build --win --publish always + run: yarn build --win --publish always - name: Tar files run: tar -cvf dist-windows.tar dist_electron diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 79dc254d7..dc89a048d 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: - name: Setup node uses: actions/setup-node@v2 with: - node-version: '16.13.1' + node-version: '16.14.0' - name: Set yarn version run: yarn set version 1.22.18 diff --git a/README.md b/README.md index 64ba8f392..ad494e5a4 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ a local SQLite file as the database. ### Pre-requisites To get the dev environment up and running you need to first set up Node.js version -16.13.1 and npm. For this, we suggest using +16.14.0 and npm. For this, we suggest using [nvm](https://github.com/nvm-sh/nvm#installing-and-updating). Next, you will need to install [yarn](https://classic.yarnpkg.com/lang/en/docs/install/#mac-stable). @@ -87,25 +87,41 @@ To run Frappe Books in development mode (with hot reload, etc): ```bash # start the electron app -yarn electron:serve +yarn dev ``` +**Note: First Boot** + +When you run `yarn dev` electron will run immediately but the UI will take a +couple of seconds to render this because of how dev mode works. Each file is +individually served by the dev server. And there are many files that have to be +sent. + + +**Note: Debug Electron Main Process** + +When in dev mode electron runs with the `--inspect` flag which allows an +external debugger to connect to port 5858. You can use chrome for this by +visiting `chrome://inspect` while Frappe Books is running in dev mode. + +See more [here](https://www.electronjs.org/docs/latest/tutorial/debugging-main-process#external-debuggers). + #### Build To build Frappe Books and create an installer: ```bash # start the electron app -yarn electron:build +yarn build ``` -**Note** +**Note: Build Target** By default the above command will build for your computer's operating system and architecture. To build for other environments (example: for linux from a windows computer) check the _Building_ section at [electron.build/cli](https://www.electron.build/cli). -So to build for linux you could use the `--linux` flag like so: `yarn electron:build --linux`. +So to build for linux you could use the `--linux` flag like so: `yarn build --linux`. ## Contributions and Community diff --git a/babel.config.js b/babel.config.js deleted file mode 100644 index 916db641e..000000000 --- a/babel.config.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - presets: ['@vue/cli-plugin-babel/preset'] -}; diff --git a/backend/database/bespoke.ts b/backend/database/bespoke.ts index 67d56329d..42fb71355 100644 --- a/backend/database/bespoke.ts +++ b/backend/database/bespoke.ts @@ -1,3 +1,10 @@ +import { + Cashflow, + IncomeExpense, + TopExpenses, + TotalCreditAndDebit, + TotalOutstanding, +} from 'utils/db/types'; import { ModelNameEnum } from '../../models/types'; import DatabaseCore from './core'; import { BespokeFunction } from './types'; @@ -43,7 +50,7 @@ export class BespokeQueries { .groupBy('account') .orderBy('total', 'desc') .limit(5); - return topExpenses; + return topExpenses as TopExpenses; } static async getTotalOutstanding( @@ -52,13 +59,13 @@ export class BespokeQueries { fromDate: string, toDate: string ) { - return await db.knex!(schemaName) + return (await db.knex!(schemaName) .sum({ total: 'baseGrandTotal' }) .sum({ outstanding: 'outstandingAmount' }) .where('submitted', true) .where('cancelled', false) .whereBetween('date', [fromDate, toDate]) - .first(); + .first()) as TotalOutstanding; } static async getCashflow(db: DatabaseCore, fromDate: string, toDate: string) { @@ -67,7 +74,7 @@ export class BespokeQueries { .where('accountType', 'in', ['Cash', 'Bank']) .andWhere('isGroup', false); const dateAsMonthYear = db.knex!.raw(`strftime('%Y-%m', ??)`, 'date'); - return await db.knex!('AccountingLedgerEntry') + return (await db.knex!('AccountingLedgerEntry') .where('reverted', false) .sum({ inflow: 'debit', @@ -78,7 +85,7 @@ export class BespokeQueries { }) .where('account', 'in', cashAndBankAccounts) .whereBetween('date', [fromDate, toDate]) - .groupBy(dateAsMonthYear); + .groupBy(dateAsMonthYear)) as Cashflow; } static async getIncomeAndExpenses( @@ -86,7 +93,7 @@ export class BespokeQueries { fromDate: string, toDate: string ) { - const income = await db.knex!.raw( + const income = (await db.knex!.raw( ` select sum(cast(credit as real) - cast(debit as real)) as balance, strftime('%Y-%m', date) as yearmonth from AccountingLedgerEntry @@ -100,9 +107,9 @@ export class BespokeQueries { ) group by yearmonth`, [fromDate, toDate] - ); + )) as IncomeExpense['income']; - const expense = await db.knex!.raw( + const expense = (await db.knex!.raw( ` select sum(cast(debit as real) - cast(credit as real)) as balance, strftime('%Y-%m', date) as yearmonth from AccountingLedgerEntry @@ -116,20 +123,20 @@ export class BespokeQueries { ) group by yearmonth`, [fromDate, toDate] - ); + )) as IncomeExpense['expense']; return { income, expense }; } static async getTotalCreditAndDebit(db: DatabaseCore) { - return await db.knex!.raw(` + return (await db.knex!.raw(` select account, sum(cast(credit as real)) as totalCredit, sum(cast(debit as real)) as totalDebit from AccountingLedgerEntry group by account - `); + `)) as unknown as TotalCreditAndDebit; } static async getStockQuantity( @@ -141,6 +148,7 @@ export class BespokeQueries { batch?: string, serialNumbers?: string[] ): Promise { + /* eslint-disable @typescript-eslint/no-floating-promises */ const query = db.knex!(ModelNameEnum.StockLedgerEntry) .sum('quantity') .where('item', item); diff --git a/backend/database/core.ts b/backend/database/core.ts index 5d1dd6c15..9c1bf9f7d 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -1,9 +1,4 @@ -import { - CannotCommitError, - getDbError, - NotFoundError, - ValueError, -} from 'fyo/utils/errors'; +import { getDbError, NotFoundError, ValueError } from 'fyo/utils/errors'; import { knex, Knex } from 'knex'; import { Field, @@ -73,16 +68,16 @@ export default class DatabaseCore extends DatabaseBase { let query: { value: string }[] = []; try { - query = await db.knex!('SingleValue').where({ + query = (await db.knex!('SingleValue').where({ fieldname: 'countryCode', parent: 'SystemSettings', - }); + })) as { value: string }[]; } catch { // Database not inialized and no countryCode passed } if (query.length > 0) { - countryCode = query[0].value as string; + countryCode = query[0].value; } await db.close(); @@ -95,9 +90,6 @@ export default class DatabaseCore extends DatabaseBase { async connect() { this.knex = knex(this.connectionParams); - this.knex.on('query-error', (error) => { - error.type = getDbError(error); - }); await this.knex.raw('PRAGMA foreign_keys=ON'); } @@ -105,22 +97,6 @@ export default class DatabaseCore extends DatabaseBase { await this.knex!.destroy(); } - async commit() { - /** - * this auto commits, commit is not required - * will later wrap the outermost functions in - * transactions. - */ - try { - // await this.knex!.raw('commit'); - } catch (err) { - const type = getDbError(err as Error); - if (type !== CannotCommitError) { - throw err; - } - } - } - async migrate() { for (const schemaName in this.schemaMap) { const schema = this.schemaMap[schemaName] as Schema; @@ -135,7 +111,6 @@ export default class DatabaseCore extends DatabaseBase { } } - await this.commit(); await this.#initializeSingles(); } @@ -149,6 +124,7 @@ export default class DatabaseCore extends DatabaseBase { try { const qb = this.knex!(schemaName); if (name !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises qb.where({ name }); } row = await qb.limit(1); @@ -178,7 +154,7 @@ export default class DatabaseCore extends DatabaseBase { async get( schemaName: string, - name: string = '', + name = '', fields?: string | string[] ): Promise { const schema = this.schemaMap[schemaName] as Schema; @@ -316,7 +292,6 @@ export default class DatabaseCore extends DatabaseBase { await this.knex!(schemaName) .update({ name: newName }) .where('name', oldName); - await this.commit(); } async update(schemaName: string, fieldValueMap: FieldValueMap) { @@ -422,6 +397,7 @@ export default class DatabaseCore extends DatabaseBase { filters: QueryFilter, options: GetQueryBuilderOptions ): Knex.QueryBuilder { + /* eslint-disable @typescript-eslint/no-floating-promises */ const builder = this.knex!.select(fields).from(schemaName); this.#applyFiltersToBuilder(builder, filters); @@ -464,12 +440,12 @@ export default class DatabaseCore extends DatabaseBase { // => `date >= 2017-09-09 and date <= 2017-11-01` const filtersArray = this.#getFiltersArray(filters); - for (const i in filtersArray) { + for (let i = 0; i < filtersArray.length; i++) { const filter = filtersArray[i]; const field = filter[0] as string; const operator = filter[1]; const comparisonValue = filter[2]; - const type = i === '0' ? 'where' : 'andWhere'; + const type = i === 0 ? 'where' : 'andWhere'; if (operator === '=') { builder[type](field, comparisonValue); @@ -505,7 +481,8 @@ export default class DatabaseCore extends DatabaseBase { if ( operator === 'like' && - !(comparisonValue as (string | number)[]).includes('%') + typeof comparisonValue === 'string' && + !comparisonValue.includes('%') ) { comparisonValue = `%${comparisonValue}%`; } @@ -595,11 +572,8 @@ export default class DatabaseCore extends DatabaseBase { } // link - if ( - field.fieldtype === FieldTypeEnum.Link && - (field as TargetField).target - ) { - const targetSchemaName = (field as TargetField).target as string; + if (field.fieldtype === FieldTypeEnum.Link && field.target) { + const targetSchemaName = field.target; const schema = this.schemaMap[targetSchemaName] as Schema; table .foreign(field.fieldname) @@ -630,7 +604,7 @@ export default class DatabaseCore extends DatabaseBase { } if (newForeignKeys.length) { - await this.#addForeignKeys(schemaName, newForeignKeys); + await this.#addForeignKeys(schemaName); } } @@ -652,15 +626,15 @@ export default class DatabaseCore extends DatabaseBase { async #getNonExtantSingleValues(singleSchemaName: string) { const existingFields = ( - await this.knex!('SingleValue') + (await this.knex!('SingleValue') .where({ parent: singleSchemaName }) - .select('fieldname') + .select('fieldname')) as { fieldname: string }[] ).map(({ fieldname }) => fieldname); return this.schemaMap[singleSchemaName]!.fields.map( ({ fieldname, default: value }) => ({ fieldname, - value: value as RawValue | undefined, + value: value, }) ).filter( ({ fieldname, value }) => @@ -710,7 +684,7 @@ export default class DatabaseCore extends DatabaseBase { child.idx ??= idx; } - async #addForeignKeys(schemaName: string, newForeignKeys: Field[]) { + async #addForeignKeys(schemaName: string) { const tableRows = await this.knex!.select().from(schemaName); await this.prestigeTheTable(schemaName, tableRows); } @@ -731,10 +705,10 @@ export default class DatabaseCore extends DatabaseBase { } async #getOne(schemaName: string, name: string, fields: string[]) { - const fieldValueMap: FieldValueMap = await this.knex!.select(fields) + const fieldValueMap = (await this.knex!.select(fields) .from(schemaName) .where('name', name) - .first(); + .first()) as FieldValueMap; return fieldValueMap; } @@ -794,9 +768,9 @@ export default class DatabaseCore extends DatabaseBase { fieldname, }; - const names: { name: string }[] = await this.knex!('SingleValue') + const names = (await this.knex!('SingleValue') .select('name') - .where(updateKey); + .where(updateKey)) as { name: string }[]; if (!names?.length) { this.#insertSingleValue(singleSchemaName, fieldname, value); @@ -899,7 +873,7 @@ export default class DatabaseCore extends DatabaseBase { continue; } - for (const child of tableFieldValue!) { + for (const child of tableFieldValue) { this.#prepareChild(schemaName, parentName, child, field, added.length); if ( diff --git a/backend/database/tests/helpers.ts b/backend/database/tests/helpers.ts index 9bfa8de34..ead1db65a 100644 --- a/backend/database/tests/helpers.ts +++ b/backend/database/tests/helpers.ts @@ -204,7 +204,7 @@ export async function assertDoesNotThrow( throw new assert.AssertionError({ message: `Got unwanted exception${ message ? `: ${message}` : '' - }\nError: ${(err as Error).message}\n${(err as Error).stack}`, + }\nError: ${(err as Error).message}\n${(err as Error).stack ?? ''}`, }); } } diff --git a/backend/helpers.ts b/backend/helpers.ts index 953891642..f9c72c304 100644 --- a/backend/helpers.ts +++ b/backend/helpers.ts @@ -55,7 +55,13 @@ export function emitMainProcessError( error: unknown, more?: Record ) { - (process.emit as Function)(CUSTOM_EVENTS.MAIN_PROCESS_ERROR, error, more); + ( + process.emit as ( + event: string, + error: unknown, + more?: Record + ) => void + )(CUSTOM_EVENTS.MAIN_PROCESS_ERROR, error, more); } export async function checkFileAccess(filePath: string, mode?: number) { diff --git a/backend/patches/testPatch.ts b/backend/patches/testPatch.ts index a18f96359..6bd74432c 100644 --- a/backend/patches/testPatch.ts +++ b/backend/patches/testPatch.ts @@ -1,5 +1,6 @@ import { DatabaseManager } from '../database/manager'; +// eslint-disable-next-line @typescript-eslint/no-unused-vars async function execute(dm: DatabaseManager) { /** * Execute function will receive the DatabaseManager which is to be used diff --git a/backend/patches/updateSchemas.ts b/backend/patches/updateSchemas.ts index 12b0e4633..e883de26c 100644 --- a/backend/patches/updateSchemas.ts +++ b/backend/patches/updateSchemas.ts @@ -30,9 +30,9 @@ async function execute(dm: DatabaseManager) { const sourceKnex = dm.db!.knex!; const version = ( - await sourceKnex('SingleValue') + (await sourceKnex('SingleValue') .select('value') - .where({ fieldname: 'version' }) + .where({ fieldname: 'version' })) as { value: string }[] )?.[0]?.value; /** @@ -58,7 +58,7 @@ async function execute(dm: DatabaseManager) { await copyData(sourceKnex, destDm); } catch (err) { const destPath = destDm.db!.dbPath; - destDm.db!.close(); + await destDm.db!.close(); await fs.unlink(destPath); throw err; } @@ -94,7 +94,7 @@ async function replaceDatabaseCore( async function copyData(sourceKnex: Knex, destDm: DatabaseManager) { const destKnex = destDm.db!.knex!; const schemaMap = destDm.getSchemaMap(); - await destKnex!.raw('PRAGMA foreign_keys=OFF'); + await destKnex.raw('PRAGMA foreign_keys=OFF'); await copySingleValues(sourceKnex, destKnex, schemaMap); await copyParty(sourceKnex, destKnex, schemaMap[ModelNameEnum.Party]!); await copyItem(sourceKnex, destKnex, schemaMap[ModelNameEnum.Item]!); @@ -111,7 +111,7 @@ async function copyData(sourceKnex: Knex, destDm: DatabaseManager) { destKnex, schemaMap[ModelNameEnum.NumberSeries]! ); - await destKnex!.raw('PRAGMA foreign_keys=ON'); + await destKnex.raw('PRAGMA foreign_keys=ON'); } async function copyNumberSeries( @@ -137,14 +137,14 @@ async function copyNumberSeries( continue; } - const indices = await sourceKnex.raw( + const indices = (await sourceKnex.raw( ` select cast(substr(name, ??) as int) as idx from ?? order by idx desc limit 1`, [name.length + 1, referenceType] - ); + )) as { idx: number }[]; value.start = 1001; value.current = indices[0]?.idx ?? value.current ?? value.start; @@ -358,7 +358,9 @@ async function getCountryCode(knex: Knex) { * Need to account for schema changes, in 0.4.3-beta.0 */ const country = ( - await knex('SingleValue').select('value').where({ fieldname: 'country' }) + (await knex('SingleValue') + .select('value') + .where({ fieldname: 'country' })) as { value: string }[] )?.[0]?.value; if (!country) { diff --git a/build/scripts/build.mjs b/build/scripts/build.mjs new file mode 100644 index 000000000..eb059f7c3 --- /dev/null +++ b/build/scripts/build.mjs @@ -0,0 +1,166 @@ +import vue from '@vitejs/plugin-vue'; +import builder from 'electron-builder'; +import esbuild from 'esbuild'; +import fs from 'fs-extra'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import * as vite from 'vite'; +import { getMainProcessCommonConfig } from './helpers.mjs'; +import yargs from 'yargs'; +import { hideBin } from 'yargs/helpers'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.join(dirname, '..', '..'); +const buildDirPath = path.join(root, 'dist_electron', 'build'); +const packageDirPath = path.join(root, 'dist_electron', 'bundled'); +const mainFileName = 'main.js'; +const commonConfig = getMainProcessCommonConfig(root); + +updatePaths(); +await buildMainProcessSource(); +await buildRendererProcessSource(); +copyPackageJson(); +await packageApp(); + +function updatePaths() { + fs.removeSync(buildDirPath); + fs.ensureDirSync(buildDirPath); + fs.removeSync(packageDirPath); + fs.ensureDirSync(packageDirPath); + fs.ensureDirSync(path.join(buildDirPath, 'node_modules')); +} + +async function buildMainProcessSource() { + const result = await esbuild.build({ + ...commonConfig, + outfile: path.join(buildDirPath, mainFileName), + }); + + if (result.errors.length) { + console.error('app build failed due to main process source build'); + result.errors.forEach((err) => console.error(err)); + process.exit(1); + } +} + +async function buildRendererProcessSource() { + const base = 'app://'; + const outDir = path.join(buildDirPath, 'src'); + await vite.build({ + base: `/${base}`, + root: path.join(root, 'src'), + build: { outDir }, + plugins: [vue()], + resolve: { + alias: { + vue: 'vue/dist/vue.esm-bundler.js', + fyo: path.join(root, 'fyo'), + src: path.join(root, 'src'), + schemas: path.join(root, 'schemas'), + backend: path.join(root, 'backend'), + models: path.join(root, 'models'), + utils: path.join(root, 'utils'), + regional: path.join(root, 'regional'), + reports: path.join(root, 'reports'), + dummy: path.join(root, 'dummy'), + fixtures: path.join(root, 'fixtures'), + }, + }, + }); + removeBaseLeadingSlash(outDir, base); +} + +/** + * Copies the package.json file to the build folder with the + * following changes: + * - Irrelevant fields are removed. + * - Non-external deps (those that are bundled) and devDeps are removed. + * - Main file is updated to the bundled main process JS file. + */ +function copyPackageJson() { + const packageJsonText = fs.readFileSync(path.join(root, 'package.json'), { + encoding: 'utf-8', + }); + + const packageJson = JSON.parse(packageJsonText); + const keys = [ + 'name', + 'version', + 'description', + 'author', + 'homepage', + 'repository', + 'license', + ]; + const modifiedPackageJson = {}; + for (const key of keys) { + modifiedPackageJson[key] = packageJson[key]; + } + + modifiedPackageJson.main = mainFileName; + modifiedPackageJson.dependencies = {}; + + for (const dep of commonConfig.external) { + modifiedPackageJson.dependencies[dep] = packageJson.dependencies[dep]; + } + + fs.writeFileSync( + path.join(buildDirPath, 'package.json'), + JSON.stringify(modifiedPackageJson, null, 2), + { + encoding: 'utf-8', + } + ); +} + +/** + * Packages the app using electron builder. + * + * Note: this also handles signing and notarization if the + * appropriate flags are set. + * + * Electron builder cli [commands](https://www.electron.build/cli) + * are passed on as builderArgs. + */ +async function packageApp() { + const { configureBuildCommand } = await await import( + 'electron-builder/out/builder.js' + ); + + const rawArgs = hideBin(process.argv); + const builderArgs = yargs(rawArgs) + .command(['build', '*'], 'Build', configureBuildCommand) + .parse(); + + const buildOptions = { + config: { + directories: { output: packageDirPath, app: buildDirPath }, + files: ['**'], + extends: null, + }, + ...builderArgs, + }; + + await builder.build(buildOptions); +} + +/** + * Removes leading slash from all renderer files + * electron uses a custom registered protocol to load the + * files: "app://" + * + * @param {string} dir + * @param {string} base + */ +function removeBaseLeadingSlash(dir, base) { + for (const file of fs.readdirSync(dir)) { + const filePath = path.join(dir, file); + if (fs.lstatSync(filePath).isDirectory()) { + removeBaseLeadingSlash(filePath, base); + continue; + } + + const contents = fs.readFileSync(filePath).toString('utf-8'); + fs.writeFileSync(filePath, contents.replaceAll('/' + base, base)); + } +} diff --git a/build/scripts/dev.mjs b/build/scripts/dev.mjs new file mode 100644 index 000000000..06b00e56b --- /dev/null +++ b/build/scripts/dev.mjs @@ -0,0 +1,142 @@ +import chokidar from 'chokidar'; +import esbuild from 'esbuild'; +import { $ } from 'execa'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { getMainProcessCommonConfig } from './helpers.mjs'; + +process.env['ELECTRON_DISABLE_SECURITY_WARNINGS'] = 'true'; +process.env['NODE_ENV'] = 'development'; +process.env['VITE_HOST'] = '0.0.0.0'; +process.env['VITE_PORT'] = 6969; + +/** + * This script does several things: + * 1. Runs the vite server in dev mode `yarn vite` (unless --no-renderer is passed) + * 2. Runs a file watcher for the main processes + * 3. Builds the main process on file changes + * 4. Runs electron which loads renderer using vite server url + */ + +/** + * @type {null | Function} global function used to stop dev + */ +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.join(dirname, '..', '..'); +const $$ = $({ stdio: 'inherit' }); +let isReload = false; + +/** + * @type {null | import('execa').ExecaChildProcess} + */ +let electronProcess = null; + +console.log(`running Frappe Books in dev mode\nroot: ${root}`); +/** + * @type {import('execa').ExecaChildProcess} + */ +const viteProcess = $$`yarn vite`; +/** + * Create esbuild context that is used + * to [re]build the main process code + */ +const ctx = await esbuild.context({ + ...getMainProcessCommonConfig(root), + outfile: path.join(root, 'dist_electron', 'dev', 'main.js'), +}); + +/** + * Create a file watcher so that rebuild + * can be triggered everytime a main process + * file changes. + */ +const fswatcher = chokidar.watch([ + path.join(root, 'main.ts'), + path.join(root, 'main'), + path.join(root, 'backend'), + path.join(root, 'schemas'), +]); + +/** + * Callback function to cleanly shut file watching + * and rebuilding objects. + * + * Called on CTRL+C and kill + */ +const terminate = async () => { + await fswatcher.close(); + await ctx.dispose(); + + if (electronProcess) { + electronProcess.kill(); + } + + if (viteProcess) { + viteProcess.kill(); + } + process.exit(); +}; +process.on('SIGINT', terminate); +process.on('SIGTERM', terminate); +if (viteProcess) { + viteProcess.on('close', terminate); +} + +/** + * Build once and run electron before setting file watcher + */ +await handleResult(await ctx.rebuild()); +electronProcess = runElectron(); + +/** + * On main process source files change + * - rebuild main process + * - restart electron + */ +fswatcher.on('change', async (path) => { + console.log(`change detected:\n\t${path}`); + const result = await ctx.rebuild(); + await handleResult(result); + console.log(`main process source rebuilt\nrestarting electron`); + + if (electronProcess) { + isReload = true; + electronProcess.kill(); + electronProcess = runElectron(); + } +}); + +/** + * @param {esbuild.BuildResult} result + */ +async function handleResult(result) { + if (!result.errors.length) { + return; + } + + console.log('error on build'); + for (const error of result.errors) { + console.log(error); + } + + await terminate(); +} + +function runElectron() { + const electronProcess = $$`npx electron --inspect=5858 ${path.join( + root, + 'dist_electron', + 'dev', + 'main.js' + )}`; + + electronProcess.on('close', async () => { + if (isReload) { + return; + } + + await terminate(); + }); + + return electronProcess; +} diff --git a/build/scripts/helpers.mjs b/build/scripts/helpers.mjs new file mode 100644 index 000000000..d51938efc --- /dev/null +++ b/build/scripts/helpers.mjs @@ -0,0 +1,51 @@ +import fs from 'fs'; +import path from 'path'; + +/** + * Common ESBuild config used for building main process source + * code for both dev and production. + * + * @param {string} root + * @returns {import('esbuild').BuildOptions} + */ +export function getMainProcessCommonConfig(root) { + return { + entryPoints: [path.join(root, 'main.ts')], + bundle: true, + sourcemap: true, + sourcesContent: false, + platform: 'node', + target: 'node16', + external: ['knex', 'electron', 'better-sqlite3', 'electron-store'], + plugins: [excludeVendorFromSourceMap], + write: true, + }; +} + +/** + * ESBuild plugin used to prevent source maps from being generated for + * packages inside node_modules, only first-party code source maps + * are to be included. + * + * Note, this is used only for the main process source code. + * + * source: https://github.com/evanw/esbuild/issues/1685#issuecomment-944916409 + * @type {import('esbuild').Plugin} + */ +export const excludeVendorFromSourceMap = { + name: 'excludeVendorFromSourceMap', + setup(build) { + build.onLoad({ filter: /node_modules/ }, (args) => { + if (args.path.endsWith('.json')) { + return; + } + + return { + contents: + fs.readFileSync(args.path, 'utf8') + + '\n//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIiJdLCJtYXBwaW5ncyI6IkEifQ==', + loader: 'default', + }; + }); + }, +}; diff --git a/colors.json b/colors.json new file mode 100644 index 000000000..545a22615 --- /dev/null +++ b/colors.json @@ -0,0 +1,115 @@ +{ + "black": "#1E293B", + "gray": { + "25": "#fcfcfd", + "50": "#f8f9fc", + "100": "#f2f4f8", + "200": "#ebeff5", + "300": "#e2e8f0", + "400": "#cad5e2", + "500": "#9aa8bc", + "600": "#8493a9", + "700": "#64748b", + "800": "#475569", + "900": "#334155" + }, + "red": { + "100": "#fff5f5", + "200": "#fed7d7", + "300": "#feb2b2", + "400": "#fc8181", + "500": "#f56565", + "600": "#e53e3e", + "700": "#c53030", + "800": "#9b2c2c", + "900": "#742a2a" + }, + "orange": { + "100": "#fffaf0", + "200": "#feebc8", + "300": "#fbd38d", + "400": "#f6ad55", + "500": "#ed8936", + "600": "#dd6b20", + "700": "#c05621", + "800": "#9c4221", + "900": "#7b341e" + }, + "yellow": { + "100": "#fffff0", + "200": "#fefcbf", + "300": "#faf089", + "400": "#f6e05e", + "500": "#ecc94b", + "600": "#d69e2e", + "700": "#b7791f", + "800": "#975a16", + "900": "#744210" + }, + "green": { + "100": "#f0fff4", + "200": "#c6f6d5", + "300": "#9ae6b4", + "400": "#68d391", + "500": "#48bb78", + "600": "#38a169", + "700": "#2f855a", + "800": "#276749", + "900": "#22543d" + }, + "teal": { + "100": "#e6fffa", + "200": "#b2f5ea", + "300": "#81e6d9", + "400": "#4fd1c5", + "500": "#38b2ac", + "600": "#319795", + "700": "#2c7a7b", + "800": "#285e61", + "900": "#234e52" + }, + "blue": { + "100": "#e5f3ff", + "200": "#cce7ff", + "300": "#99d0ff", + "400": "#66b8ff", + "500": "#33a1ff", + "600": "#2490ef", + "700": "#006ecc", + "800": "#005299", + "900": "#003766" + }, + "indigo": { + "100": "#ebf4ff", + "200": "#c3dafe", + "300": "#a3bffa", + "400": "#7f9cf5", + "500": "#667eea", + "600": "#5a67d8", + "700": "#4c51bf", + "800": "#434190", + "900": "#3c366b" + }, + "purple": { + "100": "#faf5ff", + "200": "#e9d8fd", + "300": "#d6bcfa", + "400": "#b794f4", + "500": "#9f7aea", + "600": "#805ad5", + "700": "#6b46c1", + "800": "#553c9a", + "900": "#44337a" + }, + "pink": { + "100": "#fdf7f8", + "200": "#fbeef1", + "300": "#f7dee5", + "400": "#eec3d2", + "500": "#df9eb8", + "600": "#cf82a7", + "700": "#ac688b", + "800": "#8f5b79", + "900": "#70485f" + } +} diff --git a/dummy/index.ts b/dummy/index.ts index 1c27ae9c3..e7827bb56 100644 --- a/dummy/index.ts +++ b/dummy/index.ts @@ -25,8 +25,8 @@ type Notifier = (stage: string, percent: number) => void; export async function setupDummyInstance( dbPath: string, fyo: Fyo, - years: number = 1, - baseCount: number = 1000, + years = 1, + baseCount = 1000, notifier?: Notifier ) { await fyo.purgeCache(); @@ -251,7 +251,7 @@ async function getSalesInvoices( * For each date create a Sales Invoice. */ - for (const d in dates) { + for (let d = 0; d < dates.length; d++) { const date = dates[d]; notifier?.( @@ -424,7 +424,7 @@ async function getSalesPurchaseInvoices( for (const item of supplierGrouped[supplier]) { await doc.append('items', {}); const quantity = purchaseQty[item]; - doc.items!.at(-1)!.set({ item, quantity }); + await doc.items!.at(-1)!.set({ item, quantity }); } invoices.push(doc); @@ -527,7 +527,7 @@ async function syncAndSubmit(docs: Doc[], notifier?: Notifier) { }; const total = docs.length; - for (const i in docs) { + for (let i = 0; i < docs.length; i++) { const doc = docs[i]; notifier?.( `Syncing ${nameMap[doc.schemaName]}, ${i} out of ${total}`, diff --git a/fyo/core/authHandler.ts b/fyo/core/authHandler.ts index 13e1c2035..ccd914c83 100644 --- a/fyo/core/authHandler.ts +++ b/fyo/core/authHandler.ts @@ -58,59 +58,8 @@ export class AuthHandler { return { ...this.#config }; } - init() {} - async login(email: string, password: string) { - if (email === 'Administrator') { - this.#session.user = 'Administrator'; - return; - } - - const response = await fetch(this.#getServerURL() + '/api/login', { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email, password }), - }); - - if (response.status === 200) { - const res = await response.json(); - - this.#session.user = email; - this.#session.token = res.token; - - return res; - } - - return response; - } - - async signup(email: string, fullName: string, password: string) { - const response = await fetch(this.#getServerURL() + '/api/signup', { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ email, fullName, password }), - }); - - if (response.status === 200) { - return await response.json(); - } - - return response; - } - - async logout() { - // TODO: Implement this with auth flow - } - - async purgeCache() {} - - #getServerURL() { - return this.#config.serverURL || ''; + init() { + return null; } async getCreds(): Promise { diff --git a/fyo/core/converter.ts b/fyo/core/converter.ts index c9220163c..fd07c9138 100644 --- a/fyo/core/converter.ts +++ b/fyo/core/converter.ts @@ -144,7 +144,7 @@ export class Converter { return this.#toRawValueMap(parentSchemaName, value.getValidDict()); } - return this.#toRawValueMap(parentSchemaName, value as DocValueMap); + return this.#toRawValueMap(parentSchemaName, value); }); } else { rawValueMap[fieldname] = Converter.toRawValue( @@ -176,7 +176,7 @@ function toDocString(value: RawValue, field: Field) { } function toDocDate(value: RawValue, field: Field) { - if ((value as any) instanceof Date) { + if ((value as unknown) instanceof Date) { return value; } @@ -286,7 +286,7 @@ function toDocAttachment(value: RawValue, field: Field): null | Attachment { } try { - return JSON.parse(value) || null; + return (JSON.parse(value) as Attachment) || null; } catch { throwError(value, field, 'doc'); } @@ -322,7 +322,7 @@ function toRawInt(value: DocValue, field: Field): number { } if (typeof value === 'number') { - return Math.floor(value as number); + return Math.floor(value); } throwError(value, field, 'raw'); @@ -363,7 +363,7 @@ function toRawDateTime(value: DocValue, field: Field): string | null { } if (value instanceof Date) { - return (value as Date).toISOString(); + return value.toISOString(); } if (value instanceof DateTime) { @@ -431,8 +431,8 @@ function toRawAttachment(value: DocValue, field: Field): null | string { function throwError(value: T, field: Field, type: 'raw' | 'doc'): never { throw new ValueError( - `invalid ${type} conversion '${value}' of type ${typeof value} found, field: ${JSON.stringify( - field - )}` + `invalid ${type} conversion '${String( + value + )}' of type ${typeof value} found, field: ${JSON.stringify(field)}` ); } diff --git a/fyo/core/dbHandler.ts b/fyo/core/dbHandler.ts index 69d17acf0..a49489ea7 100644 --- a/fyo/core/dbHandler.ts +++ b/fyo/core/dbHandler.ts @@ -7,10 +7,15 @@ import { translateSchema } from 'fyo/utils/translation'; import { Field, RawValue, SchemaMap } from 'schemas/types'; import { getMapFromList } from 'utils'; import { + Cashflow, DatabaseBase, DatabaseDemuxBase, GetAllOptions, + IncomeExpense, QueryFilter, + TopExpenses, + TotalCreditAndDebit, + TotalOutstanding, } from 'utils/db/types'; import { schemaTranslateables } from 'utils/translationHelpers'; import { LanguageMap } from 'utils/types'; @@ -22,20 +27,10 @@ import { RawValueMap, } from './types'; -// Return types of Bespoke Queries -type TopExpenses = { account: string; total: number }[]; -type TotalOutstanding = { total: number; outstanding: number }; -type Cashflow = { inflow: number; outflow: number; yearmonth: string }[]; -type Balance = { balance: number; yearmonth: string }[]; -type IncomeExpense = { income: Balance; expense: Balance }; -type TotalCreditAndDebit = { - account: string; - totalCredit: number; - totalDebit: number; -}; type FieldMap = Record>; export class DatabaseHandler extends DatabaseBase { + /* eslint-disable @typescript-eslint/no-floating-promises */ #fyo: Fyo; converter: Converter; #demux: DatabaseDemuxBase; @@ -83,7 +78,7 @@ export class DatabaseHandler extends DatabaseBase { } async init() { - this.#schemaMap = (await this.#demux.getSchemaMap()) as SchemaMap; + this.#schemaMap = await this.#demux.getSchemaMap(); this.#setFieldMap(); this.observer = new Observable(); } @@ -92,7 +87,7 @@ export class DatabaseHandler extends DatabaseBase { if (languageMap) { translateSchema(this.#schemaMap, languageMap, schemaTranslateables); } else { - this.#schemaMap = (await this.#demux.getSchemaMap()) as SchemaMap; + this.#schemaMap = await this.#demux.getSchemaMap(); this.#setFieldMap(); } } @@ -142,6 +137,7 @@ export class DatabaseHandler extends DatabaseBase { options: GetAllOptions = {} ): Promise { const rawValueMap = await this.#getAll(schemaName, options); + this.observer.trigger(`getAll:${schemaName}`, options); return this.converter.toDocValueMap( schemaName, @@ -154,6 +150,7 @@ export class DatabaseHandler extends DatabaseBase { options: GetAllOptions = {} ): Promise { const all = await this.#getAll(schemaName, options); + this.observer.trigger(`getAllRaw:${schemaName}`, options); return all; } @@ -188,6 +185,7 @@ export class DatabaseHandler extends DatabaseBase { ): Promise { const rawValueMap = await this.#getAll(schemaName, options); const count = rawValueMap.length; + this.observer.trigger(`count:${schemaName}`, options); return count; } @@ -199,18 +197,21 @@ export class DatabaseHandler extends DatabaseBase { newName: string ): Promise { await this.#demux.call('rename', schemaName, oldName, newName); + this.observer.trigger(`rename:${schemaName}`, { oldName, newName }); } async update(schemaName: string, docValueMap: DocValueMap): Promise { const rawValueMap = this.converter.toRawValueMap(schemaName, docValueMap); await this.#demux.call('update', schemaName, rawValueMap); + this.observer.trigger(`update:${schemaName}`, docValueMap); } // Delete async delete(schemaName: string, name: string): Promise { await this.#demux.call('delete', schemaName, name); + this.observer.trigger(`delete:${schemaName}`, name); } @@ -220,6 +221,7 @@ export class DatabaseHandler extends DatabaseBase { schemaName, filters )) as number; + this.observer.trigger(`deleteAll:${schemaName}`, filters); return count; } @@ -231,6 +233,7 @@ export class DatabaseHandler extends DatabaseBase { schemaName, name )) as boolean; + this.observer.trigger(`exists:${schemaName}`, name); return doesExist; } @@ -302,7 +305,7 @@ export class DatabaseHandler extends DatabaseBase { )) as IncomeExpense; } - async getTotalCreditAndDebit(): Promise { + async getTotalCreditAndDebit(): Promise { return (await this.#demux.callBespoke( 'getTotalCreditAndDebit' )) as TotalCreditAndDebit[]; diff --git a/fyo/core/docHandler.ts b/fyo/core/docHandler.ts index 6630bf2ab..d331a8e01 100644 --- a/fyo/core/docHandler.ts +++ b/fyo/core/docHandler.ts @@ -28,7 +28,7 @@ export class DocHandler { this.observer = new Observable(); } - async purgeCache() { + purgeCache() { this.init(); } @@ -82,10 +82,10 @@ export class DocHandler { getNewDoc( schemaName: string, data: DocValueMap | RawValueMap = {}, - cacheDoc: boolean = true, + cacheDoc = true, schema?: Schema, Model?: typeof Doc, - isRawValueMap: boolean = true + isRawValueMap = true ): Doc { if (!this.models[schemaName] && Model) { this.models[schemaName] = Model; @@ -153,7 +153,8 @@ export class DocHandler { // propagate change to `docs` doc.on('change', (params: unknown) => { - this.docs!.trigger('change', params); + // eslint-disable-next-line @typescript-eslint/no-floating-promises + this.docs.trigger('change', params); }); doc.on('afterSync', () => { @@ -167,22 +168,24 @@ export class DocHandler { } #setCacheUpdationListeners(schemaName: string) { - this.fyo.db.observer.on(`delete:${schemaName}`, (name: string) => { + this.fyo.db.observer.on(`delete:${schemaName}`, (name) => { + if (typeof name !== 'string') { + return; + } + this.removeFromCache(schemaName, name); }); - this.fyo.db.observer.on( - `rename:${schemaName}`, - (names: { oldName: string; newName: string }) => { - const doc = this.#getFromCache(schemaName, names.oldName); - if (doc === undefined) { - return; - } - - this.removeFromCache(schemaName, names.oldName); - this.#addToCache(doc); + this.fyo.db.observer.on(`rename:${schemaName}`, (names) => { + const { oldName } = names as { oldName: string }; + const doc = this.#getFromCache(schemaName, oldName); + if (doc === undefined) { + return; } - ); + + this.removeFromCache(schemaName, oldName); + this.#addToCache(doc); + }); } removeFromCache(schemaName: string, name: string) { diff --git a/fyo/core/types.ts b/fyo/core/types.ts index 8530f0be8..513605a54 100644 --- a/fyo/core/types.ts +++ b/fyo/core/types.ts @@ -1,8 +1,8 @@ -import { Doc } from 'fyo/model/doc'; -import { Money } from 'pesa'; -import { RawValue } from 'schemas/types'; -import { AuthDemuxBase } from 'utils/auth/types'; -import { DatabaseDemuxBase } from 'utils/db/types'; +import type { Doc } from 'fyo/model/doc'; +import type { Money } from 'pesa'; +import type { RawValue } from 'schemas/types'; +import type { AuthDemuxBase } from 'utils/auth/types'; +import type { DatabaseDemuxBase } from 'utils/db/types'; export type Attachment = { name: string; type: string; data: string }; export type DocValue = @@ -31,12 +31,12 @@ export type DatabaseDemuxConstructor = new ( export type AuthDemuxConstructor = new (isElectron?: boolean) => AuthDemuxBase; -export enum ConfigKeys { - Files = 'files', - LastSelectedFilePath = 'lastSelectedFilePath', - Language = 'language', - DeviceId = 'deviceId', -} +export type ConfigMap = { + files: ConfigFile[]; + lastSelectedFilePath: null | string; + language: string + deviceId: string +}; export interface ConfigFile { id: string; diff --git a/fyo/demux/auth.ts b/fyo/demux/auth.ts index e22607d1a..9a9df5275 100644 --- a/fyo/demux/auth.ts +++ b/fyo/demux/auth.ts @@ -1,10 +1,10 @@ -import { ipcRenderer } from 'electron'; import { AuthDemuxBase } from 'utils/auth/types'; import { IPC_ACTIONS } from 'utils/messages'; import { Creds } from 'utils/types'; +const { ipcRenderer } = require('electron'); export class AuthDemux extends AuthDemuxBase { - #isElectron: boolean = false; + #isElectron = false; constructor(isElectron: boolean) { super(); this.#isElectron = isElectron; diff --git a/fyo/demux/config.ts b/fyo/demux/config.ts index 252810c14..22691c086 100644 --- a/fyo/demux/config.ts +++ b/fyo/demux/config.ts @@ -1,55 +1,46 @@ -import config from 'utils/config'; +import type Store from 'electron-store'; +import { ConfigMap } from 'fyo/core/types'; export class Config { - #useElectronConfig: boolean; - fallback: Map = new Map(); + config: Map | Store; constructor(isElectron: boolean) { - this.#useElectronConfig = isElectron; + this.config = new Map(); + if (isElectron) { + const Config = require('electron-store') as typeof Store; + this.config = new Config(); + } } - get store(): Record { - if (this.#useElectronConfig) { - return config.store; - } else { + get store() { + if (this.config instanceof Map) { const store: Record = {}; - - for (const key of this.fallback.keys()) { - store[key] = this.fallback.get(key); + for (const key of this.config.keys()) { + store[key] = this.config.get(key); } return store; + } else { + return this.config; } } - get(key: string, defaultValue?: unknown): unknown { - if (this.#useElectronConfig) { - return config.get(key, defaultValue); - } else { - return this.fallback.get(key) ?? defaultValue; - } + get( + key: K, + defaultValue?: ConfigMap[K] + ): ConfigMap[K] | undefined { + const value = this.config.get(key) as ConfigMap[K] | undefined; + return value ?? defaultValue; } - set(key: string, value: unknown) { - if (this.#useElectronConfig) { - config.set(key, value); - } else { - this.fallback.set(key, value); - } + set(key: K, value: ConfigMap[K]) { + this.config.set(key, value); } - delete(key: string) { - if (this.#useElectronConfig) { - config.delete(key); - } else { - this.fallback.delete(key); - } + delete(key: keyof ConfigMap) { + this.config.delete(key); } clear() { - if (this.#useElectronConfig) { - config.clear(); - } else { - this.fallback.clear(); - } + this.config.clear(); } } diff --git a/fyo/demux/db.ts b/fyo/demux/db.ts index 43ad37ea1..959903b06 100644 --- a/fyo/demux/db.ts +++ b/fyo/demux/db.ts @@ -1,4 +1,4 @@ -import { ipcRenderer } from 'electron'; +const { ipcRenderer } = require('electron'); import { DatabaseError, NotImplemented } from 'fyo/utils/errors'; import { SchemaMap } from 'schemas/types'; import { DatabaseDemuxBase, DatabaseMethod } from 'utils/db/types'; @@ -6,7 +6,7 @@ import { BackendResponse } from 'utils/ipc/types'; import { IPC_ACTIONS } from 'utils/messages'; export class DatabaseDemux extends DatabaseDemuxBase { - #isElectron: boolean = false; + #isElectron = false; constructor(isElectron: boolean) { super(); this.#isElectron = isElectron; @@ -27,70 +27,76 @@ export class DatabaseDemux extends DatabaseDemuxBase { } async getSchemaMap(): Promise { - if (this.#isElectron) { - return (await this.#handleDBCall(async function dbFunc() { - return await ipcRenderer.invoke(IPC_ACTIONS.DB_SCHEMA); - })) as SchemaMap; + if (!this.#isElectron) { + throw new NotImplemented(); } - throw new NotImplemented(); + return (await this.#handleDBCall(async () => { + return (await ipcRenderer.invoke( + IPC_ACTIONS.DB_SCHEMA + )) as BackendResponse; + })) as SchemaMap; } async createNewDatabase( dbPath: string, countryCode?: string ): Promise { - if (this.#isElectron) { - return (await this.#handleDBCall(async function dbFunc() { - return await ipcRenderer.invoke( - IPC_ACTIONS.DB_CREATE, - dbPath, - countryCode - ); - })) as string; + if (!this.#isElectron) { + throw new NotImplemented(); } - throw new NotImplemented(); + return (await this.#handleDBCall(async () => { + return (await ipcRenderer.invoke( + IPC_ACTIONS.DB_CREATE, + dbPath, + countryCode + )) as BackendResponse; + })) as string; } async connectToDatabase( dbPath: string, countryCode?: string ): Promise { - if (this.#isElectron) { - return (await this.#handleDBCall(async function dbFunc() { - return await ipcRenderer.invoke( - IPC_ACTIONS.DB_CONNECT, - dbPath, - countryCode - ); - })) as string; + if (!this.#isElectron) { + throw new NotImplemented(); } - throw new NotImplemented(); + return (await this.#handleDBCall(async () => { + return (await ipcRenderer.invoke( + IPC_ACTIONS.DB_CONNECT, + dbPath, + countryCode + )) as BackendResponse; + })) as string; } async call(method: DatabaseMethod, ...args: unknown[]): Promise { - if (this.#isElectron) { - return (await this.#handleDBCall(async function dbFunc() { - return await ipcRenderer.invoke(IPC_ACTIONS.DB_CALL, method, ...args); - })) as unknown; + if (!this.#isElectron) { + throw new NotImplemented(); } - throw new NotImplemented(); + return await this.#handleDBCall(async () => { + return (await ipcRenderer.invoke( + IPC_ACTIONS.DB_CALL, + method, + ...args + )) as BackendResponse; + }); } async callBespoke(method: string, ...args: unknown[]): Promise { - if (this.#isElectron) { - return (await this.#handleDBCall(async function dbFunc() { - return await ipcRenderer.invoke( - IPC_ACTIONS.DB_BESPOKE, - method, - ...args - ); - })) as unknown; + if (!this.#isElectron) { + throw new NotImplemented(); } - throw new NotImplemented(); + return await this.#handleDBCall(async () => { + return (await ipcRenderer.invoke( + IPC_ACTIONS.DB_BESPOKE, + method, + ...args + )) as BackendResponse; + }); } } diff --git a/fyo/index.ts b/fyo/index.ts index fa8625589..1b41b6724 100644 --- a/fyo/index.ts +++ b/fyo/index.ts @@ -35,7 +35,7 @@ export class Fyo { doc: DocHandler; db: DatabaseHandler; - _initialized: boolean = false; + _initialized = false; errorLog: ErrorLog[] = []; temp?: Record; @@ -94,10 +94,9 @@ export class Fyo { return format(value, field, doc ?? null, this); } - async setIsElectron() { + setIsElectron() { try { - const { ipcRenderer } = await import('electron'); - this.isElectron = Boolean(ipcRenderer); + this.isElectron = Boolean(require('electron')); } catch { this.isElectron = false; } @@ -106,7 +105,7 @@ export class Fyo { async initializeAndRegister( models: ModelMap = {}, regionalModels: ModelMap = {}, - force: boolean = false + force = false ) { if (this._initialized && !force) return; @@ -122,8 +121,8 @@ export class Fyo { // temp params while calling routes this.temp = {}; - await this.doc.init(); - await this.auth.init(); + this.doc.init(); + this.auth.init(); await this.db.init(); } @@ -165,7 +164,6 @@ export class Fyo { async close() { await this.db.close(); - await this.auth.logout(); } getField(schemaName: string, fieldname: string) { @@ -190,14 +188,14 @@ export class Fyo { let value: DocValue | Doc[]; try { doc = await this.doc.getDoc(schemaName, name); - value = doc.get(fieldname!); + value = doc.get(fieldname); } catch (err) { value = undefined; } if (value === undefined && schemaName === name) { const sv = await this.db.getSingleValues({ - fieldname: fieldname!, + fieldname: fieldname, parent: schemaName, }); @@ -222,8 +220,7 @@ export class Fyo { this.errorLog = []; this.temp = {}; await this.db.purgeCache(); - await this.auth.purgeCache(); - await this.doc.purgeCache(); + this.doc.purgeCache(); } store = { diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index c3abdc1cd..9c3783f00 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -9,7 +9,6 @@ import { DynamicLinkField, Field, FieldTypeEnum, - OptionField, RawValue, Schema, TargetField, @@ -37,8 +36,8 @@ import { FormulaMap, FormulaReturn, HiddenMap, - ListsMap, ListViewSettings, + ListsMap, ReadOnlyMap, RequiredMap, TreeViewSettings, @@ -47,6 +46,7 @@ import { import { validateOptions, validateRequired } from './validationFunction'; export class Doc extends Observable { + /* eslint-disable @typescript-eslint/no-floating-promises */ name?: string; schema: Readonly; fyo: Fyo; @@ -62,15 +62,15 @@ export class Doc extends Observable { parentSchemaName?: string; links?: Record; - _dirty: boolean = true; - _notInserted: boolean = true; + _dirty = true; + _notInserted = true; _syncing = false; constructor( schema: Schema, data: DocValueMap, fyo: Fyo, - convertToDocValue: boolean = true + convertToDocValue = true ) { super(); this.fyo = markRaw(fyo); @@ -289,10 +289,10 @@ export class Doc extends Observable { async set( fieldname: string | DocValueMap, value?: DocValue | Doc[] | DocValueMap[], - retriggerChildDocApplyChange: boolean = false + retriggerChildDocApplyChange = false ): Promise { if (typeof fieldname === 'object') { - return await this.setMultiple(fieldname as DocValueMap); + return await this.setMultiple(fieldname); } if (!this._canSet(fieldname, value)) { @@ -391,7 +391,7 @@ export class Doc extends Observable { } if (field.fieldtype === FieldTypeEnum.Currency && !isPesa(defaultValue)) { - defaultValue = this.fyo.pesa!(defaultValue as string | number); + defaultValue = this.fyo.pesa(defaultValue as string | number); } this[field.fieldname] = defaultValue; @@ -418,7 +418,7 @@ export class Doc extends Observable { push( fieldname: string, docValueMap: Doc | DocValueMap | RawValueMap = {}, - convertToDocValue: boolean = false + convertToDocValue = false ) { const childDocs = [ (this[fieldname] ?? []) as Doc[], @@ -449,7 +449,7 @@ export class Doc extends Observable { _getChildDoc( docValueMap: Doc | DocValueMap | RawValueMap, fieldname: string, - convertToDocValue: boolean = false + convertToDocValue = false ): Doc { if (!this.name && this.schema.naming !== 'manual') { this.name = this.fyo.doc.getTemporaryName(this.schema); @@ -528,7 +528,7 @@ export class Doc extends Observable { field.fieldtype === FieldTypeEnum.Select || field.fieldtype === FieldTypeEnum.AutoComplete ) { - validateOptions(field as OptionField, value as string, this); + validateOptions(field, value as string, this); } validateRequired(field, value, this); @@ -544,10 +544,7 @@ export class Doc extends Observable { await validator(value); } - getValidDict( - filterMeta: boolean = false, - filterComputed: boolean = false - ): DocValueMap { + getValidDict(filterMeta = false, filterComputed = false): DocValueMap { let fields = this.schema.fields; if (filterMeta) { fields = this.schema.fields.filter((f) => !f.meta); @@ -639,11 +636,11 @@ export class Doc extends Observable { async _loadLink(field: Field) { if (field.fieldtype === FieldTypeEnum.Link) { - return await this._loadLinkField(field as TargetField); + return await this._loadLinkField(field); } if (field.fieldtype === FieldTypeEnum.DynamicLink) { - return await this._loadDynamicLinkField(field as DynamicLinkField); + return await this._loadDynamicLinkField(field); } } @@ -767,14 +764,13 @@ export class Doc extends Observable { changedFieldname?: string, retriggerChildDocApplyChange?: boolean ): Promise { - const doc = this; let changed = await this._callAllTableFieldsApplyFormula(changedFieldname); changed = - (await this._applyFormulaForFields(doc, changedFieldname)) || changed; + (await this._applyFormulaForFields(this, changedFieldname)) || changed; if (changed && retriggerChildDocApplyChange) { await this._callAllTableFieldsApplyFormula(changedFieldname); - await this._applyFormulaForFields(doc, changedFieldname); + await this._applyFormulaForFields(this, changedFieldname); } return changed; @@ -803,7 +799,7 @@ export class Doc extends Observable { childDocs: Doc[], fieldname?: string ): Promise { - let changed: boolean = false; + let changed = false; for (const childDoc of childDocs) { if (!childDoc._applyFormula) { continue; @@ -978,7 +974,7 @@ export class Doc extends Observable { async trigger(event: string, params?: unknown) { if (this[event]) { - await (this[event] as Function)(params); + await (this[event] as (args: unknown) => Promise)(params); } await super.trigger(event, params); @@ -993,9 +989,9 @@ export class Doc extends Observable { try { return this.fyo.pesa(value as string | number); } catch (err) { - ( - err as Error - ).message += ` value: '${value}' of type: ${typeof value}, fieldname: '${tablefield}', childfield: '${childfield}'`; + (err as Error).message += ` value: '${String( + value + )}' of type: ${typeof value}, fieldname: '${tablefield}', childfield: '${childfield}'`; throw err; } } @@ -1032,7 +1028,7 @@ export class Doc extends Observable { if (this.numberSeries) { delete updateMap.name; } else { - updateMap.name = updateMap.name + ' CPY'; + updateMap.name = String(updateMap.name) + ' CPY'; } const rawUpdateMap = this.fyo.db.converter.toRawValueMap( @@ -1054,6 +1050,8 @@ export class Doc extends Observable { * * This may cause the lifecycle function to execute incorrectly. */ + + /* eslint-disable @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars */ async change(ch: ChangeArg) {} async validate() {} async beforeSync() {} @@ -1084,7 +1082,9 @@ export class Doc extends Observable { return {}; } - static getTreeSettings(fyo: Fyo): TreeViewSettings | void {} + static getTreeSettings(fyo: Fyo): TreeViewSettings | void { + return; + } static getActions(fyo: Fyo): Action[] { return []; diff --git a/fyo/model/errorHelpers.ts b/fyo/model/errorHelpers.ts index 9c148512a..5dc7568fc 100644 --- a/fyo/model/errorHelpers.ts +++ b/fyo/model/errorHelpers.ts @@ -99,23 +99,18 @@ async function getNotFoundDetailsIfDoesNotExists( ): Promise { const value = doc.get(field.fieldname); if (field.fieldtype === FieldTypeEnum.Link && value) { - return getNotFoundLinkDetails(field as TargetField, value as string, fyo); + return getNotFoundLinkDetails(field, value as string, fyo); } if (field.fieldtype === FieldTypeEnum.DynamicLink && value) { - return getNotFoundDynamicLinkDetails( - field as DynamicLinkField, - value as string, - fyo, - doc - ); + return getNotFoundDynamicLinkDetails(field, value as string, fyo, doc); } if ( field.fieldtype === FieldTypeEnum.Table && (value as Doc[] | undefined)?.length ) { - return getNotFoundTableDetails(value as Doc[], fyo); + return await getNotFoundTableDetails(value as Doc[], fyo); } return null; @@ -127,7 +122,7 @@ async function getNotFoundLinkDetails( fyo: Fyo ): Promise { const { target } = field; - const exists = await fyo.db.exists(target as string, value); + const exists = await fyo.db.exists(target, value); if (!exists) { return { label: field.label, value }; } @@ -160,7 +155,7 @@ async function getNotFoundTableDetails( fyo: Fyo ): Promise { for (const childDoc of value) { - const details = getNotFoundDetails(childDoc, fyo); + const details = await getNotFoundDetails(childDoc, fyo); if (details) { return details; } diff --git a/fyo/model/helpers.ts b/fyo/model/helpers.ts index 711c9fcbf..98f8c5c2d 100644 --- a/fyo/model/helpers.ts +++ b/fyo/model/helpers.ts @@ -34,7 +34,7 @@ export function getPreDefaultValues( case FieldTypeEnum.Table: return [] as Doc[]; case FieldTypeEnum.Currency: - return fyo.pesa!(0.0); + return fyo.pesa(0.0); case FieldTypeEnum.Int: case FieldTypeEnum.Float: return 0; @@ -145,9 +145,9 @@ export function isDocValueTruthy(docValue: DocValue | Doc[]) { } export function setChildDocIdx(childDocs: Doc[]) { - for (const idx in childDocs) { - childDocs[idx].idx = +idx; - } + childDocs.forEach((cd, idx) => { + cd.idx = idx; + }); } export function getFormulaSequence(formulas: FormulaMap) { diff --git a/fyo/model/validationFunction.ts b/fyo/model/validationFunction.ts index 858198f55..0e03d0cdc 100644 --- a/fyo/model/validationFunction.ts +++ b/fyo/model/validationFunction.ts @@ -7,14 +7,26 @@ import { getIsNullOrUndef } from 'utils'; import { Doc } from './doc'; export function validateEmail(value: DocValue) { - const isValid = /(.+)@(.+){2,}\.(.+){2,}/.test(value as string); + if (typeof value !== 'string') { + throw new TypeError( + `Invalid email ${String(value)} of type ${typeof value}` + ); + } + + const isValid = /(.+)@(.+){2,}\.(.+){2,}/.test(value); if (!isValid) { throw new ValidationError(`Invalid email: ${value}`); } } export function validatePhoneNumber(value: DocValue) { - const isValid = /[+]{0,1}[\d ]+/.test(value as string); + if (typeof value !== 'string') { + throw new TypeError( + `Invalid phone ${String(value)} of type ${typeof value}` + ); + } + + const isValid = /[+]{0,1}[\d ]+/.test(value); if (!isValid) { throw new ValidationError(`Invalid phone: ${value}`); } diff --git a/fyo/models/SystemSettings.ts b/fyo/models/SystemSettings.ts index 6d6f1d74d..594321876 100644 --- a/fyo/models/SystemSettings.ts +++ b/fyo/models/SystemSettings.ts @@ -18,7 +18,7 @@ export default class SystemSettings extends Doc { instanceId?: string; validations: ValidationMap = { - async displayPrecision(value: DocValue) { + displayPrecision(value: DocValue) { if ((value as number) >= 0 && (value as number) <= 9) { return; } @@ -38,7 +38,7 @@ export default class SystemSettings extends Doc { (c) => ({ value: countryInfo[c]?.locale, - label: `${c} (${countryInfo[c]?.locale})`, + label: `${c} (${countryInfo[c]?.locale ?? t`Not Found`})`, } as SelectOption) ); }, diff --git a/fyo/telemetry/telemetry.ts b/fyo/telemetry/telemetry.ts index bb84fcd27..4ddf63712 100644 --- a/fyo/telemetry/telemetry.ts +++ b/fyo/telemetry/telemetry.ts @@ -1,5 +1,4 @@ import { Fyo } from 'fyo'; -import { ConfigKeys } from 'fyo/core/types'; import { Noun, Telemetry, Verb } from './types'; /** @@ -28,8 +27,8 @@ import { Noun, Telemetry, Verb } from './types'; */ export class TelemetryManager { - #url: string = ''; - #token: string = ''; + #url = ''; + #token = ''; #started = false; fyo: Fyo; @@ -67,6 +66,7 @@ export class TelemetryManager { log(verb: Verb, noun: Noun, more?: Record) { if (!this.#started && this.fyo.db.isConnected) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.start().then(() => this.#sendBeacon(verb, noun, more)); return; } @@ -108,16 +108,12 @@ export class TelemetryManager { noun: Noun, more?: Record ): Telemetry { - const countryCode = this.fyo.singles.SystemSettings?.countryCode as - | string - | undefined; - + const countryCode = this.fyo.singles.SystemSettings?.countryCode; return { country: countryCode ?? '', language: this.fyo.store.language, deviceId: - this.fyo.store.deviceId || - (this.fyo.config.get(ConfigKeys.DeviceId) as string), + this.fyo.store.deviceId || (this.fyo.config.get('deviceId') ?? '-'), instanceId: this.fyo.store.instanceId, version: this.fyo.store.appVersion, openCount: this.fyo.store.openCount, diff --git a/fyo/tests/helpers.ts b/fyo/tests/helpers.ts index 9754d8c4c..e48731f01 100644 --- a/fyo/tests/helpers.ts +++ b/fyo/tests/helpers.ts @@ -2,6 +2,7 @@ import { AuthDemuxBase } from 'utils/auth/types'; import { Creds } from 'utils/types'; export class DummyAuthDemux extends AuthDemuxBase { + // eslint-disable-next-line @typescript-eslint/require-await async getCreds(): Promise { return { errorLogUrl: '', tokenString: '', telemetryUrl: '' }; } diff --git a/fyo/utils/errors.ts b/fyo/utils/errors.ts index 54d206171..ad3489b3f 100644 --- a/fyo/utils/errors.ts +++ b/fyo/utils/errors.ts @@ -4,11 +4,7 @@ export class BaseError extends Error { statusCode: number; shouldStore: boolean; - constructor( - statusCode: number, - message: string, - shouldStore: boolean = true - ) { + constructor(statusCode: number, message: string, shouldStore = true) { super(message); this.name = 'BaseError'; this.statusCode = statusCode; @@ -18,63 +14,63 @@ export class BaseError extends Error { } export class ValidationError extends BaseError { - constructor(message: string, shouldStore: boolean = false) { + constructor(message: string, shouldStore = false) { super(417, message, shouldStore); this.name = 'ValidationError'; } } export class NotFoundError extends BaseError { - constructor(message: string, shouldStore: boolean = true) { + constructor(message: string, shouldStore = true) { super(404, message, shouldStore); this.name = 'NotFoundError'; } } export class ForbiddenError extends BaseError { - constructor(message: string, shouldStore: boolean = true) { + constructor(message: string, shouldStore = true) { super(403, message, shouldStore); this.name = 'ForbiddenError'; } } export class DuplicateEntryError extends ValidationError { - constructor(message: string, shouldStore: boolean = false) { + constructor(message: string, shouldStore = false) { super(message, shouldStore); this.name = 'DuplicateEntryError'; } } export class LinkValidationError extends ValidationError { - constructor(message: string, shouldStore: boolean = false) { + constructor(message: string, shouldStore = false) { super(message, shouldStore); this.name = 'LinkValidationError'; } } export class MandatoryError extends ValidationError { - constructor(message: string, shouldStore: boolean = false) { + constructor(message: string, shouldStore = false) { super(message, shouldStore); this.name = 'MandatoryError'; } } export class DatabaseError extends BaseError { - constructor(message: string, shouldStore: boolean = true) { + constructor(message: string, shouldStore = true) { super(500, message, shouldStore); this.name = 'DatabaseError'; } } export class CannotCommitError extends DatabaseError { - constructor(message: string, shouldStore: boolean = true) { + constructor(message: string, shouldStore = true) { super(message, shouldStore); this.name = 'CannotCommitError'; } } export class NotImplemented extends BaseError { - constructor(message: string = '', shouldStore: boolean = false) { + constructor(message = '', shouldStore = false) { super(501, message, shouldStore); this.name = 'NotImplemented'; } diff --git a/fyo/utils/format.ts b/fyo/utils/format.ts index dfc5d1428..24e7b99c7 100644 --- a/fyo/utils/format.ts +++ b/fyo/utils/format.ts @@ -60,7 +60,7 @@ function toDatetime(value: unknown): DateTime | null { } else if (value instanceof Date) { return DateTime.fromJSDate(value); } else if (typeof value === 'number') { - return DateTime.fromSeconds(value as number); + return DateTime.fromSeconds(value); } return null; @@ -120,7 +120,9 @@ function formatCurrency( try { valueString = formatNumber(value, fyo); } catch (err) { - (err as Error).message += ` value: '${value}', type: ${typeof value}`; + (err as Error).message += ` value: '${String( + value + )}', type: ${typeof value}`; throw err; } @@ -148,7 +150,9 @@ function formatNumber(value: unknown, fyo: Fyo): string { if (formattedNumber === 'NaN') { throw Error( - `invalid value passed to formatNumber: '${value}' of type ${typeof value}` + `invalid value passed to formatNumber: '${String( + value + )}' of type ${typeof value}` ); } diff --git a/fyo/utils/index.ts b/fyo/utils/index.ts index 35fef677a..2bbf4ec0b 100644 --- a/fyo/utils/index.ts +++ b/fyo/utils/index.ts @@ -8,7 +8,7 @@ import { getIsNullOrUndef, safeParseInt } from 'utils'; export function slug(str: string) { return str - .replace(/(?:^\w|[A-Z]|\b\w)/g, function (letter, index) { + .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => { return index == 0 ? letter.toLowerCase() : letter.toUpperCase(); }) .replace(/\s+/g, ''); @@ -24,7 +24,7 @@ export function unique(list: T[], key = (it: T) => String(it)) { export function getDuplicates(array: unknown[]) { const duplicates: unknown[] = []; - for (const i in array) { + for (let i = 0; i < array.length; i++) { const previous = array[safeParseInt(i) - 1]; const current = array[i]; @@ -118,7 +118,7 @@ function getRawOptionList(field: Field, doc: Doc | undefined | null) { return []; } - const Model = doc!.fyo.models[doc!.schemaName]; + const Model = doc.fyo.models[doc.schemaName]; if (Model === undefined) { return []; } @@ -128,7 +128,7 @@ function getRawOptionList(field: Field, doc: Doc | undefined | null) { return []; } - return getList(doc!); + return getList(doc); } export function getEmptyValuesByFieldTypes( diff --git a/fyo/utils/observable.ts b/fyo/utils/observable.ts index 5847c8884..32f06f41b 100644 --- a/fyo/utils/observable.ts +++ b/fyo/utils/observable.ts @@ -3,13 +3,16 @@ enum EventType { OnceListeners = '_onceListeners', } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +type Listener = (...args: any[]) => unknown | Promise; + export default class Observable { [key: string]: unknown | T; _isHot: Map; _eventQueue: Map; _map: Map; - _listeners: Map; - _onceListeners: Map; + _listeners: Map; + _onceListeners: Map; constructor() { this._map = new Map(); @@ -37,6 +40,7 @@ export default class Observable { */ set(key: string, value: T) { this[key] = value; + // eslint-disable-next-line @typescript-eslint/no-floating-promises this.trigger('change', { doc: this, changed: key, @@ -50,7 +54,7 @@ export default class Observable { * @param event : name of the event for which the listener is checked * @param listener : specific listener that is checked for */ - hasListener(event: string, listener?: Function) { + hasListener(event: string, listener?: Listener) { const listeners = this[EventType.Listeners].get(event) ?? []; const onceListeners = this[EventType.OnceListeners].get(event) ?? []; @@ -69,7 +73,7 @@ export default class Observable { * @param event : name of the event for which the listener is set * @param listener : listener that is executed when the event is triggered */ - on(event: string, listener: Function) { + on(event: string, listener: Listener) { this._addListener(EventType.Listeners, event, listener); } @@ -80,7 +84,7 @@ export default class Observable { * @param event : name of the event for which the listener is set * @param listener : listener that is executed when the event is triggered */ - once(event: string, listener: Function) { + once(event: string, listener: Listener) { this._addListener(EventType.OnceListeners, event, listener); } @@ -90,7 +94,7 @@ export default class Observable { * @param event : name of the event from which to remove the listener * @param listener : listener that was set for the event */ - off(event: string, listener: Function) { + off(event: string, listener: Listener) { this._removeListener(EventType.Listeners, event, listener); this._removeListener(EventType.OnceListeners, event, listener); } @@ -111,7 +115,7 @@ export default class Observable { * @param throttle : wait time before triggering the event. */ - async trigger(event: string, params?: unknown, throttle: number = 0) { + async trigger(event: string, params?: unknown, throttle = 0) { let isHot = false; if (throttle > 0) { isHot = this._throttled(event, params, throttle); @@ -125,7 +129,7 @@ export default class Observable { await this._executeTriggers(event, params); } - _removeListener(type: EventType, event: string, listener: Function) { + _removeListener(type: EventType, event: string, listener: Listener) { const listeners = (this[type].get(event) ?? []).filter( (l) => l !== listener ); @@ -160,6 +164,7 @@ export default class Observable { const params = this._eventQueue.get(event); if (params !== undefined) { + // eslint-disable-next-line @typescript-eslint/no-floating-promises this._executeTriggers(event, params); this._eventQueue.delete(event); } @@ -168,7 +173,7 @@ export default class Observable { return false; } - _addListener(type: EventType, event: string, listener: Function) { + _addListener(type: EventType, event: string, listener: Listener) { this._initLiseners(type, event); const list = this[type].get(event)!; if (list.includes(listener)) { diff --git a/fyo/utils/translation.ts b/fyo/utils/translation.ts index 4c80b0e46..598ba41c2 100644 --- a/fyo/utils/translation.ts +++ b/fyo/utils/translation.ts @@ -31,7 +31,7 @@ class TranslationString { } #formatArg(arg: string | number | boolean) { - return arg ?? ''; + return String(arg ?? ''); } #translate() { @@ -48,22 +48,23 @@ class TranslationString { } #stitch() { - if (!((this.args[0] as any) instanceof Array)) { + if (!((this.args[0] as unknown) instanceof Array)) { throw new ValueError( - `invalid args passed to TranslationString ${ + `invalid args passed to TranslationString ${String( this.args - } of type ${typeof this.args[0]}` + )} of type ${typeof this.args[0]}` ); } - this.strList = this.args[0] as any as string[]; + this.strList = this.args[0] as unknown as string[]; this.argList = this.args.slice(1) as TranslationArgs[]; if (this.languageMap) { this.#translate(); } - return this.strList!.map((s, i) => s + this.#formatArg(this.argList![i])) + return this.strList + .map((s, i) => s + this.#formatArg(this.argList![i])) .join('') .replace(/\s+/g, ' ') .trim(); diff --git a/main.ts b/main.ts index 792e6dd7e..a74b9df84 100644 --- a/main.ts +++ b/main.ts @@ -1,27 +1,33 @@ -'use strict'; +// eslint-disable-next-line +require('source-map-support').install({ + handleUncaughtException: false, + environment: 'node', +}); import { app, BrowserWindow, BrowserWindowConstructorOptions, protocol, + ProtocolRequest, + ProtocolResponse, } from 'electron'; import Store from 'electron-store'; import { autoUpdater } from 'electron-updater'; +import fs from 'fs'; import path from 'path'; -import { createProtocol } from 'vue-cli-plugin-electron-builder/lib'; import registerAppLifecycleListeners from './main/registerAppLifecycleListeners'; import registerAutoUpdaterListeners from './main/registerAutoUpdaterListeners'; import registerIpcMainActionListeners from './main/registerIpcMainActionListeners'; import registerIpcMainMessageListeners from './main/registerIpcMainMessageListeners'; import registerProcessListeners from './main/registerProcessListeners'; +import { emitMainProcessError } from 'backend/helpers'; export class Main { - title: string = 'Frappe Books'; + title = 'Frappe Books'; icon: string; - winURL: string = ''; - isWebpackUrl: boolean = false; + winURL = ''; checkedForUpdate = false; mainWindow: BrowserWindow | null = null; @@ -57,7 +63,7 @@ export class Main { } get isDevelopment() { - return process.env.NODE_ENV !== 'production'; + return process.env.NODE_ENV === 'development'; } get isTest() { @@ -116,35 +122,42 @@ export class Main { return options; } - createWindow() { + async createWindow() { const options = this.getOptions(); this.mainWindow = new BrowserWindow(options); - this.isWebpackUrl = !!process.env.WEBPACK_DEV_SERVER_URL; - if (this.isWebpackUrl) { - this.loadWebpackDevServerURL(); + if (this.isDevelopment) { + this.setViteServerURL(); } else { - this.loadAppUrl(); + this.registerAppProtocol(); + } + + await this.mainWindow.loadURL(this.winURL); + if (this.isDevelopment && !this.isTest) { + this.mainWindow.webContents.openDevTools(); } this.setMainWindowListeners(); } - loadWebpackDevServerURL() { - // Load the url of the dev server if in development mode - this.winURL = process.env.WEBPACK_DEV_SERVER_URL as string; - this.mainWindow!.loadURL(this.winURL); + setViteServerURL() { + let port = 6969; + let host = '0.0.0.0'; - if (this.isDevelopment && !this.isTest) { - this.mainWindow!.webContents.openDevTools(); + if (process.env.VITE_PORT && process.env.VITE_HOST) { + port = Number(process.env.VITE_PORT); + host = process.env.VITE_HOST; } + + // Load the url of the dev server if in development mode + this.winURL = `http://${host}:${port}/`; } - loadAppUrl() { - createProtocol('app'); - // Load the index.html when not in development + registerAppProtocol() { + protocol.registerBufferProtocol('app', bufferProtocolCallback); + + // Use the registered protocol url to load the files. this.winURL = 'app://./index.html'; - this.mainWindow!.loadURL(this.winURL); } setMainWindowListeners() { @@ -157,9 +170,43 @@ export class Main { }); this.mainWindow.webContents.on('did-fail-load', () => { - this.mainWindow!.loadURL(this.winURL); + this.mainWindow!.loadURL(this.winURL).catch((err) => + emitMainProcessError(err) + ); }); } } +/** + * Callback used to register the custom app protocol, + * during prod, files are read and served by using this + * protocol. + */ +function bufferProtocolCallback( + request: ProtocolRequest, + callback: (response: ProtocolResponse) => void +) { + const { pathname, host } = new URL(request.url); + const filePath = path.join( + __dirname, + 'src', + decodeURI(host), + decodeURI(pathname) + ); + + fs.readFile(filePath, (_, data) => { + const extension = path.extname(filePath).toLowerCase(); + const mimeType = + { + '.js': 'text/javascript', + '.css': 'text/css', + '.html': 'text/html', + '.svg': 'image/svg+xml', + '.json': 'application/json', + }[extension] ?? ''; + + callback({ mimeType, data }); + }); +} + export default new Main(); diff --git a/main/contactMothership.ts b/main/contactMothership.ts index 24ecace34..2f4159118 100644 --- a/main/contactMothership.ts +++ b/main/contactMothership.ts @@ -4,6 +4,7 @@ import fetch from 'node-fetch'; import path from 'path'; import { Creds } from 'utils/types'; import { rendererLog } from './helpers'; +import type { Main } from 'main'; export function getUrlAndTokenString(): Creds { const inProduction = app.isPackaged; @@ -13,10 +14,11 @@ export function getUrlAndTokenString(): Creds { '../creds/log_creds.txt' ); if (!fs.existsSync(errLogCredsPath)) { - errLogCredsPath = path.join(__dirname, '../log_creds.txt'); + errLogCredsPath = path.join(__dirname, '..', '..', 'log_creds.txt'); } if (!fs.existsSync(errLogCredsPath)) { + // eslint-disable-next-line no-console !inProduction && console.log(`${errLogCredsPath} doesn't exist, can't log`); return empty; } @@ -29,7 +31,9 @@ export function getUrlAndTokenString(): Creds { .filter((f) => f.length); } catch (err) { if (!inProduction) { + // eslint-disable-next-line no-console console.log(`logging error using creds at: ${errLogCredsPath} failed`); + // eslint-disable-next-line no-console console.log(err); } return empty; @@ -42,7 +46,7 @@ export function getUrlAndTokenString(): Creds { }; } -export async function sendError(body: string) { +export async function sendError(body: string, main: Main) { const { errorLogUrl, tokenString } = getUrlAndTokenString(); const headers = { Authorization: tokenString, @@ -51,6 +55,6 @@ export async function sendError(body: string) { }; await fetch(errorLogUrl, { method: 'POST', headers, body }).catch((err) => { - rendererLog(err); + rendererLog(main, err); }); } diff --git a/main/getLanguageMap.ts b/main/getLanguageMap.ts index 23cd0b408..917d36828 100644 --- a/main/getLanguageMap.ts +++ b/main/getLanguageMap.ts @@ -16,8 +16,7 @@ import fs from 'fs/promises'; import path from 'path'; import { parseCSV } from 'utils/csvParser'; import { LanguageMap } from 'utils/types'; - -const fetch = require('node-fetch').default; +import fetch from 'node-fetch'; const VALENTINES_DAY = 1644796800000; @@ -100,7 +99,7 @@ async function fetchContentsFromApi(code: string) { return null; } - const resJson = await res.json(); + const resJson = (await res.json()) as { content: string }; return Buffer.from(resJson.content, 'base64').toString(); } @@ -138,7 +137,9 @@ async function getLastUpdated(code: string): Promise { return new Date(VALENTINES_DAY); } - const resJson = await res.json(); + const resJson = (await res.json()) as { + commit: { author: { date: string } }; + }[]; try { return new Date(resJson[0].commit.author.date); } catch { @@ -187,7 +188,7 @@ async function storeFile(code: string, contents: string) { async function errorHandledFetch(url: string) { try { - return (await fetch(url)) as Response; + return await fetch(url); } catch { return null; } diff --git a/main/getPrintTemplates.ts b/main/getPrintTemplates.ts index d0aa48df2..062fec1a4 100644 --- a/main/getPrintTemplates.ts +++ b/main/getPrintTemplates.ts @@ -29,7 +29,7 @@ async function getPrintTemplatePaths(): Promise<{ const files = await fs.readdir(root); return { files, root }; } catch { - root = path.join(__dirname, `../templates`); + root = path.join(__dirname, '..', '..', `templates`); } try { diff --git a/main/helpers.ts b/main/helpers.ts index 24022450f..3ec78849b 100644 --- a/main/helpers.ts +++ b/main/helpers.ts @@ -1,6 +1,6 @@ import { constants } from 'fs'; import fs from 'fs/promises'; -import { ConfigFile, ConfigKeys } from 'fyo/core/types'; +import { ConfigFile } from 'fyo/core/types'; import { Main } from 'main'; import config from 'utils/config'; import { BackendResponse } from 'utils/ipc/types'; @@ -8,7 +8,7 @@ import { IPC_CHANNELS } from 'utils/messages'; import type { ConfigFilesWithModified } from 'utils/types'; export async function setAndGetCleanedConfigFiles() { - const files = config.get(ConfigKeys.Files, []) as ConfigFile[]; + const files = config.get('files', []); const cleanedFileMap: Map = new Map(); for (const file of files) { @@ -30,7 +30,7 @@ export async function setAndGetCleanedConfigFiles() { } const cleanedFiles = Array.from(cleanedFileMap.values()); - config.set(ConfigKeys.Files, cleanedFiles); + config.set('files', cleanedFiles); return cleanedFiles; } @@ -50,7 +50,9 @@ export async function getConfigFilesWithModified(files: ConfigFile[]) { return filesWithModified; } -export async function getErrorHandledReponse(func: () => Promise) { +export async function getErrorHandledReponse( + func: () => Promise | unknown +) { const response: BackendResponse = {}; try { diff --git a/main/registerAppLifecycleListeners.ts b/main/registerAppLifecycleListeners.ts index 6959e41e6..588dffb61 100644 --- a/main/registerAppLifecycleListeners.ts +++ b/main/registerAppLifecycleListeners.ts @@ -2,6 +2,7 @@ import { app } from 'electron'; import installExtension, { VUEJS3_DEVTOOLS } from 'electron-devtools-installer'; import { Main } from '../main'; import { rendererLog } from './helpers'; +import { emitMainProcessError } from 'backend/helpers'; export default function registerAppLifecycleListeners(main: Main) { app.on('window-all-closed', () => { @@ -12,16 +13,16 @@ export default function registerAppLifecycleListeners(main: Main) { app.on('activate', () => { if (main.mainWindow === null) { - main.createWindow(); + main.createWindow().catch((err) => emitMainProcessError(err)); } }); - app.on('ready', async () => { + app.on('ready', () => { if (main.isDevelopment && !main.isTest) { - await installDevTools(main); + installDevTools(main).catch((err) => emitMainProcessError(err)); } - main.createWindow(); + main.createWindow().catch((err) => emitMainProcessError(err)); }); } diff --git a/main/registerAutoUpdaterListeners.ts b/main/registerAutoUpdaterListeners.ts index ac85ec86b..73ada9305 100644 --- a/main/registerAutoUpdaterListeners.ts +++ b/main/registerAutoUpdaterListeners.ts @@ -21,6 +21,7 @@ export default function registerAutoUpdaterListeners(main: Main) { emitMainProcessError(error); }); + // eslint-disable-next-line @typescript-eslint/no-misused-promises autoUpdater.on('update-available', async (info: UpdateInfo) => { const currentVersion = app.getVersion(); const nextVersion = info.version; @@ -46,6 +47,7 @@ export default function registerAutoUpdaterListeners(main: Main) { await autoUpdater.downloadUpdate(); }); + // eslint-disable-next-line @typescript-eslint/no-misused-promises autoUpdater.on('update-downloaded', async () => { const option = await dialog.showMessageBox({ type: 'info', diff --git a/main/registerIpcMainActionListeners.ts b/main/registerIpcMainActionListeners.ts index 8cc1a3dd7..814e8a6a0 100644 --- a/main/registerIpcMainActionListeners.ts +++ b/main/registerIpcMainActionListeners.ts @@ -1,4 +1,11 @@ -import { app, dialog, ipcMain } from 'electron'; +import { + MessageBoxOptions, + OpenDialogOptions, + SaveDialogOptions, + app, + dialog, + ipcMain, +} from 'electron'; import { autoUpdater } from 'electron-updater'; import fs from 'fs/promises'; import path from 'path'; @@ -20,39 +27,60 @@ import { import { saveHtmlAsPdf } from './saveHtmlAsPdf'; export default function registerIpcMainActionListeners(main: Main) { - ipcMain.handle(IPC_ACTIONS.GET_OPEN_FILEPATH, async (event, options) => { - return await dialog.showOpenDialog(main.mainWindow!, options); - }); - - ipcMain.handle(IPC_ACTIONS.GET_SAVE_FILEPATH, async (event, options) => { - return await dialog.showSaveDialog(main.mainWindow!, options); - }); + ipcMain.handle( + IPC_ACTIONS.GET_OPEN_FILEPATH, + async (_, options: OpenDialogOptions) => { + return await dialog.showOpenDialog(main.mainWindow!, options); + } + ); - ipcMain.handle(IPC_ACTIONS.GET_DIALOG_RESPONSE, async (event, options) => { - if (main.isDevelopment || main.isLinux) { - Object.assign(options, { icon: main.icon }); + ipcMain.handle( + IPC_ACTIONS.GET_SAVE_FILEPATH, + async (_, options: SaveDialogOptions) => { + return await dialog.showSaveDialog(main.mainWindow!, options); } + ); - return await dialog.showMessageBox(main.mainWindow!, options); - }); + ipcMain.handle( + IPC_ACTIONS.GET_DIALOG_RESPONSE, + async (_, options: MessageBoxOptions) => { + if (main.isDevelopment || main.isLinux) { + Object.assign(options, { icon: main.icon }); + } - ipcMain.handle(IPC_ACTIONS.SHOW_ERROR, async (event, { title, content }) => { - return await dialog.showErrorBox(title, content); - }); + return await dialog.showMessageBox(main.mainWindow!, options); + } + ); + + ipcMain.handle( + IPC_ACTIONS.SHOW_ERROR, + (_, { title, content }: { title: string; content: string }) => { + return dialog.showErrorBox(title, content); + } + ); ipcMain.handle( IPC_ACTIONS.SAVE_HTML_AS_PDF, - async (event, html, savePath, width: number, height: number) => { + async ( + _, + html: string, + savePath: string, + width: number, + height: number + ) => { return await saveHtmlAsPdf(html, savePath, app, width, height); } ); - ipcMain.handle(IPC_ACTIONS.SAVE_DATA, async (event, data, savePath) => { - return await fs.writeFile(savePath, data, { encoding: 'utf-8' }); - }); + ipcMain.handle( + IPC_ACTIONS.SAVE_DATA, + async (_, data: string, savePath: string) => { + return await fs.writeFile(savePath, data, { encoding: 'utf-8' }); + } + ); - ipcMain.handle(IPC_ACTIONS.SEND_ERROR, (event, bodyJson) => { - sendError(bodyJson); + ipcMain.handle(IPC_ACTIONS.SEND_ERROR, async (_, bodyJson: string) => { + await sendError(bodyJson, main); }); ipcMain.handle(IPC_ACTIONS.CHECK_FOR_UPDATES, async () => { @@ -72,7 +100,7 @@ export default function registerIpcMainActionListeners(main: Main) { main.checkedForUpdate = true; }); - ipcMain.handle(IPC_ACTIONS.GET_LANGUAGE_MAP, async (event, code) => { + ipcMain.handle(IPC_ACTIONS.GET_LANGUAGE_MAP, async (_, code: string) => { const obj = { languageMap: {}, success: true, message: '' }; try { obj.languageMap = await getLanguageMap(code); @@ -117,20 +145,20 @@ export default function registerIpcMainActionListeners(main: Main) { } ); - ipcMain.handle(IPC_ACTIONS.GET_CREDS, async (event) => { + ipcMain.handle(IPC_ACTIONS.GET_CREDS, () => { return getUrlAndTokenString(); }); - ipcMain.handle(IPC_ACTIONS.DELETE_FILE, async (_, filePath) => { + ipcMain.handle(IPC_ACTIONS.DELETE_FILE, async (_, filePath: string) => { return getErrorHandledReponse(async () => await fs.unlink(filePath)); }); - ipcMain.handle(IPC_ACTIONS.GET_DB_LIST, async (_) => { + ipcMain.handle(IPC_ACTIONS.GET_DB_LIST, async () => { const files = await setAndGetCleanedConfigFiles(); return await getConfigFilesWithModified(files); }); - ipcMain.handle(IPC_ACTIONS.GET_ENV, async (_) => { + ipcMain.handle(IPC_ACTIONS.GET_ENV, () => { return { isDevelopment: main.isDevelopment, platform: process.platform, @@ -149,7 +177,7 @@ export default function registerIpcMainActionListeners(main: Main) { ipcMain.handle( IPC_ACTIONS.DB_CREATE, async (_, dbPath: string, countryCode: string) => { - return await getErrorHandledReponse(async function dbFunc() { + return await getErrorHandledReponse(async () => { return await databaseManager.createNewDatabase(dbPath, countryCode); }); } @@ -158,7 +186,7 @@ export default function registerIpcMainActionListeners(main: Main) { ipcMain.handle( IPC_ACTIONS.DB_CONNECT, async (_, dbPath: string, countryCode?: string) => { - return await getErrorHandledReponse(async function dbFunc() { + return await getErrorHandledReponse(async () => { return await databaseManager.connectToDatabase(dbPath, countryCode); }); } @@ -167,7 +195,7 @@ export default function registerIpcMainActionListeners(main: Main) { ipcMain.handle( IPC_ACTIONS.DB_CALL, async (_, method: DatabaseMethod, ...args: unknown[]) => { - return await getErrorHandledReponse(async function dbFunc() { + return await getErrorHandledReponse(async () => { return await databaseManager.call(method, ...args); }); } @@ -176,15 +204,15 @@ export default function registerIpcMainActionListeners(main: Main) { ipcMain.handle( IPC_ACTIONS.DB_BESPOKE, async (_, method: string, ...args: unknown[]) => { - return await getErrorHandledReponse(async function dbFunc() { + return await getErrorHandledReponse(async () => { return await databaseManager.callBespoke(method, ...args); }); } ); - ipcMain.handle(IPC_ACTIONS.DB_SCHEMA, async (_) => { - return await getErrorHandledReponse(async function dbFunc() { - return await databaseManager.getSchemaMap(); + ipcMain.handle(IPC_ACTIONS.DB_SCHEMA, async () => { + return await getErrorHandledReponse(() => { + return databaseManager.getSchemaMap(); }); }); } diff --git a/main/registerIpcMainMessageListeners.ts b/main/registerIpcMainMessageListeners.ts index b62b1c336..65c420397 100644 --- a/main/registerIpcMainMessageListeners.ts +++ b/main/registerIpcMainMessageListeners.ts @@ -1,6 +1,7 @@ import { ipcMain, Menu, shell } from 'electron'; import { Main } from '../main'; import { IPC_MESSAGES } from '../utils/messages'; +import { emitMainProcessError } from 'backend/helpers'; export default function registerIpcMainMessageListeners(main: Main) { ipcMain.on(IPC_MESSAGES.OPEN_MENU, (event) => { @@ -20,11 +21,11 @@ export default function registerIpcMainMessageListeners(main: Main) { main.mainWindow!.reload(); }); - ipcMain.on(IPC_MESSAGES.OPEN_EXTERNAL, (_, link) => { - shell.openExternal(link); + ipcMain.on(IPC_MESSAGES.OPEN_EXTERNAL, (_, link: string) => { + shell.openExternal(link).catch((err) => emitMainProcessError(err)); }); - ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath) => { + ipcMain.on(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, (_, filePath: string) => { return shell.showItemInFolder(filePath); }); } diff --git a/main/saveHtmlAsPdf.ts b/main/saveHtmlAsPdf.ts index dbf9e4228..68a075fe5 100644 --- a/main/saveHtmlAsPdf.ts +++ b/main/saveHtmlAsPdf.ts @@ -1,3 +1,4 @@ +import { emitMainProcessError } from 'backend/helpers'; import { App, BrowserWindow } from 'electron'; import fs from 'fs/promises'; import path from 'path'; @@ -18,7 +19,7 @@ export async function saveHtmlAsPdf( const htmlPath = path.join(tempRoot, `${filename}.html`); await fs.writeFile(htmlPath, html, { encoding: 'utf-8' }); - const printWindow = getInitializedPrintWindow(htmlPath, width, height); + const printWindow = await getInitializedPrintWindow(htmlPath, width, height); const printOptions = { marginsType: 1, // no margin pageSize: { @@ -36,19 +37,26 @@ export async function saveHtmlAsPdf( */ return await new Promise((resolve) => { printWindow.webContents.once('did-finish-load', () => { - printWindow.webContents.printToPDF(printOptions).then((data) => { - fs.writeFile(savePath, data).then(() => { - printWindow.close(); - fs.unlink(htmlPath).then(() => { - resolve(true); - }); - }); - }); + printWindow.webContents + .printToPDF(printOptions) + .then((data) => { + fs.writeFile(savePath, data) + .then(() => { + printWindow.close(); + fs.unlink(htmlPath) + .then(() => { + resolve(true); + }) + .catch((err) => emitMainProcessError(err)); + }) + .catch((err) => emitMainProcessError(err)); + }) + .catch((err) => emitMainProcessError(err)); }); }); } -function getInitializedPrintWindow( +async function getInitializedPrintWindow( printFilePath: string, width: number, height: number @@ -59,6 +67,6 @@ function getInitializedPrintWindow( show: false, }); - printWindow.loadFile(printFilePath); + await printWindow.loadFile(printFilePath); return printWindow; } diff --git a/models/Transactional/LedgerPosting.ts b/models/Transactional/LedgerPosting.ts index a448a2792..1af625963 100644 --- a/models/Transactional/LedgerPosting.ts +++ b/models/Transactional/LedgerPosting.ts @@ -71,9 +71,9 @@ export class LedgerPosting { const roundOffAccount = await this._getRoundOffAccount(); if (difference.gt(0)) { - this.credit(roundOffAccount, absoluteValue); + await this.credit(roundOffAccount, absoluteValue); } else { - this.debit(roundOffAccount, absoluteValue); + await this.debit(roundOffAccount, absoluteValue); } } diff --git a/models/baseModels/Account/Account.ts b/models/baseModels/Account/Account.ts index 1d222d1b0..289f5655e 100644 --- a/models/baseModels/Account/Account.ts +++ b/models/baseModels/Account/Account.ts @@ -59,10 +59,7 @@ export class Account extends Doc { return; } - const account = await this.fyo.db.get( - 'Account', - this.parentAccount as string - ); + const account = await this.fyo.db.get('Account', this.parentAccount); this.accountType = account.accountType as AccountType; } diff --git a/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts b/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts index 634c56ec8..0518f30d5 100644 --- a/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts +++ b/models/baseModels/AccountingLedgerEntry/AccountingLedgerEntry.ts @@ -40,14 +40,7 @@ export class AccountingLedgerEntry extends Doc { static getListViewSettings(): ListViewSettings { return { - columns: [ - 'date', - 'account', - 'party', - 'debit', - 'credit', - 'referenceName', - ], + columns: ['date', 'account', 'party', 'debit', 'credit', 'referenceName'], }; } } diff --git a/models/baseModels/Address/Address.ts b/models/baseModels/Address/Address.ts index c021e0f91..ab6fcbcd5 100644 --- a/models/baseModels/Address/Address.ts +++ b/models/baseModels/Address/Address.ts @@ -1,10 +1,10 @@ -import { Fyo, t } from 'fyo'; +import { t } from 'fyo'; import { Doc } from 'fyo/model/doc'; import { EmptyMessageMap, FormulaMap, - ListsMap, ListViewSettings, + ListsMap, } from 'fyo/model/types'; import { codeStateMap } from 'regional/in'; import { getCountryInfo } from 'utils/misc'; @@ -12,7 +12,7 @@ import { getCountryInfo } from 'utils/misc'; export class Address extends Doc { formulas: FormulaMap = { addressDisplay: { - formula: async () => { + formula: () => { return [ this.addressLine1, this.addressLine2, diff --git a/models/baseModels/Invoice/Invoice.ts b/models/baseModels/Invoice/Invoice.ts index c3a2756a0..917a1e4d2 100644 --- a/models/baseModels/Invoice/Invoice.ts +++ b/models/baseModels/Invoice/Invoice.ts @@ -143,7 +143,7 @@ export abstract class Invoice extends Transactional { outstandingAmount: this.baseGrandTotal!, }); - const party = (await this.fyo.doc.getDoc('Party', this.party!)) as Party; + const party = (await this.fyo.doc.getDoc('Party', this.party)) as Party; await party.updateOutstandingAmount(); if (this.makeAutoPayment && this.autoPaymentAccount) { @@ -181,7 +181,7 @@ export abstract class Invoice extends Transactional { async _updatePartyOutStanding() { const partyDoc = (await this.fyo.doc.getDoc( ModelNameEnum.Party, - this.party! + this.party )) as Party; await partyDoc.updateOutstandingAmount(); @@ -223,7 +223,7 @@ export abstract class Invoice extends Transactional { return 1.0; } const exchangeRate = await getExchangeRate({ - fromCurrency: this.currency!, + fromCurrency: this.currency, toCurrency: currency as string, }); @@ -247,7 +247,7 @@ export abstract class Invoice extends Transactional { continue; } - const tax = await this.getTax(item.tax!); + const tax = await this.getTax(item.tax); for (const { account, rate } of (tax.details ?? []) as TaxDetail[]) { taxes[account] ??= { account, @@ -256,7 +256,11 @@ export abstract class Invoice extends Transactional { }; let amount = item.amount!; - if (this.enableDiscounting && !this.discountAfterTax && !item.itemDiscountedTotal?.isZero()) { + if ( + this.enableDiscounting && + !this.discountAfterTax && + !item.itemDiscountedTotal?.isZero() + ) { amount = item.itemDiscountedTotal!; } @@ -285,7 +289,7 @@ export abstract class Invoice extends Transactional { } async getTax(tax: string) { - if (!this._taxes![tax]) { + if (!this._taxes[tax]) { this._taxes[tax] = await this.fyo.doc.getDoc('Tax', tax); } @@ -302,7 +306,7 @@ export abstract class Invoice extends Transactional { return itemDiscountAmount.add(invoiceDiscountAmount); } - async getGrandTotal() { + getGrandTotal() { const totalDiscount = this.getTotalDiscount(); return ((this.taxes ?? []) as Doc[]) .map((doc) => doc.amount as Money) @@ -407,16 +411,15 @@ export abstract class Invoice extends Transactional { }, dependsOn: ['party', 'currency'], }, - netTotal: { formula: async () => this.getSum('items', 'amount', false) }, + netTotal: { formula: () => this.getSum('items', 'amount', false) }, taxes: { formula: async () => await this.getTaxSummary() }, - grandTotal: { formula: async () => await this.getGrandTotal() }, + grandTotal: { formula: () => this.getGrandTotal() }, baseGrandTotal: { - formula: async () => - (this.grandTotal as Money).mul(this.exchangeRate! ?? 1), + formula: () => (this.grandTotal as Money).mul(this.exchangeRate! ?? 1), dependsOn: ['grandTotal', 'exchangeRate'], }, outstandingAmount: { - formula: async () => { + formula: () => { if (this.submitted) { return; } @@ -425,7 +428,7 @@ export abstract class Invoice extends Transactional { }, }, stockNotTransferred: { - formula: async () => { + formula: () => { if (this.submitted) { return; } @@ -526,7 +529,7 @@ export abstract class Invoice extends Transactional { !!doc.autoStockTransferLocation, numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo), terms: (doc) => { - const defaults = doc.fyo.singles.Defaults as Defaults | undefined; + const defaults = doc.fyo.singles.Defaults; if (doc.schemaName === ModelNameEnum.SalesInvoice) { return defaults?.salesInvoiceTerms ?? ''; } @@ -616,9 +619,7 @@ export abstract class Invoice extends Transactional { return this.fyo.doc.getNewDoc(ModelNameEnum.Payment, data) as Payment; } - async getStockTransfer( - isAuto: boolean = false - ): Promise { + async getStockTransfer(isAuto = false): Promise { if (!this.isSubmitted) { return null; } diff --git a/models/baseModels/InvoiceItem/InvoiceItem.ts b/models/baseModels/InvoiceItem/InvoiceItem.ts index 03760fece..a01c5dd0b 100644 --- a/models/baseModels/InvoiceItem/InvoiceItem.ts +++ b/models/baseModels/InvoiceItem/InvoiceItem.ts @@ -188,7 +188,7 @@ export abstract class InvoiceItem extends Doc { dependsOn: ['item', 'unit'], }, transferQuantity: { - formula: async (fieldname) => { + formula: (fieldname) => { if (fieldname === 'quantity' || this.unit === this.transferUnit) { return this.quantity; } @@ -205,7 +205,7 @@ export abstract class InvoiceItem extends Doc { const itemDoc = await this.fyo.doc.getDoc( ModelNameEnum.Item, - this.item as string + this.item ); const unitDoc = itemDoc.getLink('uom'); @@ -321,7 +321,7 @@ export abstract class InvoiceItem extends Doc { ], }, itemTaxedTotal: { - formula: async (fieldname) => { + formula: async () => { const totalTaxRate = await this.getTotalTaxRate(); const rate = this.rate ?? this.fyo.pesa(0); const quantity = this.quantity ?? 1; @@ -389,7 +389,7 @@ export abstract class InvoiceItem extends Doc { }; validations: ValidationMap = { - rate: async (value: DocValue) => { + rate: (value: DocValue) => { if ((value as Money).gte(0)) { return; } @@ -401,7 +401,7 @@ export abstract class InvoiceItem extends Doc { )}) cannot be less zero.` ); }, - itemDiscountAmount: async (value: DocValue) => { + itemDiscountAmount: (value: DocValue) => { if ((value as Money).lte(this.amount!)) { return; } @@ -416,7 +416,7 @@ export abstract class InvoiceItem extends Doc { )}).` ); }, - itemDiscountPercent: async (value: DocValue) => { + itemDiscountPercent: (value: DocValue) => { if ((value as number) < 100) { return; } @@ -434,13 +434,14 @@ export abstract class InvoiceItem extends Doc { const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, { fields: ['parent'], - filters: { uom: value as string, parent: this.item! }, + filters: { uom: value as string, parent: this.item }, }); if (item.length < 1) throw new ValidationError( - t`Transfer Unit ${value as string} is not applicable for Item ${this - .item!}` + t`Transfer Unit ${value as string} is not applicable for Item ${ + this.item + }` ); }, }; diff --git a/models/baseModels/Item/Item.ts b/models/baseModels/Item/Item.ts index c1453d052..351bf5b57 100644 --- a/models/baseModels/Item/Item.ts +++ b/models/baseModels/Item/Item.ts @@ -71,7 +71,7 @@ export class Item extends Doc { }; validations: ValidationMap = { - rate: async (value: DocValue) => { + rate: (value: DocValue) => { if ((value as Money).isNegative()) { throw new ValidationError(this.fyo.t`Rate can't be negative.`); } @@ -85,13 +85,13 @@ export class Item extends Doc { label: fyo.t`Sales Invoice`, condition: (doc) => !doc.notInserted && doc.for !== 'Purchases', action: async (doc, router) => { - const invoice = await fyo.doc.getNewDoc('SalesInvoice'); + const invoice = fyo.doc.getNewDoc('SalesInvoice'); await invoice.append('items', { item: doc.name as string, rate: doc.rate as Money, tax: doc.tax as string, }); - router.push(`/edit/SalesInvoice/${invoice.name}`); + await router.push(`/edit/SalesInvoice/${invoice.name!}`); }, }, { @@ -99,13 +99,13 @@ export class Item extends Doc { label: fyo.t`Purchase Invoice`, condition: (doc) => !doc.notInserted && doc.for !== 'Sales', action: async (doc, router) => { - const invoice = await fyo.doc.getNewDoc('PurchaseInvoice'); + const invoice = fyo.doc.getNewDoc('PurchaseInvoice'); await invoice.append('items', { item: doc.name as string, rate: doc.rate as Money, tax: doc.tax as string, }); - router.push(`/edit/PurchaseInvoice/${invoice.name}`); + await router.push(`/edit/PurchaseInvoice/${invoice.name!}`); }, }, ]; @@ -126,7 +126,9 @@ export class Item extends Doc { hasBatch: () => !(this.fyo.singles.InventorySettings?.enableBatches && this.trackItem), hasSerialNumber: () => - !(this.fyo.singles.InventorySettings?.enableSerialNumber && this.trackItem), + !( + this.fyo.singles.InventorySettings?.enableSerialNumber && this.trackItem + ), uomConversions: () => !this.fyo.singles.InventorySettings?.enableUomConversions, }; diff --git a/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts b/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts index 55945e216..ee71fd64a 100644 --- a/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts +++ b/models/baseModels/JournalEntryAccount/JournalEntryAccount.ts @@ -29,10 +29,10 @@ export class JournalEntryAccount extends Doc { formulas: FormulaMap = { debit: { - formula: async () => this.getAutoDebitCredit('debit'), + formula: () => this.getAutoDebitCredit('debit'), }, credit: { - formula: async () => this.getAutoDebitCredit('credit'), + formula: () => this.getAutoDebitCredit('credit'), }, }; diff --git a/models/baseModels/Party/Party.ts b/models/baseModels/Party/Party.ts index 16520efba..c2a29634f 100644 --- a/models/baseModels/Party/Party.ts +++ b/models/baseModels/Party/Party.ts @@ -89,7 +89,7 @@ export class Party extends Doc { dependsOn: ['role'], }, currency: { - formula: async () => { + formula: () => { if (!this.currency) { return this.fyo.singles.SystemSettings!.currency as string; } @@ -132,12 +132,13 @@ export class Party extends Doc { condition: (doc: Doc) => !doc.notInserted && (doc.role as PartyRole) !== 'Customer', action: async (partyDoc, router) => { - const doc = await fyo.doc.getNewDoc('PurchaseInvoice', { + const doc = fyo.doc.getNewDoc('PurchaseInvoice', { party: partyDoc.name, account: partyDoc.defaultAccount as string, }); - router.push({ - path: `/edit/PurchaseInvoice/${doc.name}`, + + await router.push({ + path: `/edit/PurchaseInvoice/${doc.name!}`, query: { schemaName: 'PurchaseInvoice', values: { @@ -170,7 +171,7 @@ export class Party extends Doc { }); await router.push({ - path: `/edit/SalesInvoice/${doc.name}`, + path: `/edit/SalesInvoice/${doc.name!}`, query: { schemaName: 'SalesInvoice', values: { @@ -186,7 +187,7 @@ export class Party extends Doc { condition: (doc: Doc) => !doc.notInserted && (doc.role as PartyRole) !== 'Supplier', action: async (partyDoc, router) => { - router.push({ + await router.push({ path: '/list/SalesInvoice', query: { filters: JSON.stringify({ party: partyDoc.name }) }, }); diff --git a/models/baseModels/Payment/Payment.ts b/models/baseModels/Payment/Payment.ts index 0f9281387..073ca6082 100644 --- a/models/baseModels/Payment/Payment.ts +++ b/models/baseModels/Payment/Payment.ts @@ -84,9 +84,7 @@ export class Payment extends Transactional { updateAmountOnReferenceUpdate() { this.amount = this.fyo.pesa(0); for (const paymentReference of (this.for ?? []) as Doc[]) { - this.amount = (this.amount as Money).add( - paymentReference.amount as Money - ); + this.amount = this.amount.add(paymentReference.amount as Money); } } @@ -123,8 +121,9 @@ export class Payment extends Transactional { if (referenceName && referenceType && !refDoc) { throw new ValidationError( - t`${referenceType} of type ${this.fyo.schemaMap?.[referenceType] - ?.label!} does not exist` + t`${referenceType} of type ${ + this.fyo.schemaMap?.[referenceType]?.label ?? referenceType + } does not exist` ); } @@ -241,8 +240,8 @@ export class Payment extends Transactional { const account = this.account as string; const amount = this.amount as Money; - await posting.debit(paymentAccount as string, amount); - await posting.credit(account as string, amount); + await posting.debit(paymentAccount, amount); + await posting.credit(account, amount); await this.applyWriteOffPosting(posting); return posting; @@ -269,7 +268,7 @@ export class Payment extends Transactional { } async validateReferences() { - const forReferences = (this.for ?? []) as PaymentFor[]; + const forReferences = this.for ?? []; if (forReferences.length === 0) { return; } @@ -334,10 +333,10 @@ export class Payment extends Transactional { } async updateReferenceDocOutstanding() { - for (const row of (this.for ?? []) as PaymentFor[]) { + for (const row of this.for ?? []) { const referenceDoc = await this.fyo.doc.getDoc( row.referenceType!, - row.referenceName! + row.referenceName ); const previousOutstandingAmount = referenceDoc.outstandingAmount as Money; @@ -348,7 +347,7 @@ export class Payment extends Transactional { async afterCancel() { await super.afterCancel(); - this.revertOutstandingAmount(); + await this.revertOutstandingAmount(); } async revertOutstandingAmount() { @@ -357,10 +356,10 @@ export class Payment extends Transactional { } async _revertReferenceOutstanding() { - for (const ref of (this.for ?? []) as PaymentFor[]) { + for (const ref of this.for ?? []) { const refDoc = await this.fyo.doc.getDoc( ref.referenceType!, - ref.referenceName! + ref.referenceName ); const outstandingAmount = (refDoc.outstandingAmount as Money).add( @@ -374,7 +373,7 @@ export class Payment extends Transactional { async updatePartyOutstanding() { const partyDoc = (await this.fyo.doc.getDoc( ModelNameEnum.Party, - this.party! + this.party )) as Party; await partyDoc.updateOutstandingAmount(); } @@ -439,7 +438,7 @@ export class Payment extends Transactional { 'referenceName' )) as Invoice | null; - return (refDoc?.account ?? null) as string | null; + return refDoc?.account ?? null; } formulas: FormulaMap = { @@ -514,17 +513,17 @@ export class Payment extends Transactional { }, }, amount: { - formula: async () => this.getSum('for', 'amount', false), + formula: () => this.getSum('for', 'amount', false), dependsOn: ['for'], }, amountPaid: { - formula: async () => this.amount!.sub(this.writeoff!), + formula: () => this.amount!.sub(this.writeoff!), dependsOn: ['amount', 'writeoff', 'for'], }, }; validations: ValidationMap = { - amount: async (value: DocValue) => { + amount: (value: DocValue) => { if ((value as Money).isNegative()) { throw new ValidationError( this.fyo.t`Payment amount cannot be less than zero.` @@ -615,7 +614,7 @@ export class Payment extends Transactional { return [getLedgerLinkAction(fyo)]; } - static getListViewSettings(fyo: Fyo): ListViewSettings { + static getListViewSettings(): ListViewSettings { return { columns: ['name', getDocStatusListColumn(), 'party', 'date', 'amount'], }; diff --git a/models/baseModels/PaymentFor/PaymentFor.ts b/models/baseModels/PaymentFor/PaymentFor.ts index 5cf55eda0..9c289a6f1 100644 --- a/models/baseModels/PaymentFor/PaymentFor.ts +++ b/models/baseModels/PaymentFor/PaymentFor.ts @@ -60,7 +60,7 @@ export class PaymentFor extends Doc { const outstandingAmount = (await this.fyo.getValue( this.referenceType as string, - this.referenceName as string, + this.referenceName, 'outstandingAmount' )) as Money; @@ -105,10 +105,11 @@ export class PaymentFor extends Doc { return; } + const referenceType = this.referenceType ?? ModelNameEnum.SalesInvoice; + const label = this.fyo.schemaMap[referenceType]?.label ?? referenceType; + throw new NotFoundError( - t`${this.fyo.schemaMap[this.referenceType!]?.label!} ${ - value as string - } does not exist`, + t`${label} ${value as string} does not exist`, false ); }, diff --git a/models/baseModels/PriceList/PriceList.ts b/models/baseModels/PriceList/PriceList.ts index 7784d16a0..10070dc16 100644 --- a/models/baseModels/PriceList/PriceList.ts +++ b/models/baseModels/PriceList/PriceList.ts @@ -1,7 +1,10 @@ import { Doc } from 'fyo/model/doc'; import { ListViewSettings } from 'fyo/model/types'; import { PriceListItem } from './PriceListItem'; -import { getPriceListEnabledColumn, getPriceListStatusColumn } from 'models/helpers'; +import { + getPriceListEnabledColumn, + getPriceListStatusColumn, +} from 'models/helpers'; export class PriceList extends Doc { isEnabled?: boolean; @@ -11,7 +14,11 @@ export class PriceList extends Doc { static getListViewSettings(): ListViewSettings { return { - columns: ['name', getPriceListEnabledColumn(), getPriceListStatusColumn()], + columns: [ + 'name', + getPriceListEnabledColumn(), + getPriceListStatusColumn(), + ], }; } } diff --git a/models/baseModels/PrintTemplate.ts b/models/baseModels/PrintTemplate.ts index 8bd2bd66d..587506430 100644 --- a/models/baseModels/PrintTemplate.ts +++ b/models/baseModels/PrintTemplate.ts @@ -46,7 +46,7 @@ export class PrintTemplate extends Doc { static lists: ListsMap = { type(doc?: Doc) { - let enableInventory: boolean = false; + let enableInventory = false; let schemaMap: SchemaMap = {}; if (doc) { enableInventory = !!doc.fyo.singles.AccountingSettings?.enableInventory; diff --git a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts index 5c37616d8..8da038bf5 100644 --- a/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts +++ b/models/baseModels/PurchaseInvoice/PurchaseInvoice.ts @@ -24,7 +24,7 @@ export class PurchaseInvoice extends Invoice { } } - const discountAmount = await this.getTotalDiscount(); + const discountAmount = this.getTotalDiscount(); const discountAccount = this.fyo.singles.AccountingSettings ?.discountAccount as string | undefined; if (discountAccount && discountAmount.isPositive()) { diff --git a/models/baseModels/SalesInvoice/SalesInvoice.ts b/models/baseModels/SalesInvoice/SalesInvoice.ts index 8514542dc..791c7554f 100644 --- a/models/baseModels/SalesInvoice/SalesInvoice.ts +++ b/models/baseModels/SalesInvoice/SalesInvoice.ts @@ -19,12 +19,12 @@ export class SalesInvoice extends Invoice { } if (this.taxes) { - for (const tax of this.taxes!) { + for (const tax of this.taxes) { await posting.credit(tax.account!, tax.amount!.mul(exchangeRate)); } } - const discountAmount = await this.getTotalDiscount(); + const discountAmount = this.getTotalDiscount(); const discountAccount = this.fyo.singles.AccountingSettings ?.discountAccount as string | undefined; if (discountAccount && discountAmount.isPositive()) { diff --git a/models/baseModels/SetupWizard/SetupWizard.ts b/models/baseModels/SetupWizard/SetupWizard.ts index 6086ae239..4415042fb 100644 --- a/models/baseModels/SetupWizard/SetupWizard.ts +++ b/models/baseModels/SetupWizard/SetupWizard.ts @@ -61,7 +61,7 @@ export class SetupWizard extends Doc { formulas: FormulaMap = { fiscalYearStart: { - formula: async (fieldname?: string) => { + formula: (fieldname?: string) => { if ( fieldname === 'fiscalYearEnd' && this.fiscalYearEnd && @@ -85,7 +85,7 @@ export class SetupWizard extends Doc { dependsOn: ['country', 'fiscalYearEnd'], }, fiscalYearEnd: { - formula: async (fieldname?: string) => { + formula: (fieldname?: string) => { if ( fieldname === 'fiscalYearStart' && this.fiscalYearStart && @@ -109,7 +109,7 @@ export class SetupWizard extends Doc { dependsOn: ['country', 'fiscalYearStart'], }, currency: { - formula: async () => { + formula: () => { const country = this.get('country'); if (typeof country !== 'string') { return; @@ -135,7 +135,7 @@ export class SetupWizard extends Doc { dependsOn: ['country'], }, chartOfAccounts: { - formula: async () => { + formula: () => { const country = this.get('country') as string | undefined; if (country === undefined) { return; diff --git a/models/helpers.ts b/models/helpers.ts index 4c508d0d1..3fbfad45e 100644 --- a/models/helpers.ts +++ b/models/helpers.ts @@ -9,10 +9,7 @@ import { AccountRootType, AccountRootTypeEnum, } from './baseModels/Account/types'; -import { - Defaults, - numberSeriesDefaultsMap, -} from './baseModels/Defaults/Defaults'; +import { numberSeriesDefaultsMap } from './baseModels/Defaults/Defaults'; import { Invoice } from './baseModels/Invoice/Invoice'; import { StockMovement } from './inventory/StockMovement'; import { StockTransfer } from './inventory/StockTransfer'; @@ -55,7 +52,7 @@ export function getMakeStockTransferAction( condition: (doc: Doc) => doc.isSubmitted && !!doc.stockNotTransferred, action: async (doc: Doc) => { const transfer = await (doc as Invoice).getStockTransfer(); - if (!transfer) { + if (!transfer || !transfer.name) { return; } @@ -81,7 +78,7 @@ export function getMakeInvoiceAction( condition: (doc: Doc) => doc.isSubmitted && !doc.backReference, action: async (doc: Doc) => { const invoice = await (doc as StockTransfer).getInvoice(); - if (!invoice) { + if (!invoice || !invoice.name) { return; } @@ -128,10 +125,7 @@ export function getMakePaymentAction(fyo: Fyo): Action { }; } -export function getLedgerLinkAction( - fyo: Fyo, - isStock: boolean = false -): Action { +export function getLedgerLinkAction(fyo: Fyo, isStock = false): Action { let label = fyo.t`Accounting Entries`; let reportClassName: 'GeneralLedger' | 'StockLedger' = 'GeneralLedger'; @@ -146,7 +140,7 @@ export function getLedgerLinkAction( condition: (doc: Doc) => doc.isSubmitted, action: async (doc: Doc, router: Router) => { const route = getLedgerLink(doc, reportClassName); - router.push(route); + await router.push(route); }, }; } @@ -174,7 +168,7 @@ export function getTransactionStatusColumn(): ColumnConfig { fieldtype: 'Select', render(doc) { const status = getDocStatus(doc) as InvoiceStatus; - const color = statusColor[status]; + const color = statusColor[status] ?? 'gray'; const label = getStatusText(status); return { @@ -389,9 +383,7 @@ export async function getExchangeRate({ let exchangeRate = 0; if (localStorage) { - exchangeRate = safeParseFloat( - localStorage.getItem(cacheKey as string) as string - ); + exchangeRate = safeParseFloat(localStorage.getItem(cacheKey) as string); } if (exchangeRate && exchangeRate !== 1) { @@ -402,9 +394,14 @@ export async function getExchangeRate({ const res = await fetch( `https://api.vatcomply.com/rates?date=${date}&base=${fromCurrency}&symbols=${toCurrency}` ); - const data = await res.json(); + const data = (await res.json()) as { + base: string; + data: string; + rates: Record; + }; exchangeRate = data.rates[toCurrency]; } catch (error) { + // eslint-disable-next-line no-console console.error(error); exchangeRate ??= 1; } @@ -439,7 +436,7 @@ export function getNumberSeries(schemaName: string, fyo: Fyo) { return undefined; } - const defaults = fyo.singles.Defaults as Defaults | undefined; + const defaults = fyo.singles.Defaults; const field = fyo.getField(schemaName, 'numberSeries'); const value = defaults?.[numberSeriesKey] as string | undefined; return value ?? (field?.default as string | undefined); diff --git a/models/inventory/Batch.ts b/models/inventory/Batch.ts index 27a041572..c027b3364 100644 --- a/models/inventory/Batch.ts +++ b/models/inventory/Batch.ts @@ -1,13 +1,10 @@ import { Doc } from 'fyo/model/doc'; -import { - ListViewSettings, -} from 'fyo/model/types'; +import { ListViewSettings } from 'fyo/model/types'; export class Batch extends Doc { static getListViewSettings(): ListViewSettings { return { - columns: ["name", "expiryDate", "manufactureDate"], + columns: ['name', 'expiryDate', 'manufactureDate'], }; } - } diff --git a/models/inventory/StockManager.ts b/models/inventory/StockManager.ts index c34666db4..16302bc68 100644 --- a/models/inventory/StockManager.ts +++ b/models/inventory/StockManager.ts @@ -40,7 +40,7 @@ export class StockManager { } for (const details of detailsList) { - await this.#createTransfer(details); + this.#createTransfer(details); } await this.#sync(); @@ -73,7 +73,7 @@ export class StockManager { } } - async #createTransfer(details: SMIDetails) { + #createTransfer(details: SMIDetails) { const item = new StockManagerItem(details, this.fyo); item.transferStock(); this.items.push(item); diff --git a/models/inventory/StockMovement.ts b/models/inventory/StockMovement.ts index dea5a6825..cd24b54b1 100644 --- a/models/inventory/StockMovement.ts +++ b/models/inventory/StockMovement.ts @@ -23,7 +23,7 @@ import { getSerialNumberFromDoc, updateSerialNumbers, validateBatch, - validateSerialNumber + validateSerialNumber, } from './helpers'; import { MovementType, MovementTypeEnum } from './types'; @@ -39,6 +39,7 @@ export class StockMovement extends Transfer { return false; } + // eslint-disable-next-line @typescript-eslint/require-await override async getPosting(): Promise { return null; } diff --git a/models/inventory/StockMovementItem.ts b/models/inventory/StockMovementItem.ts index 41f61c684..613ce79f0 100644 --- a/models/inventory/StockMovementItem.ts +++ b/models/inventory/StockMovementItem.ts @@ -1,6 +1,5 @@ import { t } from 'fyo'; import { DocValue } from 'fyo/core/types'; -import { Doc } from 'fyo/model/doc'; import { FiltersMap, FormulaMap, @@ -14,8 +13,8 @@ import { ModelNameEnum } from 'models/types'; import { Money } from 'pesa'; import { safeParseFloat } from 'utils/index'; import { StockMovement } from './StockMovement'; -import { MovementTypeEnum } from './types'; import { TransferItem } from './TransferItem'; +import { MovementTypeEnum } from './types'; export class StockMovementItem extends TransferItem { name?: string; @@ -78,8 +77,8 @@ export class StockMovementItem extends TransferItem { return null; } - const defaultLocation = this.fyo.singles.InventorySettings - ?.defaultLocation as string | undefined; + const defaultLocation = + this.fyo.singles.InventorySettings?.defaultLocation; if (defaultLocation && !this.fromLocation && this.isIssue) { return defaultLocation; } @@ -94,8 +93,8 @@ export class StockMovementItem extends TransferItem { return null; } - const defaultLocation = this.fyo.singles.InventorySettings - ?.defaultLocation as string | undefined; + const defaultLocation = + this.fyo.singles.InventorySettings?.defaultLocation; if (defaultLocation && !this.toLocation && this.isReceipt) { return defaultLocation; } @@ -128,7 +127,7 @@ export class StockMovementItem extends TransferItem { dependsOn: ['item', 'unit'], }, transferQuantity: { - formula: async (fieldname) => { + formula: (fieldname) => { if (fieldname === 'quantity' || this.unit === this.transferUnit) { return this.quantity; } @@ -145,7 +144,7 @@ export class StockMovementItem extends TransferItem { const itemDoc = await this.fyo.doc.getDoc( ModelNameEnum.Item, - this.item as string + this.item ); const unitDoc = itemDoc.getLink('uom'); @@ -217,13 +216,14 @@ export class StockMovementItem extends TransferItem { const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, { fields: ['parent'], - filters: { uom: value as string, parent: this.item! }, + filters: { uom: value as string, parent: this.item }, }); if (item.length < 1) throw new ValidationError( - t`Transfer Unit ${value as string} is not applicable for Item ${this - .item!}` + t`Transfer Unit ${value as string} is not applicable for Item ${ + this.item + }` ); }, }; diff --git a/models/inventory/StockTransfer.ts b/models/inventory/StockTransfer.ts index ec55f3345..6cee6b0e0 100644 --- a/models/inventory/StockTransfer.ts +++ b/models/inventory/StockTransfer.ts @@ -26,7 +26,6 @@ import { validateBatch, validateSerialNumber, } from './helpers'; -import { Item } from 'models/baseModels/Item/Item'; export abstract class StockTransfer extends Transfer { name?: string; @@ -67,7 +66,7 @@ export abstract class StockTransfer extends Transfer { static defaults: DefaultMap = { numberSeries: (doc) => getNumberSeries(doc.schemaName, doc.fyo), terms: (doc) => { - const defaults = doc.fyo.singles.Defaults as Defaults | undefined; + const defaults = doc.fyo.singles.Defaults; if (doc.schemaName === ModelNameEnum.Shipment) { return defaults?.shipmentTerms ?? ''; } diff --git a/models/inventory/StockTransferItem.ts b/models/inventory/StockTransferItem.ts index c02c60cb8..2a60f906d 100644 --- a/models/inventory/StockTransferItem.ts +++ b/models/inventory/StockTransferItem.ts @@ -1,4 +1,3 @@ -import { t } from 'fyo'; import { DocValue } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; import { @@ -73,7 +72,7 @@ export class StockTransferItem extends TransferItem { dependsOn: ['item', 'unit'], }, transferQuantity: { - formula: async (fieldname) => { + formula: (fieldname) => { if (fieldname === 'quantity' || this.unit === this.transferUnit) { return this.quantity; } @@ -90,7 +89,7 @@ export class StockTransferItem extends TransferItem { const itemDoc = await this.fyo.doc.getDoc( ModelNameEnum.Item, - this.item as string + this.item ); const unitDoc = itemDoc.getLink('uom'); @@ -146,7 +145,7 @@ export class StockTransferItem extends TransferItem { dependsOn: ['rate', 'quantity'], }, rate: { - formula: async (fieldname) => { + formula: async () => { const rate = (await this.fyo.getValue( 'Item', this.item as string, @@ -177,8 +176,8 @@ export class StockTransferItem extends TransferItem { return; } - const defaultLocation = this.fyo.singles.InventorySettings - ?.defaultLocation as string | undefined; + const defaultLocation = + this.fyo.singles.InventorySettings?.defaultLocation; if (defaultLocation && !this.location) { return defaultLocation; @@ -195,13 +194,14 @@ export class StockTransferItem extends TransferItem { const item = await this.fyo.db.getAll(ModelNameEnum.UOMConversionItem, { fields: ['parent'], - filters: { uom: value as string, parent: this.item! }, + filters: { uom: value as string, parent: this.item }, }); if (item.length < 1) throw new ValidationError( - t`Transfer Unit ${value as string} is not applicable for Item ${this - .item!}` + this.fyo.t`Transfer Unit ${ + value as string + } is not applicable for Item ${this.item}` ); }, }; diff --git a/models/inventory/stockQueue.ts b/models/inventory/stockQueue.ts index 975dcbe41..2d9ba3ba4 100644 --- a/models/inventory/stockQueue.ts +++ b/models/inventory/stockQueue.ts @@ -55,7 +55,7 @@ export class StockQueue { return null; } - let incomingRate: number = 0; + let incomingRate = 0; this.quantity -= quantity; let remaining = quantity; diff --git a/models/inventory/tests/helpers.ts b/models/inventory/tests/helpers.ts index b9697de8f..b473370a6 100644 --- a/models/inventory/tests/helpers.ts +++ b/models/inventory/tests/helpers.ts @@ -37,17 +37,22 @@ interface TransferTwo extends Omit { location: string; } -export function getItem(name: string, rate: number, hasBatch: boolean = false, hasSerialNumber: boolean = false) { +export function getItem( + name: string, + rate: number, + hasBatch = false, + hasSerialNumber = false +) { return { name, rate, trackItem: true, hasBatch, hasSerialNumber }; } -export async function getBatch( +export function getBatch( schemaName: ModelNameEnum.Batch, batch: string, expiryDate: Date, manufactureDate: Date, fyo: Fyo -): Promise { +): Batch { const doc = fyo.doc.getNewDoc(schemaName, { batch, expiryDate, diff --git a/models/regionalModels/in/Address.ts b/models/regionalModels/in/Address.ts index 1c9e94e81..0cf5b92fd 100644 --- a/models/regionalModels/in/Address.ts +++ b/models/regionalModels/in/Address.ts @@ -5,7 +5,7 @@ import { codeStateMap } from 'regional/in'; export class Address extends BaseAddress { formulas: FormulaMap = { addressDisplay: { - formula: async () => { + formula: () => { return [ this.addressLine1, this.addressLine2, @@ -28,7 +28,7 @@ export class Address extends BaseAddress { }, pos: { - formula: async () => { + formula: () => { const stateList = Object.values(codeStateMap).sort(); const state = this.state as string; if (stateList.includes(state)) { diff --git a/models/regionalModels/in/Party.ts b/models/regionalModels/in/Party.ts index 9571c9339..523cd2a2d 100644 --- a/models/regionalModels/in/Party.ts +++ b/models/regionalModels/in/Party.ts @@ -6,6 +6,7 @@ export class Party extends BaseParty { gstin?: string; gstType?: GSTType; + // eslint-disable-next-line @typescript-eslint/require-await async beforeSync() { const gstin = this.get('gstin') as string | undefined; const gstType = this.get('gstType') as GSTType; diff --git a/package.json b/package.json index 6e510d4d0..a60f52ae5 100644 --- a/package.json +++ b/package.json @@ -2,23 +2,20 @@ "name": "frappe-books", "version": "0.16.0", "description": "Simple book-keeping app for everyone", - "main": "background.js", "author": { "name": "Frappe Technologies Pvt. Ltd.", "email": "hello@frappe.io" }, "scripts": { - "serve": "vue-cli-service serve", - "build": "vue-cli-service build", - "lint": "vue-cli-service lint", + "dev": "node build/scripts/dev.mjs", + "build": "node build/scripts/build.mjs", "release": "scripts/publish-mac-arm.sh", "postinstall": "electron-rebuild", "postuninstall": "electron-rebuild", - "electron:build": "vue-cli-service electron:build", - "electron:serve": "vue-cli-service electron:serve", "script:translate": "scripts/runner.sh scripts/generateTranslations.ts", "script:profile": "scripts/profile.sh", - "test": "scripts/test.sh" + "test": "scripts/test.sh", + "lint": "eslint . --ext ts,vue" }, "dependencies": { "@codemirror/autocomplete": "^6.4.2", @@ -34,12 +31,15 @@ "luxon": "^2.5.2", "node-fetch": "2", "pesa": "^1.1.12", + "source-map-support": "^0.5.21", "vue": "^3.2.40", "vue-router": "^4.0.12" }, "devDependencies": { - "@babel/core": "^7.16.0", - "@babel/eslint-parser": "^7.16.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/common": "^1.0.0", "@types/assert": "^1.5.6", "@types/electron-devtools-installer": "^2.2.0", "@types/lodash": "^4.14.179", @@ -48,30 +48,25 @@ "@types/node": "^17.0.23", "@types/node-fetch": "^2.6.1", "@types/tape": "^4.13.2", - "@typescript-eslint/eslint-plugin": "^4.15.1", - "@typescript-eslint/parser": "^4.15.1", - "@vue/cli-plugin-babel": "^4.5.0", - "@vue/cli-plugin-eslint": "^5.0.0-beta.7", - "@vue/cli-plugin-router": "^4.5.0", - "@vue/cli-plugin-typescript": "~4.5.0", - "@vue/cli-service": "^4.5.0", - "@vue/eslint-config-typescript": "^7.0.0", + "@typescript-eslint/eslint-plugin": "5.60.0", + "@typescript-eslint/parser": "5.60.0", + "@vitejs/plugin-vue": "^4.2.3", "autoprefixer": "^9", - "babel-loader": "^8.2.3", + "chokidar": "^3.5.3", "dotenv": "^16.0.0", "electron": "18.3.7", - "electron-builder": "24.0.0-alpha.12", + "electron-builder": "^24.4.0", "electron-devtools-installer": "^3.2.0", "electron-rebuild": "^3.2.9", "electron-updater": "^5.2.1", - "eslint": "^7.32.0", + "eslint": "^8.43.0", "eslint-config-prettier": "^8.3.0", "eslint-plugin-prettier": "^4.0.0", - "eslint-plugin-vue": "^7.0.0", - "lint-staged": "^11.2.6", + "eslint-plugin-vue": "^9.15.0", + "execa": "^7.1.1", + "fs-extra": "^11.1.1", "postcss": "^8", "prettier": "^2.4.1", - "raw-loader": "^4.0.2", "tailwindcss": "npm:@tailwindcss/postcss7-compat", "tailwindcss-rtl": "^0.9.0", "tap-spec": "^5.0.0", @@ -80,29 +75,18 @@ "tsconfig-paths": "^3.14.1", "tslib": "^2.3.1", "typescript": "^4.6.2", - "vue-cli-plugin-electron-builder": "https://github.com/nklayman/vue-cli-plugin-electron-builder#ebb9183f4913f927d4e4f4eb1fbab61a960f7a09", - "webpack": "^5.76.0" - }, - "resolutions": { - "electron-builder": "24.0.0-alpha.12" + "vite": "^4.3.9", + "vue-tsc": "^1.6.5", + "yargs": "^17.7.2" }, "prettier": { "semi": true, "singleQuote": true, "trailingComma": "es5" }, - "engineStrict": true, - "engines": { - "node": ">=16.13.1 <17" - }, - "gitHooks": { - "pre-commit": "lint-staged" - }, "homepage": "https://frappebooks.com", - "lint-staged": { - "*.{js,vue}": "vue-cli-service lint" - }, "repository": { "url": "https://github.com/frappe/books" - } + }, + "license": "AGPL-3.0-only" } diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 0ca6eee16..000000000 --- a/public/index.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - - - Frappe Books - - - - - - diff --git a/reports/AccountReport.ts b/reports/AccountReport.ts index 0ead699df..2f0bba605 100644 --- a/reports/AccountReport.ts +++ b/reports/AccountReport.ts @@ -34,11 +34,11 @@ export const ACC_BAL_WIDTH = 1.25; export abstract class AccountReport extends LedgerReport { toDate?: string; - count: number = 3; + count = 3; fromYear?: number; toYear?: number; - consolidateColumns: boolean = false; - hideGroupAmounts: boolean = false; + consolidateColumns = false; + hideGroupAmounts = false; periodicity: Periodicity = 'Monthly'; basedOn: BasedOn = 'Until Date'; @@ -88,10 +88,7 @@ export abstract class AccountReport extends LedgerReport { }; } - async getTotalNode( - rootNode: AccountTreeNode, - name: string - ): Promise { + getTotalNode(rootNode: AccountTreeNode, name: string): AccountListNode { const accountTree = { [rootNode.name]: rootNode }; const leafNodes = getListOfLeafNodes(accountTree) as AccountTreeNode[]; @@ -153,7 +150,7 @@ export abstract class AccountReport extends LedgerReport { ): Promise { const accountValueMap: AccountNameValueMapMap = new Map(); if (!this.accountMap) { - await this._getAccountMap(); + await this._setAndReturnAccountMap(); } for (const account of map.keys()) { @@ -169,17 +166,17 @@ export abstract class AccountReport extends LedgerReport { } if (!this.accountMap?.[entry.account]) { - this._getAccountMap(true); + await this._setAndReturnAccountMap(true); } - const totalBalance = valueMap.get(key!)?.balance ?? 0; + const totalBalance = valueMap.get(key)?.balance ?? 0; const balance = (entry.debit ?? 0) - (entry.credit ?? 0); const rootType = this.accountMap![entry.account]?.rootType; if (isCredit(rootType)) { - valueMap.set(key!, { balance: totalBalance - balance }); + valueMap.set(key, { balance: totalBalance - balance }); } else { - valueMap.set(key!, { balance: totalBalance + balance }); + valueMap.set(key, { balance: totalBalance + balance }); } } accountValueMap.set(account, valueMap); @@ -189,7 +186,9 @@ export abstract class AccountReport extends LedgerReport { } async _getAccountTree(rangeGroupedMap: AccountNameValueMapMap) { - const accountTree = cloneDeep(await this._getAccountMap()) as AccountTree; + const accountTree = cloneDeep( + await this._setAndReturnAccountMap() + ) as AccountTree; setPruneFlagOnAccountTreeNodes(accountTree); setValueMapOnAccountTreeNodes(accountTree, rangeGroupedMap); @@ -200,7 +199,7 @@ export abstract class AccountReport extends LedgerReport { return accountTree; } - async _getAccountMap(force: boolean = false) { + async _setAndReturnAccountMap(force = false) { if (this.accountMap && !force) { return this.accountMap; } @@ -510,14 +509,14 @@ function updateParentAccountWithChildValues( parentAccount.valueMap ??= new Map(); for (const key of valueMap.keys()) { - const value = parentAccount.valueMap!.get(key); + const value = parentAccount.valueMap.get(key); const childValue = valueMap.get(key); const map: Record = {}; for (const key of Object.keys(childValue!)) { map[key] = (value?.[key] ?? 0) + (childValue?.[key] ?? 0); } - parentAccount.valueMap!.set(key, map); + parentAccount.valueMap.set(key, map); } return parentAccount.parentAccount!; @@ -533,7 +532,7 @@ function setChildrenOnAccountTreeNodes(accountTree: AccountTree) { } accountTree[ac.parentAccount].children ??= []; - accountTree[ac.parentAccount].children!.push(ac!); + accountTree[ac.parentAccount].children!.push(ac); parentNodes.add(ac.parentAccount); } diff --git a/reports/BalanceSheet/BalanceSheet.ts b/reports/BalanceSheet/BalanceSheet.ts index b730e3d74..b4650b0c3 100644 --- a/reports/BalanceSheet/BalanceSheet.ts +++ b/reports/BalanceSheet/BalanceSheet.ts @@ -13,7 +13,7 @@ import { getMapFromList } from 'utils'; export class BalanceSheet extends AccountReport { static title = t`Balance Sheet`; static reportName = 'balance-sheet'; - loading: boolean = false; + loading = false; get rootTypes(): AccountRootType[] { return [ @@ -54,15 +54,15 @@ export class BalanceSheet extends AccountReport { }) .filter((row) => !!row.rootNode); - this.reportData = await this.getReportDataFromRows( + this.reportData = this.getReportDataFromRows( getMapFromList(rootTypeRows, 'rootType') ); this.loading = false; } - async getReportDataFromRows( + getReportDataFromRows( rootTypeRows: Record - ): Promise { + ): ReportData { const typeNameList = [ { rootType: AccountRootTypeEnum.Asset, @@ -89,7 +89,7 @@ export class BalanceSheet extends AccountReport { reportData.push(...row.rows); if (row.rootNode) { - const totalNode = await this.getTotalNode(row.rootNode, totalName); + const totalNode = this.getTotalNode(row.rootNode, totalName); const totalRow = this.getRowFromAccountListNode(totalNode); reportData.push(totalRow); } diff --git a/reports/GeneralLedger/GeneralLedger.ts b/reports/GeneralLedger/GeneralLedger.ts index 6c3c8c7c7..1752c9933 100644 --- a/reports/GeneralLedger/GeneralLedger.ts +++ b/reports/GeneralLedger/GeneralLedger.ts @@ -24,11 +24,11 @@ type ReferenceType = export class GeneralLedger extends LedgerReport { static title = t`General Ledger`; static reportName = 'general-ledger'; - usePagination: boolean = true; - loading: boolean = false; + usePagination = true; + loading = false; - ascending: boolean = false; - reverted: boolean = false; + ascending = false; + reverted = false; referenceType: ReferenceType = 'All'; groupBy: 'none' | 'party' | 'account' | 'referenceName' = 'none'; _rawData: LedgerEntry[] = []; @@ -37,7 +37,7 @@ export class GeneralLedger extends LedgerReport { super(fyo); } - async setDefaultFilters() { + setDefaultFilters() { if (!this.toDate) { this.toDate = DateTime.now().plus({ days: 1 }).toISODate(); this.fromDate = DateTime.now().minus({ years: 1 }).toISODate(); @@ -239,7 +239,7 @@ export class GeneralLedger extends LedgerReport { return { totalDebit, totalCredit }; } - async _getQueryFilters(): Promise { + _getQueryFilters(): QueryFilter { const filters: QueryFilter = {}; const stringFilters = ['account', 'party', 'referenceName']; diff --git a/reports/GoodsAndServiceTax/BaseGSTR.ts b/reports/GoodsAndServiceTax/BaseGSTR.ts index 245bec083..beebaaac4 100644 --- a/reports/GoodsAndServiceTax/BaseGSTR.ts +++ b/reports/GoodsAndServiceTax/BaseGSTR.ts @@ -17,9 +17,9 @@ export abstract class BaseGSTR extends Report { toDate?: string; fromDate?: string; transferType?: TransferType; - usePagination: boolean = true; + usePagination = true; gstrRows?: GSTRRow[]; - loading: boolean = false; + loading = false; abstract gstrType: GSTRType; @@ -111,7 +111,7 @@ export abstract class BaseGSTR extends Report { return (row) => row.rate === 0; // this takes care of both nil rated, exempted goods } - return (_) => true; + return () => true; } async getEntries() { @@ -133,7 +133,7 @@ export abstract class BaseGSTR extends Report { const entries = await this.getEntries(); const gstrRows: GSTRRow[] = []; for (const entry of entries) { - const gstrRow = await this.getGstrRow(entry.name as string); + const gstrRow = await this.getGstrRow(entry.name); gstrRows.push(gstrRow); } return gstrRows; @@ -149,7 +149,7 @@ export abstract class BaseGSTR extends Report { 'gstin' )) as string | null; - const party = (await this.fyo.doc.getDoc('Party', entry.party!)) as Party; + const party = (await this.fyo.doc.getDoc('Party', entry.party)) as Party; let place = ''; if (party.address) { @@ -216,7 +216,7 @@ export abstract class BaseGSTR extends Report { } } - async setDefaultFilters() { + setDefaultFilters() { if (!this.toDate) { this.toDate = DateTime.local().toISODate(); } diff --git a/reports/GoodsAndServiceTax/gstExporter.ts b/reports/GoodsAndServiceTax/gstExporter.ts index 62fdec320..972fc5dd3 100644 --- a/reports/GoodsAndServiceTax/gstExporter.ts +++ b/reports/GoodsAndServiceTax/gstExporter.ts @@ -175,7 +175,7 @@ async function getCanExport(report: BaseGSTR) { return true; } - showDialog({ + await showDialog({ title: report.fyo.t`Cannot Export`, detail: report.fyo.t`Please set GSTIN in General Settings.`, type: 'error', @@ -204,7 +204,7 @@ export async function getGstrJsonData(report: BaseGSTR): Promise { } else if (transferType === TransferTypeEnum.B2CL) { gstData.b2cl = await generateB2clData(report); } else if (transferType === TransferTypeEnum.B2CS) { - gstData.b2cs = await generateB2csData(report); + gstData.b2cs = generateB2csData(report); } return JSON.stringify(gstData); diff --git a/reports/LedgerReport.ts b/reports/LedgerReport.ts index 8a7d6231e..4b1c280a8 100644 --- a/reports/LedgerReport.ts +++ b/reports/LedgerReport.ts @@ -14,7 +14,7 @@ export abstract class LedgerReport extends Report { static reportName = 'general-ledger'; _rawData: LedgerEntry[] = []; - shouldRefresh: boolean = false; + shouldRefresh = false; constructor(fyo: Fyo) { super(fyo); @@ -117,7 +117,7 @@ export abstract class LedgerReport extends Report { }); } - abstract _getQueryFilters(): Promise; + abstract _getQueryFilters(): QueryFilter | Promise; getActions(): Action[] { return getCommonExportActions(this); diff --git a/reports/ProfitAndLoss/ProfitAndLoss.ts b/reports/ProfitAndLoss/ProfitAndLoss.ts index 1df9c5c33..859a3e420 100644 --- a/reports/ProfitAndLoss/ProfitAndLoss.ts +++ b/reports/ProfitAndLoss/ProfitAndLoss.ts @@ -17,7 +17,7 @@ import { export class ProfitAndLoss extends AccountReport { static title = t`Profit And Loss`; static reportName = 'profit-and-loss'; - loading: boolean = false; + loading = false; get rootTypes(): AccountRootType[] { return [AccountRootTypeEnum.Income, AccountRootTypeEnum.Expense]; @@ -62,7 +62,7 @@ export class ProfitAndLoss extends AccountReport { const expenseList = convertAccountRootNodeToAccountList(expenseRoot); const expenseRows = this.getReportRowsFromAccountList(expenseList); - this.reportData = await this.getReportDataFromRows( + this.reportData = this.getReportDataFromRows( incomeRows, expenseRows, incomeRoot, @@ -71,14 +71,14 @@ export class ProfitAndLoss extends AccountReport { this.loading = false; } - async getReportDataFromRows( + getReportDataFromRows( incomeRows: ReportData, expenseRows: ReportData, incomeRoot: AccountTreeNode | undefined, expenseRoot: AccountTreeNode | undefined - ): Promise { + ): ReportData { if (incomeRoot && !expenseRoot) { - return await this.getIncomeOrExpenseRows( + return this.getIncomeOrExpenseRows( incomeRoot, incomeRows, t`Total Income (Credit)` @@ -86,7 +86,7 @@ export class ProfitAndLoss extends AccountReport { } if (expenseRoot && !incomeRoot) { - return await this.getIncomeOrExpenseRows( + return this.getIncomeOrExpenseRows( expenseRoot, expenseRows, t`Total Income (Credit)` @@ -97,7 +97,7 @@ export class ProfitAndLoss extends AccountReport { return []; } - return await this.getIncomeAndExpenseRows( + return this.getIncomeAndExpenseRows( incomeRows, expenseRows, incomeRoot, @@ -105,30 +105,27 @@ export class ProfitAndLoss extends AccountReport { ); } - async getIncomeOrExpenseRows( + getIncomeOrExpenseRows( root: AccountTreeNode, rows: ReportData, totalRowName: string - ): Promise { - const total = await this.getTotalNode(root, totalRowName); + ): ReportData { + const total = this.getTotalNode(root, totalRowName); const totalRow = this.getRowFromAccountListNode(total); return [rows, totalRow].flat(); } - async getIncomeAndExpenseRows( + getIncomeAndExpenseRows( incomeRows: ReportData, expenseRows: ReportData, incomeRoot: AccountTreeNode, expenseRoot: AccountTreeNode ) { - const totalIncome = await this.getTotalNode( - incomeRoot, - t`Total Income (Credit)` - ); + const totalIncome = this.getTotalNode(incomeRoot, t`Total Income (Credit)`); const totalIncomeRow = this.getRowFromAccountListNode(totalIncome); - const totalExpense = await this.getTotalNode( + const totalExpense = this.getTotalNode( expenseRoot, t`Total Expense (Debit)` ); diff --git a/reports/Report.ts b/reports/Report.ts index 744ac7706..53d25bcee 100644 --- a/reports/Report.ts +++ b/reports/Report.ts @@ -10,14 +10,14 @@ import { ColumnField, ReportData } from './types'; export abstract class Report extends Observable { static title: string; static reportName: string; - static isInventory: boolean = false; + static isInventory = false; fyo: Fyo; columns: ColumnField[] = []; filters: Field[] = []; reportData: ReportData; - usePagination: boolean = false; - shouldRefresh: boolean = false; + usePagination = false; + shouldRefresh = false; abstract loading: boolean; constructor(fyo: Fyo) { @@ -26,14 +26,12 @@ export abstract class Report extends Observable { this.reportData = []; } - get title() { - // @ts-ignore - return this.constructor.title; + get title(): string { + return (this.constructor as typeof Report).title; } - get reportName() { - // @ts-ignore - return this.constructor.reportName; + get reportName(): string { + return (this.constructor as typeof Report).reportName; } async initialize() { @@ -61,7 +59,7 @@ export abstract class Report extends Observable { return filterMap; } - async set(key: string, value: DocValue, callPostSet: boolean = true) { + async set(key: string, value: DocValue, callPostSet = true) { const field = this.filters.find((f) => f.fieldname === key); if (field === undefined) { return; @@ -95,7 +93,7 @@ export abstract class Report extends Observable { * Should first check if filter value is set * and update only if it is not set. */ - async setDefaultFilters() {} + abstract setDefaultFilters(): void | Promise; abstract getActions(): Action[]; abstract getFilters(): Field[] | Promise; abstract getColumns(): ColumnField[] | Promise; diff --git a/reports/TrialBalance/TrialBalance.ts b/reports/TrialBalance/TrialBalance.ts index 82b2f4945..edc3047c5 100644 --- a/reports/TrialBalance/TrialBalance.ts +++ b/reports/TrialBalance/TrialBalance.ts @@ -35,8 +35,8 @@ export class TrialBalance extends AccountReport { fromDate?: string; toDate?: string; - hideGroupAmounts: boolean = false; - loading: boolean = false; + hideGroupAmounts = false; + loading = false; _rawData: LedgerEntry[] = []; _dateRanges?: DateRange[]; @@ -79,6 +79,7 @@ export class TrialBalance extends AccountReport { this.loading = false; } + // eslint-disable-next-line @typescript-eslint/require-await async getReportDataFromRows( rootTypeRows: RootTypeRow[] ): Promise { @@ -93,6 +94,7 @@ export class TrialBalance extends AccountReport { return reportData; } + // eslint-disable-next-line @typescript-eslint/require-await async _getGroupedByDateRanges( map: GroupedMap ): Promise { @@ -108,11 +110,11 @@ export class TrialBalance extends AccountReport { const key = this._getRangeMapKey(entry); if (key === null) { throw new ValueError( - `invalid entry in trial balance ${entry.date?.toISOString()}` + `invalid entry in trial balance ${entry.date?.toISOString() ?? ''}` ); } - const map = valueMap.get(key!); + const map = valueMap.get(key); const totalCredit = map?.credit ?? 0; const totalDebit = map?.debit ?? 0; @@ -188,6 +190,7 @@ export class TrialBalance extends AccountReport { } as ReportRow; } + // eslint-disable-next-line @typescript-eslint/require-await async _getQueryFilters(): Promise { const filters: QueryFilter = {}; filters.reverted = false; diff --git a/reports/commonExporter.ts b/reports/commonExporter.ts index d4cdcdc5e..ea55aa5d4 100644 --- a/reports/commonExporter.ts +++ b/reports/commonExporter.ts @@ -1,4 +1,4 @@ -import { Fyo, t } from 'fyo'; +import { t } from 'fyo'; import { Action } from 'fyo/model/types'; import { Verb } from 'fyo/telemetry/types'; import { getSavePath, saveData, showExportInFolder } from 'src/utils/ipcCalls'; @@ -88,7 +88,7 @@ function getJsonData(report: Report): string { } const rowObj: Record = {}; - for (const c in row.cells) { + for (let c = 0; c < row.cells.length; c++) { const { label } = columns[c]; const cell = getValueFromCell(row.cells[c], displayPrecision); rowObj[label] = cell; @@ -129,7 +129,7 @@ function convertReportToCSVMatrix(report: Report): unknown[][] { const displayPrecision = (report.fyo.singles.SystemSettings?.displayPrecision as number) ?? 2; const reportData = report.reportData; - const columns = report.columns!; + const columns = report.columns; const csvdata: unknown[][] = []; csvdata.push(columns.map((c) => c.label)); @@ -140,7 +140,7 @@ function convertReportToCSVMatrix(report: Report): unknown[][] { } const csvrow: unknown[] = []; - for (const c in row.cells) { + for (let c = 0; c < row.cells.length; c++) { const cell = getValueFromCell(row.cells[c], displayPrecision); csvrow.push(cell); } diff --git a/reports/inventory/StockBalance.ts b/reports/inventory/StockBalance.ts index cf6795e86..49096868d 100644 --- a/reports/inventory/StockBalance.ts +++ b/reports/inventory/StockBalance.ts @@ -13,9 +13,9 @@ export class StockBalance extends StockLedger { static reportName = 'stock-balance'; static isInventory = true; - override ascending: boolean = true; + override ascending = true; override referenceType: ReferenceType = 'All'; - override referenceName: string = ''; + override referenceName = ''; override async _getReportData(force?: boolean): Promise { if (this.shouldRefresh || force || !this._rawData?.length) { diff --git a/reports/inventory/StockLedger.ts b/reports/inventory/StockLedger.ts index 675a8085d..7d80e3ce8 100644 --- a/reports/inventory/StockLedger.ts +++ b/reports/inventory/StockLedger.ts @@ -19,11 +19,11 @@ export class StockLedger extends Report { static reportName = 'stock-ledger'; static isInventory = true; - usePagination: boolean = true; + usePagination = true; _rawData?: ComputedStockLedgerEntry[]; - loading: boolean = false; - shouldRefresh: boolean = false; + loading = false; + shouldRefresh = false; item?: string; location?: string; @@ -52,7 +52,7 @@ export class StockLedger extends Report { this._setObservers(); } - async setDefaultFilters() { + setDefaultFilters() { if (!this.toDate) { this.toDate = DateTime.now().plus({ days: 1 }).toISODate(); this.fromDate = DateTime.now().minus({ years: 1 }).toISODate(); @@ -91,9 +91,8 @@ export class StockLedger extends Report { async _setRawData() { const valuationMethod = - (this.fyo.singles.InventorySettings?.valuationMethod as - | ValuationMethod - | undefined) ?? ValuationMethod.FIFO; + this.fyo.singles.InventorySettings?.valuationMethod ?? + ValuationMethod.FIFO; const rawSLEs = await getRawStockLedgerEntries(this.fyo); this._rawData = getStockLedgerEntries(rawSLEs, valuationMethod); @@ -113,7 +112,7 @@ export class StockLedger extends Report { } let i = 0; - for (const idx in rawData) { + for (let idx = 0; idx < rawData.length; idx++) { const row = rawData[idx]; if (this.item && row.item !== this.item) { continue; diff --git a/schemas/index.ts b/schemas/index.ts index eccd6b394..37ab4f72f 100644 --- a/schemas/index.ts +++ b/schemas/index.ts @@ -12,7 +12,7 @@ const NAME_FIELD = { readOnly: true, }; -export function getSchemas(countryCode: string = '-'): Readonly { +export function getSchemas(countryCode = '-'): Readonly { const builtCoreSchemas = getCoreSchemas(); const builtAppSchemas = getAppSchemas(countryCode); @@ -209,14 +209,14 @@ export function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap { for (const name of extendingSchemaNames) { const extendingSchema = schemas[name] as Schema; - const abstractSchema = schemas[extendingSchema.extends!] as SchemaStub; + const abstractSchema = schemas[extendingSchema.extends!]; schemaMap[name] = getCombined(extendingSchema, abstractSchema) as Schema; } - for (const name in abstractSchemaNames) { + abstractSchemaNames.forEach((name) => { delete schemaMap[name]; - } + }); return schemaMap; } diff --git a/scripts/generateTranslations.ts b/scripts/generateTranslations.ts index 6460141c8..a0b743e8d 100644 --- a/scripts/generateTranslations.ts +++ b/scripts/generateTranslations.ts @@ -8,6 +8,8 @@ import { schemaTranslateables, } from '../utils/translationHelpers'; +/* eslint-disable no-console, @typescript-eslint/no-floating-promises */ + const translationsFolder = path.resolve(__dirname, '..', 'translations'); const PATTERN = /(? { const contents: string[] = await fs.readdir(root); const files: string[] = []; @@ -86,7 +88,7 @@ function getTStrings(content: string): Promise { } function tStringFinder(content: string): string[] { - return [...content.matchAll(PATTERN)].map(([_, t]) => { + return [...content.matchAll(PATTERN)].map(([, t]) => { t = getIndexFormat(t); return getWhitespaceSanitized(t); }); diff --git a/scripts/profile.ts b/scripts/profile.ts index 34c4557ec..6120a2ef3 100644 --- a/scripts/profile.ts +++ b/scripts/profile.ts @@ -19,4 +19,5 @@ async function run() { await unlink(dbPath); } +// eslint-disable-next-line @typescript-eslint/no-floating-promises run(); diff --git a/scripts/publish-mac-arm.sh b/scripts/publish-mac-arm.sh index d08e28577..deeec3bc2 100755 --- a/scripts/publish-mac-arm.sh +++ b/scripts/publish-mac-arm.sh @@ -3,14 +3,10 @@ set -e # Check node and yarn versions -NODE_VERSION=$(node --version) YARN_VERSION=$(yarn --version) if [ "$YARN_VERSION" != "1.22.18" ]; then echo "Incorrect yarn version: $YARN_VERSION" exit 1 -elif [ "$NODE_VERSION" != "v16.13.1" ]; then - echo "Incorrect node version: $NODE_VERSION" - exit 1 fi # Source secrets @@ -42,6 +38,6 @@ export GH_TOKEN=$GH_TOKEN && export APPLE_ID=$APPLE_ID && export APPLE_TEAM_ID=$APPLE_TEAM_ID && export APPLE_APP_SPECIFIC_PASSWORD=$APPLE_APP_SPECIFIC_PASSWORD && - yarn electron:build --mac --publish=always + yarn build --mac --publish=always cd ../books diff --git a/src/App.vue b/src/App.vue index fc1471ff7..58a953710 100644 --- a/src/App.vue +++ b/src/App.vue @@ -12,13 +12,13 @@ /> diff --git a/src/components/Charts/DonutChart.vue b/src/components/Charts/DonutChart.vue index 783b6d253..706d68f6e 100644 --- a/src/components/Charts/DonutChart.vue +++ b/src/components/Charts/DonutChart.vue @@ -22,12 +22,6 @@ :cx="cx" :cy="cy" :r="radius" - @mouseover=" - $emit( - 'change', - thetasAndStarts.length === 1 ? thetasAndStarts[0][0] : null - ) - " :stroke-width=" thickness + (hasNonZeroValues && active === thetasAndStarts[0][0] ? 4 : 0) @@ -38,12 +32,18 @@ :class="hasNonZeroValues ? 'sector' : ''" :style="{ transformOrigin: `${cx}px ${cy}px` }" fill="transparent" + @mouseover=" + $emit( + 'change', + thetasAndStarts.length === 1 ? thetasAndStarts[0][0] : null + ) + " />