2
0
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:
18alantom 2022-05-04 13:49:40 +05:30
parent 104a0fc43f
commit fefc79024d
22 changed files with 283 additions and 160 deletions

View File

@ -41,6 +41,7 @@ export class BespokeQueries {
.sum({ total: 'baseGrandTotal' })
.sum({ outstanding: 'outstandingAmount' })
.where('submitted', true)
.where('cancelled', false)
.whereBetween('date', [fromDate, toDate])
.first();
}

View File

@ -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.

View File

@ -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
*

View File

@ -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) {

View File

@ -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() {}

View File

@ -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);

View File

@ -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();
}
}
}

View File

@ -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',
],
};
}
}

View File

@ -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();
}
}

View File

@ -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 });
}
}

View File

@ -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"]
}

View File

@ -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() {

View File

@ -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);

View File

@ -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;
}

View File

@ -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) {

View File

@ -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 = {

View 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');

View File

@ -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);
},
},
};

View File

@ -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) {

View File

@ -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;

View File

@ -1,6 +1,6 @@
export interface MessageDialogButton {
label: string;
action: () => void;
action: () => Promise<unknown> | unknown;
}
export interface MessageDialogOptions {

View File

@ -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;
},
},
],