2022-04-20 12:08:47 +05:30
|
|
|
/**
|
|
|
|
* Utils to do UI stuff such as opening dialogs, toasts, etc.
|
|
|
|
* Basically anything that may directly or indirectly import a Vue file.
|
|
|
|
*/
|
|
|
|
import { t } from 'fyo';
|
2023-01-30 16:20:40 +05:30
|
|
|
import type { Doc } from 'fyo/model/doc';
|
2022-04-20 12:08:47 +05:30
|
|
|
import { Action } from 'fyo/model/types';
|
|
|
|
import { getActions } from 'fyo/utils';
|
2022-11-22 14:42:49 +05:30
|
|
|
import { getDbError, LinkValidationError, ValueError } from 'fyo/utils/errors';
|
2023-03-15 14:02:52 +05:30
|
|
|
import { getLedgerLink } from 'models/helpers';
|
|
|
|
import { Transfer } from 'models/inventory/Transfer';
|
|
|
|
import { Transactional } from 'models/Transactional/Transactional';
|
2022-05-04 18:17:45 +05:30
|
|
|
import { ModelNameEnum } from 'models/types';
|
2023-02-20 10:22:19 +05:30
|
|
|
import { Schema } from 'schemas/types';
|
2022-04-20 12:08:47 +05:30
|
|
|
import { handleErrorWithDialog } from 'src/errorHandling';
|
|
|
|
import { fyo } from 'src/initFyo';
|
|
|
|
import router from 'src/router';
|
2023-03-13 12:36:56 +05:30
|
|
|
import { SelectFileOptions } from 'utils/types';
|
2022-04-20 12:08:47 +05:30
|
|
|
import { RouteLocationRaw } from 'vue-router';
|
|
|
|
import { stringifyCircular } from './';
|
2023-02-21 11:04:35 +05:30
|
|
|
import { evaluateHidden } from './doc';
|
2023-03-25 13:16:25 +05:30
|
|
|
import { showDialog, showToast } from './interactive';
|
2023-03-13 12:36:56 +05:30
|
|
|
import { selectFile } from './ipcCalls';
|
2023-03-07 15:07:02 +05:30
|
|
|
import { showSidebar } from './refs';
|
2022-04-20 12:08:47 +05:30
|
|
|
import {
|
2023-02-20 10:22:19 +05:30
|
|
|
ActionGroup,
|
2023-03-25 13:16:25 +05:30
|
|
|
DialogButton,
|
2022-04-20 12:08:47 +05:30
|
|
|
QuickEditOptions,
|
|
|
|
SettingsTab,
|
|
|
|
ToastOptions,
|
2023-02-20 10:22:19 +05:30
|
|
|
UIGroupedFields,
|
2022-04-20 12:08:47 +05:30
|
|
|
} from './types';
|
|
|
|
|
2023-03-23 12:25:25 +05:30
|
|
|
export const toastDurationMap = { short: 2_500, long: 5_000 } as const;
|
|
|
|
|
2022-04-20 12:08:47 +05:30
|
|
|
export async function openQuickEdit({
|
2022-11-22 14:42:49 +05:30
|
|
|
doc,
|
2022-04-20 12:08:47 +05:30
|
|
|
schemaName,
|
|
|
|
name,
|
2022-04-28 12:04:55 +05:30
|
|
|
hideFields = [],
|
|
|
|
showFields = [],
|
2022-04-20 12:08:47 +05:30
|
|
|
defaults = {},
|
2023-01-09 12:33:26 +05:30
|
|
|
listFilters = {},
|
2022-04-20 12:08:47 +05:30
|
|
|
}: QuickEditOptions) {
|
2022-11-22 14:42:49 +05:30
|
|
|
if (doc) {
|
|
|
|
schemaName = doc.schemaName;
|
|
|
|
name = doc.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!doc && (!schemaName || !name)) {
|
|
|
|
throw new ValueError(t`Schema Name or Name not passed to Open Quick Edit`);
|
|
|
|
}
|
|
|
|
|
2022-04-20 12:08:47 +05:30
|
|
|
const currentRoute = router.currentRoute.value;
|
|
|
|
const query = currentRoute.query;
|
|
|
|
let method: 'push' | 'replace' = 'push';
|
|
|
|
|
2022-04-28 12:04:55 +05:30
|
|
|
if (query.edit && query.schemaName === schemaName) {
|
2022-04-20 12:08:47 +05:30
|
|
|
method = 'replace';
|
|
|
|
}
|
2022-04-28 12:04:55 +05:30
|
|
|
|
|
|
|
if (query.name === name) {
|
|
|
|
return;
|
|
|
|
}
|
2022-04-20 12:08:47 +05:30
|
|
|
|
|
|
|
const forWhat = (defaults?.for ?? []) as string[];
|
|
|
|
if (forWhat[0] === 'not in') {
|
|
|
|
const purpose = forWhat[1]?.[0];
|
|
|
|
|
|
|
|
defaults = Object.assign({
|
|
|
|
for:
|
2022-05-05 16:14:26 +05:30
|
|
|
purpose === 'Sales'
|
|
|
|
? 'Purchases'
|
|
|
|
: purpose === 'Purchases'
|
|
|
|
? 'Sales'
|
|
|
|
: 'Both',
|
2022-04-20 12:08:47 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-05-05 16:14:26 +05:30
|
|
|
if (forWhat[0] === 'not in' && forWhat[1] === 'Sales') {
|
|
|
|
defaults = Object.assign({ for: 'Purchases' });
|
2022-04-20 12:08:47 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
router[method]({
|
|
|
|
query: {
|
2023-01-09 12:33:26 +05:30
|
|
|
edit: 1,
|
2022-04-28 12:04:55 +05:30
|
|
|
schemaName,
|
2022-04-20 12:08:47 +05:30
|
|
|
name,
|
2022-04-28 12:04:55 +05:30
|
|
|
showFields,
|
2022-04-20 12:08:47 +05:30
|
|
|
hideFields,
|
2022-04-28 12:04:55 +05:30
|
|
|
defaults: stringifyCircular(defaults),
|
2023-01-09 12:33:26 +05:30
|
|
|
filters: JSON.stringify(listFilters),
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-03-06 11:56:54 +05:30
|
|
|
export async function openSettings(tab: SettingsTab) {
|
|
|
|
await routeTo({ path: '/settings', query: { tab } });
|
2022-04-20 12:08:47 +05:30
|
|
|
}
|
|
|
|
|
2023-01-09 12:33:26 +05:30
|
|
|
export async function routeTo(route: RouteLocationRaw) {
|
2022-04-20 12:08:47 +05:30
|
|
|
if (
|
|
|
|
typeof route === 'string' &&
|
|
|
|
route === router.currentRoute.value.fullPath
|
|
|
|
) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-01-04 17:53:42 +05:30
|
|
|
return await router.push(route);
|
2022-04-20 12:08:47 +05:30
|
|
|
}
|
|
|
|
|
2022-05-04 13:49:40 +05:30
|
|
|
export async function deleteDocWithPrompt(doc: Doc) {
|
2022-05-03 18:43:47 +05:30
|
|
|
const schemaLabel = fyo.schemaMap[doc.schemaName]!.label;
|
|
|
|
let detail = t`This action is permanent.`;
|
2022-07-28 14:29:04 +05:30
|
|
|
if (doc.isTransactional && doc.isSubmitted) {
|
2022-05-04 13:49:40 +05:30
|
|
|
detail = t`This action is permanent and will delete associated ledger entries.`;
|
2022-05-03 18:43:47 +05:30
|
|
|
}
|
|
|
|
|
2023-03-25 13:16:25 +05:30
|
|
|
return await showDialog({
|
|
|
|
title: t`Delete ${getActionLabel(doc)}?`,
|
2022-05-04 13:49:40 +05:30
|
|
|
detail,
|
2023-03-25 13:16:25 +05:30
|
|
|
type: 'warning',
|
2022-05-04 13:49:40 +05:30
|
|
|
buttons: [
|
|
|
|
{
|
2023-03-23 14:28:15 +05:30
|
|
|
label: t`Yes`,
|
2022-05-04 13:49:40 +05:30
|
|
|
async action() {
|
|
|
|
try {
|
|
|
|
await doc.delete();
|
|
|
|
} catch (err) {
|
2022-05-30 17:04:25 +05:30
|
|
|
if (getDbError(err as Error) === LinkValidationError) {
|
2023-03-25 13:16:25 +05:30
|
|
|
showDialog({
|
|
|
|
title: t`Delete Failed`,
|
2022-05-30 17:04:25 +05:30
|
|
|
detail: t`Cannot delete ${schemaLabel} ${doc.name!} because of linked entries.`,
|
2023-03-25 13:16:25 +05:30
|
|
|
type: 'error',
|
2022-05-30 17:04:25 +05:30
|
|
|
});
|
|
|
|
} else {
|
|
|
|
handleErrorWithDialog(err as Error, doc);
|
|
|
|
}
|
|
|
|
|
2022-05-04 13:49:40 +05:30
|
|
|
return false;
|
|
|
|
}
|
2023-03-25 13:16:25 +05:30
|
|
|
|
|
|
|
return true;
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
2023-03-25 13:16:25 +05:30
|
|
|
isPrimary: true,
|
2022-05-04 13:49:40 +05:30
|
|
|
},
|
|
|
|
{
|
2023-03-23 14:28:15 +05:30
|
|
|
label: t`No`,
|
2022-05-04 13:49:40 +05:30
|
|
|
action() {
|
|
|
|
return false;
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
2023-03-25 13:16:25 +05:30
|
|
|
isEscape: true,
|
2022-05-04 13:49:40 +05:30
|
|
|
},
|
|
|
|
],
|
2022-04-20 12:08:47 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function cancelDocWithPrompt(doc: Doc) {
|
|
|
|
let detail = t`This action is permanent`;
|
|
|
|
if (['SalesInvoice', 'PurchaseInvoice'].includes(doc.schemaName)) {
|
|
|
|
const payments = (
|
|
|
|
await fyo.db.getAll('Payment', {
|
|
|
|
fields: ['name'],
|
|
|
|
filters: { cancelled: false },
|
|
|
|
})
|
|
|
|
).map(({ name }) => name);
|
|
|
|
|
|
|
|
const query = (
|
|
|
|
await fyo.db.getAll('PaymentFor', {
|
|
|
|
fields: ['parent'],
|
|
|
|
filters: {
|
|
|
|
referenceName: doc.name!,
|
|
|
|
},
|
|
|
|
})
|
|
|
|
).filter(({ parent }) => payments.includes(parent));
|
|
|
|
|
|
|
|
const paymentList = [...new Set(query.map(({ parent }) => parent))];
|
|
|
|
|
|
|
|
if (paymentList.length === 1) {
|
|
|
|
detail = t`This action is permanent and will cancel the following payment: ${
|
|
|
|
paymentList[0] as string
|
|
|
|
}`;
|
|
|
|
} else if (paymentList.length > 1) {
|
|
|
|
detail = t`This action is permanent and will cancel the following payments: ${paymentList.join(
|
|
|
|
', '
|
|
|
|
)}`;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-25 13:16:25 +05:30
|
|
|
return await showDialog({
|
|
|
|
title: t`Cancel ${getActionLabel(doc)}?`,
|
2022-05-04 13:49:40 +05:30
|
|
|
detail,
|
2023-03-25 13:16:25 +05:30
|
|
|
type: 'warning',
|
2022-05-04 13:49:40 +05:30
|
|
|
buttons: [
|
|
|
|
{
|
|
|
|
label: t`Yes`,
|
|
|
|
async action() {
|
|
|
|
try {
|
|
|
|
await doc.cancel();
|
|
|
|
} catch (err) {
|
|
|
|
handleErrorWithDialog(err as Error, doc);
|
|
|
|
return false;
|
|
|
|
}
|
2023-03-25 13:16:25 +05:30
|
|
|
|
|
|
|
return true;
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
2023-03-25 13:16:25 +05:30
|
|
|
isPrimary: true,
|
2022-05-04 13:49:40 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t`No`,
|
|
|
|
action() {
|
|
|
|
return false;
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
2023-03-25 13:16:25 +05:30
|
|
|
isEscape: true,
|
2022-05-04 13:49:40 +05:30
|
|
|
},
|
|
|
|
],
|
2022-04-20 12:08:47 +05:30
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-11-30 19:05:49 +05:30
|
|
|
export function getActionsForDoc(doc?: Doc): Action[] {
|
2022-04-20 12:08:47 +05:30
|
|
|
if (!doc) return [];
|
|
|
|
|
|
|
|
const actions: Action[] = [
|
2022-04-21 18:38:36 +05:30
|
|
|
...getActions(doc),
|
2022-04-20 12:08:47 +05:30
|
|
|
getDuplicateAction(doc),
|
|
|
|
getDeleteAction(doc),
|
|
|
|
getCancelAction(doc),
|
|
|
|
];
|
|
|
|
|
|
|
|
return actions
|
|
|
|
.filter((d) => d.condition?.(doc) ?? true)
|
|
|
|
.map((d) => {
|
|
|
|
return {
|
2022-11-30 12:29:52 +05:30
|
|
|
group: d.group,
|
2022-04-20 12:08:47 +05:30
|
|
|
label: d.label,
|
|
|
|
component: d.component,
|
|
|
|
action: d.action,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2023-02-20 10:22:19 +05:30
|
|
|
export function getGroupedActionsForDoc(doc?: Doc): ActionGroup[] {
|
2022-11-30 19:05:49 +05:30
|
|
|
const actions = getActionsForDoc(doc);
|
2022-11-30 12:29:52 +05:30
|
|
|
const actionsMap = actions.reduce((acc, ac) => {
|
|
|
|
if (!ac.group) {
|
|
|
|
ac.group = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
acc[ac.group] ??= {
|
|
|
|
group: ac.group,
|
|
|
|
label: ac.label ?? '',
|
|
|
|
type: ac.type ?? 'secondary',
|
|
|
|
actions: [],
|
|
|
|
};
|
|
|
|
|
|
|
|
acc[ac.group].actions.push(ac);
|
|
|
|
return acc;
|
2023-02-20 10:22:19 +05:30
|
|
|
}, {} as Record<string, ActionGroup>);
|
2022-11-30 12:29:52 +05:30
|
|
|
|
|
|
|
const grouped = Object.keys(actionsMap)
|
|
|
|
.filter(Boolean)
|
|
|
|
.sort()
|
|
|
|
.map((k) => actionsMap[k]);
|
|
|
|
|
2022-11-30 19:05:49 +05:30
|
|
|
return [grouped, actionsMap['']].flat().filter(Boolean);
|
2022-11-30 12:29:52 +05:30
|
|
|
}
|
|
|
|
|
2022-04-20 12:08:47 +05:30
|
|
|
function getCancelAction(doc: Doc): Action {
|
|
|
|
return {
|
|
|
|
label: t`Cancel`,
|
|
|
|
component: {
|
|
|
|
template: '<span class="text-red-700">{{ t`Cancel` }}</span>',
|
|
|
|
},
|
2022-12-14 14:32:09 +05:30
|
|
|
condition: (doc: Doc) => doc.canCancel,
|
2022-05-04 13:49:40 +05:30
|
|
|
async action() {
|
2023-03-23 12:25:25 +05:30
|
|
|
await commonDocCancel(doc);
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getDeleteAction(doc: Doc): Action {
|
|
|
|
return {
|
|
|
|
label: t`Delete`,
|
|
|
|
component: {
|
|
|
|
template: '<span class="text-red-700">{{ t`Delete` }}</span>',
|
|
|
|
},
|
2022-07-28 14:29:04 +05:30
|
|
|
condition: (doc: Doc) => doc.canDelete,
|
2022-05-04 13:49:40 +05:30
|
|
|
async action() {
|
2023-03-23 12:25:25 +05:30
|
|
|
await commongDocDelete(doc);
|
2022-05-04 13:49:40 +05:30
|
|
|
},
|
2022-04-20 12:08:47 +05:30
|
|
|
};
|
|
|
|
}
|
2022-04-29 18:30:24 +05:30
|
|
|
|
2023-03-06 11:56:54 +05:30
|
|
|
async function openEdit({ name, schemaName }: Doc) {
|
|
|
|
if (!name) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const route = getFormRoute(schemaName, name);
|
|
|
|
return await routeTo(route);
|
2022-05-04 18:17:45 +05:30
|
|
|
}
|
|
|
|
|
2022-04-20 12:08:47 +05:30
|
|
|
function getDuplicateAction(doc: Doc): Action {
|
|
|
|
const isSubmittable = !!doc.schema.isSubmittable;
|
|
|
|
return {
|
|
|
|
label: t`Duplicate`,
|
2022-11-30 12:29:52 +05:30
|
|
|
group: t`Create`,
|
2022-04-20 12:08:47 +05:30
|
|
|
condition: (doc: Doc) =>
|
|
|
|
!!(
|
2022-05-04 18:17:45 +05:30
|
|
|
((isSubmittable && doc.submitted) || !isSubmittable) &&
|
|
|
|
!doc.notInserted
|
2022-04-20 12:08:47 +05:30
|
|
|
),
|
2022-05-04 13:49:40 +05:30
|
|
|
async action() {
|
2023-03-15 14:02:52 +05:30
|
|
|
try {
|
|
|
|
const dupe = doc.duplicate();
|
|
|
|
await openEdit(dupe);
|
|
|
|
} catch (err) {
|
|
|
|
handleErrorWithDialog(err as Error, doc);
|
|
|
|
}
|
2022-04-20 12:08:47 +05:30
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
2022-11-30 19:05:49 +05:30
|
|
|
|
2023-02-20 10:22:19 +05:30
|
|
|
export function getFieldsGroupedByTabAndSection(
|
2023-02-21 11:04:35 +05:30
|
|
|
schema: Schema,
|
|
|
|
doc: Doc
|
2023-02-20 10:22:19 +05:30
|
|
|
): UIGroupedFields {
|
|
|
|
const grouped: UIGroupedFields = new Map();
|
|
|
|
for (const field of schema?.fields ?? []) {
|
2023-04-14 10:43:18 +05:30
|
|
|
const tab = field.tab ?? 'Main';
|
2023-02-20 10:22:19 +05:30
|
|
|
const section = field.section ?? 'Default';
|
|
|
|
if (!grouped.has(tab)) {
|
|
|
|
grouped.set(tab, new Map());
|
|
|
|
}
|
|
|
|
|
|
|
|
const tabbed = grouped.get(tab)!;
|
|
|
|
if (!tabbed.has(section)) {
|
|
|
|
tabbed.set(section, []);
|
|
|
|
}
|
2022-11-30 19:05:49 +05:30
|
|
|
|
2023-02-21 11:04:35 +05:30
|
|
|
if (field.meta) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (evaluateHidden(field, doc)) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2023-02-20 10:22:19 +05:30
|
|
|
tabbed.get(section)!.push(field);
|
2022-11-30 19:05:49 +05:30
|
|
|
}
|
|
|
|
|
2023-04-14 10:43:18 +05:30
|
|
|
// Delete empty tabs and sections
|
|
|
|
for (const tkey of grouped.keys()) {
|
|
|
|
const section = grouped.get(tkey);
|
|
|
|
if (!section) {
|
|
|
|
grouped.delete(tkey);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const skey of section.keys()) {
|
|
|
|
const fields = section.get(skey);
|
|
|
|
if (!fields || !fields.length) {
|
|
|
|
section.delete(skey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!section?.size) {
|
|
|
|
grouped.delete(tkey);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-02-20 10:22:19 +05:30
|
|
|
return grouped;
|
2022-11-30 19:05:49 +05:30
|
|
|
}
|
2023-03-03 17:50:16 +05:30
|
|
|
|
|
|
|
export function getFormRoute(
|
|
|
|
schemaName: string,
|
|
|
|
name: string
|
|
|
|
): RouteLocationRaw {
|
|
|
|
const route = fyo.models[schemaName]
|
|
|
|
?.getListViewSettings(fyo)
|
|
|
|
?.formRoute?.(name);
|
|
|
|
|
|
|
|
if (typeof route === 'string') {
|
|
|
|
return route;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
[
|
|
|
|
ModelNameEnum.SalesInvoice,
|
|
|
|
ModelNameEnum.PurchaseInvoice,
|
|
|
|
ModelNameEnum.JournalEntry,
|
|
|
|
ModelNameEnum.Shipment,
|
|
|
|
ModelNameEnum.PurchaseReceipt,
|
|
|
|
ModelNameEnum.StockMovement,
|
|
|
|
ModelNameEnum.Payment,
|
|
|
|
ModelNameEnum.Item,
|
|
|
|
].includes(schemaName as ModelNameEnum)
|
|
|
|
) {
|
|
|
|
return `/edit/${schemaName}/${name}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `/list/${schemaName}?edit=1&schemaName=${schemaName}&name=${name}`;
|
|
|
|
}
|
2023-02-22 15:51:20 +05:30
|
|
|
|
|
|
|
export async function getDocFromNameIfExistsElseNew(
|
|
|
|
schemaName: string,
|
|
|
|
name?: string
|
|
|
|
) {
|
|
|
|
if (!name) {
|
|
|
|
return fyo.doc.getNewDoc(schemaName);
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
return await fyo.doc.getDoc(schemaName, name);
|
|
|
|
} catch {
|
|
|
|
return fyo.doc.getNewDoc(schemaName);
|
|
|
|
}
|
|
|
|
}
|
2023-03-06 15:13:54 +05:30
|
|
|
|
|
|
|
export async function isPrintable(schemaName: string) {
|
|
|
|
const numTemplates = await fyo.db.count(ModelNameEnum.PrintTemplate, {
|
|
|
|
filters: { type: schemaName },
|
|
|
|
});
|
|
|
|
return numTemplates > 0;
|
|
|
|
}
|
2023-03-07 15:07:02 +05:30
|
|
|
|
|
|
|
export function toggleSidebar(value?: boolean) {
|
|
|
|
if (typeof value !== 'boolean') {
|
|
|
|
value = !showSidebar.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
showSidebar.value = value;
|
|
|
|
}
|
2023-03-10 13:20:21 +05:30
|
|
|
|
2023-03-10 17:24:14 +05:30
|
|
|
export function focusOrSelectFormControl(
|
|
|
|
doc: Doc,
|
|
|
|
ref: any,
|
|
|
|
clear: boolean = true
|
|
|
|
) {
|
2023-03-20 13:16:53 +05:30
|
|
|
if (!doc?.fyo) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:20:21 +05:30
|
|
|
const naming = doc.fyo.schemaMap[doc.schemaName]?.naming;
|
|
|
|
if (naming !== 'manual' || doc.inserted) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-20 13:16:53 +05:30
|
|
|
if (!doc.fyo.doc.isTemporaryName(doc.name ?? '', doc.schema)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:20:21 +05:30
|
|
|
if (Array.isArray(ref) && ref.length > 0) {
|
|
|
|
ref = ref[0];
|
|
|
|
}
|
|
|
|
|
2023-03-10 17:24:14 +05:30
|
|
|
if (!clear && typeof ref?.select === 'function') {
|
|
|
|
ref.select();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-03-10 13:20:21 +05:30
|
|
|
if (typeof ref?.clear === 'function') {
|
|
|
|
ref.clear();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (typeof ref?.focus === 'function') {
|
|
|
|
ref.focus();
|
|
|
|
}
|
2023-03-10 17:24:14 +05:30
|
|
|
|
|
|
|
doc.name = '';
|
2023-03-10 13:20:21 +05:30
|
|
|
}
|
2023-03-13 12:36:56 +05:30
|
|
|
|
|
|
|
export async function selectTextFile(filters?: SelectFileOptions['filters']) {
|
|
|
|
const options = {
|
|
|
|
title: t`Select File`,
|
|
|
|
filters,
|
|
|
|
};
|
|
|
|
const { success, canceled, filePath, data, name } = await selectFile(options);
|
|
|
|
|
|
|
|
if (canceled || !success) {
|
|
|
|
await showToast({
|
|
|
|
type: 'error',
|
|
|
|
message: t`File selection failed`,
|
|
|
|
});
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
const text = new TextDecoder().decode(data);
|
|
|
|
if (!text) {
|
|
|
|
await showToast({
|
|
|
|
type: 'error',
|
|
|
|
message: t`Empty file selected`,
|
|
|
|
});
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
|
|
|
return { text, filePath, name };
|
|
|
|
}
|
|
|
|
|
|
|
|
export enum ShortcutKey {
|
|
|
|
enter = 'enter',
|
|
|
|
ctrl = 'ctrl',
|
|
|
|
pmod = 'pmod',
|
|
|
|
shift = 'shift',
|
|
|
|
alt = 'alt',
|
|
|
|
delete = 'delete',
|
|
|
|
esc = 'esc',
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getShortcutKeyMap(
|
|
|
|
platform: string
|
|
|
|
): Record<ShortcutKey, string> {
|
|
|
|
if (platform === 'Mac') {
|
|
|
|
return {
|
|
|
|
[ShortcutKey.alt]: '⌥',
|
|
|
|
[ShortcutKey.ctrl]: '⌃',
|
|
|
|
[ShortcutKey.pmod]: '⌘',
|
|
|
|
[ShortcutKey.shift]: 'shift',
|
|
|
|
[ShortcutKey.delete]: 'delete',
|
|
|
|
[ShortcutKey.esc]: 'esc',
|
|
|
|
[ShortcutKey.enter]: 'return',
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {
|
|
|
|
[ShortcutKey.alt]: 'Alt',
|
|
|
|
[ShortcutKey.ctrl]: 'Ctrl',
|
|
|
|
[ShortcutKey.pmod]: 'Ctrl',
|
|
|
|
[ShortcutKey.shift]: '⇧',
|
|
|
|
[ShortcutKey.delete]: 'Backspace',
|
|
|
|
[ShortcutKey.esc]: 'Esc',
|
|
|
|
[ShortcutKey.enter]: 'Enter',
|
|
|
|
};
|
|
|
|
}
|
2023-03-15 14:02:52 +05:30
|
|
|
|
2023-03-23 12:25:25 +05:30
|
|
|
export async function commongDocDelete(doc: Doc): Promise<boolean> {
|
|
|
|
const res = await deleteDocWithPrompt(doc);
|
|
|
|
if (!res) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
showActionToast(doc, 'delete');
|
|
|
|
router.back();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function commonDocCancel(doc: Doc): Promise<boolean> {
|
|
|
|
const res = await cancelDocWithPrompt(doc);
|
|
|
|
if (!res) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
showActionToast(doc, 'cancel');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function commonDocSync(
|
|
|
|
doc: Doc,
|
|
|
|
useDialog: boolean = false
|
|
|
|
): Promise<boolean> {
|
|
|
|
let success: boolean;
|
|
|
|
if (useDialog) {
|
|
|
|
success = !!(await showSubmitOrSyncDialog(doc, 'sync'));
|
|
|
|
} else {
|
|
|
|
success = await syncWithoutDialog(doc);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!success) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
showActionToast(doc, 'sync');
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function syncWithoutDialog(doc: Doc): Promise<boolean> {
|
2023-03-15 14:02:52 +05:30
|
|
|
try {
|
|
|
|
await doc.sync();
|
|
|
|
} catch (error) {
|
|
|
|
handleErrorWithDialog(error, doc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
export async function commonDocSubmit(doc: Doc): Promise<boolean> {
|
2023-03-23 12:25:25 +05:30
|
|
|
const success = await showSubmitOrSyncDialog(doc, 'submit');
|
2023-03-15 14:02:52 +05:30
|
|
|
if (!success) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
showSubmitToast(doc);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-03-23 12:25:25 +05:30
|
|
|
async function showSubmitOrSyncDialog(doc: Doc, type: 'submit' | 'sync') {
|
2023-03-23 14:28:15 +05:30
|
|
|
const label = getActionLabel(doc);
|
2023-03-25 13:16:25 +05:30
|
|
|
let title = t`Submit ${label}?`;
|
2023-03-23 12:25:25 +05:30
|
|
|
if (type === 'sync') {
|
2023-03-25 13:16:25 +05:30
|
|
|
title = t`Save ${label}?`;
|
|
|
|
}
|
|
|
|
|
|
|
|
let detail = t`Mark ${doc.schema.label} as submitted`;
|
|
|
|
if (type === 'sync') {
|
|
|
|
detail = t`Save ${doc.schema.label} to database`;
|
2023-03-23 12:25:25 +05:30
|
|
|
}
|
|
|
|
|
2023-03-15 14:02:52 +05:30
|
|
|
const yesAction = async () => {
|
|
|
|
try {
|
2023-03-23 12:25:25 +05:30
|
|
|
await doc[type]();
|
2023-03-15 14:02:52 +05:30
|
|
|
} catch (error) {
|
|
|
|
handleErrorWithDialog(error, doc);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
};
|
|
|
|
|
2023-03-25 13:16:25 +05:30
|
|
|
const buttons: DialogButton[] = [
|
2023-03-15 14:02:52 +05:30
|
|
|
{
|
|
|
|
label: t`Yes`,
|
|
|
|
action: yesAction,
|
2023-03-25 13:16:25 +05:30
|
|
|
isPrimary: true,
|
2023-03-15 14:02:52 +05:30
|
|
|
},
|
|
|
|
{
|
|
|
|
label: t`No`,
|
|
|
|
action: () => false,
|
2023-03-25 13:16:25 +05:30
|
|
|
isEscape: true,
|
2023-03-15 14:02:52 +05:30
|
|
|
},
|
|
|
|
];
|
|
|
|
|
2023-03-25 13:16:25 +05:30
|
|
|
return await showDialog({
|
|
|
|
title,
|
|
|
|
detail,
|
2023-03-15 14:02:52 +05:30
|
|
|
buttons,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
function showActionToast(doc: Doc, type: 'sync' | 'cancel' | 'delete') {
|
2023-03-23 14:28:15 +05:30
|
|
|
const label = getActionLabel(doc);
|
2023-03-15 14:02:52 +05:30
|
|
|
const message = {
|
|
|
|
sync: t`${label} saved`,
|
|
|
|
cancel: t`${label} cancelled`,
|
|
|
|
delete: t`${label} deleted`,
|
|
|
|
}[type];
|
|
|
|
|
2023-03-23 12:25:25 +05:30
|
|
|
showToast({ type: 'success', message, duration: 'short' });
|
2023-03-15 14:02:52 +05:30
|
|
|
}
|
|
|
|
|
|
|
|
function showSubmitToast(doc: Doc) {
|
2023-03-23 14:28:15 +05:30
|
|
|
const label = getActionLabel(doc);
|
2023-03-15 14:02:52 +05:30
|
|
|
const message = t`${label} submitted`;
|
|
|
|
const toastOption: ToastOptions = {
|
|
|
|
type: 'success',
|
|
|
|
message,
|
2023-03-23 12:25:25 +05:30
|
|
|
duration: 'long',
|
2023-03-15 14:02:52 +05:30
|
|
|
...getSubmitSuccessToastAction(doc),
|
|
|
|
};
|
|
|
|
showToast(toastOption);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getSubmitSuccessToastAction(doc: Doc) {
|
|
|
|
const isStockTransfer = doc instanceof Transfer;
|
|
|
|
const isTransactional = doc instanceof Transactional;
|
|
|
|
|
|
|
|
if (isStockTransfer) {
|
|
|
|
return {
|
|
|
|
async action() {
|
|
|
|
const route = getLedgerLink(doc, 'StockLedger');
|
|
|
|
await routeTo(route);
|
|
|
|
},
|
|
|
|
actionText: t`View Stock Entries`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isTransactional) {
|
|
|
|
return {
|
|
|
|
async action() {
|
|
|
|
const route = getLedgerLink(doc, 'GeneralLedger');
|
|
|
|
await routeTo(route);
|
|
|
|
},
|
|
|
|
actionText: t`View Accounting Entries`,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return {};
|
|
|
|
}
|
2023-03-23 12:25:25 +05:30
|
|
|
|
|
|
|
export function showCannotSaveOrSubmitToast(doc: Doc) {
|
2023-03-23 14:28:15 +05:30
|
|
|
const label = getActionLabel(doc);
|
2023-03-23 12:25:25 +05:30
|
|
|
let message = t`${label} already saved`;
|
|
|
|
|
|
|
|
if (doc.schema.isSubmittable && doc.isSubmitted) {
|
|
|
|
message = t`${label} already submitted`;
|
|
|
|
}
|
|
|
|
|
|
|
|
showToast({ type: 'warning', message, duration: 'short' });
|
|
|
|
}
|
|
|
|
|
|
|
|
export function showCannotCancelOrDeleteToast(doc: Doc) {
|
2023-03-23 14:28:15 +05:30
|
|
|
const label = getActionLabel(doc);
|
2023-03-23 12:25:25 +05:30
|
|
|
let message = t`${label} cannot be deleted`;
|
|
|
|
if (doc.schema.isSubmittable && !doc.isCancelled) {
|
|
|
|
message = t`${label} cannot be cancelled`;
|
|
|
|
}
|
|
|
|
|
|
|
|
showToast({ type: 'warning', message, duration: 'short' });
|
|
|
|
}
|
|
|
|
|
2023-03-23 14:28:15 +05:30
|
|
|
function getActionLabel(doc: Doc) {
|
2023-03-23 12:25:25 +05:30
|
|
|
const label = doc.schema.label || doc.schemaName;
|
|
|
|
if (doc.schema.naming === 'random') {
|
|
|
|
return label;
|
|
|
|
}
|
|
|
|
|
|
|
|
return doc.name || label;
|
|
|
|
}
|