2
0
mirror of https://github.com/frappe/books.git synced 2025-02-08 15:08:29 +00:00

incr: refactor and type src/utils

This commit is contained in:
18alantom 2022-04-20 12:08:47 +05:30
parent ae6a5e52f2
commit c56850d08f
69 changed files with 902 additions and 946 deletions

View File

@ -1,6 +1,6 @@
import { showMessageDialog } from '@/utils';
import frappe, { t } from 'fyo'; import frappe, { t } from 'fyo';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { showMessageDialog } from 'src/utils';
import { stateCodeMap } from '../regional/in'; import { stateCodeMap } from '../regional/in';
import { exportCsv, saveExportData } from '../reports/commonExporter'; import { exportCsv, saveExportData } from '../reports/commonExporter';
import { getSavePath } from '../src/utils'; import { getSavePath } from '../src/utils';
@ -50,7 +50,7 @@ export async function generateGstr1Json(getReportData) {
if (!gstin) { if (!gstin) {
showMessageDialog({ showMessageDialog({
message: t`Export Failed`, message: t`Export Failed`,
description: t`Please set GSTIN in General Settings.`, detail: t`Please set GSTIN in General Settings.`,
}); });
return; return;
} }
@ -232,7 +232,7 @@ export async function generateGstr2Csv(getReportData) {
if (!gstin) { if (!gstin) {
showMessageDialog({ showMessageDialog({
message: t`Export Failed`, message: t`Export Failed`,
description: t`Please set GSTIN in General Settings.`, detail: t`Please set GSTIN in General Settings.`,
}); });
return; return;
} }
@ -313,7 +313,7 @@ export async function generateGstr1Csv(getReportData) {
if (!gstin) { if (!gstin) {
showMessageDialog({ showMessageDialog({
message: t`Export Failed`, message: t`Export Failed`,
description: t`Please set GSTIN in General Settings.`, detail: t`Please set GSTIN in General Settings.`,
}); });
return; return;
} }

View File

@ -59,8 +59,11 @@ export type ListsMap = Record<string, ListFunction>;
export interface Action { export interface Action {
label: string; label: string;
condition: (doc: Doc) => boolean; action: (doc: Doc, router: Router) => Promise<void> | void;
action: (doc: Doc, router: Router) => Promise<void>; condition?: (doc: Doc) => boolean;
component?: {
template?: string;
};
} }
export interface ColumnConfig { export interface ColumnConfig {

View File

@ -41,7 +41,7 @@ describe('Fyo Init', function () {
describe('Fyo Docs', function () { describe('Fyo Docs', function () {
const countryCode = 'in'; const countryCode = 'in';
let fyo: Fyo; let fyo: Fyo;
const schemas = getSchemas(countryCode); const schemaMap = getSchemas(countryCode);
this.beforeEach(async function () { this.beforeEach(async function () {
fyo = new Fyo({ fyo = new Fyo({
DatabaseDemux: DatabaseManager, DatabaseDemux: DatabaseManager,
@ -58,7 +58,14 @@ describe('Fyo Docs', function () {
await fyo.close(); await fyo.close();
}); });
specify('temp', async function () { specify('getEmptyDoc', async function () {
fyo.db.schemaMap; for (const schemaName in schemaMap) {
const schema = schemaMap[schemaName];
if (schema?.isSingle) {
continue;
}
const doc = fyo.doc.getEmptyDoc(schemaName);
}
}); });
}); });

View File

@ -1,3 +1,6 @@
import { Fyo } from 'fyo';
import Doc from 'fyo/model/doc';
import { Action } from 'fyo/model/types';
import { pesa } from 'pesa'; import { pesa } from 'pesa';
export function slug(str: string) { export function slug(str: string) {
@ -50,3 +53,12 @@ export function getDuplicates(array: unknown[]) {
export function isPesa(value: unknown): boolean { export function isPesa(value: unknown): boolean {
return value instanceof pesa().constructor; return value instanceof pesa().constructor;
} }
export function getActions(doc: Doc, fyo: Fyo): Action[] {
const Model = fyo.models[doc.schemaName];
if (Model === undefined) {
return [];
}
return Model.getActions(fyo);
}

View File

@ -90,6 +90,8 @@ export function t(...args: TranslationLiteral[]): string {
return new TranslationString(...args).s; return new TranslationString(...args).s;
} }
export function setLanguageMapOnTranslationString(languageMap: LanguageMap) { export function setLanguageMapOnTranslationString(
languageMap: LanguageMap | undefined
) {
TranslationString.prototype.languageMap = languageMap; TranslationString.prototype.languageMap = languageMap;
} }

View File

@ -18,8 +18,12 @@ export abstract class Invoice extends Doc {
currency?: string; currency?: string;
netTotal?: Money; netTotal?: Money;
baseGrandTotal?: Money; baseGrandTotal?: Money;
outstandingAmount?: Money;
exchangeRate?: number; exchangeRate?: number;
submitted?: boolean;
cancelled?: boolean;
abstract getPosting(): Promise<LedgerPosting>; abstract getPosting(): Promise<LedgerPosting>;
get isSales() { get isSales() {

View File

@ -43,7 +43,7 @@ export function getTransactionActions(schemaName: string, fyo: Fyo): Action[] {
const paymentType = isSales ? 'Receive' : 'Pay'; const paymentType = isSales ? 'Receive' : 'Pay';
const hideAccountField = isSales ? 'account' : 'paymentAccount'; const hideAccountField = isSales ? 'account' : 'paymentAccount';
const { openQuickEdit } = await import('../src/utils'); const { openQuickEdit } = await import('src/utils/ui');
await openQuickEdit({ await openQuickEdit({
schemaName: 'Payment', schemaName: 'Payment',
name: payment.name as string, name: payment.name as string,

View File

@ -1,7 +1,33 @@
import { partyWithAvatar } from '@/utils';
import { t } from 'fyo'; import { t } from 'fyo';
import Avatar from 'src/components/Avatar.vue';
import { fyo } from 'src/initFyo';
import getCommonExportActions from '../commonExporter'; import getCommonExportActions from '../commonExporter';
export function getPartyWithAvatar(partyName) {
return {
data() {
return {
imageURL: null,
label: null,
};
},
components: {
Avatar,
},
async mounted() {
const p = await fyo.db.get('Party', partyName);
this.imageURL = p.image;
this.label = partyName;
},
template: `
<div class="flex items-center" v-if="label">
<Avatar class="flex-shrink-0" :imageURL="imageURL" :label="label" size="sm" />
<span class="ml-2 truncate">{{ label }}</span>
</div>
`,
};
}
let title = t`General Ledger`; let title = t`General Ledger`;
const viewConfig = { const viewConfig = {
@ -117,7 +143,7 @@ const viewConfig = {
fieldtype: 'Link', fieldtype: 'Link',
fieldname: 'party', fieldname: 'party',
component(cellValue) { component(cellValue) {
return partyWithAvatar(cellValue); return getPartyWithAvatar(cellValue);
}, },
}, },
]; ];

View File

@ -30,16 +30,16 @@
</template> </template>
<script> <script>
import WindowsTitleBar from '@/components/WindowsTitleBar'; import { ipcRenderer } from 'electron';
import config from '@/config'; import fs from 'fs/promises';
import frappe from 'fyo';
import WindowsTitleBar from 'src/components/WindowsTitleBar';
import config from 'src/config';
import { import {
connectToLocalDatabase, connectToLocalDatabase,
postSetup, postSetup,
purgeCache purgeCache
} from '@/initialization'; } from 'src/initialization';
import { ipcRenderer } from 'electron';
import fs from 'fs/promises';
import frappe from 'fyo';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import TelemetryModal from './components/once/TelemetryModal.vue'; import TelemetryModal from './components/once/TelemetryModal.vue';
import { showErrorDialog } from './errorHandling'; import { showErrorDialog } from './errorHandling';

View File

@ -32,9 +32,9 @@
</template> </template>
<script> <script>
import Dropdown from 'src/components/Dropdown';
import { fuzzyMatch } from 'src/utils';
import Base from './Base'; import Base from './Base';
import Dropdown from '@/components/Dropdown';
import { fuzzyMatch } from '@/utils';
export default { export default {
name: 'AutoComplete', name: 'AutoComplete',

View File

@ -55,9 +55,9 @@
</template> </template>
<script> <script>
import Popover from 'src/components/Popover';
import Row from 'src/components/Row';
import Base from './Base'; import Base from './Base';
import Row from '@/components/Row';
import Popover from '@/components/Popover';
export default { export default {
name: 'Color', name: 'Color',

View File

@ -7,10 +7,10 @@
/> />
</template> </template>
<script> <script>
import config from '@/config';
import { languageCodeMap } from '@/languageCodeMap';
import { setLanguageMap } from '@/utils';
import { DEFAULT_LANGUAGE } from 'frappe/utils/consts'; import { DEFAULT_LANGUAGE } from 'frappe/utils/consts';
import config from 'src/config';
import { languageCodeMap } from 'src/languageCodeMap';
import { setLanguageMap } from 'src/utils';
import FormControl from './FormControl'; import FormControl from './FormControl';
export default { export default {

View File

@ -1,12 +1,12 @@
import Badge from '@/components/Badge'; import Badge from 'src/components/Badge';
import { openQuickEdit } from '@/utils'; import { openQuickEdit } from 'src/utils';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import { markRaw } from 'vue'; import { markRaw } from 'vue';
import AutoComplete from './AutoComplete'; import AutoComplete from './AutoComplete';
<script> <script>
import Badge from '@/components/Badge';
import { openQuickEdit } from '@/utils';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import Badge from 'src/components/Badge';
import { openQuickEdit } from 'src/utils';
import { markRaw } from 'vue'; import { markRaw } from 'vue';
import AutoComplete from './AutoComplete'; import AutoComplete from './AutoComplete';

View File

@ -62,7 +62,7 @@
<script> <script>
import frappe from 'frappe'; import frappe from 'frappe';
import Row from '@/components/Row'; import Row from 'src/components/Row';
import Base from './Base'; import Base from './Base';
import TableRow from './TableRow'; import TableRow from './TableRow';

View File

@ -32,9 +32,9 @@
</Row> </Row>
</template> </template>
<script> <script>
import FormControl from './FormControl'; import Row from 'src/components/Row';
import Row from '@/components/Row';
import { getErrorMessage } from '../../errorHandling'; import { getErrorMessage } from '../../errorHandling';
import FormControl from './FormControl';
export default { export default {
name: 'TableRow', name: 'TableRow',

View File

@ -21,8 +21,8 @@
</template> </template>
<script> <script>
import Dropdown from '@/components/Dropdown'; import Button from 'src/components/Button';
import Button from '@/components/Button'; import Dropdown from 'src/components/Dropdown';
export default { export default {
name: 'DropdownWithActions', name: 'DropdownWithActions',

View File

@ -47,10 +47,10 @@
</div> </div>
</template> </template>
<script> <script>
import InvoiceTemplate1 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate1'; import InvoiceTemplate1 from 'src/../models/doctype/SalesInvoice/Templates/InvoiceTemplate1';
import InvoiceTemplate2 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate2'; import InvoiceTemplate2 from 'src/../models/doctype/SalesInvoice/Templates/InvoiceTemplate2';
import InvoiceTemplate3 from '@/../models/doctype/SalesInvoice/Templates/InvoiceTemplate3'; import InvoiceTemplate3 from 'src/../models/doctype/SalesInvoice/Templates/InvoiceTemplate3';
import InvoiceCustomizer from '@/components/InvoiceCustomizer'; import InvoiceCustomizer from 'src/components/InvoiceCustomizer';
const invoiceTemplates = { const invoiceTemplates = {
'Basic I': InvoiceTemplate1, 'Basic I': InvoiceTemplate1,

View File

@ -41,9 +41,9 @@
</div> </div>
</template> </template>
<script> <script>
import Dropdown from '@/components/Dropdown';
import { routeTo } from '@/utils';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import Dropdown from 'src/components/Dropdown';
import { routeTo } from 'src/utils';
import reports from '../../reports/view'; import reports from '../../reports/view';
export default { export default {

View File

@ -96,10 +96,10 @@
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button';
import { reportIssue } from '@/errorHandling';
import { routeTo } from '@/utils';
import path from 'path'; import path from 'path';
import Button from 'src/components/Button';
import { reportIssue } from 'src/errorHandling';
import { routeTo } from 'src/utils';
import router from '../router'; import router from '../router';
import sidebarConfig from '../sidebarConfig'; import sidebarConfig from '../sidebarConfig';
import Icon from './Icon.vue'; import Icon from './Icon.vue';

View File

@ -4,8 +4,8 @@
}}</Badge> }}</Badge>
</template> </template>
<script> <script>
import Badge from 'src/components/Badge';
import { statusColor } from '../colors'; import { statusColor } from '../colors';
import Badge from '@/components/Badge';
export default { export default {
name: 'StatusBadge', name: 'StatusBadge',

View File

@ -1,5 +1,5 @@
import Button from '@/components/Button'; import Button from 'src/components/Button';
import FormControl from '@/components/Controls/FormControl'; import FormControl from 'src/components/Controls/FormControl';
import frappe from 'frappe'; import frappe from 'frappe';
import { getErrorMessage, handleErrorWithDialog } from '../errorHandling'; import { getErrorMessage, handleErrorWithDialog } from '../errorHandling';
<template> <template>
@ -90,9 +90,9 @@ import { getErrorMessage, handleErrorWithDialog } from '../errorHandling';
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button';
import FormControl from '@/components/Controls/FormControl';
import frappe from 'frappe'; import frappe from 'frappe';
import Button from 'src/components/Button';
import FormControl from 'src/components/Controls/FormControl';
import { getErrorMessage, handleErrorWithDialog } from '../errorHandling'; import { getErrorMessage, handleErrorWithDialog } from '../errorHandling';
let TwoColumnForm = { let TwoColumnForm = {

View File

@ -49,8 +49,8 @@
</template> </template>
<script> <script>
import { routeTo } from '@/utils';
import frappe from 'frappe'; import frappe from 'frappe';
import { routeTo } from 'src/utils';
import { getStatusColumn } from '../Transaction/Transaction'; import { getStatusColumn } from '../Transaction/Transaction';
export default { export default {

View File

@ -19,7 +19,7 @@
</template> </template>
<script> <script>
import { runWindowAction } from '@/utils'; import { runWindowAction } from 'src/utils';
export default { export default {
name: 'WindowControls', name: 'WindowControls',

View File

@ -31,7 +31,7 @@
<script> <script>
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { runWindowAction } from '@/utils'; import { runWindowAction } from 'src/utils';
import { IPC_MESSAGES } from 'utils/messages'; import { IPC_MESSAGES } from 'utils/messages';
export default { export default {

View File

@ -49,10 +49,10 @@
</template> </template>
<script> <script>
import config, { ConfigKeys, TelemetrySetting } from '@/config'; import config, { ConfigKeys, TelemetrySetting } from 'src/config';
import { getTelemetryOptions } from '@/telemetry/helpers'; import { getTelemetryOptions } from 'src/telemetry/helpers';
import telemetry from '@/telemetry/telemetry'; import telemetry from 'src/telemetry/telemetry';
import { NounEnum, Verb } from '@/telemetry/types'; import { NounEnum, Verb } from 'src/telemetry/types';
import Button from '../Button.vue'; import Button from '../Button.vue';
import FormControl from '../Controls/FormControl'; import FormControl from '../Controls/FormControl';
import FeatherIcon from '../FeatherIcon.vue'; import FeatherIcon from '../FeatherIcon.vue';

View File

@ -1,11 +1,11 @@
import frappe from 'fyo'; import { t } from 'fyo';
import { DocValueMap } from 'fyo/core/types'; import { DocValueMap } from 'fyo/core/types';
import Doc from 'fyo/model/doc'; import Doc from 'fyo/model/doc';
import { isNameAutoSet } from 'fyo/model/naming'; import { isNameAutoSet } from 'fyo/model/naming';
import { Noun, Verb } from 'fyo/telemetry/types';
import { FieldType, FieldTypeEnum } from 'schemas/types'; import { FieldType, FieldTypeEnum } from 'schemas/types';
import telemetry from '../frappe/telemetry/telemetry';
import { Noun, Verb } from '../frappe/telemetry/types';
import { parseCSV } from '../utils/csvParser'; import { parseCSV } from '../utils/csvParser';
import { fyo } from './initFyo';
export const importable = [ export const importable = [
'SalesInvoice', 'SalesInvoice',
@ -364,7 +364,7 @@ export class Importer {
async importData(setLoadingStatus: LoadingStatusCallback): Promise<Status> { async importData(setLoadingStatus: LoadingStatusCallback): Promise<Status> {
const status: Status = { success: false, names: [], message: '' }; const status: Status = { success: false, names: [], message: '' };
const shouldDeleteName = isNameAutoSet(this.doctype); const shouldDeleteName = isNameAutoSet(this.doctype, fyo);
const docObjs = this.getDocs(); const docObjs = this.getDocs();
let entriesMade = 0; let entriesMade = 0;
@ -383,7 +383,7 @@ export class Importer {
delete docObj[key]; delete docObj[key];
} }
const doc: Doc = frappe.doc.getEmptyDoc(this.doctype, false); const doc: Doc = fyo.doc.getEmptyDoc(this.doctype, false);
try { try {
await this.makeEntry(doc, docObj); await this.makeEntry(doc, docObj);
entriesMade += 1; entriesMade += 1;
@ -391,7 +391,7 @@ export class Importer {
} catch (err) { } catch (err) {
setLoadingStatus(false, entriesMade, docObjs.length); setLoadingStatus(false, entriesMade, docObjs.length);
telemetry.log(Verb.Imported, this.doctype as Noun, { fyo.telemetry.log(Verb.Imported, this.doctype as Noun, {
success: false, success: false,
count: entriesMade, count: entriesMade,
}); });
@ -405,7 +405,7 @@ export class Importer {
setLoadingStatus(false, entriesMade, docObjs.length); setLoadingStatus(false, entriesMade, docObjs.length);
status.success = true; status.success = true;
telemetry.log(Verb.Imported, this.doctype as Noun, { fyo.telemetry.log(Verb.Imported, this.doctype as Noun, {
success: true, success: true,
count: entriesMade, count: entriesMade,
}); });
@ -426,18 +426,18 @@ export class Importer {
} }
handleError(doc: Doc, err: Error, status: Status): Status { handleError(doc: Doc, err: Error, status: Status): Status {
const messages = [frappe.t`Could not import ${this.doctype} ${doc.name!}.`]; const messages = [t`Could not import ${this.doctype} ${doc.name!}.`];
const message = err.message; const message = err.message;
if (message?.includes('UNIQUE constraint failed')) { if (message?.includes('UNIQUE constraint failed')) {
messages.push(frappe.t`${doc.name!} already exists.`); messages.push(t`${doc.name!} already exists.`);
} else if (message) { } else if (message) {
messages.push(message); messages.push(message);
} }
if (status.names.length) { if (status.names.length) {
messages.push( messages.push(
frappe.t`The following ${ t`The following ${
status.names.length status.names.length
} entries were created: ${status.names.join(', ')}` } entries were created: ${status.names.join(', ')}`
); );

View File

@ -1,6 +1,8 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe, { t } from 'fyo'; import { t } from 'fyo';
import { ConfigKeys } from 'fyo/core/types';
import Doc from 'fyo/model/doc'; import Doc from 'fyo/model/doc';
import { TelemetrySetting } from 'fyo/telemetry/types';
import { import {
DuplicateEntryError, DuplicateEntryError,
LinkValidationError, LinkValidationError,
@ -9,12 +11,12 @@ import {
} from 'fyo/utils/errors'; } from 'fyo/utils/errors';
import { ErrorLog } from 'fyo/utils/types'; import { ErrorLog } from 'fyo/utils/types';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages'; import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import telemetry from '../frappe/telemetry/telemetry'; import { fyo } from './initFyo';
import config, { ConfigKeys, TelemetrySetting } from '../utils/config'; import { ToastOptions } from './utils/types';
import { showMessageDialog, showToast } from './utils.js'; import { showMessageDialog, showToast } from './utils/ui';
function getCanLog(): boolean { function getCanLog(): boolean {
const telemetrySetting = config.get(ConfigKeys.Telemetry); const telemetrySetting = fyo.config.get(ConfigKeys.Telemetry);
return telemetrySetting !== TelemetrySetting.dontLogAnything; return telemetrySetting !== TelemetrySetting.dontLogAnything;
} }
@ -36,7 +38,7 @@ async function reportError(errorLogObj: ErrorLog, cb?: Function) {
more: JSON.stringify(errorLogObj.more ?? {}), more: JSON.stringify(errorLogObj.more ?? {}),
}; };
if (frappe.store.isDevelopment) { if (fyo.store.isDevelopment) {
console.log('errorHandling'); console.log('errorHandling');
console.log(body); console.log(body);
} }
@ -46,7 +48,7 @@ async function reportError(errorLogObj: ErrorLog, cb?: Function) {
} }
function getToastProps(errorLogObj: ErrorLog, canLog: boolean, cb?: Function) { function getToastProps(errorLogObj: ErrorLog, canLog: boolean, cb?: Function) {
const props = { const props: ToastOptions = {
message: t`Error: ` + errorLogObj.name, message: t`Error: ` + errorLogObj.name,
type: 'error', type: 'error',
}; };
@ -73,7 +75,7 @@ export function getErrorLogObject(
const errorLogObj = { name, stack, message, more }; const errorLogObj = { name, stack, message, more };
// @ts-ignore // @ts-ignore
frappe.errorLog.push(errorLogObj); fyo.errorLog.push(errorLogObj);
return errorLogObj; return errorLogObj;
} }
@ -84,7 +86,7 @@ export function handleError(
more?: Record<string, unknown>, more?: Record<string, unknown>,
cb?: Function cb?: Function
) { ) {
telemetry.error(error.name); fyo.telemetry.error(error.name);
if (shouldLog) { if (shouldLog) {
console.error(error); console.error(error);
} }
@ -124,7 +126,7 @@ export function handleErrorWithDialog(error: Error, doc?: Doc) {
const errorMessage = getErrorMessage(error, doc); const errorMessage = getErrorMessage(error, doc);
handleError(false, error, { errorMessage, doc }); handleError(false, error, { errorMessage, doc });
showMessageDialog({ message: error.name, description: errorMessage }); showMessageDialog({ message: error.name, detail: errorMessage });
throw error; throw error;
} }

View File

@ -1,9 +0,0 @@
// Language: Language Code in books/translations
export const languageCodeMap = {
English: 'en',
French: 'fr',
German: 'de',
Portuguese: 'pt',
Arabic: 'ar',
Catalan: 'ca-ES',
};

View File

@ -1,10 +1,10 @@
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe from 'frappe';
import { createApp } from 'vue'; import { createApp } from 'vue';
import App from './App'; import App from './App';
import FeatherIcon from './components/FeatherIcon'; import FeatherIcon from './components/FeatherIcon';
import config, { ConfigKeys } from './config'; import config, { ConfigKeys } from './config';
import { getErrorHandled, handleError } from './errorHandling'; import { getErrorHandled, handleError } from './errorHandling';
import { fyo } from './initFyo';
import { IPC_ACTIONS } from './messages'; import { IPC_ACTIONS } from './messages';
import { incrementOpenCount } from './renderer/helpers'; import { incrementOpenCount } from './renderer/helpers';
import registerIpcRendererListeners from './renderer/registerIpcRendererListeners'; import registerIpcRendererListeners from './renderer/registerIpcRendererListeners';
@ -13,7 +13,7 @@ import { outsideClickDirective } from './ui';
import { setLanguageMap, stringifyCircular } from './utils'; import { setLanguageMap, stringifyCircular } from './utils';
(async () => { (async () => {
const language = config.get(ConfigKeys.Language); const language = fyo.config.get(ConfigKeys.Language);
if (language) { if (language) {
await setLanguageMap(language); await setLanguageMap(language);
} }
@ -22,15 +22,15 @@ import { setLanguageMap, stringifyCircular } from './utils';
window.config = config; window.config = config;
} }
frappe.isElectron = true; fyo.isElectron = true;
const models = (await import('../models')).default; const models = (await import('../models')).default;
await frappe.initializeAndRegister(models); await fyo.initializeAndRegister(models);
ipcRenderer.send = getErrorHandled(ipcRenderer.send); ipcRenderer.send = getErrorHandled(ipcRenderer.send);
ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke); ipcRenderer.invoke = getErrorHandled(ipcRenderer.invoke);
window.frappe = frappe; window.frappe = fyo;
window.onerror = (message, source, lineno, colno, error) => { window.onerror = (message, source, lineno, colno, error) => {
error = error ?? new Error('triggered in window.onerror'); error = error ?? new Error('triggered in window.onerror');
@ -50,7 +50,7 @@ import { setLanguageMap, stringifyCircular } from './utils';
app.mixin({ app.mixin({
computed: { computed: {
frappe() { frappe() {
return frappe; return fyo;
}, },
platform() { platform() {
switch (process.platform) { switch (process.platform) {
@ -66,8 +66,8 @@ import { setLanguageMap, stringifyCircular } from './utils';
}, },
}, },
methods: { methods: {
t: frappe.t, t: fyo.t,
T: frappe.T, T: fyo.T,
}, },
}); });
@ -88,7 +88,7 @@ import { setLanguageMap, stringifyCircular } from './utils';
console.error(err, vm, info); console.error(err, vm, info);
}; };
frappe.store.appVersion = await ipcRenderer.invoke(IPC_ACTIONS.GET_VERSION); fyo.store.appVersion = await ipcRenderer.invoke(IPC_ACTIONS.GET_VERSION);
incrementOpenCount(); incrementOpenCount();
app.mount('body'); app.mount('body');
@ -100,4 +100,3 @@ import { setLanguageMap, stringifyCircular } from './utils';
handleError(true, error, {}, () => process.exit(1)); handleError(true, error, {}, () => process.exit(1));
}); });
})(); })();

View File

@ -1,6 +1,6 @@
import PageHeader from '@/components/PageHeader'; import PageHeader from 'src/components/PageHeader';
import SearchBar from '@/components/SearchBar'; import SearchBar from 'src/components/SearchBar';
import { openQuickEdit } from '@/utils'; import { openQuickEdit } from 'src/utils';
import frappe from 'frappe'; import frappe from 'frappe';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { handleErrorWithDialog } from '../errorHandling'; import { handleErrorWithDialog } from '../errorHandling';
@ -135,10 +135,10 @@ import { handleErrorWithDialog } from '../errorHandling';
</div> </div>
</template> </template>
<script> <script>
import PageHeader from '@/components/PageHeader';
import SearchBar from '@/components/SearchBar';
import { openQuickEdit } from '@/utils';
import frappe from 'frappe'; import frappe from 'frappe';
import PageHeader from 'src/components/PageHeader';
import SearchBar from 'src/components/SearchBar';
import { openQuickEdit } from 'src/utils';
import { nextTick } from 'vue'; import { nextTick } from 'vue';
import { handleErrorWithDialog } from '../errorHandling'; import { handleErrorWithDialog } from '../errorHandling';

View File

@ -101,12 +101,12 @@
</template> </template>
<script> <script>
import frappe from 'frappe'; import frappe from 'frappe';
import PeriodSelector from './PeriodSelector'; import { getYMax } from 'src/components/Charts/chartUtils';
import LineChart from 'src/components/Charts/LineChart.vue';
import { formatXLabels } from 'src/utils';
import Cashflow from '../../../reports/Cashflow/Cashflow'; import Cashflow from '../../../reports/Cashflow/Cashflow';
import { getDatesAndPeriodicity } from './getDatesAndPeriodicity'; import { getDatesAndPeriodicity } from './getDatesAndPeriodicity';
import LineChart from '@/components/Charts/LineChart.vue'; import PeriodSelector from './PeriodSelector';
import { getYMax } from '@/components/Charts/chartUtils';
import { formatXLabels } from '@/utils';
export default { export default {
name: 'Cashflow', name: 'Cashflow',

View File

@ -23,12 +23,12 @@
</template> </template>
<script> <script>
import PageHeader from '@/components/PageHeader'; import PageHeader from 'src/components/PageHeader';
import SearchBar from '@/components/SearchBar'; import SearchBar from 'src/components/SearchBar';
import Cashflow from './Cashflow'; import Cashflow from './Cashflow';
import UnpaidInvoices from './UnpaidInvoices';
import ProfitAndLoss from './ProfitAndLoss';
import Expenses from './Expenses'; import Expenses from './Expenses';
import ProfitAndLoss from './ProfitAndLoss';
import UnpaidInvoices from './UnpaidInvoices';
export default { export default {
name: 'Dashboard', name: 'Dashboard',

View File

@ -50,8 +50,8 @@
</template> </template>
<script> <script>
import theme from '@/theme';
import frappe from 'frappe'; import frappe from 'frappe';
import theme from 'src/theme';
import DonutChart from '../../components/Charts/DonutChart.vue'; import DonutChart from '../../components/Charts/DonutChart.vue';
import { getDatesAndPeriodicity } from './getDatesAndPeriodicity'; import { getDatesAndPeriodicity } from './getDatesAndPeriodicity';
import PeriodSelector from './PeriodSelector'; import PeriodSelector from './PeriodSelector';

View File

@ -36,8 +36,8 @@
</template> </template>
<script> <script>
import Dropdown from '@/components/Dropdown';
import { t } from 'frappe'; import { t } from 'frappe';
import Dropdown from 'src/components/Dropdown';
export default { export default {
name: 'PeriodSelector', name: 'PeriodSelector',

View File

@ -29,13 +29,13 @@
</template> </template>
<script> <script>
import frappe from 'frappe'; import frappe from 'frappe';
import PeriodSelector from './PeriodSelector'; import BarChart from 'src/components/Charts/BarChart.vue';
import SectionHeader from './SectionHeader'; import { getYMax, getYMin } from 'src/components/Charts/chartUtils';
import { formatXLabels } from 'src/utils';
import ProfitAndLoss from '../../../reports/ProfitAndLoss/ProfitAndLoss'; import ProfitAndLoss from '../../../reports/ProfitAndLoss/ProfitAndLoss';
import { getDatesAndPeriodicity } from './getDatesAndPeriodicity'; import { getDatesAndPeriodicity } from './getDatesAndPeriodicity';
import BarChart from '@/components/Charts/BarChart.vue'; import PeriodSelector from './PeriodSelector';
import { getYMax, getYMin } from '@/components/Charts/chartUtils'; import SectionHeader from './SectionHeader';
import { formatXLabels } from '@/utils';
export default { export default {
name: 'ProfitAndLoss', name: 'ProfitAndLoss',

View File

@ -68,9 +68,9 @@
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button';
import { routeTo } from '@/utils';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import Button from 'src/components/Button';
import { routeTo } from 'src/utils';
import { getDatesAndPeriodicity } from './getDatesAndPeriodicity'; import { getDatesAndPeriodicity } from './getDatesAndPeriodicity';
import PeriodSelector from './PeriodSelector'; import PeriodSelector from './PeriodSelector';
import SectionHeader from './SectionHeader'; import SectionHeader from './SectionHeader';

View File

@ -336,17 +336,17 @@
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button.vue';
import FormControl from '@/components/Controls/FormControl';
import DropdownWithActions from '@/components/DropdownWithActions.vue';
import FeatherIcon from '@/components/FeatherIcon.vue';
import HowTo from '@/components/HowTo.vue';
import PageHeader from '@/components/PageHeader.vue';
import { importable, Importer } from '@/dataImport';
import { IPC_ACTIONS } from 'utils/messages';
import { getSavePath, saveData, showMessageDialog } from '@/utils';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe from 'frappe'; import frappe from 'frappe';
import Button from 'src/components/Button.vue';
import FormControl from 'src/components/Controls/FormControl';
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
import FeatherIcon from 'src/components/FeatherIcon.vue';
import HowTo from 'src/components/HowTo.vue';
import PageHeader from 'src/components/PageHeader.vue';
import { importable, Importer } from 'src/dataImport';
import { getSavePath, saveData, showMessageDialog } from 'src/utils';
import { IPC_ACTIONS } from 'utils/messages';
import Loading from '../components/Loading.vue'; import Loading from '../components/Loading.vue';
export default { export default {
components: { components: {
@ -550,7 +550,7 @@ export default {
if (this.isRequiredUnassigned) { if (this.isRequiredUnassigned) {
showMessageDialog({ showMessageDialog({
message: this.t`Required Fields not Assigned`, message: this.t`Required Fields not Assigned`,
description: this detail: this
.t`Please assign the following fields ${this.requiredUnassigned.join( .t`Please assign the following fields ${this.requiredUnassigned.join(
', ' ', '
)}`, )}`,
@ -561,7 +561,7 @@ export default {
if (this.importer.assignedMatrix.length === 0) { if (this.importer.assignedMatrix.length === 0) {
showMessageDialog({ showMessageDialog({
message: this.t`No Data to Import`, message: this.t`No Data to Import`,
description: this.t`Please select a file with data to import.`, detail: this.t`Please select a file with data to import.`,
}); });
return; return;
} }
@ -572,7 +572,7 @@ export default {
if (!success) { if (!success) {
showMessageDialog({ showMessageDialog({
message: this.t`Import Failed`, message: this.t`Import Failed`,
description: message, detail: message,
}); });
return; return;
} }
@ -617,7 +617,7 @@ export default {
if (!isValid) { if (!isValid) {
showMessageDialog({ showMessageDialog({
message: this.t`Bad import data.`, message: this.t`Bad import data.`,
description: this.t`Could not select file.`, detail: this.t`Could not select file.`,
}); });
return; return;
} }

View File

@ -158,14 +158,14 @@
</div> </div>
</template> </template>
<script> <script>
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
import config from '@/config';
import { connectToLocalDatabase, createNewDatabase } from '@/initialization';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import fs from 'fs'; import fs from 'fs';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { showErrorDialog } from '../errorHandling'; import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import config from 'src/config';
import { connectToLocalDatabase, createNewDatabase } from 'src/initialization';
import { DB_CONN_FAILURE, IPC_ACTIONS } from '../../utils/messages'; import { DB_CONN_FAILURE, IPC_ACTIONS } from '../../utils/messages';
import { showErrorDialog } from '../errorHandling';
export default { export default {
name: 'DatabaseSelector', name: 'DatabaseSelector',

View File

@ -84,12 +84,12 @@
</template> </template>
<script> <script>
import Button from '@/components/Button';
import Icon from '@/components/Icon';
import PageHeader from '@/components/PageHeader';
import { openSettings, routeTo } from '@/utils';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import Button from 'src/components/Button';
import Icon from 'src/components/Icon';
import PageHeader from 'src/components/PageHeader';
import { openSettings, routeTo } from 'src/utils';
import { IPC_MESSAGES } from 'utils/messages'; import { IPC_MESSAGES } from 'utils/messages';
import { h } from 'vue'; import { h } from 'vue';

View File

@ -198,20 +198,20 @@
</div> </div>
</template> </template>
<script> <script>
import BackLink from '@/components/BackLink'; import frappe from 'frappe';
import Button from '@/components/Button'; import { getInvoiceStatus } from 'models/helpers';
import FormControl from '@/components/Controls/FormControl'; import BackLink from 'src/components/BackLink';
import DropdownWithActions from '@/components/DropdownWithActions'; import Button from 'src/components/Button';
import PageHeader from '@/components/PageHeader'; import FormControl from 'src/components/Controls/FormControl';
import StatusBadge from '@/components/StatusBadge'; import DropdownWithActions from 'src/components/DropdownWithActions';
import PageHeader from 'src/components/PageHeader';
import StatusBadge from 'src/components/StatusBadge';
import { import {
getActionsForDocument, getActionsForDocument,
getInvoiceStatus,
openSettings, openSettings,
routeTo, routeTo,
showMessageDialog showMessageDialog
} from '@/utils'; } from 'src/utils';
import frappe from 'frappe';
import { handleErrorWithDialog } from '../errorHandling'; import { handleErrorWithDialog } from '../errorHandling';
export default { export default {

View File

@ -128,14 +128,14 @@
</div> </div>
</template> </template>
<script> <script>
import BackLink from '@/components/BackLink';
import Button from '@/components/Button';
import FormControl from '@/components/Controls/FormControl';
import DropdownWithActions from '@/components/DropdownWithActions';
import PageHeader from '@/components/PageHeader';
import StatusBadge from '@/components/StatusBadge';
import { getActionsForDocument, routeTo, showMessageDialog } from '@/utils';
import frappe from 'frappe'; import frappe from 'frappe';
import BackLink from 'src/components/BackLink';
import Button from 'src/components/Button';
import FormControl from 'src/components/Controls/FormControl';
import DropdownWithActions from 'src/components/DropdownWithActions';
import PageHeader from 'src/components/PageHeader';
import StatusBadge from 'src/components/StatusBadge';
import { getActionsForDocument, routeTo, showMessageDialog } from 'src/utils';
import { handleErrorWithDialog } from '../errorHandling'; import { handleErrorWithDialog } from '../errorHandling';
export default { export default {

View File

@ -56,11 +56,11 @@
</div> </div>
</template> </template>
<script> <script>
import Avatar from '@/components/Avatar';
import Button from '@/components/Button';
import Row from '@/components/Row';
import { openQuickEdit, routeTo } from '@/utils';
import frappe from 'frappe'; import frappe from 'frappe';
import Avatar from 'src/components/Avatar';
import Button from 'src/components/Button';
import Row from 'src/components/Row';
import { openQuickEdit, routeTo } from 'src/utils';
import ListCell from './ListCell'; import ListCell from './ListCell';
export default { export default {

View File

@ -30,12 +30,12 @@
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button';
import FilterDropdown from '@/components/FilterDropdown';
import PageHeader from '@/components/PageHeader';
import SearchBar from '@/components/SearchBar';
import { routeTo } from '@/utils';
import frappe from 'fyo'; import frappe from 'fyo';
import Button from 'src/components/Button';
import FilterDropdown from 'src/components/FilterDropdown';
import PageHeader from 'src/components/PageHeader';
import SearchBar from 'src/components/SearchBar';
import { routeTo } from 'src/utils';
import List from './List'; import List from './List';
export default { export default {

View File

@ -51,16 +51,16 @@
</div> </div>
</template> </template>
<script> <script>
import BackLink from '@/components/BackLink';
import Button from '@/components/Button';
import PageHeader from '@/components/PageHeader';
import SearchBar from '@/components/SearchBar';
import TwoColumnForm from '@/components/TwoColumnForm';
import telemetry from '@/telemetry/telemetry';
import { Verb } from '@/telemetry/types';
import { makePDF } from '@/utils';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe from 'fyo'; import frappe from 'fyo';
import BackLink from 'src/components/BackLink';
import Button from 'src/components/Button';
import PageHeader from 'src/components/PageHeader';
import SearchBar from 'src/components/SearchBar';
import TwoColumnForm from 'src/components/TwoColumnForm';
import telemetry from 'src/telemetry/telemetry';
import { Verb } from 'src/telemetry/types';
import { makePDF } from 'src/utils';
import { IPC_ACTIONS } from 'utils/messages'; import { IPC_ACTIONS } from 'utils/messages';
export default { export default {

View File

@ -77,13 +77,13 @@
</template> </template>
<script> <script>
import Button from '@/components/Button';
import FormControl from '@/components/Controls/FormControl';
import DropdownWithActions from '@/components/DropdownWithActions';
import StatusBadge from '@/components/StatusBadge';
import TwoColumnForm from '@/components/TwoColumnForm';
import { getActionsForDocument, openQuickEdit } from '@/utils';
import frappe, { t } from 'frappe'; import frappe, { t } from 'frappe';
import Button from 'src/components/Button';
import FormControl from 'src/components/Controls/FormControl';
import DropdownWithActions from 'src/components/DropdownWithActions';
import StatusBadge from 'src/components/StatusBadge';
import TwoColumnForm from 'src/components/TwoColumnForm';
import { getActionsForDocument, openQuickEdit } from 'src/utils';
export default { export default {
name: 'QuickEditForm', name: 'QuickEditForm',

View File

@ -136,15 +136,15 @@
</div> </div>
</template> </template>
<script> <script>
import reportViewConfig from '@/../reports/view';
import Button from '@/components/Button';
import FormControl from '@/components/Controls/FormControl';
import FeatherIcon from '@/components/FeatherIcon.vue';
import PageHeader from '@/components/PageHeader';
import Row from '@/components/Row';
import SearchBar from '@/components/SearchBar';
import WithScroll from '@/components/WithScroll';
import frappe from 'frappe'; import frappe from 'frappe';
import reportViewConfig from 'src/../reports/view';
import Button from 'src/components/Button';
import FormControl from 'src/components/Controls/FormControl';
import FeatherIcon from 'src/components/FeatherIcon.vue';
import PageHeader from 'src/components/PageHeader';
import Row from 'src/components/Row';
import SearchBar from 'src/components/SearchBar';
import WithScroll from 'src/components/WithScroll';
import { h, markRaw } from 'vue'; import { h, markRaw } from 'vue';
import { getReportData } from '../../reports/index'; import { getReportData } from '../../reports/index';
import DropdownWithActions from '../components/DropdownWithActions.vue'; import DropdownWithActions from '../components/DropdownWithActions.vue';

View File

@ -51,14 +51,14 @@
</div> </div>
</template> </template>
<script> <script>
import Button from '@/components/Button';
import Icon from '@/components/Icon';
import PageHeader from '@/components/PageHeader';
import Row from '@/components/Row';
import StatusBadge from '@/components/StatusBadge';
import WindowControls from '@/components/WindowControls';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe, { t } from 'fyo'; import frappe, { t } from 'fyo';
import Button from 'src/components/Button';
import Icon from 'src/components/Icon';
import PageHeader from 'src/components/PageHeader';
import Row from 'src/components/Row';
import StatusBadge from 'src/components/StatusBadge';
import WindowControls from 'src/components/WindowControls';
import { IPC_MESSAGES } from 'utils/messages'; import { IPC_MESSAGES } from 'utils/messages';
import { h, markRaw } from 'vue'; import { h, markRaw } from 'vue';
import { callInitializeMoneyMaker, showToast } from '../../utils'; import { callInitializeMoneyMaker, showToast } from '../../utils';

View File

@ -12,8 +12,8 @@
</template> </template>
<script> <script>
import TwoColumnForm from '@/components/TwoColumnForm';
import frappe from 'fyo'; import frappe from 'fyo';
import TwoColumnForm from 'src/components/TwoColumnForm';
export default { export default {
name: 'TabGeneral', name: 'TabGeneral',

View File

@ -46,10 +46,10 @@
</div> </div>
</template> </template>
<script> <script>
import FormControl from '@/components/Controls/FormControl';
import TwoColumnForm from '@/components/TwoColumnForm';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import frappe from 'fyo'; import frappe from 'fyo';
import FormControl from 'src/components/Controls/FormControl';
import TwoColumnForm from 'src/components/TwoColumnForm';
import { IPC_ACTIONS } from 'utils/messages'; import { IPC_ACTIONS } from 'utils/messages';
export default { export default {

View File

@ -38,17 +38,17 @@
</template> </template>
<script> <script>
import FormControl from '@/components/Controls/FormControl'; import frappe from 'fyo';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue'; import FormControl from 'src/components/Controls/FormControl';
import TwoColumnForm from '@/components/TwoColumnForm'; import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import TwoColumnForm from 'src/components/TwoColumnForm';
import config, { import config, {
ConfigKeys, ConfigKeys,
TelemetrySetting TelemetrySetting
} from '@/config'; } from 'src/config';
import { getTelemetryOptions } from '@/telemetry/helpers'; import { getTelemetryOptions } from 'src/telemetry/helpers';
import telemetry from '@/telemetry/telemetry'; import telemetry from 'src/telemetry/telemetry';
import { checkForUpdates } from '@/utils'; import { checkForUpdates } from 'src/utils';
import frappe from 'fyo';
export default { export default {
name: 'TabSystem', name: 'TabSystem',

View File

@ -89,17 +89,17 @@
</template> </template>
<script> <script>
import FormControl from '@/components/Controls/FormControl';
import LanguageSelector from '@/components/Controls/LanguageSelector.vue';
import Popover from '@/components/Popover';
import TwoColumnForm from '@/components/TwoColumnForm';
import config from '@/config';
import { connectToLocalDatabase, purgeCache } from '@/initialization';
import { setLanguageMap, showMessageDialog } from '@/utils';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import fs from 'fs'; import fs from 'fs';
import frappe from 'fyo'; import frappe from 'fyo';
import path from 'path'; import path from 'path';
import FormControl from 'src/components/Controls/FormControl';
import LanguageSelector from 'src/components/Controls/LanguageSelector.vue';
import Popover from 'src/components/Popover';
import TwoColumnForm from 'src/components/TwoColumnForm';
import config from 'src/config';
import { connectToLocalDatabase, purgeCache } from 'src/initialization';
import { setLanguageMap, showMessageDialog } from 'src/utils';
import { IPC_MESSAGES } from 'utils/messages'; import { IPC_MESSAGES } from 'utils/messages';
import { import {
getErrorMessage, getErrorMessage,

View File

@ -32,7 +32,7 @@
</template> </template>
<script> <script>
import Button from '@/components/Button.vue'; import Button from 'src/components/Button.vue';
export default { export default {
emits: ['primary-clicked', 'secondary-clicked'], emits: ['primary-clicked', 'secondary-clicked'],

View File

@ -1,7 +1,7 @@
import config from '@/config'; import countryList from 'fixtures/countryInfo.json';
import frappe from 'fyo'; import frappe from 'fyo';
import { DEFAULT_LOCALE } from 'fyo/utils/consts'; import { DEFAULT_LOCALE } from 'fyo/utils/consts';
import countryList from '~/fixtures/countryInfo.json'; import config from 'src/config';
import importCharts from '../../../accounting/importCOA'; import importCharts from '../../../accounting/importCOA';
import generateTaxes from '../../../models/doctype/Tax/RegionalEntries'; import generateTaxes from '../../../models/doctype/Tax/RegionalEntries';
import regionalModelUpdates from '../../../models/regionalModelUpdates'; import regionalModelUpdates from '../../../models/regionalModelUpdates';

View File

@ -1,5 +1,5 @@
import { createNumberSeries } from 'frappe/model/naming'; import { createNumberSeries } from 'fyo/model/naming';
import { DEFAULT_SERIES_START } from 'frappe/utils/consts'; import { DEFAULT_SERIES_START } from 'fyo/utils/consts';
import { getValueMapFromList } from 'utils'; import { getValueMapFromList } from 'utils';
import { fyo } from './initFyo'; import { fyo } from './initFyo';

View File

@ -1,4 +1,4 @@
import { fyo } from '@/initFyo'; import { fyo } from 'src/initFyo';
export type TaxType = 'GST' | 'IGST' | 'Exempt-GST' | 'Exempt-IGST'; export type TaxType = 'GST' | 'IGST' | 'Exempt-GST' | 'Exempt-IGST';

View File

@ -1,5 +1,5 @@
import { fyo } from '@/initFyo';
import { ConfigKeys } from 'fyo/core/types'; import { ConfigKeys } from 'fyo/core/types';
import { fyo } from 'src/initFyo';
export function incrementOpenCount() { export function incrementOpenCount() {
let openCount = fyo.config.get(ConfigKeys.OpenCount); let openCount = fyo.config.get(ConfigKeys.OpenCount);

View File

@ -1,7 +1,7 @@
import { handleError } from '@/errorHandling';
import { fyo } from '@/initFyo';
import { showToast } from '@/utils';
import { ipcRenderer } from 'electron'; import { ipcRenderer } from 'electron';
import { handleError } from 'src/errorHandling';
import { fyo } from 'src/initFyo';
import { showToast } from 'src/utils/ui';
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages'; import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
export default function registerIpcRendererListeners() { export default function registerIpcRendererListeners() {

View File

@ -1,17 +1,17 @@
import ChartOfAccounts from '@/pages/ChartOfAccounts.vue'; import { NounEnum, Verb } from 'fyo/telemetry/types';
import Dashboard from '@/pages/Dashboard/Dashboard.vue'; import ChartOfAccounts from 'src/pages/ChartOfAccounts.vue';
import DataImport from '@/pages/DataImport.vue'; import Dashboard from 'src/pages/Dashboard/Dashboard.vue';
import GetStarted from '@/pages/GetStarted.vue'; import DataImport from 'src/pages/DataImport.vue';
import InvoiceForm from '@/pages/InvoiceForm.vue'; import GetStarted from 'src/pages/GetStarted.vue';
import JournalEntryForm from '@/pages/JournalEntryForm.vue'; import InvoiceForm from 'src/pages/InvoiceForm.vue';
import ListView from '@/pages/ListView/ListView.vue'; import JournalEntryForm from 'src/pages/JournalEntryForm.vue';
import PrintView from '@/pages/PrintView/PrintView.vue'; import ListView from 'src/pages/ListView/ListView.vue';
import QuickEditForm from '@/pages/QuickEditForm.vue'; import PrintView from 'src/pages/PrintView/PrintView.vue';
import Report from '@/pages/Report.vue'; import QuickEditForm from 'src/pages/QuickEditForm.vue';
import Settings from '@/pages/Settings/Settings.vue'; import Report from 'src/pages/Report.vue';
import Settings from 'src/pages/Settings/Settings.vue';
import { createRouter, createWebHistory } from 'vue-router'; import { createRouter, createWebHistory } from 'vue-router';
import telemetry from '../frappe/telemetry/telemetry'; import { fyo } from './initFyo';
import { NounEnum, Verb } from '../frappe/telemetry/types';
const routes = [ const routes = [
{ {
@ -112,7 +112,7 @@ const routes = [
}, },
]; ];
let router = createRouter({ routes, history: createWebHistory() }); const router = createRouter({ routes, history: createWebHistory() });
function removeDetails(path) { function removeDetails(path) {
if (!path) { if (!path) {
@ -133,7 +133,7 @@ router.afterEach((to, from) => {
to: removeDetails(to.fullPath), to: removeDetails(to.fullPath),
}; };
telemetry.log(Verb.Navigated, NounEnum.Route, more); fyo.telemetry.log(Verb.Navigated, NounEnum.Route, more);
}); });
if (process.env.NODE_ENV === 'development') { if (process.env.NODE_ENV === 'development') {

View File

@ -1,603 +0,0 @@
import Avatar from '@/components/Avatar.vue';
import Toast from '@/components/Toast.vue';
import router from '@/router';
import { ipcRenderer } from 'electron';
import frappe, { t } from 'frappe';
import { isPesa } from 'frappe/utils';
import { DEFAULT_LANGUAGE } from 'frappe/utils/consts';
import { setLanguageMapOnTranslationString } from 'frappe/utils/translation';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import { createApp, h } from 'vue';
import config from './config';
import { handleErrorWithDialog } from './errorHandling';
import { languageCodeMap } from './languageCodeMap';
export async function showMessageDialog({
message,
description,
buttons = [],
}) {
const options = {
message,
detail: description,
buttons: buttons.map((a) => a.label),
};
const { response } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_DIALOG_RESPONSE,
options
);
let button = buttons[response];
if (button && button.action) {
button.action();
}
}
export function deleteDocWithPrompt(doc) {
return new Promise((resolve) => {
showMessageDialog({
message: t`Are you sure you want to delete ${doc.doctype} ${doc.name}?`,
description: t`This action is permanent`,
buttons: [
{
label: t`Delete`,
action: () => {
doc
.delete()
.then(() => resolve(true))
.catch((e) => {
handleErrorWithDialog(e, doc);
});
},
},
{
label: t`Cancel`,
action() {
resolve(false);
},
},
],
});
});
}
export async function cancelDocWithPrompt(doc) {
let description = t`This action is permanent`;
if (['SalesInvoice', 'PurchaseInvoice'].includes(doc.doctype)) {
const payments = (
await frappe.db.getAll('Payment', {
fields: ['name'],
filters: { cancelled: false },
})
).map(({ name }) => name);
const query = (
await frappe.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) {
description = t`This action is permanent and will cancel the following payment: ${paymentList[0]}`;
} else if (paymentList.length > 1) {
description = t`This action is permanent and will cancel the following payments: ${paymentList.join(
', '
)}`;
}
}
return new Promise((resolve) => {
showMessageDialog({
message: t`Are you sure you want to cancel ${doc.doctype} ${doc.name}?`,
description,
buttons: [
{
label: t`Yes`,
async action() {
const entryDoc = await frappe.doc.getDoc(doc.doctype, doc.name);
entryDoc.cancelled = 1;
await entryDoc.update();
entryDoc
.revert()
.then(() => resolve(true))
.catch((e) => {
handleErrorWithDialog(e, doc);
});
},
},
{
label: t`No`,
action() {
resolve(false);
},
},
],
});
});
}
export function partyWithAvatar(party) {
return {
data() {
return {
imageURL: null,
label: null,
};
},
components: {
Avatar,
},
async mounted() {
const p = await frappe.db.get('Party', party);
this.imageURL = p.image;
this.label = party;
},
template: `
<div class="flex items-center" v-if="label">
<Avatar class="flex-shrink-0" :imageURL="imageURL" :label="label" size="sm" />
<span class="ml-2 truncate">{{ label }}</span>
</div>
`,
};
}
function getShowFields(doctype) {
if (doctype === 'Party') {
return ['customer'];
}
return [];
}
export function openQuickEdit({
doctype,
name,
hideFields,
showFields,
defaults = {},
}) {
let currentRoute = router.currentRoute.value;
let query = currentRoute.query;
let method = 'push';
if (query.edit && query.doctype === doctype) {
// replace the current route if we are
// editing another document of the same doctype
method = 'replace';
}
if (query.name === name) return;
if (defaults?.for?.[0] === 'not in') {
const purpose = defaults.for?.[1]?.[0];
defaults = Object.assign({
for:
purpose === 'sales'
? 'purchases'
: purpose === 'purchases'
? 'sales'
: 'both',
});
}
if (defaults?.for?.[0] === 'not in' && defaults?.for?.[1] === 'sales') {
defaults = Object.assign({ for: 'purchases' });
}
router[method]({
query: {
edit: 1,
doctype,
name,
showFields: showFields ?? getShowFields(doctype),
hideFields,
valueJSON: stringifyCircular(defaults),
lastRoute: currentRoute,
},
});
}
export async function makePDF(html, savePath) {
await ipcRenderer.invoke(IPC_ACTIONS.SAVE_HTML_AS_PDF, html, savePath);
showExportInFolder(frappe.t`Save as PDF Successful`, savePath);
}
export function showExportInFolder(message, filePath) {
showToast({
message,
actionText: frappe.t`Open Folder`,
type: 'success',
action: async () => {
await showItemInFolder(filePath);
},
});
}
export async function saveData(data, savePath) {
await ipcRenderer.invoke(IPC_ACTIONS.SAVE_DATA, data, savePath);
}
export async function showItemInFolder(filePath) {
await ipcRenderer.send(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, filePath);
}
export function getActionsForDocument(doc) {
if (!doc) return [];
let deleteAction = {
component: {
template: '<span class="text-red-700">{{ t`Delete` }}</span>',
},
condition: (doc) =>
!doc.isNew() && !doc.submitted && !doc.meta.isSingle && !doc.cancelled,
action: () =>
deleteDocWithPrompt(doc).then((res) => {
if (res) {
routeTo(`/list/${doc.doctype}`);
}
}),
};
let cancelAction = {
component: {
template: '<span class="text-red-700">{{ t`Cancel` }}</span>',
},
condition: (doc) => doc.submitted && !doc.cancelled,
action: () => {
cancelDocWithPrompt(doc).then((res) => {
if (res) {
router.push(`/list/${doc.doctype}`);
}
});
},
};
const isSubmittable = !!doc.meta.isSubmittable;
const duplicateAction = {
label: frappe.t`Duplicate`,
condition: (doc) =>
((isSubmittable && doc && doc.submitted) || !isSubmittable) &&
!doc._notInserted &&
!(doc.cancelled || false),
action: () => {
showMessageDialog({
message: t`Duplicate ${doc.doctype} ${doc.name}?`,
buttons: [
{
label: t`Yes`,
async action() {
doc.duplicate();
},
},
{
label: t`No`,
action() {
resolve(false);
},
},
],
});
},
};
let actions = [
...(doc.meta.actions || []),
duplicateAction,
deleteAction,
cancelAction,
]
.filter((d) => (d.condition ? d.condition(doc) : true))
.map((d) => {
return {
label: d.label,
component: d.component,
action: d.action.bind(this, doc, router),
};
});
return actions;
}
export async function runWindowAction(name) {
switch (name) {
case 'close':
ipcRenderer.send(IPC_MESSAGES.CLOSE_CURRENT_WINDOW);
break;
case 'minimize':
ipcRenderer.send(IPC_MESSAGES.MINIMIZE_CURRENT_WINDOW);
break;
case 'maximize':
const maximizing = await ipcRenderer.invoke(
IPC_ACTIONS.TOGGLE_MAXIMIZE_CURRENT_WINDOW
);
name = maximizing ? name : 'unmaximize';
break;
}
return name;
}
export function getInvoiceStatus(doc) {
let status = `Unpaid`;
if (!doc.submitted) {
status = 'Draft';
}
if (doc.submitted === 1 && doc.outstandingAmount.isZero()) {
status = 'Paid';
}
if (doc.cancelled === 1) {
status = 'Cancelled';
}
return status;
}
export function routeTo(route) {
let routeOptions = route;
if (
typeof route === 'string' &&
route === router.currentRoute.value.fullPath
) {
return;
}
if (typeof route === 'string') {
routeOptions = { path: route };
}
router.push(routeOptions);
}
export function fuzzyMatch(keyword, candidate) {
const keywordLetters = [...keyword];
const candidateLetters = [...candidate];
let keywordLetter = keywordLetters.shift();
let candidateLetter = candidateLetters.shift();
let isMatch = true;
let distance = 0;
while (keywordLetter && candidateLetter) {
if (keywordLetter.toLowerCase() === candidateLetter.toLowerCase()) {
keywordLetter = keywordLetters.shift();
} else {
distance += 1;
}
candidateLetter = candidateLetters.shift();
}
if (keywordLetter !== undefined) {
distance = -1;
isMatch = false;
} else {
distance += candidateLetters.length;
}
return { isMatch, distance };
}
export function openSettings(tab) {
routeTo({ path: '/settings', query: { tab } });
}
export async function getSavePath(name, extention) {
let { canceled, filePath } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_SAVE_FILEPATH,
{
title: t`Select Folder`,
defaultPath: `${name}.${extention}`,
}
);
if (filePath && !filePath.endsWith(extention)) {
filePath = filePath + extention;
}
return { canceled, filePath };
}
function replaceAndAppendMount(app, replaceId) {
const fragment = document.createDocumentFragment();
const target = document.getElementById(replaceId);
if (target === null) {
return;
}
const parent = target.parentElement;
const clone = target.cloneNode();
app.mount(fragment);
target.replaceWith(fragment);
parent.append(clone);
}
export function showToast(props) {
const toast = createApp({
render() {
return h(Toast, { ...props });
},
});
replaceAndAppendMount(toast, 'toast-target');
}
export async function getIsSetupComplete() {
try {
const { setupComplete } = await frappe.getSingle('AccountingSettings');
return !!setupComplete;
} catch {
return false;
}
}
export async function getCurrency() {
let currency = frappe?.AccountingSettings?.currency ?? undefined;
if (!currency) {
try {
currency = (
await frappe.db.getSingleValues({
fieldname: 'currency',
parent: 'AccountingSettings',
})
)[0].value;
} catch (err) {
currency = undefined;
}
}
return currency;
}
export async function callInitializeMoneyMaker(currency, force = false) {
currency ??= await getCurrency();
if (!force && !currency && frappe.pesa) {
return;
}
if (!force && currency && frappe.pesa().options.currency === currency) {
return;
}
await frappe.initializeMoneyMaker(currency);
}
export function convertPesaValuesToFloat(obj) {
Object.keys(obj).forEach((key) => {
if (!isPesa(obj[key])) return;
obj[key] = obj[key].float;
});
}
export function formatXLabels(label) {
// Format: Mmm YYYY -> Mm YY
let [month, year] = label.split(' ');
year = year.slice(2);
return `${month} ${year}`;
}
export function stringifyCircular(
obj,
ignoreCircular = false,
convertDocument = false
) {
const cacheKey = [];
const cacheValue = [];
return JSON.stringify(obj, (key, value) => {
if (typeof value !== 'object' || value === null) {
cacheKey.push(key);
cacheValue.push(value);
return value;
}
if (cacheValue.includes(value)) {
const circularKey = cacheKey[cacheValue.indexOf(value)] || '{self}';
return ignoreCircular ? undefined : `[Circular:${circularKey}]`;
}
cacheKey.push(key);
cacheValue.push(value);
if (convertDocument && value instanceof frappe.Document) {
return value.getValidDict();
}
return value;
});
}
export async function checkForUpdates(force = false) {
ipcRenderer.invoke(IPC_ACTIONS.CHECK_FOR_UPDATES, force);
await setLanguageMap();
}
async function fetchAndSetLanguageMap(code) {
const { success, message, languageMap } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_LANGUAGE_MAP,
code
);
if (!success) {
showToast({ type: 'error', message });
} else {
setLanguageMapOnTranslationString(languageMap);
}
return success;
}
export async function setLanguageMap(initLanguage, dontReload = false) {
const oldLanguage = config.get('language');
initLanguage ??= oldLanguage;
const [code, language, usingDefault] = getLanguageCode(
initLanguage,
oldLanguage
);
let success = true;
if (code === 'en') {
setLanguageMapOnTranslationString(undefined);
} else {
success = await fetchAndSetLanguageMap(code);
}
if (success && !usingDefault) {
config.set('language', language);
}
if (!dontReload && success && initLanguage !== oldLanguage) {
await ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
}
return success;
}
function getLanguageCode(initLanguage, oldLanguage) {
let language = initLanguage ?? oldLanguage;
let usingDefault = false;
if (!language) {
language = DEFAULT_LANGUAGE;
usingDefault = true;
}
const code = languageCodeMap[language] ?? 'en';
return [code, language, usingDefault];
}
export function getCOAList() {
if (!frappe.temp.coaList) {
const coaList = [
{ name: t`Standard Chart of Accounts`, countryCode: '' },
{ countryCode: 'ae', name: 'U.A.E - Chart of Accounts' },
{
countryCode: 'ca',
name: 'Canada - Plan comptable pour les provinces francophones',
},
{ countryCode: 'gt', name: 'Guatemala - Cuentas' },
{ countryCode: 'hu', name: 'Hungary - Chart of Accounts' },
{ countryCode: 'id', name: 'Indonesia - Chart of Accounts' },
{ countryCode: 'in', name: 'India - Chart of Accounts' },
{ countryCode: 'mx', name: 'Mexico - Plan de Cuentas' },
{ countryCode: 'ni', name: 'Nicaragua - Catalogo de Cuentas' },
{ countryCode: 'nl', name: 'Netherlands - Grootboekschema' },
{ countryCode: 'sg', name: 'Singapore - Chart of Accounts' },
];
frappe.temp.coaList = coaList;
}
return frappe.temp.coaList;
}
export function invertMap(map) {
const keys = Object.keys(map);
const inverted = {};
for (const key of keys) {
const val = map[key];
inverted[val] = key;
}
return inverted;
}

View File

@ -1,99 +0,0 @@
import Doc from 'fyo/model/doc';
export interface QuickEditOptions {
schemaName: string;
name: string;
hideFields?: string[];
showFields?: string[];
defaults?: Record<string, unknown>;
}
export async function openQuickEdit({
schemaName,
name,
hideFields,
showFields,
defaults = {},
}: QuickEditOptions) {
const router = (await import('./router')).default;
const currentRoute = router.currentRoute.value;
const query = currentRoute.query;
let method: 'push' | 'replace' = 'push';
if (query.edit && query.doctype === schemaName) {
// replace the current route if we are
// editing another document of the same doctype
method = 'replace';
}
if (query.name === name) return;
const forWhat = (defaults?.for ?? []) as string[];
if (forWhat[0] === 'not in') {
const purpose = forWhat[1]?.[0];
defaults = Object.assign({
for:
purpose === 'sales'
? 'purchases'
: purpose === 'purchases'
? 'sales'
: 'both',
});
}
if (forWhat[0] === 'not in' && forWhat[1] === 'sales') {
defaults = Object.assign({ for: 'purchases' });
}
router[method]({
query: {
edit: 1,
doctype: schemaName,
name,
showFields: showFields ?? getShowFields(schemaName),
hideFields,
valueJSON: stringifyCircular(defaults),
// @ts-ignore
lastRoute: currentRoute,
},
});
}
function getShowFields(schemaName: string) {
if (schemaName === 'Party') {
return ['customer'];
}
return [];
}
export function stringifyCircular(
obj: Record<string, unknown>,
ignoreCircular = false,
convertDocument = false
) {
const cacheKey: string[] = [];
const cacheValue: unknown[] = [];
return JSON.stringify(obj, (key, value) => {
if (typeof value !== 'object' || value === null) {
cacheKey.push(key);
cacheValue.push(value);
return value;
}
if (cacheValue.includes(value)) {
const circularKey = cacheKey[cacheValue.indexOf(value)] || '{self}';
return ignoreCircular ? undefined : `[Circular:${circularKey}]`;
}
cacheKey.push(key);
cacheValue.push(value);
if (convertDocument && value instanceof Doc) {
return value.getValidDict();
}
return value;
});
}

View File

@ -17,26 +17,26 @@ export const statusColor = {
Cancelled: 'red', Cancelled: 'red',
}; };
const getValidColor = (color) => { const getValidColor = (color: string) => {
const isValid = ['gray', 'orange', 'green', 'red', 'yellow', 'blue'].includes( const isValid = ['gray', 'orange', 'green', 'red', 'yellow', 'blue'].includes(
color color
); );
return isValid ? color : 'gray'; return isValid ? color : 'gray';
}; };
export function getBgColorClass(color) { export function getBgColorClass(color: string) {
return `bg-${getValidColor(color)}-100`; return `bg-${getValidColor(color)}-100`;
} }
export function getColorClass(color, type, value = 300) { export function getColorClass(color: string, type: 'bg' | 'text', value = 300) {
return `${type}-${getValidColor(color)}-${value}`; return `${type}-${getValidColor(color)}-${value}`;
} }
export function getTextColorClass(color) { export function getTextColorClass(color: string) {
return `text-${getValidColor(color)}-600`; return `text-${getValidColor(color)}-600`;
} }
export function getBgTextColorClass(color) { export function getBgTextColorClass(color: string) {
const bg = getBgColorClass(color); const bg = getBgColorClass(color);
const text = getTextColorClass(color); const text = getTextColorClass(color);
return [bg, text].join(' '); return [bg, text].join(' ');

97
src/utils/index.ts Normal file
View File

@ -0,0 +1,97 @@
/**
* General purpose utils used by the frontend.
*/
import Doc from 'fyo/model/doc';
import { isPesa } from 'fyo/utils';
import Money from 'pesa/dist/types/src/money';
import { fyo } from 'src/initFyo';
export function stringifyCircular(
obj: unknown,
ignoreCircular: boolean = false,
convertDocument: boolean = false
) {
const cacheKey: string[] = [];
const cacheValue: unknown[] = [];
return JSON.stringify(obj, (key, value) => {
if (typeof value !== 'object' || value === null) {
cacheKey.push(key);
cacheValue.push(value);
return value;
}
if (cacheValue.includes(value)) {
const circularKey = cacheKey[cacheValue.indexOf(value)] || '{self}';
return ignoreCircular ? undefined : `[Circular:${circularKey}]`;
}
cacheKey.push(key);
cacheValue.push(value);
if (convertDocument && value instanceof Doc) {
return value.getValidDict();
}
return value;
});
}
export function fuzzyMatch(keyword: string, candidate: string) {
const keywordLetters = [...keyword];
const candidateLetters = [...candidate];
let keywordLetter = keywordLetters.shift();
let candidateLetter = candidateLetters.shift();
let isMatch = true;
let distance = 0;
while (keywordLetter && candidateLetter) {
if (keywordLetter.toLowerCase() === candidateLetter.toLowerCase()) {
keywordLetter = keywordLetters.shift();
} else {
distance += 1;
}
candidateLetter = candidateLetters.shift();
}
if (keywordLetter !== undefined) {
distance = -1;
isMatch = false;
} else {
distance += candidateLetters.length;
}
return { isMatch, distance };
}
export function formatXLabels(label: string) {
// Format: Mmm YYYY -> Mm YY
const splits = label.split(' ');
const month = splits[0];
const year = splits[1].slice(2);
return `${month} ${year}`;
}
export function convertPesaValuesToFloat(obj: Record<string, unknown>) {
Object.keys(obj).forEach((key) => {
const value = obj[key];
if (!isPesa(value)) {
return;
}
obj[key] = (value as Money).float;
});
}
export async function getIsSetupComplete() {
try {
const { setupComplete } = await fyo.doc.getSingle('AccountingSettings');
return !!setupComplete;
} catch {
return false;
}
}

72
src/utils/ipcCalls.ts Normal file
View File

@ -0,0 +1,72 @@
/**
* Utils that make ipcRenderer calls.
*/
import { ipcRenderer } from 'electron';
import { t } from 'fyo';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import { setLanguageMap } from './language';
import { WindowAction } from './types';
import { showToast } from './ui';
export async function checkForUpdates(force = false) {
ipcRenderer.invoke(IPC_ACTIONS.CHECK_FOR_UPDATES, force);
await setLanguageMap();
}
export async function saveData(data: string, savePath: string) {
await ipcRenderer.invoke(IPC_ACTIONS.SAVE_DATA, data, savePath);
}
export async function showItemInFolder(filePath: string) {
await ipcRenderer.send(IPC_MESSAGES.SHOW_ITEM_IN_FOLDER, filePath);
}
export async function makePDF(html: string, savePath: string) {
await ipcRenderer.invoke(IPC_ACTIONS.SAVE_HTML_AS_PDF, html, savePath);
showExportInFolder(t`Save as PDF Successful`, savePath);
}
export async function runWindowAction(name: WindowAction) {
switch (name) {
case 'close':
ipcRenderer.send(IPC_MESSAGES.CLOSE_CURRENT_WINDOW);
break;
case 'minimize':
ipcRenderer.send(IPC_MESSAGES.MINIMIZE_CURRENT_WINDOW);
break;
case 'maximize':
const maximizing = await ipcRenderer.invoke(
IPC_ACTIONS.TOGGLE_MAXIMIZE_CURRENT_WINDOW
);
name = maximizing ? name : 'unmaximize';
break;
}
return name;
}
export function showExportInFolder(message: string, filePath: string) {
showToast({
message,
actionText: t`Open Folder`,
type: 'success',
action: async () => {
await showItemInFolder(filePath);
},
});
}
export async function getSavePath(name: string, extention: string) {
const response = (await ipcRenderer.invoke(IPC_ACTIONS.GET_SAVE_FILEPATH, {
title: t`Select Folder`,
defaultPath: `${name}.${extention}`,
})) as { canceled: boolean; filePath?: string };
const canceled = response.canceled;
let filePath = response.filePath;
if (filePath && !filePath.endsWith(extention)) {
filePath = filePath + extention;
}
return { canceled, filePath };
}

71
src/utils/language.ts Normal file
View File

@ -0,0 +1,71 @@
import { ipcRenderer } from 'electron';
import { DEFAULT_LANGUAGE } from 'fyo/utils/consts';
import { setLanguageMapOnTranslationString } from 'fyo/utils/translation';
import { fyo } from 'src/initFyo';
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
import { showToast } from './ui';
// Language: Language Code in books/translations
export const languageCodeMap: Record<string, string> = {
English: 'en',
French: 'fr',
German: 'de',
Portuguese: 'pt',
Arabic: 'ar',
Catalan: 'ca-ES',
};
export async function setLanguageMap(
initLanguage?: string,
dontReload: boolean = false
) {
const oldLanguage = fyo.config.get('language') as string;
initLanguage ??= oldLanguage;
const { code, language, usingDefault } = getLanguageCode(
initLanguage,
oldLanguage
);
let success = true;
if (code === 'en') {
setLanguageMapOnTranslationString(undefined);
} else {
success = await fetchAndSetLanguageMap(code);
}
if (success && !usingDefault) {
fyo.config.set('language', language);
}
if (!dontReload && success && initLanguage !== oldLanguage) {
await ipcRenderer.send(IPC_MESSAGES.RELOAD_MAIN_WINDOW);
}
return success;
}
function getLanguageCode(initLanguage: string, oldLanguage: string) {
let language = initLanguage ?? oldLanguage;
let usingDefault = false;
if (!language) {
language = DEFAULT_LANGUAGE;
usingDefault = true;
}
const code = languageCodeMap[language] ?? 'en';
return { code, language, usingDefault };
}
async function fetchAndSetLanguageMap(code: string) {
const { success, message, languageMap } = await ipcRenderer.invoke(
IPC_ACTIONS.GET_LANGUAGE_MAP,
code
);
if (!success) {
showToast({ type: 'error', message });
} else {
setLanguageMapOnTranslationString(languageMap);
}
return success;
}

29
src/utils/types.ts Normal file
View File

@ -0,0 +1,29 @@
export interface MessageDialogButton {
label: string;
action: () => void;
}
export interface MessageDialogOptions {
message: string;
detail?: string;
buttons?: MessageDialogButton[];
}
export interface ToastOptions {
message: string;
type?: 'info' | 'warning' | 'error' | 'success';
duration?: number;
action?: () => void;
actionText?: string;
}
export type WindowAction = 'close' | 'minimize' | 'maximize' | 'unmaximize';
export type SettingsTab = 'Invoice' | 'General' | 'System';
export interface QuickEditOptions {
schemaName: string;
name: string;
hideFields?: string[];
showFields?: string[];
defaults?: Record<string, unknown>;
}

329
src/utils/ui.ts Normal file
View File

@ -0,0 +1,329 @@
/**
* Utils to do UI stuff such as opening dialogs, toasts, etc.
* Basically anything that may directly or indirectly import a Vue file.
*/
import { ipcRenderer } from 'electron';
import { t } from 'fyo';
import Doc from 'fyo/model/doc';
import { Action } from 'fyo/model/types';
import { getActions } from 'fyo/utils';
import { handleErrorWithDialog } from 'src/errorHandling';
import { fyo } from 'src/initFyo';
import router from 'src/router';
import { IPC_ACTIONS } from 'utils/messages';
import { App, createApp, h } from 'vue';
import { RouteLocationRaw } from 'vue-router';
import { stringifyCircular } from './';
import {
MessageDialogOptions,
QuickEditOptions,
SettingsTab,
ToastOptions,
} from './types';
export async function openQuickEdit({
schemaName,
name,
hideFields,
showFields,
defaults = {},
}: QuickEditOptions) {
const router = (await import('src/router')).default;
const currentRoute = router.currentRoute.value;
const query = currentRoute.query;
let method: 'push' | 'replace' = 'push';
if (query.edit && query.doctype === schemaName) {
// replace the current route if we are
// editing another document of the same doctype
method = 'replace';
}
if (query.name === name) return;
const forWhat = (defaults?.for ?? []) as string[];
if (forWhat[0] === 'not in') {
const purpose = forWhat[1]?.[0];
defaults = Object.assign({
for:
purpose === 'sales'
? 'purchases'
: purpose === 'purchases'
? 'sales'
: 'both',
});
}
if (forWhat[0] === 'not in' && forWhat[1] === 'sales') {
defaults = Object.assign({ for: 'purchases' });
}
router[method]({
query: {
edit: 1,
doctype: schemaName,
name,
showFields: showFields ?? getShowFields(schemaName),
hideFields,
valueJSON: stringifyCircular(defaults),
// @ts-ignore
lastRoute: currentRoute,
},
});
}
function getShowFields(schemaName: string) {
if (schemaName === 'Party') {
return ['customer'];
}
return [];
}
export async function showMessageDialog({
message,
detail,
buttons = [],
}: MessageDialogOptions) {
const options = {
message,
detail,
buttons: buttons.map((a) => a.label),
};
const { response } = (await ipcRenderer.invoke(
IPC_ACTIONS.GET_DIALOG_RESPONSE,
options
)) as { response: number };
const button = buttons[response];
if (button && button.action) {
button.action();
}
}
export async function showToast(options: ToastOptions) {
const Toast = (await import('src/components/Toast.vue')).default;
const toast = createApp({
render() {
return h(Toast, { ...options });
},
});
replaceAndAppendMount(toast, 'toast-target');
}
function replaceAndAppendMount(app: App<Element>, replaceId: string) {
const fragment = document.createDocumentFragment();
const target = document.getElementById(replaceId);
if (target === null) {
return;
}
const parent = target.parentElement;
const clone = target.cloneNode();
// @ts-ignore
app.mount(fragment);
target.replaceWith(fragment);
parent!.append(clone);
}
export function openSettings(tab: SettingsTab) {
routeTo({ path: '/settings', query: { tab } });
}
export function routeTo(route: string | RouteLocationRaw) {
let routeOptions = route;
if (
typeof route === 'string' &&
route === router.currentRoute.value.fullPath
) {
return;
}
if (typeof route === 'string') {
routeOptions = { path: route };
}
router.push(routeOptions);
}
export function deleteDocWithPrompt(doc: Doc) {
return new Promise((resolve) => {
showMessageDialog({
message: t`Are you sure you want to delete ${
doc.schemaName
} ${doc.name!}?`,
detail: t`This action is permanent`,
buttons: [
{
label: t`Delete`,
action: () => {
doc
.delete()
.then(() => resolve(true))
.catch((e: Error) => {
handleErrorWithDialog(e, doc);
});
},
},
{
label: t`Cancel`,
action() {
resolve(false);
},
},
],
});
});
}
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(
', '
)}`;
}
}
return new Promise((resolve) => {
showMessageDialog({
message: t`Are you sure you want to cancel ${
doc.schemaName
} ${doc.name!}?`,
detail,
buttons: [
{
label: t`Yes`,
async action() {
const entryDoc = await fyo.doc.getDoc(doc.schemaName, doc.name!);
entryDoc.cancelled = 1;
await entryDoc.update();
entryDoc
.revert()
.then(() => resolve(true))
.catch((e) => {
handleErrorWithDialog(e, doc);
});
},
},
{
label: t`No`,
action() {
resolve(false);
},
},
],
});
});
}
export function getActionsForDocument(doc?: Doc): Action[] {
if (!doc) return [];
const actions: Action[] = [
...getActions(doc, fyo),
getDuplicateAction(doc),
getDeleteAction(doc),
getCancelAction(doc),
];
return actions
.filter((d) => d.condition?.(doc) ?? true)
.map((d) => {
return {
label: d.label,
component: d.component,
action: d.action,
};
});
}
function getCancelAction(doc: Doc): Action {
return {
label: t`Cancel`,
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}`);
}
});
},
};
}
function getDeleteAction(doc: Doc): Action {
return {
label: t`Delete`,
component: {
template: '<span class="text-red-700">{{ t`Delete` }}</span>',
},
condition: (doc: Doc) =>
!doc.isNew && !doc.submitted && !doc.schema.isSingle && !doc.cancelled,
action: () =>
deleteDocWithPrompt(doc).then((res) => {
if (res) {
routeTo(`/list/${doc.schemaName}`);
}
}),
};
}
function getDuplicateAction(doc: Doc): Action {
const isSubmittable = !!doc.schema.isSubmittable;
return {
label: t`Duplicate`,
condition: (doc: Doc) =>
!!(
((isSubmittable && doc && doc.submitted) || !isSubmittable) &&
!doc._notInserted &&
!(doc.cancelled || false)
),
action: () => {
showMessageDialog({
message: t`Duplicate ${doc.schemaName} ${doc.name!}?`,
buttons: [
{
label: t`Yes`,
async action() {
doc.duplicate();
},
},
{
label: t`No`,
action() {
// no-op
},
},
],
});
},
};
}

View File

@ -15,10 +15,11 @@
"baseUrl": ".", "baseUrl": ".",
"types": ["webpack-env"], "types": ["webpack-env"],
"paths": { "paths": {
"@/*": ["src/*"], "src/*": ["src/*"],
"schemas/*": ["schemas/*"], "schemas/*": ["schemas/*"],
"backend/*": ["backend/*"], "backend/*": ["backend/*"],
"regional/*": ["regional/*"], "regional/*": ["regional/*"],
"fixtures/*": ["fixtures/*"],
"utils/*": ["utils/*"] "utils/*": ["utils/*"]
}, },
"lib": ["esnext", "dom", "dom.iterable", "scripthost"] "lib": ["esnext", "dom", "dom.iterable", "scripthost"]
@ -43,7 +44,8 @@
"accounting/**/*.ts", "accounting/**/*.ts",
"scripts/**/*.ts", "scripts/**/*.ts",
"utils/csvParser.ts" "utils/csvParser.ts",
, "utils/config.ts" ], "utils/config.ts"
],
"exclude": ["node_modules"] "exclude": ["node_modules"]
} }

View File

@ -68,3 +68,14 @@ export function titleCase(phrase: string): string {
}) })
.join(' '); .join(' ');
} }
export function invertMap(map: Record<string, string>): Record<string, string> {
const keys = Object.keys(map);
const inverted: Record<string, string> = {};
for (const key of keys) {
const val = map[key];
inverted[val] = key;
}
return inverted;
}

View File

@ -40,11 +40,12 @@ module.exports = {
configureWebpack(config) { configureWebpack(config) {
Object.assign(config.resolve.alias, { Object.assign(config.resolve.alias, {
fyo: path.resolve(__dirname, './fyo'), fyo: path.resolve(__dirname, './fyo'),
'~': path.resolve('.'), src: path.resolve(__dirname, './src'),
schemas: path.resolve(__dirname, './schemas'), schemas: path.resolve(__dirname, './schemas'),
backend: path.resolve(__dirname, './backend'), backend: path.resolve(__dirname, './backend'),
utils: path.resolve(__dirname, './utils'), utils: path.resolve(__dirname, './utils'),
regional: path.resolve(__dirname, './regional'), regional: path.resolve(__dirname, './regional'),
fixtures: path.resolve(__dirname, './fixtures'),
}); });
config.plugins.push( config.plugins.push(