diff --git a/backend/database/core.ts b/backend/database/core.ts index 41e0a590..3fc19d5a 100644 --- a/backend/database/core.ts +++ b/backend/database/core.ts @@ -1,8 +1,6 @@ import { CannotCommitError, - DatabaseError, - DuplicateEntryError, - LinkValidationError, + getDbError, NotFoundError, ValueError, } from 'fyo/utils/errors'; @@ -98,7 +96,7 @@ export default class DatabaseCore extends DatabaseBase { async connect() { this.knex = knex(this.connectionParams); this.knex.on('query-error', (error) => { - error.type = this.#getError(error); + error.type = getDbError(error); }); await this.knex.raw('PRAGMA foreign_keys=ON'); } @@ -116,7 +114,7 @@ export default class DatabaseCore extends DatabaseBase { try { // await this.knex!.raw('commit'); } catch (err) { - const type = this.#getError(err as Error); + const type = getDbError(err as Error); if (type !== CannotCommitError) { throw err; } @@ -155,7 +153,7 @@ export default class DatabaseCore extends DatabaseBase { } row = await qb.limit(1); } catch (err) { - if (this.#getError(err as Error) !== NotFoundError) { + if (getDbError(err as Error) !== NotFoundError) { throw err; } } @@ -293,7 +291,7 @@ export default class DatabaseCore extends DatabaseBase { try { values = await builder.select('fieldname', 'value', 'parent'); } catch (err) { - if (this.#getError(err as Error) === NotFoundError) { + if (getDbError(err as Error) === NotFoundError) { return []; } @@ -364,23 +362,6 @@ export default class DatabaseCore extends DatabaseBase { this.prestigeTheTable(schemaName, tableRows); } - #getError(err: Error) { - let errorType = DatabaseError; - if (err.message.includes('SQLITE_ERROR: no such table')) { - errorType = NotFoundError; - } - if (err.message.includes('FOREIGN KEY')) { - errorType = LinkValidationError; - } - if (err.message.includes('SQLITE_ERROR: cannot commit')) { - errorType = CannotCommitError; - } - if (err.message.includes('SQLITE_CONSTRAINT: UNIQUE constraint failed:')) { - errorType = DuplicateEntryError; - } - return errorType; - } - async prestigeTheTable(schemaName: string, tableRows: FieldValueMap[]) { const max = 200; diff --git a/fyo/model/validationFunction.ts b/fyo/model/validationFunction.ts index 8471dd2b..858198f5 100644 --- a/fyo/model/validationFunction.ts +++ b/fyo/model/validationFunction.ts @@ -36,7 +36,6 @@ export function validateOptions(field: OptionField, value: string, doc: Doc) { return; } - const labels = options.map((o) => o.label).join(', '); throw new ValueError(t`Invalid value ${value} for ${field.label}`); } diff --git a/fyo/utils/errors.ts b/fyo/utils/errors.ts index 7923ecb5..e401a403 100644 --- a/fyo/utils/errors.ts +++ b/fyo/utils/errors.ts @@ -76,3 +76,27 @@ export class NotImplemented extends BaseError { export class ValueError extends ValidationError {} export class ConflictError extends ValidationError {} export class InvalidFieldError extends ValidationError {} + +export function getDbError(err: Error) { + if (!err.message) { + return DatabaseError; + } + + if (err.message.includes('SQLITE_ERROR: no such table')) { + return NotFoundError; + } + + if (err.message.includes('FOREIGN KEY')) { + return LinkValidationError; + } + + if (err.message.includes('SQLITE_ERROR: cannot commit')) { + return CannotCommitError; + } + + if (err.message.includes('SQLITE_CONSTRAINT: UNIQUE constraint failed:')) { + return DuplicateEntryError; + } + + return DatabaseError; +} diff --git a/reports/AccountReport.ts b/reports/AccountReport.ts index a6ab85bf..49850b3b 100644 --- a/reports/AccountReport.ts +++ b/reports/AccountReport.ts @@ -50,7 +50,7 @@ export abstract class AccountReport extends LedgerReport { async setDefaultFilters(): Promise { if (this.basedOn === 'Until Date' && !this.toDate) { - this.toDate = DateTime.now().toISODate(); + this.toDate = DateTime.now().plus({ days: 1 }).toISODate(); } if (this.basedOn === 'Fiscal Year' && !this.toYear) { diff --git a/reports/BalanceSheet/BalanceSheet.ts b/reports/BalanceSheet/BalanceSheet.ts index 9129f899..b730e3d7 100644 --- a/reports/BalanceSheet/BalanceSheet.ts +++ b/reports/BalanceSheet/BalanceSheet.ts @@ -23,9 +23,9 @@ export class BalanceSheet extends AccountReport { ]; } - async setReportData(filter?: string) { + async setReportData(filter?: string, force?: boolean) { this.loading = true; - if (filter !== 'hideGroupAmounts') { + if (force || filter !== 'hideGroupAmounts') { await this._setRawData(); } @@ -86,11 +86,14 @@ export class BalanceSheet extends AccountReport { continue; } - const totalNode = await this.getTotalNode(row.rootNode, totalName); - const totalRow = this.getRowFromAccountListNode(totalNode); - reportData.push(...row.rows); - reportData.push(totalRow); + + if (row.rootNode) { + const totalNode = await this.getTotalNode(row.rootNode, totalName); + const totalRow = this.getRowFromAccountListNode(totalNode); + reportData.push(totalRow); + } + reportData.push(emptyRow); } diff --git a/reports/GeneralLedger/GeneralLedger.ts b/reports/GeneralLedger/GeneralLedger.ts index 0c4bc40c..bb6f122a 100644 --- a/reports/GeneralLedger/GeneralLedger.ts +++ b/reports/GeneralLedger/GeneralLedger.ts @@ -37,15 +37,15 @@ export class GeneralLedger extends LedgerReport { async setDefaultFilters() { if (!this.toDate) { - this.toDate = DateTime.now().toISODate(); + this.toDate = DateTime.now().plus({ days: 1 }).toISODate(); this.fromDate = DateTime.now().minus({ years: 1 }).toISODate(); } } - async setReportData(filter?: string) { + async setReportData(filter?: string, force?: boolean) { this.loading = true; let sort = true; - if (filter !== 'grouped' || this._rawData.length === 0) { + if (force || filter !== 'grouped' || this._rawData.length === 0) { await this._setRawData(); sort = false; } @@ -141,6 +141,10 @@ export class GeneralLedger extends LedgerReport { value = String(value); } + if (fieldname === 'referenceType') { + value = this.fyo.schemaMap[value]?.label ?? value; + } + row.cells.push({ italics: entry.name === -1, bold: entry.name === -2, diff --git a/reports/LedgerReport.ts b/reports/LedgerReport.ts index 95832265..da8bd72b 100644 --- a/reports/LedgerReport.ts +++ b/reports/LedgerReport.ts @@ -1,4 +1,4 @@ -import { t } from 'fyo'; +import { Fyo, t } from 'fyo'; import { Action } from 'fyo/model/types'; import { ModelNameEnum } from 'models/types'; import { Report } from 'reports/Report'; @@ -13,6 +13,26 @@ export abstract class LedgerReport extends Report { static reportName = 'general-ledger'; _rawData: LedgerEntry[] = []; + shouldRefresh: boolean = false; + + constructor(fyo: Fyo) { + super(fyo); + this._setObservers(); + } + + _setObservers() { + const listener = () => (this.shouldRefresh = true); + + this.fyo.doc.observer.on( + `sync:${ModelNameEnum.AccountingLedgerEntry}`, + listener + ); + + this.fyo.doc.observer.on( + `delete:${ModelNameEnum.AccountingLedgerEntry}`, + listener + ); + } _getGroupByKey() { let groupBy: GroupByKey = 'referenceName'; diff --git a/reports/ProfitAndLoss/ProfitAndLoss.ts b/reports/ProfitAndLoss/ProfitAndLoss.ts index 40f5f67b..5624488d 100644 --- a/reports/ProfitAndLoss/ProfitAndLoss.ts +++ b/reports/ProfitAndLoss/ProfitAndLoss.ts @@ -23,9 +23,9 @@ export class ProfitAndLoss extends AccountReport { return [AccountRootTypeEnum.Income, AccountRootTypeEnum.Expense]; } - async setReportData(filter?: string) { + async setReportData(filter?: string, force?: boolean) { this.loading = true; - if (filter !== 'hideGroupAmounts') { + if (force || filter !== 'hideGroupAmounts') { await this._setRawData(); } @@ -77,6 +77,10 @@ export class ProfitAndLoss extends AccountReport { incomeRoot: AccountTreeNode, expenseRoot: AccountTreeNode ): Promise { + if (!incomeRoot || !expenseRoot) { + return []; + } + const totalIncome = await this.getTotalNode( incomeRoot, t`Total Income (Credit)` diff --git a/reports/Report.ts b/reports/Report.ts index b264f540..44ed3086 100644 --- a/reports/Report.ts +++ b/reports/Report.ts @@ -14,6 +14,7 @@ export abstract class Report extends Observable { filters: Field[] = []; reportData: ReportData; usePagination: boolean = false; + shouldRefresh: boolean = false; abstract loading: boolean; constructor(fyo: Fyo) { @@ -75,15 +76,15 @@ export abstract class Report extends Observable { } if (callPostSet) { - await this.postSet(key); + await this.updateData(key); } } - async postSet(key?: string) { + async updateData(key?: string, force?: boolean) { await this.setDefaultFilters(); this.filters = await this.getFilters(); this.columns = await this.getColumns(); - await this.setReportData(key); + await this.setReportData(key, force); } /** @@ -94,5 +95,5 @@ export abstract class Report extends Observable { abstract getActions(): Action[]; abstract getFilters(): Field[] | Promise; abstract getColumns(): ColumnField[] | Promise; - abstract setReportData(filter?: string): Promise; + abstract setReportData(filter?: string, force?: boolean): Promise; } diff --git a/reports/TrialBalance/TrialBalance.ts b/reports/TrialBalance/TrialBalance.ts index 75be94ee..194c19c8 100644 --- a/reports/TrialBalance/TrialBalance.ts +++ b/reports/TrialBalance/TrialBalance.ts @@ -53,9 +53,9 @@ export class TrialBalance extends AccountReport { ]; } - async setReportData(filter?: string) { + async setReportData(filter?: string, force?: boolean) { this.loading = true; - if (filter !== 'hideGroupAmounts') { + if (force || filter !== 'hideGroupAmounts') { await this._setRawData(); } diff --git a/src/errorHandling.ts b/src/errorHandling.ts index dff40664..194445cb 100644 --- a/src/errorHandling.ts +++ b/src/errorHandling.ts @@ -11,7 +11,7 @@ import { truncate } from 'lodash'; import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; import { fyo } from './initFyo'; import router from './router'; -import { getErrorMessage } from './utils'; +import { getErrorMessage, stringifyCircular } from './utils'; import { MessageDialogOptions, ToastOptions } from './utils/types'; import { showMessageDialog, showToast } from './utils/ui'; @@ -30,7 +30,7 @@ async function reportError(errorLogObj: ErrorLog) { error_name: errorLogObj.name, message: errorLogObj.message, stack: errorLogObj.stack, - more: JSON.stringify(errorLogObj.more ?? {}), + more: stringifyCircular(errorLogObj.more ?? {}), }; if (fyo.store.isDevelopment) { diff --git a/src/pages/Dashboard/Cashflow.vue b/src/pages/Dashboard/Cashflow.vue index ba5798e7..d4b7a721 100644 --- a/src/pages/Dashboard/Cashflow.vue +++ b/src/pages/Dashboard/Cashflow.vue @@ -77,7 +77,7 @@ export default { let colors = ['#2490EF', '#B7BFC6']; if (!this.hasData) { data = dummyData; - colors = ['#E9EBED', '#B7BFC6']; + colors = ['#E9EBED', '#DFE1E2']; } const xLabels = data.map((cf) => cf['yearmonth']); diff --git a/src/pages/Report.vue b/src/pages/Report.vue index b74f7ca5..ecbb5602 100644 --- a/src/pages/Report.vue +++ b/src/pages/Report.vue @@ -74,7 +74,7 @@ export default defineComponent({ } if (filterKeys.length) { - await this.report.postSet() + await this.report.postSet(); } if (fyo.store.isDevelopment) { @@ -117,6 +117,8 @@ export default defineComponent({ if (!this.report.reportData.length) { await this.report.setReportData(); + } else if (this.report.shouldRefresh) { + await this.report.setReportData(undefined, true); } }, }, diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 1fb102d8..6f416ea6 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -9,7 +9,7 @@ import { fyo } from 'src/initFyo'; export function getDatesAndPeriodList( period: 'This Year' | 'This Quarter' | 'This Month' ): { periodList: DateTime[]; fromDate: DateTime; toDate: DateTime } { - const toDate: DateTime = DateTime.now(); + const toDate: DateTime = DateTime.now().plus({ days: 1 }); let fromDate: DateTime; if (period === 'This Year') { diff --git a/src/utils/search.ts b/src/utils/search.ts index df34f92f..6a9eb19d 100644 --- a/src/utils/search.ts +++ b/src/utils/search.ts @@ -417,7 +417,7 @@ export class Search { _setFilterDefaults() { const totalChildKeywords = Object.values(this.searchables) .filter((s) => s.isChild) - .map((s) => this.keywords[s.schemaName].length) + .map((s) => this.keywords[s.schemaName]?.length ?? 0) .reduce((a, b) => a + b); if (totalChildKeywords > 2_000) { diff --git a/src/utils/ui.ts b/src/utils/ui.ts index 553e185f..24b9e1ab 100644 --- a/src/utils/ui.ts +++ b/src/utils/ui.ts @@ -7,6 +7,7 @@ import { t } from 'fyo'; import { Doc } from 'fyo/model/doc'; import { Action } from 'fyo/model/types'; import { getActions } from 'fyo/utils'; +import { getDbError, LinkValidationError } from 'fyo/utils/errors'; import { ModelNameEnum } from 'models/types'; import { handleErrorWithDialog } from 'src/errorHandling'; import { fyo } from 'src/initFyo'; @@ -106,7 +107,7 @@ export async function showToast(options: ToastOptions) { } // @ts-ignore -window.st = showToast +window.st = showToast; function replaceAndAppendMount(app: App, replaceId: string) { const fragment = document.createDocumentFragment(); @@ -162,7 +163,15 @@ export async function deleteDocWithPrompt(doc: Doc) { await doc.delete(); return true; } catch (err) { - handleErrorWithDialog(err as Error, doc); + if (getDbError(err as Error) === LinkValidationError) { + showMessageDialog({ + message: t`Delete Failed`, + detail: t`Cannot delete ${schemaLabel} ${doc.name!} because of linked entries.`, + }); + } else { + handleErrorWithDialog(err as Error, doc); + } + return false; } },