mirror of
https://github.com/frappe/books.git
synced 2024-11-09 23:30:56 +00:00
fix: async issue, transactional deletion cascade
- add db observers
This commit is contained in:
parent
104a0fc43f
commit
fefc79024d
@ -41,6 +41,7 @@ export class BespokeQueries {
|
||||
.sum({ total: 'baseGrandTotal' })
|
||||
.sum({ outstanding: 'outstandingAmount' })
|
||||
.where('submitted', true)
|
||||
.where('cancelled', false)
|
||||
.whereBetween('date', [fromDate, toDate])
|
||||
.first();
|
||||
}
|
||||
|
@ -25,7 +25,7 @@ located in `model`, all classes exported from `books/models` extend this.
|
||||
|
||||
### Terminology
|
||||
|
||||
- **Schema**: object that defines shape of the data in the database
|
||||
- **Schema**: object that defines shape of the data in the database.
|
||||
- **Model**: the controller class that extends the `Doc` class or the `Doc`
|
||||
class itself (if a controller doesn't exist).
|
||||
- **Doc**: instance of a Model, i.e. what has the data.
|
||||
@ -95,3 +95,16 @@ This can be done using `fyo/utils/translation.ts/setLanguageMapOnTranslationStri
|
||||
Since translations are runtime, if the code is evaluated before the language map
|
||||
is loaded, translations won't work. To prevent this, don't maintain translation
|
||||
strings globally.
|
||||
|
||||
## Observers
|
||||
|
||||
The doc and db handlers have observers (instances of `Observable`) as
|
||||
properties, these can be accessed using
|
||||
- `fyo.db.observer`
|
||||
- `fyo.doc.observer`
|
||||
|
||||
The purpose of the observer is to trigger registered callbacks when some `doc`
|
||||
operation or `db` operation takes place.
|
||||
|
||||
These are schema level observers i.e. they are registered like so:
|
||||
`method:schemaName`. The callbacks receive args passed to the functions.
|
@ -1,6 +1,7 @@
|
||||
import { SingleValue } from 'backend/database/types';
|
||||
import { Fyo } from 'fyo';
|
||||
import { DatabaseDemux } from 'fyo/demux/db';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import { Field, RawValue, SchemaMap } from 'schemas/types';
|
||||
import { getMapFromList } from 'utils';
|
||||
import { DatabaseBase, DatabaseDemuxBase, GetAllOptions } from 'utils/db/types';
|
||||
@ -23,6 +24,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
#demux: DatabaseDemuxBase;
|
||||
dbPath?: string;
|
||||
schemaMap: Readonly<SchemaMap> = {};
|
||||
observer: Observable<never> = new Observable();
|
||||
fieldValueMap: Record<string, Record<string, Field>> = {};
|
||||
|
||||
constructor(fyo: Fyo, Demux?: DatabaseDemuxConstructor) {
|
||||
@ -62,6 +64,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
const fields = this.schemaMap[schemaName]!.fields!;
|
||||
this.fieldValueMap[schemaName] = getMapFromList(fields, 'fieldname');
|
||||
}
|
||||
this.observer = new Observable();
|
||||
}
|
||||
|
||||
purgeCache() {
|
||||
@ -83,6 +86,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
schemaName,
|
||||
rawValueMap
|
||||
)) as RawValueMap;
|
||||
this.observer.trigger(`insert:${schemaName}`, docValueMap);
|
||||
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
||||
}
|
||||
|
||||
@ -98,6 +102,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
name,
|
||||
fields
|
||||
)) as RawValueMap;
|
||||
this.observer.trigger(`get:${schemaName}`, { name, fields });
|
||||
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
||||
}
|
||||
|
||||
@ -106,6 +111,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
options: GetAllOptions = {}
|
||||
): Promise<DocValueMap[]> {
|
||||
const rawValueMap = await this.#getAll(schemaName, options);
|
||||
this.observer.trigger(`getAll:${schemaName}`, options);
|
||||
return this.converter.toDocValueMap(
|
||||
schemaName,
|
||||
rawValueMap
|
||||
@ -116,7 +122,9 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
schemaName: string,
|
||||
options: GetAllOptions = {}
|
||||
): Promise<DocValueMap[]> {
|
||||
return await this.#getAll(schemaName, options);
|
||||
const all = await this.#getAll(schemaName, options);
|
||||
this.observer.trigger(`getAllRaw:${schemaName}`, options);
|
||||
return all;
|
||||
}
|
||||
|
||||
async getSingleValues(
|
||||
@ -139,6 +147,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
});
|
||||
}
|
||||
|
||||
this.observer.trigger(`getSingleValues`, fieldnames);
|
||||
return docSingleValue;
|
||||
}
|
||||
|
||||
@ -147,7 +156,9 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
options: GetAllOptions = {}
|
||||
): Promise<number> {
|
||||
const rawValueMap = await this.#getAll(schemaName, options);
|
||||
return rawValueMap.length;
|
||||
const count = rawValueMap.length;
|
||||
this.observer.trigger(`count:${schemaName}`, options);
|
||||
return count;
|
||||
}
|
||||
|
||||
// Update
|
||||
@ -157,28 +168,37 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
newName: string
|
||||
): Promise<void> {
|
||||
await this.#demux.call('rename', schemaName, oldName, newName);
|
||||
this.observer.trigger(`rename:${schemaName}`, { oldName, newName });
|
||||
}
|
||||
|
||||
async update(schemaName: string, docValueMap: DocValueMap): Promise<void> {
|
||||
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<void> {
|
||||
await this.#demux.call('delete', schemaName, name);
|
||||
this.observer.trigger(`delete:${schemaName}`, name);
|
||||
}
|
||||
|
||||
// Other
|
||||
async exists(schemaName: string, name?: string): Promise<boolean> {
|
||||
const doesExist = (await this.#demux.call(
|
||||
'exists',
|
||||
schemaName,
|
||||
name
|
||||
)) as boolean;
|
||||
this.observer.trigger(`exists:${schemaName}`, name);
|
||||
return doesExist;
|
||||
}
|
||||
|
||||
async close(): Promise<void> {
|
||||
await this.#demux.call('close');
|
||||
this.purgeCache();
|
||||
}
|
||||
|
||||
async exists(schemaName: string, name?: string): Promise<boolean> {
|
||||
return (await this.#demux.call('exists', schemaName, name)) as boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bespoke function
|
||||
*
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { DocMap, ModelMap, SinglesMap } from 'fyo/model/types';
|
||||
import { coreModels } from 'fyo/models';
|
||||
import { NotFoundError } from 'fyo/utils/errors';
|
||||
import Observable from 'fyo/utils/observable';
|
||||
import { Schema } from 'schemas/types';
|
||||
import { getRandomString } from 'utils';
|
||||
@ -87,7 +88,7 @@ export class DocHandler {
|
||||
schema ??= this.fyo.schemaMap[schemaName];
|
||||
|
||||
if (schema === undefined) {
|
||||
throw new Error(`Schema not found for ${schemaName}`);
|
||||
throw new NotFoundError(`Schema not found for ${schemaName}`);
|
||||
}
|
||||
|
||||
const doc = new Model!(schema, data, this.fyo);
|
||||
@ -113,6 +114,7 @@ export class DocHandler {
|
||||
|
||||
if (!this.docs[schemaName]) {
|
||||
this.docs.set(schemaName, {});
|
||||
this.#setCacheUpdationListeners(schemaName);
|
||||
}
|
||||
|
||||
this.docs.get(schemaName)![name] = doc;
|
||||
@ -127,11 +129,6 @@ export class DocHandler {
|
||||
this.docs!.trigger('change', params);
|
||||
});
|
||||
|
||||
doc.on('afterRename', (names: { oldName: string }) => {
|
||||
this.#removeFromCache(doc.schemaName, names.oldName);
|
||||
this.#addToCache(doc);
|
||||
});
|
||||
|
||||
doc.on('afterSync', () => {
|
||||
if (doc.name === name) {
|
||||
return;
|
||||
@ -140,10 +137,25 @@ export class DocHandler {
|
||||
this.#removeFromCache(doc.schemaName, name);
|
||||
this.#addToCache(doc);
|
||||
});
|
||||
}
|
||||
|
||||
doc.once('afterDelete', () => {
|
||||
this.#removeFromCache(doc.schemaName, doc.name!);
|
||||
#setCacheUpdationListeners(schemaName: string) {
|
||||
this.fyo.db.observer.on(`delete:${schemaName}`, (name: string) => {
|
||||
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);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#removeFromCache(schemaName: string, name: string) {
|
||||
|
@ -374,7 +374,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
_setBaseMetaValues() {
|
||||
if (this.schema.isSubmittable && typeof this.submitted !== 'boolean') {
|
||||
if (this.schema.isSubmittable) {
|
||||
this.submitted = false;
|
||||
this.cancelled = false;
|
||||
}
|
||||
@ -602,7 +602,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
return this;
|
||||
}
|
||||
|
||||
async sync() {
|
||||
async sync(): Promise<Doc> {
|
||||
await this.trigger('beforeSync');
|
||||
let doc;
|
||||
if (this.notInserted) {
|
||||
@ -744,6 +744,11 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
||||
*
|
||||
* Abstractish methods that are called using `this.trigger`.
|
||||
* These are to be overridden if required when subclassing.
|
||||
*
|
||||
* Refrain from running methods that call `this.sync`
|
||||
* in the `beforeLifecycle` methods.
|
||||
*
|
||||
* This may cause the lifecycle function to execute incorrectly.
|
||||
*/
|
||||
async change(ch: ChangeArg) {}
|
||||
async validate() {}
|
||||
|
@ -117,7 +117,8 @@ export class LedgerPosting {
|
||||
reverted: this.reverted,
|
||||
debit: this.fyo.pesa(0),
|
||||
credit: this.fyo.pesa(0),
|
||||
}
|
||||
},
|
||||
false
|
||||
) as AccountingLedgerEntry;
|
||||
|
||||
this.entries.push(ledgerEntry);
|
||||
|
@ -20,25 +20,32 @@ import { LedgerPosting } from './LedgerPosting';
|
||||
*/
|
||||
|
||||
export abstract class Transactional extends Doc {
|
||||
isTransactional = true;
|
||||
get isTransactional() {
|
||||
return true;
|
||||
}
|
||||
|
||||
abstract getPosting(): Promise<LedgerPosting>;
|
||||
|
||||
async validate() {
|
||||
super.validate();
|
||||
const posting = await this.getPosting();
|
||||
posting.validate();
|
||||
}
|
||||
|
||||
async afterSubmit(): Promise<void> {
|
||||
super.afterSubmit();
|
||||
const posting = await this.getPosting();
|
||||
await posting.post();
|
||||
}
|
||||
|
||||
async afterCancel(): Promise<void> {
|
||||
super.afterCancel();
|
||||
const posting = await this.getPosting();
|
||||
await posting.postReverse();
|
||||
}
|
||||
|
||||
async afterDelete(): Promise<void> {
|
||||
super.afterDelete();
|
||||
const ledgerEntryIds = (await this.fyo.db.getAll(
|
||||
ModelNameEnum.AccountingLedgerEntry,
|
||||
{
|
||||
@ -51,7 +58,11 @@ export abstract class Transactional extends Doc {
|
||||
)) as { name: string }[];
|
||||
|
||||
for (const { name } of ledgerEntryIds) {
|
||||
await this.fyo.db.delete(ModelNameEnum.AccountingLedgerEntry, name);
|
||||
const ledgerEntryDoc = await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.AccountingLedgerEntry,
|
||||
name
|
||||
);
|
||||
await ledgerEntryDoc.delete();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,15 @@ export class AccountingLedgerEntry extends Doc {
|
||||
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['account', 'party', 'debit', 'credit', 'balance'],
|
||||
columns: [
|
||||
'account',
|
||||
'party',
|
||||
'debit',
|
||||
'credit',
|
||||
'balance',
|
||||
'referenceName',
|
||||
'reverted',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -43,8 +43,13 @@ export abstract class Invoice extends Transactional {
|
||||
await party.updateOutstandingAmount();
|
||||
}
|
||||
|
||||
async beforeCancel() {
|
||||
await super.beforeCancel();
|
||||
async afterCancel() {
|
||||
await super.afterCancel();
|
||||
await this._cancelPayments();
|
||||
await this._updatePartyOutStanding();
|
||||
}
|
||||
|
||||
async _cancelPayments() {
|
||||
const paymentIds = await this.getPaymentIds();
|
||||
for (const paymentId of paymentIds) {
|
||||
const paymentDoc = (await this.fyo.doc.getDoc(
|
||||
@ -55,8 +60,7 @@ export abstract class Invoice extends Transactional {
|
||||
}
|
||||
}
|
||||
|
||||
async afterCancel() {
|
||||
await super.afterCancel();
|
||||
async _updatePartyOutStanding() {
|
||||
const partyDoc = (await this.fyo.doc.getDoc(
|
||||
ModelNameEnum.Party,
|
||||
this.party!
|
||||
@ -69,7 +73,8 @@ export abstract class Invoice extends Transactional {
|
||||
await super.afterDelete();
|
||||
const paymentIds = await this.getPaymentIds();
|
||||
for (const name of paymentIds) {
|
||||
await this.fyo.db.delete(ModelNameEnum.AccountingLedgerEntry, name);
|
||||
const paymentDoc = await this.fyo.doc.getDoc(ModelNameEnum.Payment, name);
|
||||
await paymentDoc.delete();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,6 +91,10 @@ export class Payment extends Transactional {
|
||||
|
||||
async validate() {
|
||||
await super.validate();
|
||||
if (this.submitted) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.validateAccounts();
|
||||
this.validateTotalReferenceAmount();
|
||||
this.validateWriteOffAccount();
|
||||
@ -243,7 +247,8 @@ export class Payment extends Transactional {
|
||||
throw new ValidationError(message);
|
||||
}
|
||||
|
||||
async beforeSubmit() {
|
||||
async afterSubmit() {
|
||||
await super.afterSubmit();
|
||||
await this.updateReferenceDocOutstanding();
|
||||
await this.updatePartyOutstanding();
|
||||
}
|
||||
@ -281,6 +286,7 @@ export class Payment extends Transactional {
|
||||
const outstandingAmount = (refDoc.outstandingAmount as Money).add(
|
||||
ref.amount!
|
||||
);
|
||||
|
||||
await refDoc.setAndSync({ outstandingAmount });
|
||||
}
|
||||
}
|
||||
|
@ -37,13 +37,14 @@
|
||||
"label": "Expense"
|
||||
}
|
||||
],
|
||||
"required": true
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "parentAccount",
|
||||
"label": "Parent Account",
|
||||
"fieldtype": "Link",
|
||||
"target": "Account"
|
||||
"target": "Account",
|
||||
"readOnly": true
|
||||
},
|
||||
{
|
||||
"fieldname": "accountType",
|
||||
@ -138,9 +139,17 @@
|
||||
{
|
||||
"fieldname": "isGroup",
|
||||
"label": "Is Group",
|
||||
"fieldtype": "Check"
|
||||
"fieldtype": "Check",
|
||||
"readOnly": true
|
||||
}
|
||||
],
|
||||
"quickEditFields": ["name", "rootType", "parentAccount", "accountType"],
|
||||
"quickEditFields": [
|
||||
"name",
|
||||
"rootType",
|
||||
"parentAccount",
|
||||
"accountType",
|
||||
"isGroup",
|
||||
"balance"
|
||||
],
|
||||
"keywordFields": ["name", "rootType", "accountType"]
|
||||
}
|
||||
|
@ -183,16 +183,16 @@ export default {
|
||||
|
||||
return emptyMessage;
|
||||
},
|
||||
selectItem(d) {
|
||||
async selectItem(d) {
|
||||
if (!d.action) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.doc) {
|
||||
return d.action(this.doc, this.$router);
|
||||
return await d.action(this.doc, this.$router);
|
||||
}
|
||||
|
||||
d.action()
|
||||
|
||||
await d.action();
|
||||
},
|
||||
toggleDropdown(flag) {
|
||||
if (flag == null) {
|
||||
@ -201,11 +201,11 @@ export default {
|
||||
this.isShown = Boolean(flag);
|
||||
}
|
||||
},
|
||||
selectHighlightedItem() {
|
||||
async selectHighlightedItem() {
|
||||
if (![-1, this.items.length].includes(this.highlightedIndex)) {
|
||||
// valid selection
|
||||
let item = this.items[this.highlightedIndex];
|
||||
this.selectItem(item);
|
||||
await this.selectItem(item);
|
||||
}
|
||||
},
|
||||
highlightItemUp() {
|
||||
|
@ -10,7 +10,7 @@
|
||||
:df="df"
|
||||
:value="doc[df.fieldname]"
|
||||
:read-only="evaluateReadOnly(df)"
|
||||
@change="(value) => onChange(df, value)"
|
||||
@change="async (value) => await onChange(df, value)"
|
||||
/>
|
||||
|
||||
<!-- Inline Field Form (Eg: Address) -->
|
||||
@ -71,9 +71,9 @@
|
||||
:value="getRegularValue(df)"
|
||||
:class="{ 'p-2': df.fieldtype === 'Check' }"
|
||||
:read-only="evaluateReadOnly(df)"
|
||||
@change="(value) => onChange(df, value)"
|
||||
@change="async (value) => await onChange(df, value)"
|
||||
@focus="activateInlineEditing(df)"
|
||||
@new-doc="(newdoc) => onChange(df, newdoc.name)"
|
||||
@new-doc="async (newdoc) => await onChange(df, newdoc.name)"
|
||||
/>
|
||||
<div
|
||||
class="text-sm text-red-600 mt-2 pl-2"
|
||||
@ -173,7 +173,7 @@ export default {
|
||||
|
||||
return evaluateReadOnly(df, this.doc);
|
||||
},
|
||||
onChange(df, value) {
|
||||
async onChange(df, value) {
|
||||
if (df.inline) {
|
||||
return;
|
||||
}
|
||||
@ -185,23 +185,23 @@ export default {
|
||||
|
||||
const oldValue = this.doc.get(df.fieldname);
|
||||
this.errors[df.fieldname] = null;
|
||||
this.onChangeCommon(df, value, oldValue);
|
||||
await this.onChangeCommon(df, value, oldValue);
|
||||
},
|
||||
onChangeCommon(df, value, oldValue) {
|
||||
this.doc
|
||||
.set(df.fieldname, value)
|
||||
.catch((e) => {
|
||||
this.errors[df.fieldname] = getErrorMessage(e, this.doc);
|
||||
})
|
||||
.then((success) => {
|
||||
if (!success) {
|
||||
return;
|
||||
}
|
||||
async onChangeCommon(df, value, oldValue) {
|
||||
let isSet = false;
|
||||
try {
|
||||
isSet = this.doc.set(df.fieldname, value);
|
||||
} catch (err) {
|
||||
this.errors[df.fieldname] = getErrorMessage(err, this.doc);
|
||||
}
|
||||
|
||||
this.handlePostSet(df, value, oldValue);
|
||||
});
|
||||
if (!isSet) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.handlePostSet(df, value, oldValue);
|
||||
},
|
||||
handlePostSet(df, value, oldValue) {
|
||||
async handlePostSet(df, value, oldValue) {
|
||||
this.setFormFields();
|
||||
if (this.emitChange) {
|
||||
this.$emit('change', df, value, oldValue);
|
||||
@ -212,14 +212,22 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.doc.sync();
|
||||
await this.doc.sync();
|
||||
}
|
||||
},
|
||||
sync() {
|
||||
return this.doc.sync().catch((e) => handleErrorWithDialog(e, this.doc));
|
||||
async sync() {
|
||||
try {
|
||||
await this.doc.sync();
|
||||
} catch (err) {
|
||||
await handleErrorWithDialog(err, this.doc);
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
return this.doc.submit().catch((e) => handleErrorWithDialog(e, this.doc));
|
||||
async submit() {
|
||||
try {
|
||||
await this.doc.submit();
|
||||
} catch (err) {
|
||||
await handleErrorWithDialog(err, this.doc);
|
||||
}
|
||||
},
|
||||
async activateInlineEditing(df) {
|
||||
if (!df.inline) {
|
||||
@ -229,8 +237,8 @@ export default {
|
||||
this.inlineEditField = df;
|
||||
if (!this.doc[df.fieldname]) {
|
||||
this.inlineEditDoc = await fyo.doc.getNewDoc(df.target);
|
||||
this.inlineEditDoc.once('afterSync', () => {
|
||||
this.onChangeCommon(df, this.inlineEditDoc.name);
|
||||
this.inlineEditDoc.once('afterSync', async () => {
|
||||
await this.onChangeCommon(df, this.inlineEditDoc.name);
|
||||
});
|
||||
} else {
|
||||
this.inlineEditDoc = this.doc.getLink(df.fieldname);
|
||||
|
@ -102,12 +102,12 @@ export function handleError(
|
||||
}
|
||||
}
|
||||
|
||||
export function handleErrorWithDialog(error: Error, doc?: Doc) {
|
||||
export async function handleErrorWithDialog(error: Error, doc?: Doc) {
|
||||
const errorMessage = getErrorMessage(error, doc);
|
||||
handleError(false, error, { errorMessage, doc });
|
||||
|
||||
const name = (error as BaseError).label ?? error.name;
|
||||
showMessageDialog({ message: name, detail: errorMessage });
|
||||
await showMessageDialog({ message: name, detail: errorMessage });
|
||||
throw error;
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ export default {
|
||||
await this.fetchChildren(parentAccount, true);
|
||||
|
||||
// open quick edit
|
||||
openQuickEdit({
|
||||
await openQuickEdit({
|
||||
schemaName: 'Account',
|
||||
name: account.name,
|
||||
});
|
||||
@ -285,7 +285,7 @@ export default {
|
||||
} catch (e) {
|
||||
// unfreeze input
|
||||
this.insertingAccount = false;
|
||||
handleErrorWithDialog(e, account);
|
||||
await handleErrorWithDialog(e, account);
|
||||
}
|
||||
},
|
||||
isQuickEditOpen(account) {
|
||||
|
@ -533,33 +533,30 @@ export default {
|
||||
}
|
||||
|
||||
if (this.isRequiredUnassigned) {
|
||||
showMessageDialog({
|
||||
return await showMessageDialog({
|
||||
message: this.t`Required Fields not Assigned`,
|
||||
detail: this
|
||||
.t`Please assign the following fields ${this.requiredUnassigned.join(
|
||||
', '
|
||||
)}`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.importer.assignedMatrix.length === 0) {
|
||||
showMessageDialog({
|
||||
return await showMessageDialog({
|
||||
message: this.t`No Data to Import`,
|
||||
detail: this.t`Please select a file with data to import.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const { success, names, message } = await this.importer.importData(
|
||||
this.setLoadingStatus
|
||||
);
|
||||
if (!success) {
|
||||
showMessageDialog({
|
||||
return await showMessageDialog({
|
||||
message: this.t`Import Failed`,
|
||||
detail: message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.names = names;
|
||||
@ -593,7 +590,9 @@ export default {
|
||||
await ipcRenderer.invoke(IPC_ACTIONS.GET_FILE, options);
|
||||
|
||||
if (!success && !canceled) {
|
||||
showMessageDialog({ message: this.t`File selection failed.` });
|
||||
return await showMessageDialog({
|
||||
message: this.t`File selection failed.`,
|
||||
});
|
||||
}
|
||||
|
||||
if (!success || canceled) {
|
||||
@ -603,11 +602,10 @@ export default {
|
||||
const text = new TextDecoder().decode(data);
|
||||
const isValid = this.importer.selectFile(text);
|
||||
if (!isValid) {
|
||||
showMessageDialog({
|
||||
return await showMessageDialog({
|
||||
message: this.t`Bad import data.`,
|
||||
detail: this.t`Could not select file.`,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.file = {
|
||||
|
@ -276,7 +276,7 @@ export default {
|
||||
routeTo(`/list/${this.schemaName}`);
|
||||
return;
|
||||
}
|
||||
this.handleError(error);
|
||||
await this.handleError(error);
|
||||
}
|
||||
this.printSettings = await fyo.doc.getSingle('PrintSettings');
|
||||
this.companyName = (
|
||||
@ -305,20 +305,29 @@ export default {
|
||||
return fyo.getField(this.schemaName, fieldname);
|
||||
},
|
||||
async sync() {
|
||||
return this.doc.sync().catch(this.handleError);
|
||||
try {
|
||||
await this.doc.sync();
|
||||
} catch (err) {
|
||||
await this.handleError(err);
|
||||
}
|
||||
},
|
||||
submit() {
|
||||
async submit() {
|
||||
const message =
|
||||
this.schemaName === ModelNameEnum.SalesInvoice
|
||||
? this.t`Submit Sales Invoice?`
|
||||
: this.t`Submit Purchase Invoice?`;
|
||||
showMessageDialog({
|
||||
const ref = this
|
||||
await showMessageDialog({
|
||||
message,
|
||||
buttons: [
|
||||
{
|
||||
label: this.t`Yes`,
|
||||
action: () => {
|
||||
this.doc.submit().catch(this.handleError);
|
||||
async action() {
|
||||
try {
|
||||
await ref.doc.submit();
|
||||
} catch (err) {
|
||||
await ref.handleError(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -328,8 +337,8 @@ export default {
|
||||
],
|
||||
});
|
||||
},
|
||||
handleError(e) {
|
||||
handleErrorWithDialog(e, this.doc);
|
||||
async handleError(e) {
|
||||
await handleErrorWithDialog(e, this.doc);
|
||||
},
|
||||
openInvoiceSettings() {
|
||||
openSettings('Invoice');
|
||||
|
@ -200,7 +200,7 @@ export default {
|
||||
return;
|
||||
}
|
||||
|
||||
this.handleError(error);
|
||||
await this.handleError(error);
|
||||
}
|
||||
|
||||
if (fyo.store.isDevelopment) {
|
||||
@ -238,16 +238,25 @@ export default {
|
||||
return fyo.getField(ModelNameEnum.JournalEntry, fieldname);
|
||||
},
|
||||
async sync() {
|
||||
return this.doc.sync().catch(this.handleError);
|
||||
try {
|
||||
await this.doc.sync();
|
||||
} catch (err) {
|
||||
this.handleError(err);
|
||||
}
|
||||
},
|
||||
async submit() {
|
||||
showMessageDialog({
|
||||
const ref = this;
|
||||
await showMessageDialog({
|
||||
message: this.t`Submit Journal Entry?`,
|
||||
buttons: [
|
||||
{
|
||||
label: this.t`Yes`,
|
||||
action: () => {
|
||||
this.doc.submit().catch(this.handleError);
|
||||
async action() {
|
||||
try {
|
||||
await ref.doc.submit();
|
||||
} catch (err) {
|
||||
await ref.handleError(err);
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -257,8 +266,8 @@ export default {
|
||||
],
|
||||
});
|
||||
},
|
||||
handleError(e) {
|
||||
handleErrorWithDialog(e, this.doc);
|
||||
async handleError(e) {
|
||||
await handleErrorWithDialog(e, this.doc);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -120,7 +120,7 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
setUpdateListeners() {
|
||||
const listener = () => {
|
||||
const listener = (name) => {
|
||||
this.updateData();
|
||||
};
|
||||
|
||||
@ -130,7 +130,7 @@ export default {
|
||||
}
|
||||
|
||||
fyo.doc.observer.on(`sync:${this.schemaName}`, listener);
|
||||
fyo.doc.observer.on(`delete:${this.schemaName}`, listener);
|
||||
fyo.db.observer.on(`delete:${this.schemaName}`, listener);
|
||||
fyo.doc.observer.on(`rename:${this.schemaName}`, listener);
|
||||
},
|
||||
openForm(doc) {
|
||||
|
@ -180,8 +180,9 @@ export default {
|
||||
},
|
||||
async submit() {
|
||||
if (!this.allValuesFilled()) {
|
||||
showMessageDialog({ message: this.t`Please fill all values` });
|
||||
return;
|
||||
return await showMessageDialog({
|
||||
message: this.t`Please fill all values`,
|
||||
});
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
@ -1,6 +1,6 @@
|
||||
export interface MessageDialogButton {
|
||||
label: string;
|
||||
action: () => void;
|
||||
action: () => Promise<unknown> | unknown;
|
||||
}
|
||||
|
||||
export interface MessageDialogOptions {
|
||||
|
137
src/utils/ui.ts
137
src/utils/ui.ts
@ -87,9 +87,11 @@ export async function showMessageDialog({
|
||||
)) as { response: number };
|
||||
|
||||
const button = buttons[response];
|
||||
if (button && button.action) {
|
||||
button.action();
|
||||
if (!button?.action) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await button.action();
|
||||
}
|
||||
|
||||
export async function showToast(options: ToastOptions) {
|
||||
@ -138,37 +140,36 @@ export async function routeTo(route: string | RouteLocationRaw) {
|
||||
await router.push(routeOptions);
|
||||
}
|
||||
|
||||
export function deleteDocWithPrompt(doc: Doc) {
|
||||
export async function deleteDocWithPrompt(doc: Doc) {
|
||||
const schemaLabel = fyo.schemaMap[doc.schemaName]!.label;
|
||||
let detail = t`This action is permanent.`;
|
||||
if (doc.isTransactional) {
|
||||
detail = t`This action is permanent and will delete all ledger entries.`;
|
||||
detail = t`This action is permanent and will delete associated ledger entries.`;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
showMessageDialog({
|
||||
message: t`Delete ${schemaLabel} ${doc.name!}?`,
|
||||
detail,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Delete`,
|
||||
action: () => {
|
||||
doc
|
||||
.delete()
|
||||
.then(() => resolve(true))
|
||||
.catch((e: Error) => {
|
||||
handleErrorWithDialog(e, doc);
|
||||
});
|
||||
},
|
||||
return await showMessageDialog({
|
||||
message: t`Delete ${schemaLabel} ${doc.name!}?`,
|
||||
detail,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Delete`,
|
||||
async action() {
|
||||
try {
|
||||
await doc.delete();
|
||||
return true;
|
||||
} catch (err) {
|
||||
handleErrorWithDialog(err as Error, doc);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t`Cancel`,
|
||||
action() {
|
||||
resolve(false);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`Cancel`,
|
||||
action() {
|
||||
return false;
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@ -204,30 +205,30 @@ export async function cancelDocWithPrompt(doc: Doc) {
|
||||
}
|
||||
}
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const schemaLabel = fyo.schemaMap[doc.schemaName]!.label;
|
||||
showMessageDialog({
|
||||
message: t`Cancel ${schemaLabel} ${doc.name!}?`,
|
||||
detail,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Yes`,
|
||||
async action() {
|
||||
const entryDoc = await fyo.doc.getDoc(doc.schemaName, doc.name!);
|
||||
entryDoc
|
||||
.cancel()
|
||||
.then(() => resolve(true))
|
||||
.catch((e) => handleErrorWithDialog(e, doc));
|
||||
},
|
||||
const schemaLabel = fyo.schemaMap[doc.schemaName]!.label;
|
||||
return await showMessageDialog({
|
||||
message: t`Cancel ${schemaLabel} ${doc.name!}?`,
|
||||
detail,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Yes`,
|
||||
async action() {
|
||||
try {
|
||||
await doc.cancel();
|
||||
return true;
|
||||
} catch (err) {
|
||||
handleErrorWithDialog(err as Error, doc);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
{
|
||||
label: t`No`,
|
||||
action() {
|
||||
resolve(false);
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`No`,
|
||||
action() {
|
||||
return false;
|
||||
},
|
||||
],
|
||||
});
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
@ -258,13 +259,13 @@ function getCancelAction(doc: Doc): Action {
|
||||
component: {
|
||||
template: '<span class="text-red-700">{{ t`Cancel` }}</span>',
|
||||
},
|
||||
condition: (doc: Doc) => !!doc.submitted && !doc.cancelled,
|
||||
action: () => {
|
||||
cancelDocWithPrompt(doc).then((res) => {
|
||||
if (res) {
|
||||
router.push(`/list/${doc.schemaName}`);
|
||||
}
|
||||
});
|
||||
condition: (doc: Doc) => doc.isSubmitted,
|
||||
async action() {
|
||||
const res = await cancelDocWithPrompt(doc);
|
||||
|
||||
if (res) {
|
||||
router.push(`/list/${doc.schemaName}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
@ -278,12 +279,12 @@ function getDeleteAction(doc: Doc): Action {
|
||||
condition: (doc: Doc) =>
|
||||
(!doc.notInserted && !doc.schema.isSubmittable && !doc.schema.isSingle) ||
|
||||
doc.isCancelled,
|
||||
action: () =>
|
||||
deleteDocWithPrompt(doc).then((res) => {
|
||||
if (res) {
|
||||
routeTo(`/list/${doc.schemaName}`);
|
||||
}
|
||||
}),
|
||||
async action() {
|
||||
const res = await deleteDocWithPrompt(doc);
|
||||
if (res) {
|
||||
routeTo(`/list/${doc.schemaName}`);
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -297,20 +298,26 @@ function getDuplicateAction(doc: Doc): Action {
|
||||
!doc.notInserted &&
|
||||
!(doc.cancelled || false)
|
||||
),
|
||||
action: () => {
|
||||
showMessageDialog({
|
||||
async action() {
|
||||
await showMessageDialog({
|
||||
message: t`Duplicate ${doc.schemaName} ${doc.name!}?`,
|
||||
buttons: [
|
||||
{
|
||||
label: t`Yes`,
|
||||
async action() {
|
||||
doc.duplicate();
|
||||
try {
|
||||
doc.duplicate();
|
||||
return true;
|
||||
} catch (err) {
|
||||
handleErrorWithDialog(err as Error, doc);
|
||||
return false;
|
||||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
label: t`No`,
|
||||
action() {
|
||||
// no-op
|
||||
return false;
|
||||
},
|
||||
},
|
||||
],
|
||||
|
Loading…
Reference in New Issue
Block a user