diff --git a/fyo/model/doc.ts b/fyo/model/doc.ts index c3d57060..3e960a2d 100644 --- a/fyo/model/doc.ts +++ b/fyo/model/doc.ts @@ -19,6 +19,7 @@ import { markRaw } from 'vue'; import { isPesa } from '../utils/index'; import { areDocValuesEqual, + getInsertionError, getMissingMandatoryMessage, getPreDefaultValues, setChildDocIdx, @@ -682,7 +683,12 @@ export class Doc extends Observable { await this._preSync(); const validDict = this.getValidDict(false, true); - const data = await this.fyo.db.insert(this.schemaName, validDict); + let data: DocValueMap; + try { + data = await this.fyo.db.insert(this.schemaName, validDict); + } catch (err) { + throw getInsertionError(err as Error, validDict); + } await this._syncValues(data); this.fyo.telemetry.log(Verb.Created, this.schemaName); diff --git a/fyo/model/helpers.ts b/fyo/model/helpers.ts index 927a8c93..0a05d8d8 100644 --- a/fyo/model/helpers.ts +++ b/fyo/model/helpers.ts @@ -1,6 +1,7 @@ import { Fyo } from 'fyo'; -import { DocValue } from 'fyo/core/types'; +import { DocValue, DocValueMap } from 'fyo/core/types'; import { isPesa } from 'fyo/utils'; +import { DuplicateEntryError } from 'fyo/utils/errors'; import { isEqual } from 'lodash'; import { Money } from 'pesa'; import { Field, FieldType, FieldTypeEnum } from 'schemas/types'; @@ -114,3 +115,37 @@ export function setChildDocIdx(childDocs: Doc[]) { childDocs[idx].idx = +idx; } } + +export function getInsertionError(err: Error, validDict: DocValueMap): Error { + if (err.message.includes('UNIQUE constraint failed:')) { + return getDuplicateEntryError(err, validDict); + } + + return err; +} + +export function getDuplicateEntryError( + err: Error, + validDict: DocValueMap +): Error | DuplicateEntryError { + const matches = err.message.match(/UNIQUE constraint failed:\s(\w+)\.(\w+)$/); + if (!matches) { + return err; + } + + const schemaName = matches[1]; + const fieldname = matches[2]; + if (!schemaName || !fieldname) { + return err; + } + + const duplicateEntryError = new DuplicateEntryError(err.message, false); + duplicateEntryError.stack = err.stack; + duplicateEntryError.more = { + schemaName, + fieldname, + value: validDict[fieldname], + }; + + return duplicateEntryError; +} diff --git a/fyo/utils/errors.ts b/fyo/utils/errors.ts index 8aede66a..54d20617 100644 --- a/fyo/utils/errors.ts +++ b/fyo/utils/errors.ts @@ -1,9 +1,14 @@ export class BaseError extends Error { + more: Record = {}; message: string; statusCode: number; shouldStore: boolean; - constructor(statusCode: number, message: string, shouldStore: boolean = true) { + constructor( + statusCode: number, + message: string, + shouldStore: boolean = true + ) { super(message); this.name = 'BaseError'; this.statusCode = statusCode; @@ -96,7 +101,7 @@ export function getDbError(err: Error) { return CannotCommitError; } - if (err.message.includes('SQLITE_CONSTRAINT: UNIQUE constraint failed:')) { + if (err.message.includes('UNIQUE constraint failed:')) { return DuplicateEntryError; } diff --git a/src/errorHandling.ts b/src/errorHandling.ts index 39e20151..eb7dd125 100644 --- a/src/errorHandling.ts +++ b/src/errorHandling.ts @@ -94,8 +94,8 @@ export async function handleErrorWithDialog( const errorMessage = getErrorMessage(error, doc); await handleError(false, error, { errorMessage, doc }); - const name = error.name ?? t`Error`; - const options: MessageDialogOptions = { message: name, detail: errorMessage }; + const label = getErrorLabel(error); + const options: MessageDialogOptions = { message: label, detail: errorMessage }; if (reportError) { options.detail = truncate(options.detail, { length: 128 }); @@ -106,7 +106,7 @@ export async function handleErrorWithDialog( reportIssue(getErrorLogObject(error, { errorMessage })); }, }, - { label: t`OK`, action() {} }, + { label: t`Cancel`, action() {} }, ]; } @@ -196,3 +196,52 @@ export function reportIssue(errorLogObj?: ErrorLog) { const urlQuery = getIssueUrlQuery(errorLogObj); ipcRenderer.send(IPC_MESSAGES.OPEN_EXTERNAL, urlQuery); } + +function getErrorLabel(error: Error) { + const name = error.name; + if (!name) { + return t`Error`; + } + + if (name === 'BaseError') { + return t`Error`; + } + + if (name === 'ValidationError') { + return t`Validation Error`; + } + + if (name === 'NotFoundError') { + return t`Not Found`; + } + + if (name === 'ForbiddenError') { + return t`Forbidden Error`; + } + + if (name === 'DuplicateEntryError') { + return t`Duplicate Entry`; + } + + if (name === 'LinkValidationError') { + return t`Link Validation Error`; + } + + if (name === 'MandatoryError') { + return t`Mandatory Error`; + } + + if (name === 'DatabaseError') { + return t`Database Error`; + } + + if (name === 'CannotCommitError') { + return t`Cannot Commit Error`; + } + + if (name === 'NotImplemented') { + return t`Error`; + } + + return t`Error`; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 1e554bce..28054f32 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -4,9 +4,14 @@ import { t } from 'fyo'; import { Doc } from 'fyo/model/doc'; import { isPesa } from 'fyo/utils'; -import { DuplicateEntryError, LinkValidationError } from 'fyo/utils/errors'; +import { + BaseError, + DuplicateEntryError, + LinkValidationError +} from 'fyo/utils/errors'; import { Money } from 'pesa'; import { Field, FieldType, FieldTypeEnum } from 'schemas/types'; +import { fyo } from 'src/initFyo'; export function stringifyCircular( obj: unknown, @@ -24,7 +29,8 @@ export function stringifyCircular( } if (cacheValue.includes(value)) { - const circularKey = cacheKey[cacheValue.indexOf(value)] || '{self}'; + const circularKey: string = + cacheKey[cacheValue.indexOf(value)] || '{self}'; return ignoreCircular ? undefined : `[Circular:${circularKey}]`; } @@ -84,16 +90,23 @@ export function convertPesaValuesToFloat(obj: Record) { } export function getErrorMessage(e: Error, doc?: Doc): string { - let errorMessage = e.message || t`An error occurred.`; + const errorMessage = e.message || t`An error occurred.`; - const { schemaName, name }: { schemaName?: string; name?: string } = - doc ?? {}; - const canElaborate = !!(schemaName && name); + let { schemaName, name } = doc ?? {}; + if (!doc) { + schemaName = (e as BaseError).more?.schemaName as string | undefined; + name = (e as BaseError).more?.value as string | undefined; + } - if (e instanceof LinkValidationError && canElaborate) { - errorMessage = t`${schemaName} ${name} is linked with existing records.`; - } else if (e instanceof DuplicateEntryError && canElaborate) { - errorMessage = t`${schemaName} ${name} already exists.`; + if (!schemaName || !name) { + return errorMessage; + } + + const label = fyo.db.schemaMap[schemaName]?.label ?? schemaName; + if (e instanceof LinkValidationError) { + return t`${label} ${name} is linked with existing records.`; + } else if (e instanceof DuplicateEntryError) { + return t`${label} ${name} already exists.`; } return errorMessage;