2
0
mirror of https://github.com/frappe/books.git synced 2024-12-22 19:09:01 +00:00

fix(ux): display can't delete cause link

- update reports on ledger entries
- pull data until .plus({days: 1}) to account for all entries of the day
This commit is contained in:
18alantom 2022-05-30 17:04:25 +05:30
parent 69f8ef11fe
commit 3975eb3f64
16 changed files with 99 additions and 52 deletions

View File

@ -1,8 +1,6 @@
import { import {
CannotCommitError, CannotCommitError,
DatabaseError, getDbError,
DuplicateEntryError,
LinkValidationError,
NotFoundError, NotFoundError,
ValueError, ValueError,
} from 'fyo/utils/errors'; } from 'fyo/utils/errors';
@ -98,7 +96,7 @@ export default class DatabaseCore extends DatabaseBase {
async connect() { async connect() {
this.knex = knex(this.connectionParams); this.knex = knex(this.connectionParams);
this.knex.on('query-error', (error) => { this.knex.on('query-error', (error) => {
error.type = this.#getError(error); error.type = getDbError(error);
}); });
await this.knex.raw('PRAGMA foreign_keys=ON'); await this.knex.raw('PRAGMA foreign_keys=ON');
} }
@ -116,7 +114,7 @@ export default class DatabaseCore extends DatabaseBase {
try { try {
// await this.knex!.raw('commit'); // await this.knex!.raw('commit');
} catch (err) { } catch (err) {
const type = this.#getError(err as Error); const type = getDbError(err as Error);
if (type !== CannotCommitError) { if (type !== CannotCommitError) {
throw err; throw err;
} }
@ -155,7 +153,7 @@ export default class DatabaseCore extends DatabaseBase {
} }
row = await qb.limit(1); row = await qb.limit(1);
} catch (err) { } catch (err) {
if (this.#getError(err as Error) !== NotFoundError) { if (getDbError(err as Error) !== NotFoundError) {
throw err; throw err;
} }
} }
@ -293,7 +291,7 @@ export default class DatabaseCore extends DatabaseBase {
try { try {
values = await builder.select('fieldname', 'value', 'parent'); values = await builder.select('fieldname', 'value', 'parent');
} catch (err) { } catch (err) {
if (this.#getError(err as Error) === NotFoundError) { if (getDbError(err as Error) === NotFoundError) {
return []; return [];
} }
@ -364,23 +362,6 @@ export default class DatabaseCore extends DatabaseBase {
this.prestigeTheTable(schemaName, tableRows); 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[]) { async prestigeTheTable(schemaName: string, tableRows: FieldValueMap[]) {
const max = 200; const max = 200;

View File

@ -36,7 +36,6 @@ export function validateOptions(field: OptionField, value: string, doc: Doc) {
return; return;
} }
const labels = options.map((o) => o.label).join(', ');
throw new ValueError(t`Invalid value ${value} for ${field.label}`); throw new ValueError(t`Invalid value ${value} for ${field.label}`);
} }

View File

@ -76,3 +76,27 @@ export class NotImplemented extends BaseError {
export class ValueError extends ValidationError {} export class ValueError extends ValidationError {}
export class ConflictError extends ValidationError {} export class ConflictError extends ValidationError {}
export class InvalidFieldError 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;
}

View File

@ -50,7 +50,7 @@ export abstract class AccountReport extends LedgerReport {
async setDefaultFilters(): Promise<void> { async setDefaultFilters(): Promise<void> {
if (this.basedOn === 'Until Date' && !this.toDate) { 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) { if (this.basedOn === 'Fiscal Year' && !this.toYear) {

View File

@ -23,9 +23,9 @@ export class BalanceSheet extends AccountReport {
]; ];
} }
async setReportData(filter?: string) { async setReportData(filter?: string, force?: boolean) {
this.loading = true; this.loading = true;
if (filter !== 'hideGroupAmounts') { if (force || filter !== 'hideGroupAmounts') {
await this._setRawData(); await this._setRawData();
} }
@ -86,11 +86,14 @@ export class BalanceSheet extends AccountReport {
continue; continue;
} }
const totalNode = await this.getTotalNode(row.rootNode, totalName);
const totalRow = this.getRowFromAccountListNode(totalNode);
reportData.push(...row.rows); 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); reportData.push(emptyRow);
} }

View File

@ -37,15 +37,15 @@ export class GeneralLedger extends LedgerReport {
async setDefaultFilters() { async setDefaultFilters() {
if (!this.toDate) { if (!this.toDate) {
this.toDate = DateTime.now().toISODate(); this.toDate = DateTime.now().plus({ days: 1 }).toISODate();
this.fromDate = DateTime.now().minus({ years: 1 }).toISODate(); this.fromDate = DateTime.now().minus({ years: 1 }).toISODate();
} }
} }
async setReportData(filter?: string) { async setReportData(filter?: string, force?: boolean) {
this.loading = true; this.loading = true;
let sort = true; let sort = true;
if (filter !== 'grouped' || this._rawData.length === 0) { if (force || filter !== 'grouped' || this._rawData.length === 0) {
await this._setRawData(); await this._setRawData();
sort = false; sort = false;
} }
@ -141,6 +141,10 @@ export class GeneralLedger extends LedgerReport {
value = String(value); value = String(value);
} }
if (fieldname === 'referenceType') {
value = this.fyo.schemaMap[value]?.label ?? value;
}
row.cells.push({ row.cells.push({
italics: entry.name === -1, italics: entry.name === -1,
bold: entry.name === -2, bold: entry.name === -2,

View File

@ -1,4 +1,4 @@
import { t } from 'fyo'; import { Fyo, t } from 'fyo';
import { Action } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { Report } from 'reports/Report'; import { Report } from 'reports/Report';
@ -13,6 +13,26 @@ export abstract class LedgerReport extends Report {
static reportName = 'general-ledger'; static reportName = 'general-ledger';
_rawData: LedgerEntry[] = []; _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() { _getGroupByKey() {
let groupBy: GroupByKey = 'referenceName'; let groupBy: GroupByKey = 'referenceName';

View File

@ -23,9 +23,9 @@ export class ProfitAndLoss extends AccountReport {
return [AccountRootTypeEnum.Income, AccountRootTypeEnum.Expense]; return [AccountRootTypeEnum.Income, AccountRootTypeEnum.Expense];
} }
async setReportData(filter?: string) { async setReportData(filter?: string, force?: boolean) {
this.loading = true; this.loading = true;
if (filter !== 'hideGroupAmounts') { if (force || filter !== 'hideGroupAmounts') {
await this._setRawData(); await this._setRawData();
} }
@ -77,6 +77,10 @@ export class ProfitAndLoss extends AccountReport {
incomeRoot: AccountTreeNode, incomeRoot: AccountTreeNode,
expenseRoot: AccountTreeNode expenseRoot: AccountTreeNode
): Promise<ReportData> { ): Promise<ReportData> {
if (!incomeRoot || !expenseRoot) {
return [];
}
const totalIncome = await this.getTotalNode( const totalIncome = await this.getTotalNode(
incomeRoot, incomeRoot,
t`Total Income (Credit)` t`Total Income (Credit)`

View File

@ -14,6 +14,7 @@ export abstract class Report extends Observable<RawValue> {
filters: Field[] = []; filters: Field[] = [];
reportData: ReportData; reportData: ReportData;
usePagination: boolean = false; usePagination: boolean = false;
shouldRefresh: boolean = false;
abstract loading: boolean; abstract loading: boolean;
constructor(fyo: Fyo) { constructor(fyo: Fyo) {
@ -75,15 +76,15 @@ export abstract class Report extends Observable<RawValue> {
} }
if (callPostSet) { if (callPostSet) {
await this.postSet(key); await this.updateData(key);
} }
} }
async postSet(key?: string) { async updateData(key?: string, force?: boolean) {
await this.setDefaultFilters(); await this.setDefaultFilters();
this.filters = await this.getFilters(); this.filters = await this.getFilters();
this.columns = await this.getColumns(); this.columns = await this.getColumns();
await this.setReportData(key); await this.setReportData(key, force);
} }
/** /**
@ -94,5 +95,5 @@ export abstract class Report extends Observable<RawValue> {
abstract getActions(): Action[]; abstract getActions(): Action[];
abstract getFilters(): Field[] | Promise<Field[]>; abstract getFilters(): Field[] | Promise<Field[]>;
abstract getColumns(): ColumnField[] | Promise<ColumnField[]>; abstract getColumns(): ColumnField[] | Promise<ColumnField[]>;
abstract setReportData(filter?: string): Promise<void>; abstract setReportData(filter?: string, force?: boolean): Promise<void>;
} }

View File

@ -53,9 +53,9 @@ export class TrialBalance extends AccountReport {
]; ];
} }
async setReportData(filter?: string) { async setReportData(filter?: string, force?: boolean) {
this.loading = true; this.loading = true;
if (filter !== 'hideGroupAmounts') { if (force || filter !== 'hideGroupAmounts') {
await this._setRawData(); await this._setRawData();
} }

View File

@ -11,7 +11,7 @@ import { truncate } from 'lodash';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import { fyo } from './initFyo'; import { fyo } from './initFyo';
import router from './router'; import router from './router';
import { getErrorMessage } from './utils'; import { getErrorMessage, stringifyCircular } from './utils';
import { MessageDialogOptions, ToastOptions } from './utils/types'; import { MessageDialogOptions, ToastOptions } from './utils/types';
import { showMessageDialog, showToast } from './utils/ui'; import { showMessageDialog, showToast } from './utils/ui';
@ -30,7 +30,7 @@ async function reportError(errorLogObj: ErrorLog) {
error_name: errorLogObj.name, error_name: errorLogObj.name,
message: errorLogObj.message, message: errorLogObj.message,
stack: errorLogObj.stack, stack: errorLogObj.stack,
more: JSON.stringify(errorLogObj.more ?? {}), more: stringifyCircular(errorLogObj.more ?? {}),
}; };
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {

View File

@ -77,7 +77,7 @@ export default {
let colors = ['#2490EF', '#B7BFC6']; let colors = ['#2490EF', '#B7BFC6'];
if (!this.hasData) { if (!this.hasData) {
data = dummyData; data = dummyData;
colors = ['#E9EBED', '#B7BFC6']; colors = ['#E9EBED', '#DFE1E2'];
} }
const xLabels = data.map((cf) => cf['yearmonth']); const xLabels = data.map((cf) => cf['yearmonth']);

View File

@ -74,7 +74,7 @@ export default defineComponent({
} }
if (filterKeys.length) { if (filterKeys.length) {
await this.report.postSet() await this.report.postSet();
} }
if (fyo.store.isDevelopment) { if (fyo.store.isDevelopment) {
@ -117,6 +117,8 @@ export default defineComponent({
if (!this.report.reportData.length) { if (!this.report.reportData.length) {
await this.report.setReportData(); await this.report.setReportData();
} else if (this.report.shouldRefresh) {
await this.report.setReportData(undefined, true);
} }
}, },
}, },

View File

@ -9,7 +9,7 @@ import { fyo } from 'src/initFyo';
export function getDatesAndPeriodList( export function getDatesAndPeriodList(
period: 'This Year' | 'This Quarter' | 'This Month' period: 'This Year' | 'This Quarter' | 'This Month'
): { periodList: DateTime[]; fromDate: DateTime; toDate: DateTime } { ): { periodList: DateTime[]; fromDate: DateTime; toDate: DateTime } {
const toDate: DateTime = DateTime.now(); const toDate: DateTime = DateTime.now().plus({ days: 1 });
let fromDate: DateTime; let fromDate: DateTime;
if (period === 'This Year') { if (period === 'This Year') {

View File

@ -417,7 +417,7 @@ export class Search {
_setFilterDefaults() { _setFilterDefaults() {
const totalChildKeywords = Object.values(this.searchables) const totalChildKeywords = Object.values(this.searchables)
.filter((s) => s.isChild) .filter((s) => s.isChild)
.map((s) => this.keywords[s.schemaName].length) .map((s) => this.keywords[s.schemaName]?.length ?? 0)
.reduce((a, b) => a + b); .reduce((a, b) => a + b);
if (totalChildKeywords > 2_000) { if (totalChildKeywords > 2_000) {

View File

@ -7,6 +7,7 @@ import { t } from 'fyo';
import { Doc } from 'fyo/model/doc'; import { Doc } from 'fyo/model/doc';
import { Action } from 'fyo/model/types'; import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils'; import { getActions } from 'fyo/utils';
import { getDbError, LinkValidationError } from 'fyo/utils/errors';
import { ModelNameEnum } from 'models/types'; import { ModelNameEnum } from 'models/types';
import { handleErrorWithDialog } from 'src/errorHandling'; import { handleErrorWithDialog } from 'src/errorHandling';
import { fyo } from 'src/initFyo'; import { fyo } from 'src/initFyo';
@ -106,7 +107,7 @@ export async function showToast(options: ToastOptions) {
} }
// @ts-ignore // @ts-ignore
window.st = showToast window.st = showToast;
function replaceAndAppendMount(app: App<Element>, replaceId: string) { function replaceAndAppendMount(app: App<Element>, replaceId: string) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
@ -162,7 +163,15 @@ export async function deleteDocWithPrompt(doc: Doc) {
await doc.delete(); await doc.delete();
return true; return true;
} catch (err) { } 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; return false;
} }
}, },