mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
incr: rem singleton from index.ts cause can't test
- update models to not use singleton export
This commit is contained in:
parent
ffdacc9637
commit
76bf6cfda5
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { NotFoundError } from 'frappe/utils/errors';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
export async function getExchangeRate({
|
||||
@ -15,7 +15,7 @@ export async function getExchangeRate({
|
||||
}
|
||||
|
||||
if (!fromCurrency || !toCurrency) {
|
||||
throw new frappe.errors.NotFoundError(
|
||||
throw new NotFoundError(
|
||||
'Please provide `fromCurrency` and `toCurrency` to get exchange rate.'
|
||||
);
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { ValidationError } from 'frappe/utils/errors';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import {
|
||||
AccountEntry,
|
||||
@ -18,7 +19,12 @@ export class LedgerPosting {
|
||||
reverted: boolean;
|
||||
accountEntries: AccountEntry[];
|
||||
|
||||
constructor({ reference, party, date, description }: LedgerPostingOptions) {
|
||||
frappe: Frappe;
|
||||
|
||||
constructor(
|
||||
{ reference, party, date, description }: LedgerPostingOptions,
|
||||
frappe: Frappe
|
||||
) {
|
||||
this.reference = reference;
|
||||
this.party = party;
|
||||
this.date = date;
|
||||
@ -28,6 +34,8 @@ export class LedgerPosting {
|
||||
this.reverted = false;
|
||||
// To change balance while entering ledger entries
|
||||
this.accountEntries = [];
|
||||
|
||||
this.frappe = frappe;
|
||||
}
|
||||
|
||||
async debit(
|
||||
@ -58,7 +66,7 @@ export class LedgerPosting {
|
||||
amount: Money
|
||||
) {
|
||||
const debitAccounts = ['Asset', 'Expense'];
|
||||
const accountDoc = await frappe.doc.getDoc('Account', accountName);
|
||||
const accountDoc = await this.frappe.doc.getDoc('Account', accountName);
|
||||
const rootType = accountDoc.rootType as string;
|
||||
|
||||
if (debitAccounts.indexOf(rootType) === -1) {
|
||||
@ -86,8 +94,8 @@ export class LedgerPosting {
|
||||
referenceName: referenceName ?? this.reference.name!,
|
||||
description: this.description,
|
||||
reverted: this.reverted,
|
||||
debit: frappe.pesa(0),
|
||||
credit: frappe.pesa(0),
|
||||
debit: this.frappe.pesa(0),
|
||||
credit: this.frappe.pesa(0),
|
||||
};
|
||||
|
||||
this.entries.push(entry);
|
||||
@ -105,7 +113,7 @@ export class LedgerPosting {
|
||||
async postReverse() {
|
||||
this.validateEntries();
|
||||
|
||||
const data = await frappe.db.getAll('AccountingLedgerEntry', {
|
||||
const data = await this.frappe.db.getAll('AccountingLedgerEntry', {
|
||||
fields: ['name'],
|
||||
filters: {
|
||||
referenceName: this.reference.name!,
|
||||
@ -114,7 +122,7 @@ export class LedgerPosting {
|
||||
});
|
||||
|
||||
for (const entry of data) {
|
||||
const entryDoc = await frappe.doc.getDoc(
|
||||
const entryDoc = await this.frappe.doc.getDoc(
|
||||
'AccountingLedgerEntry',
|
||||
entry.name as string
|
||||
);
|
||||
@ -157,18 +165,21 @@ export class LedgerPosting {
|
||||
validateEntries() {
|
||||
const { debit, credit } = this.getTotalDebitAndCredit();
|
||||
if (debit.neq(credit)) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
`Total Debit: ${frappe.format(
|
||||
throw new ValidationError(
|
||||
`Total Debit: ${this.frappe.format(
|
||||
debit,
|
||||
'Currency'
|
||||
)} must be equal to Total Credit: ${frappe.format(credit, 'Currency')}`
|
||||
)} must be equal to Total Credit: ${this.frappe.format(
|
||||
credit,
|
||||
'Currency'
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
getTotalDebitAndCredit() {
|
||||
let debit = frappe.pesa(0);
|
||||
let credit = frappe.pesa(0);
|
||||
let debit = this.frappe.pesa(0);
|
||||
let credit = this.frappe.pesa(0);
|
||||
|
||||
for (const entry of this.entries) {
|
||||
debit = debit.add(entry.debit);
|
||||
@ -180,12 +191,12 @@ export class LedgerPosting {
|
||||
|
||||
async insertEntries() {
|
||||
for (const entry of this.entries) {
|
||||
const entryDoc = frappe.doc.getNewDoc('AccountingLedgerEntry');
|
||||
const entryDoc = this.frappe.doc.getNewDoc('AccountingLedgerEntry');
|
||||
Object.assign(entryDoc, entry);
|
||||
await entryDoc.insert();
|
||||
}
|
||||
for (const entry of this.accountEntries) {
|
||||
const entryDoc = await frappe.doc.getDoc('Account', entry.name);
|
||||
const entryDoc = await this.frappe.doc.getDoc('Account', entry.name);
|
||||
const balance = entryDoc.get('balance') as Money;
|
||||
entryDoc.balance = balance.add(entry.balanceChange);
|
||||
await entryDoc.update();
|
||||
@ -193,6 +204,6 @@ export class LedgerPosting {
|
||||
}
|
||||
|
||||
getRoundOffAccount() {
|
||||
return frappe.singles.AccountingSettings!.roundOffAccount as string;
|
||||
return this.frappe.singles.AccountingSettings!.roundOffAccount as string;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,12 @@ import {
|
||||
import { getRandomString, getValueMapFromList } from '../../utils';
|
||||
import { DatabaseBase, GetAllOptions, QueryFilter } from '../../utils/db/types';
|
||||
import { getDefaultMetaFieldValueMap, sqliteTypeMap, SYSTEM } from '../helpers';
|
||||
import { ColumnDiff, FieldValueMap, GetQueryBuilderOptions } from './types';
|
||||
import {
|
||||
ColumnDiff,
|
||||
FieldValueMap,
|
||||
GetQueryBuilderOptions,
|
||||
SingleValue,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* # DatabaseCore
|
||||
@ -256,7 +261,7 @@ export default class DatabaseCore extends DatabaseBase {
|
||||
|
||||
async getSingleValues(
|
||||
...fieldnames: ({ fieldname: string; parent?: string } | string)[]
|
||||
): Promise<{ fieldname: string; parent: string; value: RawValue }[]> {
|
||||
): Promise<SingleValue<RawValue>> {
|
||||
const fieldnameList = fieldnames.map((fieldname) => {
|
||||
if (typeof fieldname === 'string') {
|
||||
return { fieldname };
|
||||
|
@ -21,8 +21,7 @@ export class DatabaseManager extends DatabaseDemuxBase {
|
||||
|
||||
async createNewDatabase(dbPath: string, countryCode: string) {
|
||||
await this.#unlinkIfExists(dbPath);
|
||||
await this.connectToDatabase(dbPath, countryCode);
|
||||
return countryCode;
|
||||
return await this.connectToDatabase(dbPath, countryCode);
|
||||
}
|
||||
|
||||
async connectToDatabase(dbPath: string, countryCode?: string) {
|
||||
|
@ -45,4 +45,9 @@ export interface SqliteTableInfo {
|
||||
dflt_value: string | null;
|
||||
}
|
||||
|
||||
export type BespokeFunction = (db:DatabaseCore, ...args: unknown[]) => Promise<unknown>
|
||||
export type BespokeFunction = (db:DatabaseCore, ...args: unknown[]) => Promise<unknown>
|
||||
export type SingleValue<T> = {
|
||||
fieldname: string;
|
||||
parent: string;
|
||||
value: T;
|
||||
}[];
|
@ -5,7 +5,7 @@ removed into a separate repo, but as of now it's in gestation.
|
||||
|
||||
The reason for maintaining a framework is to allow for varied backends.
|
||||
Currently Books runs on the electron renderer process and all db stuff happens
|
||||
on the electron main process which has access to node libs. As the development
|
||||
on the electron main process which has access to nodelibs. As the development
|
||||
of `Fyo` progresses it will allow for a browser frontend and a node server
|
||||
backend.
|
||||
|
||||
@ -70,6 +70,9 @@ other things.
|
||||
- Get models and `regionalModels` using `countryCode` from `models/index.ts/getRegionalModels`.
|
||||
- Call `fyo.initializeAndRegister` with the all models.
|
||||
|
||||
_Note: since **SystemSettings** are initialized on `fyo.initializeAndRegister`
|
||||
db needs to be set first else an error will be thrown_
|
||||
|
||||
## Testing
|
||||
|
||||
For testing the `fyo` class, `mocha` is used (`node` side). So for this the
|
||||
@ -77,15 +80,18 @@ demux classes are directly replaced by `node` side managers such as
|
||||
`DatabaseManager`.
|
||||
|
||||
For this to work the class signatures of the demux class and the manager have to
|
||||
be the same.
|
||||
be the same which is maintained by abstract demux classes.
|
||||
|
||||
`DatabaseManager` is used as the `DatabaseDemux` for testing without API or IPC
|
||||
calls. For `AuthDemux` the `DummyAuthDemux` class is used.
|
||||
|
||||
## Translations
|
||||
|
||||
All translations take place during runtime, for translations to work, a
|
||||
`LanguageMap` (for def check `utils/types.ts`) has to be set.
|
||||
`LanguageMap` (for def check `utils/types.ts`) has to be set.
|
||||
|
||||
This can be done using `fyo/utils/translation.ts/setLanguageMapOnTranslationString`.
|
||||
|
||||
Since translations are runtime, if the code is evaluated before the language map
|
||||
is loaded, translations won't work. To prevent this, don't maintain translation
|
||||
strings globally.
|
||||
strings globally.
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { Frappe } from 'frappe';
|
||||
import { AuthDemux } from 'frappe/demux/auth';
|
||||
import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types';
|
||||
import { AuthDemuxConstructor } from './types';
|
||||
|
||||
interface AuthConfig {
|
||||
serverURL: string;
|
||||
@ -15,8 +18,9 @@ export class AuthHandler {
|
||||
#config: AuthConfig;
|
||||
#session: Session;
|
||||
frappe: Frappe;
|
||||
#demux: AuthDemuxBase;
|
||||
|
||||
constructor(frappe: Frappe) {
|
||||
constructor(frappe: Frappe, Demux?: AuthDemuxConstructor) {
|
||||
this.frappe = frappe;
|
||||
this.#config = {
|
||||
serverURL: '',
|
||||
@ -28,6 +32,12 @@ export class AuthHandler {
|
||||
user: '',
|
||||
token: '',
|
||||
};
|
||||
|
||||
if (Demux !== undefined) {
|
||||
this.#demux = new Demux(frappe.isElectron);
|
||||
} else {
|
||||
this.#demux = new AuthDemux(frappe.isElectron);
|
||||
}
|
||||
}
|
||||
|
||||
get session(): Readonly<Session> {
|
||||
@ -90,4 +100,8 @@ export class AuthHandler {
|
||||
#getServerURL() {
|
||||
return this.#config.serverURL || '';
|
||||
}
|
||||
|
||||
async getTelemetryCreds(): Promise<TelemetryCreds> {
|
||||
return await this.#demux.getTelemetryCreds();
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { FieldType, FieldTypeEnum, RawValue } from 'schemas/types';
|
||||
@ -22,9 +22,11 @@ import { DocValue, DocValueMap, RawValueMap } from './types';
|
||||
|
||||
export class Converter {
|
||||
db: DatabaseHandler;
|
||||
frappe: Frappe;
|
||||
|
||||
constructor(db: DatabaseHandler) {
|
||||
constructor(db: DatabaseHandler, frappe: Frappe) {
|
||||
this.db = db;
|
||||
this.frappe = frappe;
|
||||
}
|
||||
|
||||
toDocValueMap(
|
||||
@ -49,7 +51,11 @@ export class Converter {
|
||||
}
|
||||
}
|
||||
|
||||
static toDocValue(value: RawValue, fieldtype: FieldType): DocValue {
|
||||
static toDocValue(
|
||||
value: RawValue,
|
||||
fieldtype: FieldType,
|
||||
frappe: Frappe
|
||||
): DocValue {
|
||||
switch (fieldtype) {
|
||||
case FieldTypeEnum.Currency:
|
||||
return frappe.pesa((value ?? 0) as string | number);
|
||||
@ -112,7 +118,8 @@ export class Converter {
|
||||
} else {
|
||||
docValueMap[fieldname] = Converter.toDocValue(
|
||||
rawValue,
|
||||
field.fieldtype
|
||||
field.fieldtype,
|
||||
this.frappe
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { SingleValue } from 'backend/database/types';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DatabaseDemux } from 'frappe/demux/db';
|
||||
import { Field, RawValue, SchemaMap } from 'schemas/types';
|
||||
@ -9,7 +10,6 @@ import {
|
||||
DocValue,
|
||||
DocValueMap,
|
||||
RawValueMap,
|
||||
SingleValue,
|
||||
} from './types';
|
||||
|
||||
// Return types of Bespoke Queries
|
||||
@ -27,7 +27,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
constructor(frappe: Frappe, Demux?: DatabaseDemuxConstructor) {
|
||||
super();
|
||||
this.#frappe = frappe;
|
||||
this.converter = new Converter(this);
|
||||
this.converter = new Converter(this, this.#frappe);
|
||||
|
||||
if (Demux !== undefined) {
|
||||
this.#demux = new Demux(frappe.isElectron);
|
||||
@ -117,7 +117,7 @@ export class DatabaseHandler extends DatabaseBase {
|
||||
const docSingleValue: SingleValue<DocValue> = [];
|
||||
for (const sv of rawSingleValue) {
|
||||
const fieldtype = this.fieldValueMap[sv.parent][sv.fieldname].fieldtype;
|
||||
const value = Converter.toDocValue(sv.value, fieldtype);
|
||||
const value = Converter.toDocValue(sv.value, fieldtype, this.#frappe);
|
||||
|
||||
docSingleValue.push({
|
||||
value,
|
||||
|
@ -164,7 +164,7 @@ export class DocHandler {
|
||||
throw new Error(`Schema not found for ${schemaName}`);
|
||||
}
|
||||
|
||||
const doc = new Model(schema, data);
|
||||
const doc = new Model(schema, data, this.frappe);
|
||||
doc.setDefaults();
|
||||
return doc;
|
||||
}
|
||||
|
@ -1,21 +1,45 @@
|
||||
import Doc from 'frappe/model/doc';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { RawValue } from 'schemas/types';
|
||||
import { AuthDemuxBase } from 'utils/auth/types';
|
||||
import { DatabaseDemuxBase } from 'utils/db/types';
|
||||
|
||||
export type DocValue = string | number | boolean | Date | Money | null;
|
||||
export type DocValueMap = Record<string, DocValue | Doc[] | DocValueMap[]>;
|
||||
export type RawValueMap = Record<string, RawValue | RawValueMap[]>;
|
||||
|
||||
export type SingleValue<T> = {
|
||||
fieldname: string;
|
||||
parent: string;
|
||||
value: T;
|
||||
}[];
|
||||
|
||||
/**
|
||||
* DatabaseDemuxConstructor: type for a constructor that returns a DatabaseDemuxBase
|
||||
* it's typed this way because `typeof AbstractClass` is invalid as abstract classes
|
||||
* can't be initialized using `new`.
|
||||
*
|
||||
* AuthDemuxConstructor: same as the above but for AuthDemuxBase
|
||||
*/
|
||||
export type DatabaseDemuxConstructor = new (isElectron?: boolean)=> DatabaseDemuxBase
|
||||
|
||||
export type DatabaseDemuxConstructor = new (
|
||||
isElectron?: boolean
|
||||
) => DatabaseDemuxBase;
|
||||
|
||||
export type AuthDemuxConstructor = new (isElectron?: boolean) => AuthDemuxBase;
|
||||
|
||||
export enum ConfigKeys {
|
||||
Files = 'files',
|
||||
LastSelectedFilePath = 'lastSelectedFilePath',
|
||||
Language = 'language',
|
||||
DeviceId = 'deviceId',
|
||||
Telemetry = 'telemetry',
|
||||
OpenCount = 'openCount',
|
||||
}
|
||||
|
||||
export interface ConfigFile {
|
||||
id: string;
|
||||
companyName: string;
|
||||
filePath: string;
|
||||
}
|
||||
|
||||
export interface FyoConfig {
|
||||
DatabaseDemux?: DatabaseDemuxConstructor;
|
||||
AuthDemux?: AuthDemuxConstructor;
|
||||
isElectron?: boolean;
|
||||
isTest?: boolean;
|
||||
}
|
||||
|
22
frappe/demux/auth.ts
Normal file
22
frappe/demux/auth.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types';
|
||||
import { IPC_ACTIONS } from 'utils/messages';
|
||||
|
||||
export class AuthDemux extends AuthDemuxBase {
|
||||
#isElectron: boolean = false;
|
||||
constructor(isElectron: boolean) {
|
||||
super();
|
||||
this.#isElectron = isElectron;
|
||||
}
|
||||
|
||||
async getTelemetryCreds(): Promise<TelemetryCreds> {
|
||||
if (this.#isElectron) {
|
||||
const creds = await ipcRenderer.invoke(IPC_ACTIONS.GET_CREDS);
|
||||
const url: string = creds?.telemetryUrl ?? '';
|
||||
const token: string = creds?.tokenString ?? '';
|
||||
return { url, token };
|
||||
} else {
|
||||
return { url: '', token: '' };
|
||||
}
|
||||
}
|
||||
}
|
55
frappe/demux/config.ts
Normal file
55
frappe/demux/config.ts
Normal file
@ -0,0 +1,55 @@
|
||||
import config from 'utils/config';
|
||||
|
||||
export class Config {
|
||||
#isElectron: boolean;
|
||||
fallback: Map<string, unknown> = new Map();
|
||||
constructor(isElectron: boolean) {
|
||||
this.#isElectron = isElectron;
|
||||
}
|
||||
|
||||
get store(): Record<string, unknown> {
|
||||
if (this.#isElectron) {
|
||||
return config.store;
|
||||
} else {
|
||||
const store: Record<string, unknown> = {};
|
||||
|
||||
for (const key of this.fallback.keys()) {
|
||||
store[key] = this.fallback.get(key);
|
||||
}
|
||||
|
||||
return store;
|
||||
}
|
||||
}
|
||||
|
||||
get(key: string, defaultValue?: unknown): unknown {
|
||||
if (this.#isElectron) {
|
||||
return config.get(key, defaultValue);
|
||||
} else {
|
||||
return this.fallback.get(key) ?? defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
set(key: string, value: unknown) {
|
||||
if (this.#isElectron) {
|
||||
config.set(key, value);
|
||||
} else {
|
||||
this.fallback.set(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
delete(key: string) {
|
||||
if (this.#isElectron) {
|
||||
config.delete(key);
|
||||
} else {
|
||||
this.fallback.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
clear() {
|
||||
if (this.#isElectron) {
|
||||
config.clear();
|
||||
} else {
|
||||
this.fallback.clear();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,14 @@
|
||||
import { getMoneyMaker, MoneyMaker } from 'pesa';
|
||||
import { Field } from 'schemas/types';
|
||||
import { markRaw } from 'vue';
|
||||
import { AuthHandler } from './core/authHandler';
|
||||
import { DatabaseHandler } from './core/dbHandler';
|
||||
import { DocHandler } from './core/docHandler';
|
||||
import { DatabaseDemuxConstructor } from './core/types';
|
||||
import { DocValue, FyoConfig } from './core/types';
|
||||
import { Config } from './demux/config';
|
||||
import Doc from './model/doc';
|
||||
import { ModelMap } from './model/types';
|
||||
import { TelemetryManager } from './telemetry/telemetry';
|
||||
import {
|
||||
DEFAULT_CURRENCY,
|
||||
DEFAULT_DISPLAY_PRECISION,
|
||||
@ -18,10 +22,9 @@ import { ErrorLog } from './utils/types';
|
||||
export class Frappe {
|
||||
t = t;
|
||||
T = T;
|
||||
format = format;
|
||||
|
||||
errors = errors;
|
||||
isElectron = false;
|
||||
isElectron: boolean;
|
||||
|
||||
pesa: MoneyMaker;
|
||||
|
||||
@ -38,21 +41,27 @@ export class Frappe {
|
||||
currencyFormatter?: Intl.NumberFormat;
|
||||
currencySymbols: Record<string, string | undefined> = {};
|
||||
|
||||
constructor(DatabaseDemux?: DatabaseDemuxConstructor) {
|
||||
/**
|
||||
* `DatabaseManager` can be passed as the `DatabaseDemux` for
|
||||
* testing this class without API or IPC calls.
|
||||
*/
|
||||
this.auth = new AuthHandler(this);
|
||||
this.db = new DatabaseHandler(this, DatabaseDemux);
|
||||
isTest: boolean;
|
||||
telemetry: TelemetryManager;
|
||||
config: Config;
|
||||
|
||||
constructor(conf: FyoConfig = {}) {
|
||||
this.isTest = conf.isTest ?? false;
|
||||
this.isElectron = conf.isElectron ?? true;
|
||||
|
||||
this.auth = new AuthHandler(this, conf.AuthDemux);
|
||||
this.db = new DatabaseHandler(this, conf.DatabaseDemux);
|
||||
this.doc = new DocHandler(this);
|
||||
|
||||
this.pesa = getMoneyMaker({
|
||||
currency: 'XXX',
|
||||
currency: DEFAULT_CURRENCY,
|
||||
precision: DEFAULT_INTERNAL_PRECISION,
|
||||
display: DEFAULT_DISPLAY_PRECISION,
|
||||
wrapper: markRaw,
|
||||
});
|
||||
|
||||
this.telemetry = new TelemetryManager(this);
|
||||
this.config = new Config(this.isElectron);
|
||||
}
|
||||
|
||||
get initialized() {
|
||||
@ -75,6 +84,19 @@ export class Frappe {
|
||||
return this.db.schemaMap;
|
||||
}
|
||||
|
||||
format(value: DocValue, field: string | Field, doc?: Doc) {
|
||||
return format(value, field, doc ?? null, this);
|
||||
}
|
||||
|
||||
async setIsElectron() {
|
||||
try {
|
||||
const { ipcRenderer } = await import('electron');
|
||||
this.isElectron = Boolean(ipcRenderer);
|
||||
} catch {
|
||||
this.isElectron = false;
|
||||
}
|
||||
}
|
||||
|
||||
async initializeAndRegister(
|
||||
models: ModelMap = {},
|
||||
regionalModels: ModelMap = {},
|
||||
@ -138,9 +160,9 @@ export class Frappe {
|
||||
});
|
||||
}
|
||||
|
||||
close() {
|
||||
this.db.close();
|
||||
this.auth.logout();
|
||||
async close() {
|
||||
await this.db.close();
|
||||
await this.auth.logout();
|
||||
}
|
||||
|
||||
store = {
|
||||
@ -150,4 +172,3 @@ export class Frappe {
|
||||
}
|
||||
|
||||
export { T, t };
|
||||
export default new Frappe();
|
||||
|
@ -1,6 +1,6 @@
|
||||
import telemetry from '@/telemetry/telemetry';
|
||||
import { Verb } from '@/telemetry/types';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue, DocValueMap } from 'frappe/core/types';
|
||||
import { Verb } from 'frappe/telemetry/types';
|
||||
import {
|
||||
Conflict,
|
||||
MandatoryError,
|
||||
@ -17,7 +17,6 @@ import {
|
||||
TargetField,
|
||||
} from 'schemas/types';
|
||||
import { getIsNullOrUndef, getMapFromList } from 'utils';
|
||||
import frappe from '..';
|
||||
import { getRandomString, isPesa } from '../utils/index';
|
||||
import {
|
||||
areDocValuesEqual,
|
||||
@ -47,6 +46,7 @@ import { validateSelect } from './validationFunction';
|
||||
export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
name?: string;
|
||||
schema: Readonly<Schema>;
|
||||
frappe: Frappe;
|
||||
fieldMap: Record<string, Field>;
|
||||
|
||||
/**
|
||||
@ -66,11 +66,16 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
revertAction: false,
|
||||
};
|
||||
|
||||
constructor(schema: Schema, data: DocValueMap) {
|
||||
constructor(schema: Schema, data: DocValueMap, frappe: Frappe) {
|
||||
super();
|
||||
this.frappe = frappe;
|
||||
this.schema = schema;
|
||||
this._setInitialValues(data);
|
||||
this.fieldMap = getMapFromList(schema.fields, 'fieldname');
|
||||
|
||||
if (this.schema.isSingle) {
|
||||
this.name = this.schemaName;
|
||||
}
|
||||
}
|
||||
|
||||
get schemaName(): string {
|
||||
@ -183,7 +188,10 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
continue;
|
||||
}
|
||||
|
||||
let defaultValue: DocValue | Doc[] = getPreDefaultValues(field.fieldtype);
|
||||
let defaultValue: DocValue | Doc[] = getPreDefaultValues(
|
||||
field.fieldtype,
|
||||
this.frappe
|
||||
);
|
||||
const defaultFunction = this.defaults[field.fieldname];
|
||||
|
||||
if (defaultFunction !== undefined) {
|
||||
@ -193,7 +201,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
if (field.fieldtype === 'Currency' && !isPesa(defaultValue)) {
|
||||
defaultValue = frappe.pesa!(defaultValue as string | number);
|
||||
defaultValue = this.frappe.pesa!(defaultValue as string | number);
|
||||
}
|
||||
|
||||
this[field.fieldname] = defaultValue;
|
||||
@ -235,8 +243,8 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
const childSchemaName = this.fieldMap[fieldname] as TargetField;
|
||||
const schema = frappe.db.schemaMap[childSchemaName.target] as Schema;
|
||||
const childDoc = new Doc(schema, data as DocValueMap);
|
||||
const schema = this.frappe.db.schemaMap[childSchemaName.target] as Schema;
|
||||
const childDoc = new Doc(schema, data as DocValueMap, this.frappe);
|
||||
childDoc.setDefaults();
|
||||
return childDoc;
|
||||
}
|
||||
@ -263,7 +271,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
if (missingMandatoryMessage.length > 0) {
|
||||
const fields = missingMandatoryMessage.join('\n');
|
||||
const message = frappe.t`Value missing for ${fields}`;
|
||||
const message = this.frappe.t`Value missing for ${fields}`;
|
||||
throw new MandatoryError(message);
|
||||
}
|
||||
}
|
||||
@ -322,7 +330,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
if (!this.createdBy) {
|
||||
this.createdBy = frappe.auth.session.user;
|
||||
this.createdBy = this.frappe.auth.session.user;
|
||||
}
|
||||
|
||||
if (!this.created) {
|
||||
@ -333,7 +341,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
updateModified() {
|
||||
this.modifiedBy = frappe.auth.session.user;
|
||||
this.modifiedBy = this.frappe.auth.session.user;
|
||||
this.modified = new Date();
|
||||
}
|
||||
|
||||
@ -342,7 +350,11 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return;
|
||||
}
|
||||
|
||||
const data = await frappe.db.get(this.schemaName, this.name);
|
||||
const data = await this.frappe.db.get(this.schemaName, this.name);
|
||||
if (this.schema.isSingle && !data?.name) {
|
||||
data.name = this.name!;
|
||||
}
|
||||
|
||||
if (data && data.name) {
|
||||
this.syncValues(data);
|
||||
if (this.schema.isSingle) {
|
||||
@ -375,7 +387,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return;
|
||||
}
|
||||
|
||||
this._links[fieldname] = await frappe.doc.getDoc(
|
||||
this._links[fieldname] = await this.frappe.doc.getDoc(
|
||||
field.target,
|
||||
value as string
|
||||
);
|
||||
@ -422,7 +434,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return;
|
||||
}
|
||||
|
||||
const currentDoc = await frappe.db.get(this.schemaName, this.name);
|
||||
const currentDoc = await this.frappe.db.get(this.schemaName, this.name);
|
||||
|
||||
// check for conflict
|
||||
if (
|
||||
@ -430,13 +442,14 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
(this.modified as Date) !== (currentDoc.modified as Date)
|
||||
) {
|
||||
throw new Conflict(
|
||||
frappe.t`Document ${this.schemaName} ${this.name} has been modified after loading`
|
||||
this.frappe
|
||||
.t`Document ${this.schemaName} ${this.name} has been modified after loading`
|
||||
);
|
||||
}
|
||||
|
||||
if (this.submitted && !this.schema.isSubmittable) {
|
||||
throw new ValidationError(
|
||||
frappe.t`Document type ${this.schemaName} is not submittable`
|
||||
this.frappe.t`Document type ${this.schemaName} is not submittable`
|
||||
);
|
||||
}
|
||||
|
||||
@ -532,24 +545,27 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
|
||||
async insert() {
|
||||
await setName(this);
|
||||
await setName(this, this.frappe);
|
||||
this.setBaseMetaValues();
|
||||
await this.commit();
|
||||
await this.validateInsert();
|
||||
await this.trigger('beforeInsert', null);
|
||||
|
||||
const oldName = this.name!;
|
||||
const data = await frappe.db.insert(this.schemaName, this.getValidDict());
|
||||
const data = await this.frappe.db.insert(
|
||||
this.schemaName,
|
||||
this.getValidDict()
|
||||
);
|
||||
this.syncValues(data);
|
||||
|
||||
if (oldName !== this.name) {
|
||||
frappe.doc.removeFromCache(this.schemaName, oldName);
|
||||
this.frappe.doc.removeFromCache(this.schemaName, oldName);
|
||||
}
|
||||
|
||||
await this.trigger('afterInsert', null);
|
||||
await this.trigger('afterSave', null);
|
||||
|
||||
telemetry.log(Verb.Created, this.schemaName);
|
||||
this.frappe.telemetry.log(Verb.Created, this.schemaName);
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -566,7 +582,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
this.updateModified();
|
||||
|
||||
const data = this.getValidDict();
|
||||
await frappe.db.update(this.schemaName, data);
|
||||
await this.frappe.db.update(this.schemaName, data);
|
||||
this.syncValues(data);
|
||||
|
||||
await this.trigger('afterUpdate');
|
||||
@ -589,10 +605,10 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
async delete() {
|
||||
await this.trigger('beforeDelete');
|
||||
await frappe.db.delete(this.schemaName, this.name!);
|
||||
await this.frappe.db.delete(this.schemaName, this.name!);
|
||||
await this.trigger('afterDelete');
|
||||
|
||||
telemetry.log(Verb.Deleted, this.schemaName);
|
||||
this.frappe.telemetry.log(Verb.Deleted, this.schemaName);
|
||||
}
|
||||
|
||||
async submitOrRevert(isSubmit: boolean) {
|
||||
@ -617,7 +633,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
|
||||
async rename(newName: string) {
|
||||
await this.trigger('beforeRename');
|
||||
await frappe.db.rename(this.schemaName, this.name!, newName);
|
||||
await this.frappe.db.rename(this.schemaName, this.name!, newName);
|
||||
this.name = newName;
|
||||
await this.trigger('afterRename');
|
||||
}
|
||||
@ -637,7 +653,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
const value = d.get(childfield) ?? 0;
|
||||
if (!isPesa(value)) {
|
||||
try {
|
||||
return frappe.pesa(value as string | number);
|
||||
return this.frappe.pesa(value as string | number);
|
||||
} catch (err) {
|
||||
(
|
||||
err as Error
|
||||
@ -647,7 +663,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
}
|
||||
return value as Money;
|
||||
})
|
||||
.reduce((a, b) => a.add(b), frappe.pesa(0));
|
||||
.reduce((a, b) => a.add(b), this.frappe.pesa(0));
|
||||
|
||||
if (convertToFloat) {
|
||||
return sum.float;
|
||||
@ -660,7 +676,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return '';
|
||||
}
|
||||
|
||||
return frappe.doc.getCachedValue(schemaName, name, fieldname);
|
||||
return this.frappe.doc.getCachedValue(schemaName, name, fieldname);
|
||||
}
|
||||
|
||||
async duplicate(shouldInsert: boolean = true): Promise<Doc> {
|
||||
@ -690,7 +706,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
updateMap.name = updateMap.name + ' CPY';
|
||||
}
|
||||
|
||||
const doc = frappe.doc.getEmptyDoc(this.schemaName, false);
|
||||
const doc = this.frappe.doc.getEmptyDoc(this.schemaName, false);
|
||||
await doc.setMultiple(updateMap);
|
||||
|
||||
if (shouldInsert) {
|
||||
@ -700,6 +716,16 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
return doc;
|
||||
}
|
||||
|
||||
async beforeInsert() {}
|
||||
async afterInsert() {}
|
||||
async beforeUpdate() {}
|
||||
async afterUpdate() {}
|
||||
async afterSave() {}
|
||||
async beforeDelete() {}
|
||||
async afterDelete() {}
|
||||
async beforeRevert() {}
|
||||
async afterRevert() {}
|
||||
|
||||
formulas: FormulaMap = {};
|
||||
defaults: DefaultMap = {};
|
||||
validations: ValidationMap = {};
|
||||
@ -711,8 +737,14 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
||||
static lists: ListsMap = {};
|
||||
static filters: FiltersMap = {};
|
||||
static emptyMessages: EmptyMessageMap = {};
|
||||
static listSettings: ListViewSettings = {};
|
||||
static treeSettings?: TreeViewSettings;
|
||||
|
||||
static actions: Action[] = [];
|
||||
static getListViewSettings(frappe: Frappe): ListViewSettings {
|
||||
return {};
|
||||
}
|
||||
|
||||
static getTreeSettings(frappe: Frappe): TreeViewSettings | void {}
|
||||
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import { isPesa } from 'frappe/utils';
|
||||
import { isEqual } from 'lodash';
|
||||
@ -26,7 +26,10 @@ export function areDocValuesEqual(
|
||||
return isEqual(dvOne, dvTwo);
|
||||
}
|
||||
|
||||
export function getPreDefaultValues(fieldtype: FieldType): DocValue | Doc[] {
|
||||
export function getPreDefaultValues(
|
||||
fieldtype: FieldType,
|
||||
frappe: Frappe
|
||||
): DocValue | Doc[] {
|
||||
switch (fieldtype) {
|
||||
case FieldTypeEnum.Table:
|
||||
return [] as Doc[];
|
||||
|
@ -1,6 +1,7 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import NumberSeries from 'frappe/models/NumberSeries';
|
||||
import { getRandomString } from 'frappe/utils';
|
||||
import { DEFAULT_SERIES_START } from 'frappe/utils/consts';
|
||||
import { BaseError } from 'frappe/utils/errors';
|
||||
import { Field, Schema } from 'schemas/types';
|
||||
import Doc from './doc';
|
||||
@ -12,7 +13,7 @@ export function getNumberSeries(schema: Schema): Field | undefined {
|
||||
return numberSeries;
|
||||
}
|
||||
|
||||
export function isNameAutoSet(schemaName: string): boolean {
|
||||
export function isNameAutoSet(schemaName: string, frappe: Frappe): boolean {
|
||||
const schema = frappe.schemaMap[schemaName]!;
|
||||
if (schema.naming === 'autoincrement') {
|
||||
return true;
|
||||
@ -26,17 +27,17 @@ export function isNameAutoSet(schemaName: string): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
export async function setName(doc: Doc) {
|
||||
export async function setName(doc: Doc, frappe: Frappe) {
|
||||
// if is server, always name again if autoincrement or other
|
||||
if (doc.schema.naming === 'autoincrement') {
|
||||
doc.name = await getNextId(doc.schemaName);
|
||||
doc.name = await getNextId(doc.schemaName, frappe);
|
||||
return;
|
||||
}
|
||||
|
||||
// Current, per doc number series
|
||||
const numberSeries = doc.numberSeries as string | undefined;
|
||||
if (numberSeries !== undefined) {
|
||||
doc.name = await getSeriesNext(numberSeries, doc.schemaName);
|
||||
doc.name = await getSeriesNext(numberSeries, doc.schemaName, frappe);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -57,9 +58,9 @@ export async function setName(doc: Doc) {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getNextId(schemaName: string) {
|
||||
export async function getNextId(schemaName: string, frappe: Frappe) {
|
||||
// get the last inserted row
|
||||
const lastInserted = await getLastInserted(schemaName);
|
||||
const lastInserted = await getLastInserted(schemaName, frappe);
|
||||
let name = 1;
|
||||
if (lastInserted) {
|
||||
let lastNumber = parseInt(lastInserted.name as string);
|
||||
@ -69,7 +70,7 @@ export async function getNextId(schemaName: string) {
|
||||
return (name + '').padStart(9, '0');
|
||||
}
|
||||
|
||||
export async function getLastInserted(schemaName: string) {
|
||||
export async function getLastInserted(schemaName: string, frappe: Frappe) {
|
||||
const lastInserted = await frappe.db.getAll(schemaName, {
|
||||
fields: ['name'],
|
||||
limit: 1,
|
||||
@ -79,7 +80,11 @@ export async function getLastInserted(schemaName: string) {
|
||||
return lastInserted && lastInserted.length ? lastInserted[0] : null;
|
||||
}
|
||||
|
||||
export async function getSeriesNext(prefix: string, schemaName: string) {
|
||||
export async function getSeriesNext(
|
||||
prefix: string,
|
||||
schemaName: string,
|
||||
frappe: Frappe
|
||||
) {
|
||||
let series: NumberSeries;
|
||||
|
||||
try {
|
||||
@ -90,7 +95,7 @@ export async function getSeriesNext(prefix: string, schemaName: string) {
|
||||
throw e;
|
||||
}
|
||||
|
||||
await createNumberSeries(prefix, schemaName);
|
||||
await createNumberSeries(prefix, schemaName, DEFAULT_SERIES_START, frappe);
|
||||
series = (await frappe.doc.getDoc('NumberSeries', prefix)) as NumberSeries;
|
||||
}
|
||||
|
||||
@ -100,7 +105,8 @@ export async function getSeriesNext(prefix: string, schemaName: string) {
|
||||
export async function createNumberSeries(
|
||||
prefix: string,
|
||||
referenceType: string,
|
||||
start = 1001
|
||||
start: number,
|
||||
frappe: Frappe
|
||||
) {
|
||||
const exists = await frappe.db.exists('NumberSeries', prefix);
|
||||
if (exists) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue, DocValueMap } from 'frappe/core/types';
|
||||
import SystemSettings from 'frappe/models/SystemSettings';
|
||||
import { FieldType } from 'schemas/types';
|
||||
@ -41,8 +42,8 @@ export type ModelMap = Record<string, typeof Doc | undefined>;
|
||||
export type DocMap = Record<string, Doc | undefined>;
|
||||
|
||||
export interface SinglesMap {
|
||||
SystemSettings?: SystemSettings
|
||||
[key: string]: Doc | undefined
|
||||
SystemSettings?: SystemSettings;
|
||||
[key: string]: Doc | undefined;
|
||||
}
|
||||
|
||||
// Static Config properties
|
||||
@ -50,7 +51,7 @@ export interface SinglesMap {
|
||||
export type FilterFunction = (doc: Doc) => QueryFilter;
|
||||
export type FiltersMap = Record<string, FilterFunction>;
|
||||
|
||||
export type EmptyMessageFunction = (doc: Doc) => string;
|
||||
export type EmptyMessageFunction = (doc: Doc, frappe: Frappe) => string;
|
||||
export type EmptyMessageMap = Record<string, EmptyMessageFunction>;
|
||||
|
||||
export type ListFunction = (doc?: Doc) => string[];
|
||||
|
@ -1,4 +1,3 @@
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
|
||||
function getPaddedName(prefix: string, next: number, padZeros: number): string {
|
||||
@ -32,7 +31,7 @@ export default class NumberSeries extends Doc {
|
||||
}
|
||||
|
||||
const name = this.getPaddedName(this.current as number);
|
||||
return await frappe.db.exists(schemaName, name);
|
||||
return await this.frappe.db.exists(schemaName, name);
|
||||
}
|
||||
|
||||
getPaddedName(next: number): string {
|
||||
|
116
frappe/telemetry/helpers.ts
Normal file
116
frappe/telemetry/helpers.ts
Normal file
@ -0,0 +1,116 @@
|
||||
import { Frappe } from 'frappe';
|
||||
import { ConfigFile, ConfigKeys } from 'frappe/core/types';
|
||||
import { DEFAULT_COUNTRY_CODE } from 'frappe/utils/consts';
|
||||
import { Count, TelemetrySetting, UniqueId } from './types';
|
||||
|
||||
export function getId(): string {
|
||||
let id: string = '';
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
id += Math.random().toString(36).slice(2, 9);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getCountry(): string {
|
||||
// @ts-ignore
|
||||
return frappe.singles.SystemSettings?.countryCode ?? DEFAULT_COUNTRY_CODE;
|
||||
}
|
||||
|
||||
export function getLanguage(frappe: Frappe): string {
|
||||
return frappe.config.get('language') as string;
|
||||
}
|
||||
|
||||
export async function getCounts(
|
||||
interestingDocs: string[],
|
||||
frappe: Frappe
|
||||
): Promise<Count> {
|
||||
const countMap: Count = {};
|
||||
// @ts-ignore
|
||||
if (frappe.db === undefined) {
|
||||
return countMap;
|
||||
}
|
||||
|
||||
for (const name of interestingDocs) {
|
||||
const count: number = (await frappe.db.getAll(name)).length;
|
||||
countMap[name] = count;
|
||||
}
|
||||
|
||||
return countMap;
|
||||
}
|
||||
|
||||
export function getDeviceId(frappe: Frappe): UniqueId {
|
||||
let deviceId = frappe.config.get(ConfigKeys.DeviceId) as string | undefined;
|
||||
if (deviceId === undefined) {
|
||||
deviceId = getId();
|
||||
frappe.config.set(ConfigKeys.DeviceId, deviceId);
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
export function getInstanceId(frappe: Frappe): UniqueId {
|
||||
const files = frappe.config.get(ConfigKeys.Files) as ConfigFile[];
|
||||
|
||||
// @ts-ignore
|
||||
const companyName = frappe.AccountingSettings?.companyName;
|
||||
if (companyName === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const file = files.find((f) => f.companyName === companyName);
|
||||
|
||||
if (file === undefined) {
|
||||
return addNewFile(companyName, files, frappe);
|
||||
}
|
||||
|
||||
if (file.id === undefined) {
|
||||
return setInstanceId(companyName, files, frappe);
|
||||
}
|
||||
|
||||
return file.id;
|
||||
}
|
||||
|
||||
function addNewFile(
|
||||
companyName: string,
|
||||
files: ConfigFile[],
|
||||
frappe: Frappe
|
||||
): UniqueId {
|
||||
const newFile: ConfigFile = {
|
||||
companyName,
|
||||
filePath: frappe.config.get(ConfigKeys.LastSelectedFilePath, '') as string,
|
||||
id: getId(),
|
||||
};
|
||||
|
||||
files.push(newFile);
|
||||
frappe.config.set(ConfigKeys.Files, files);
|
||||
return newFile.id;
|
||||
}
|
||||
|
||||
function setInstanceId(
|
||||
companyName: string,
|
||||
files: ConfigFile[],
|
||||
frappe: Frappe
|
||||
): UniqueId {
|
||||
let id = '';
|
||||
for (const file of files) {
|
||||
if (file.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
file.id = getId();
|
||||
if (file.companyName === companyName) {
|
||||
id = file.id;
|
||||
}
|
||||
}
|
||||
|
||||
frappe.config.set(ConfigKeys.Files, files);
|
||||
return id;
|
||||
}
|
||||
|
||||
export const getTelemetryOptions = (frappe: Frappe) => ({
|
||||
[TelemetrySetting.allow]: frappe.t`Allow Telemetry`,
|
||||
[TelemetrySetting.dontLogUsage]: frappe.t`Don't Log Usage`,
|
||||
[TelemetrySetting.dontLogAnything]: frappe.t`Don't Log Anything`,
|
||||
});
|
@ -1,15 +1,21 @@
|
||||
import config, { ConfigKeys, TelemetrySetting } from '@/config';
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import { ConfigKeys } from 'frappe/core/types';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import {
|
||||
getCountry,
|
||||
getCounts,
|
||||
getCreds,
|
||||
getDeviceId,
|
||||
getInstanceId,
|
||||
getLanguage,
|
||||
} from './helpers';
|
||||
import { Noun, NounEnum, Platform, Telemetry, Verb } from './types';
|
||||
import {
|
||||
Noun,
|
||||
NounEnum,
|
||||
Platform,
|
||||
Telemetry,
|
||||
TelemetrySetting,
|
||||
Verb,
|
||||
} from './types';
|
||||
|
||||
/**
|
||||
* # Telemetry
|
||||
@ -44,16 +50,26 @@ import { Noun, NounEnum, Platform, Telemetry, Verb } from './types';
|
||||
* telemetry and not app usage.
|
||||
*/
|
||||
|
||||
class TelemetryManager {
|
||||
export class TelemetryManager {
|
||||
#url: string = '';
|
||||
#token: string = '';
|
||||
#started = false;
|
||||
#telemetryObject: Partial<Telemetry> = {};
|
||||
#interestingDocs: string[] = [];
|
||||
frappe: Frappe;
|
||||
|
||||
constructor(frappe: Frappe) {
|
||||
this.frappe = frappe;
|
||||
}
|
||||
|
||||
set platform(value: Platform) {
|
||||
this.#telemetryObject.platform ||= value;
|
||||
}
|
||||
|
||||
set interestingDocs(schemaNames: string[]) {
|
||||
this.#interestingDocs = schemaNames;
|
||||
}
|
||||
|
||||
get hasCreds() {
|
||||
return !!this.#url && !!this.#token;
|
||||
}
|
||||
@ -68,9 +84,9 @@ class TelemetryManager {
|
||||
|
||||
async start() {
|
||||
this.#telemetryObject.country ||= getCountry();
|
||||
this.#telemetryObject.language ??= getLanguage();
|
||||
this.#telemetryObject.deviceId ||= getDeviceId();
|
||||
this.#telemetryObject.instanceId ||= getInstanceId();
|
||||
this.#telemetryObject.language ??= getLanguage(this.frappe);
|
||||
this.#telemetryObject.deviceId ||= getDeviceId(this.frappe);
|
||||
this.#telemetryObject.instanceId ||= getInstanceId(this.frappe);
|
||||
this.#telemetryObject.openTime ||= new Date().valueOf();
|
||||
this.#telemetryObject.timeline ??= [];
|
||||
this.#telemetryObject.errors ??= {};
|
||||
@ -120,7 +136,10 @@ class TelemetryManager {
|
||||
|
||||
this.#clear();
|
||||
|
||||
if (config.get(ConfigKeys.Telemetry) === TelemetrySetting.dontLogAnything) {
|
||||
if (
|
||||
this.frappe.config.get(ConfigKeys.Telemetry) ===
|
||||
TelemetrySetting.dontLogAnything
|
||||
) {
|
||||
return;
|
||||
}
|
||||
navigator.sendBeacon(this.#url, data);
|
||||
@ -141,7 +160,10 @@ class TelemetryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
this.#telemetryObject.counts = await getCounts();
|
||||
this.#telemetryObject.counts = await getCounts(
|
||||
this.#interestingDocs,
|
||||
this.frappe
|
||||
);
|
||||
}
|
||||
|
||||
async #setCreds() {
|
||||
@ -149,13 +171,15 @@ class TelemetryManager {
|
||||
return;
|
||||
}
|
||||
|
||||
const { url, token } = await getCreds();
|
||||
const { url, token } = await this.frappe.auth.getTelemetryCreds();
|
||||
this.#url = url;
|
||||
this.#token = token;
|
||||
}
|
||||
|
||||
#getCanLog(): boolean {
|
||||
const telemetrySetting = config.get(ConfigKeys.Telemetry) as string;
|
||||
const telemetrySetting = this.frappe.config.get(
|
||||
ConfigKeys.Telemetry
|
||||
) as string;
|
||||
return telemetrySetting === TelemetrySetting.allow;
|
||||
}
|
||||
|
||||
@ -170,5 +194,3 @@ class TelemetryManager {
|
||||
delete this.#telemetryObject.country;
|
||||
}
|
||||
}
|
||||
|
||||
export default new TelemetryManager();
|
@ -1,5 +1,3 @@
|
||||
import { DoctypeName } from 'models/types';
|
||||
|
||||
export type AppVersion = string;
|
||||
export type UniqueId = string;
|
||||
export type Timestamp = number;
|
||||
@ -11,9 +9,7 @@ export interface InteractionEvent {
|
||||
more?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export type Count = Partial<{
|
||||
[key in DoctypeName]: number;
|
||||
}>;
|
||||
export type Count = Record<string, number>;
|
||||
export type Platform = 'Windows' | 'Mac' | 'Linux';
|
||||
|
||||
export interface Telemetry {
|
||||
@ -46,3 +42,9 @@ export enum NounEnum {
|
||||
}
|
||||
|
||||
export type Noun = string | NounEnum;
|
||||
|
||||
export enum TelemetrySetting {
|
||||
allow = 'allow',
|
||||
dontLogUsage = 'dontLogUsage',
|
||||
dontLogAnything = 'dontLogAnything',
|
||||
}
|
7
frappe/tests/helpers.ts
Normal file
7
frappe/tests/helpers.ts
Normal file
@ -0,0 +1,7 @@
|
||||
import { AuthDemuxBase, TelemetryCreds } from 'utils/auth/types';
|
||||
|
||||
export class DummyAuthDemux extends AuthDemuxBase {
|
||||
async getTelemetryCreds(): Promise<TelemetryCreds> {
|
||||
return { url: '', token: '' };
|
||||
}
|
||||
}
|
@ -1,10 +1,18 @@
|
||||
import * as assert from 'assert';
|
||||
import 'mocha';
|
||||
import models, { getRegionalModels } from 'models';
|
||||
import { getSchemas } from 'schemas';
|
||||
import { Frappe } from '..';
|
||||
import { DatabaseManager } from '../../backend/database/manager';
|
||||
import { DummyAuthDemux } from './helpers';
|
||||
|
||||
describe('Frappe', function () {
|
||||
const frappe = new Frappe(DatabaseManager);
|
||||
const frappe = new Frappe({
|
||||
DatabaseDemux: DatabaseManager,
|
||||
AuthDemux: DummyAuthDemux,
|
||||
isTest: true,
|
||||
isElectron: false,
|
||||
});
|
||||
|
||||
specify('Init', async function () {
|
||||
assert.strictEqual(
|
||||
@ -12,7 +20,7 @@ describe('Frappe', function () {
|
||||
0,
|
||||
'zero schemas one'
|
||||
);
|
||||
await frappe.initializeAndRegister();
|
||||
|
||||
assert.strictEqual(
|
||||
Object.keys(frappe.schemaMap).length,
|
||||
0,
|
||||
@ -20,7 +28,7 @@ describe('Frappe', function () {
|
||||
);
|
||||
|
||||
await frappe.db.createNewDatabase(':memory:', 'in');
|
||||
await frappe.initializeAndRegister({}, {}, true);
|
||||
await frappe.initializeAndRegister({}, {});
|
||||
assert.strictEqual(
|
||||
Object.keys(frappe.schemaMap).length > 0,
|
||||
true,
|
||||
@ -29,3 +37,28 @@ describe('Frappe', function () {
|
||||
await frappe.db.close();
|
||||
});
|
||||
});
|
||||
|
||||
describe('Frappe', function () {
|
||||
const countryCode = 'in';
|
||||
let frappe: Frappe;
|
||||
const schemas = getSchemas(countryCode);
|
||||
this.beforeEach(async function () {
|
||||
frappe = new Frappe({
|
||||
DatabaseDemux: DatabaseManager,
|
||||
isTest: true,
|
||||
isElectron: false,
|
||||
});
|
||||
|
||||
const regionalModels = await getRegionalModels(countryCode);
|
||||
await frappe.db.createNewDatabase(':memory:', countryCode);
|
||||
await frappe.initializeAndRegister(models, regionalModels);
|
||||
});
|
||||
|
||||
this.afterEach(async function () {
|
||||
await frappe.close();
|
||||
});
|
||||
|
||||
specify('temp', async function () {
|
||||
frappe.db.schemaMap;
|
||||
});
|
||||
});
|
||||
|
@ -5,14 +5,4 @@ export const DEFAULT_LOCALE = 'en-IN';
|
||||
export const DEFAULT_COUNTRY_CODE = 'in';
|
||||
export const DEFAULT_CURRENCY = 'INR';
|
||||
export const DEFAULT_LANGUAGE = 'English';
|
||||
export const DEFAULT_NUMBER_SERIES = {
|
||||
SalesInvoice: 'SINV-',
|
||||
PurchaseInvoice: 'PINV-',
|
||||
Payment: 'PAY-',
|
||||
JournalEntry: 'JV-',
|
||||
Quotation: 'QTN-',
|
||||
SalesOrder: 'SO-',
|
||||
Fulfillment: 'OF-',
|
||||
PurchaseOrder: 'PO-',
|
||||
PurchaseReceipt: 'PREC-',
|
||||
};
|
||||
export const DEFAULT_SERIES_START = 1001;
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { DateTime } from 'luxon';
|
||||
@ -14,8 +14,9 @@ import {
|
||||
|
||||
export function format(
|
||||
value: DocValue,
|
||||
df?: string | Field,
|
||||
doc?: Doc
|
||||
df: string | Field | null,
|
||||
doc: Doc | null,
|
||||
frappe: Frappe
|
||||
): string {
|
||||
if (!df) {
|
||||
return String(value);
|
||||
@ -24,11 +25,11 @@ export function format(
|
||||
const field: Field = getField(df);
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.Currency) {
|
||||
return formatCurrency(value, field, doc);
|
||||
return formatCurrency(value, field, doc, frappe);
|
||||
}
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.Date) {
|
||||
return formatDate(value);
|
||||
return formatDate(value, frappe);
|
||||
}
|
||||
|
||||
if (field.fieldtype === FieldTypeEnum.Check) {
|
||||
@ -42,7 +43,7 @@ export function format(
|
||||
return String(value);
|
||||
}
|
||||
|
||||
function formatDate(value: DocValue): string {
|
||||
function formatDate(value: DocValue, frappe: Frappe): string {
|
||||
const dateFormat =
|
||||
(frappe.singles.SystemSettings?.dateFormat as string) ??
|
||||
DEFAULT_DATE_FORMAT;
|
||||
@ -64,12 +65,17 @@ function formatDate(value: DocValue): string {
|
||||
return formattedDate;
|
||||
}
|
||||
|
||||
function formatCurrency(value: DocValue, field: Field, doc?: Doc): string {
|
||||
const currency = getCurrency(field, doc);
|
||||
function formatCurrency(
|
||||
value: DocValue,
|
||||
field: Field,
|
||||
doc: Doc | null,
|
||||
frappe: Frappe
|
||||
): string {
|
||||
const currency = getCurrency(field, doc, frappe);
|
||||
|
||||
let valueString;
|
||||
try {
|
||||
valueString = formatNumber(value);
|
||||
valueString = formatNumber(value, frappe);
|
||||
} catch (err) {
|
||||
(err as Error).message += ` value: '${value}', type: ${typeof value}`;
|
||||
throw err;
|
||||
@ -83,8 +89,8 @@ function formatCurrency(value: DocValue, field: Field, doc?: Doc): string {
|
||||
return valueString;
|
||||
}
|
||||
|
||||
function formatNumber(value: DocValue): string {
|
||||
const numberFormatter = getNumberFormatter();
|
||||
function formatNumber(value: DocValue, frappe: Frappe): string {
|
||||
const numberFormatter = getNumberFormatter(frappe);
|
||||
if (typeof value === 'number') {
|
||||
return numberFormatter.format(value);
|
||||
}
|
||||
@ -106,7 +112,7 @@ function formatNumber(value: DocValue): string {
|
||||
return formattedNumber;
|
||||
}
|
||||
|
||||
function getNumberFormatter() {
|
||||
function getNumberFormatter(frappe: Frappe) {
|
||||
if (frappe.currencyFormatter) {
|
||||
return frappe.currencyFormatter;
|
||||
}
|
||||
@ -123,7 +129,7 @@ function getNumberFormatter() {
|
||||
}));
|
||||
}
|
||||
|
||||
function getCurrency(field: Field, doc?: Doc): string {
|
||||
function getCurrency(field: Field, doc: Doc | null, frappe: Frappe): string {
|
||||
if (doc && doc.getCurrencies[field.fieldname]) {
|
||||
return doc.getCurrencies[field.fieldname]();
|
||||
}
|
||||
|
@ -36,6 +36,9 @@ directly use the the `Frappe` class and will be run using `mocha` on `node`.
|
||||
Importing frontend code will break all the tests. This also implies that one
|
||||
should be wary about transitive dependencies.
|
||||
|
||||
It should also not import the `frappe` object (singleton) from `src`, where ever
|
||||
frappe is required in models it should be passed to it.
|
||||
|
||||
_Note: Frontend specific code can be imported but they should be done so, only
|
||||
using dynamic imports i.e. `await import('...')`._
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
FiltersMap,
|
||||
@ -6,36 +6,45 @@ import {
|
||||
TreeViewSettings,
|
||||
} from 'frappe/model/types';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { AccountRootType, AccountType } from './types';
|
||||
|
||||
export class Account extends Doc {
|
||||
rootType?: AccountRootType;
|
||||
accountType?: AccountType;
|
||||
parentAccount?: string;
|
||||
|
||||
async beforeInsert() {
|
||||
if (this.accountType || !this.parentAccount) {
|
||||
return;
|
||||
}
|
||||
|
||||
const account = await frappe.db.get(
|
||||
const account = await this.frappe.db.get(
|
||||
'Account',
|
||||
this.parentAccount as string
|
||||
);
|
||||
this.accountType = account.accountType as string;
|
||||
this.accountType = account.accountType as AccountType;
|
||||
}
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
columns: ['name', 'parentAccount', 'rootType'],
|
||||
};
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['name', 'parentAccount', 'rootType'],
|
||||
};
|
||||
}
|
||||
|
||||
static treeSettings: TreeViewSettings = {
|
||||
parentField: 'parentAccount',
|
||||
async getRootLabel(): Promise<string> {
|
||||
const accountingSettings = await frappe.doc.getSingle(
|
||||
'AccountingSettings'
|
||||
);
|
||||
return accountingSettings.companyName as string;
|
||||
},
|
||||
};
|
||||
static getTreeSettings(frappe: Frappe): void | TreeViewSettings {
|
||||
return {
|
||||
parentField: 'parentAccount',
|
||||
async getRootLabel(): Promise<string> {
|
||||
const accountingSettings = await frappe.doc.getSingle(
|
||||
'AccountingSettings'
|
||||
);
|
||||
return accountingSettings.companyName as string;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
static filters: FiltersMap = {
|
||||
parentAccount: (doc: Doc) => {
|
||||
parentAccount: (doc: Account) => {
|
||||
const filter: QueryFilter = {
|
||||
isGroup: true,
|
||||
};
|
||||
|
27
models/baseModels/Account/types.ts
Normal file
27
models/baseModels/Account/types.ts
Normal file
@ -0,0 +1,27 @@
|
||||
export type AccountType =
|
||||
| 'Accumulated Depreciation'
|
||||
| 'Bank'
|
||||
| 'Cash'
|
||||
| 'Chargeable'
|
||||
| 'Cost of Goods Sold'
|
||||
| 'Depreciation'
|
||||
| 'Equity'
|
||||
| 'Expense Account'
|
||||
| 'Expenses Included In Valuation'
|
||||
| 'Fixed Asset'
|
||||
| 'Income Account'
|
||||
| 'Payable'
|
||||
| 'Receivable'
|
||||
| 'Round Off'
|
||||
| 'Stock'
|
||||
| 'Stock Adjustment'
|
||||
| 'Stock Received But Not Billed'
|
||||
| 'Tax'
|
||||
| 'Temporary';
|
||||
|
||||
export type AccountRootType =
|
||||
| 'Asset'
|
||||
| 'Liability'
|
||||
| 'Equity'
|
||||
| 'Income'
|
||||
| 'Expense';
|
@ -2,7 +2,9 @@ import Doc from 'frappe/model/doc';
|
||||
import { ListViewSettings } from 'frappe/model/types';
|
||||
|
||||
export class AccountingLedgerEntry extends Doc {
|
||||
static listSettings: ListViewSettings = {
|
||||
columns: ['account', 'party', 'debit', 'credit', 'balance'],
|
||||
};
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['account', 'party', 'debit', 'credit', 'balance'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { EmptyMessageMap, FormulaMap, ListsMap } from 'frappe/model/types';
|
||||
import { stateCodeMap } from 'regional/in';
|
||||
@ -37,7 +36,7 @@ export class Address extends Doc {
|
||||
};
|
||||
|
||||
static emptyMessages: EmptyMessageMap = {
|
||||
state: (doc: Doc) => {
|
||||
state: (doc: Doc, frappe) => {
|
||||
if (doc.country) {
|
||||
return frappe.t`Enter State`;
|
||||
}
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { LedgerPosting } from 'accounting/ledgerPosting';
|
||||
import frappe from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { DefaultMap, FiltersMap, FormulaMap } from 'frappe/model/types';
|
||||
@ -28,7 +27,7 @@ export abstract class Invoice extends Doc {
|
||||
}
|
||||
|
||||
async getPayments() {
|
||||
const payments = await frappe.db.getAll('PaymentFor', {
|
||||
const payments = await this.frappe.db.getAll('PaymentFor', {
|
||||
fields: ['parent'],
|
||||
filters: { referenceName: this.name! },
|
||||
orderBy: 'name',
|
||||
@ -56,12 +55,12 @@ export abstract class Invoice extends Doc {
|
||||
await entries.post();
|
||||
|
||||
// update outstanding amounts
|
||||
await frappe.db.update(this.schemaName, {
|
||||
await this.frappe.db.update(this.schemaName, {
|
||||
name: this.name as string,
|
||||
outstandingAmount: this.baseGrandTotal!,
|
||||
});
|
||||
|
||||
const party = (await frappe.doc.getDoc('Party', this.party!)) as Party;
|
||||
const party = (await this.frappe.doc.getDoc('Party', this.party!)) as Party;
|
||||
await party.updateOutstandingAmount();
|
||||
}
|
||||
|
||||
@ -69,7 +68,7 @@ export abstract class Invoice extends Doc {
|
||||
const paymentRefList = await this.getPayments();
|
||||
for (const paymentFor of paymentRefList) {
|
||||
const paymentReference = paymentFor.parent;
|
||||
const payment = (await frappe.doc.getDoc(
|
||||
const payment = (await this.frappe.doc.getDoc(
|
||||
'Payment',
|
||||
paymentReference as string
|
||||
)) as Payment;
|
||||
@ -80,7 +79,7 @@ export abstract class Invoice extends Doc {
|
||||
}
|
||||
|
||||
// To set the payment status as unsubmitted.
|
||||
await frappe.db.update('Payment', {
|
||||
await this.frappe.db.update('Payment', {
|
||||
name: paymentReference,
|
||||
submitted: false,
|
||||
cancelled: true,
|
||||
@ -93,7 +92,9 @@ export abstract class Invoice extends Doc {
|
||||
async getExchangeRate() {
|
||||
if (!this.currency) return 1.0;
|
||||
|
||||
const accountingSettings = await frappe.doc.getSingle('AccountingSettings');
|
||||
const accountingSettings = await this.frappe.doc.getSingle(
|
||||
'AccountingSettings'
|
||||
);
|
||||
const companyCurrency = accountingSettings.currency;
|
||||
if (this.currency === companyCurrency) {
|
||||
return 1.0;
|
||||
@ -129,8 +130,8 @@ export abstract class Invoice extends Doc {
|
||||
taxes[account] = taxes[account] || {
|
||||
account,
|
||||
rate,
|
||||
amount: frappe.pesa(0),
|
||||
baseAmount: frappe.pesa(0),
|
||||
amount: this.frappe.pesa(0),
|
||||
baseAmount: this.frappe.pesa(0),
|
||||
};
|
||||
|
||||
const amount = (row.amount as Money).mul(rate).div(100);
|
||||
@ -149,7 +150,7 @@ export abstract class Invoice extends Doc {
|
||||
|
||||
async getTax(tax: string) {
|
||||
if (!this._taxes![tax]) {
|
||||
this._taxes[tax] = await frappe.doc.getDoc('Tax', tax);
|
||||
this._taxes[tax] = await this.frappe.doc.getDoc('Tax', tax);
|
||||
}
|
||||
|
||||
return this._taxes[tax];
|
||||
@ -166,7 +167,7 @@ export abstract class Invoice extends Doc {
|
||||
this.getFrom('Party', this.party!, 'defaultAccount') as string,
|
||||
currency: async () =>
|
||||
(this.getFrom('Party', this.party!, 'currency') as string) ||
|
||||
(frappe.singles.AccountingSettings!.currency as string),
|
||||
(this.frappe.singles.AccountingSettings!.currency as string),
|
||||
exchangeRate: async () => await this.getExchangeRate(),
|
||||
netTotal: async () => this.getSum('items', 'amount', false),
|
||||
baseNetTotal: async () => this.netTotal!.mul(this.exchangeRate!),
|
||||
|
@ -1,4 +1,3 @@
|
||||
import frappe from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
@ -7,6 +6,7 @@ import {
|
||||
FormulaMap,
|
||||
ValidationMap,
|
||||
} from 'frappe/model/types';
|
||||
import { ValidationError } from 'frappe/utils/errors';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { Invoice } from '../Invoice/Invoice';
|
||||
|
||||
@ -32,7 +32,7 @@ export abstract class InvoiceItem extends Doc {
|
||||
'Item',
|
||||
this.item as string,
|
||||
'rate'
|
||||
)) || frappe.pesa(0)) as Money;
|
||||
)) || this.frappe.pesa(0)) as Money;
|
||||
|
||||
return baseRate.div(this.exchangeRate!);
|
||||
},
|
||||
@ -73,8 +73,8 @@ export abstract class InvoiceItem extends Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Rate (${frappe.format(
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Rate (${this.frappe.format(
|
||||
value,
|
||||
'Currency'
|
||||
)}) cannot be less zero.`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
@ -9,6 +9,7 @@ import {
|
||||
ListViewSettings,
|
||||
ValidationMap,
|
||||
} from 'frappe/model/types';
|
||||
import { ValidationError } from 'frappe/utils/errors';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
|
||||
export class Item extends Doc {
|
||||
@ -19,11 +20,11 @@ export class Item extends Doc {
|
||||
accountName = 'Sales';
|
||||
}
|
||||
|
||||
const accountExists = await frappe.db.exists('Account', accountName);
|
||||
const accountExists = await this.frappe.db.exists('Account', accountName);
|
||||
return accountExists ? accountName : '';
|
||||
},
|
||||
expenseAccount: async () => {
|
||||
const cogs = await frappe.db.getAllRaw('Account', {
|
||||
const cogs = await this.frappe.db.getAllRaw('Account', {
|
||||
filters: {
|
||||
accountType: 'Cost of Goods Sold',
|
||||
},
|
||||
@ -56,43 +57,45 @@ export class Item extends Doc {
|
||||
validations: ValidationMap = {
|
||||
rate: async (value: DocValue) => {
|
||||
if ((value as Money).isNegative()) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Rate can't be negative.`
|
||||
);
|
||||
throw new ValidationError(this.frappe.t`Rate can't be negative.`);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
static actions: Action[] = [
|
||||
{
|
||||
label: frappe.t`New Invoice`,
|
||||
condition: (doc) => !doc.isNew,
|
||||
action: async (doc, router) => {
|
||||
const invoice = await frappe.doc.getEmptyDoc('SalesInvoice');
|
||||
invoice.append('items', {
|
||||
item: doc.name as string,
|
||||
rate: doc.rate as Money,
|
||||
tax: doc.tax as string,
|
||||
});
|
||||
router.push(`/edit/SalesInvoice/${invoice.name}`);
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return [
|
||||
{
|
||||
label: frappe.t`New Invoice`,
|
||||
condition: (doc) => !doc.isNew,
|
||||
action: async (doc, router) => {
|
||||
const invoice = await frappe.doc.getEmptyDoc('SalesInvoice');
|
||||
invoice.append('items', {
|
||||
item: doc.name as string,
|
||||
rate: doc.rate as Money,
|
||||
tax: doc.tax as string,
|
||||
});
|
||||
router.push(`/edit/SalesInvoice/${invoice.name}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`New Bill`,
|
||||
condition: (doc) => !doc.isNew,
|
||||
action: async (doc, router) => {
|
||||
const invoice = await frappe.doc.getEmptyDoc('PurchaseInvoice');
|
||||
invoice.append('items', {
|
||||
item: doc.name as string,
|
||||
rate: doc.rate as Money,
|
||||
tax: doc.tax as string,
|
||||
});
|
||||
router.push(`/edit/PurchaseInvoice/${invoice.name}`);
|
||||
{
|
||||
label: frappe.t`New Bill`,
|
||||
condition: (doc) => !doc.isNew,
|
||||
action: async (doc, router) => {
|
||||
const invoice = await frappe.doc.getEmptyDoc('PurchaseInvoice');
|
||||
invoice.append('items', {
|
||||
item: doc.name as string,
|
||||
rate: doc.rate as Money,
|
||||
tax: doc.tax as string,
|
||||
});
|
||||
router.push(`/edit/PurchaseInvoice/${invoice.name}`);
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
}
|
||||
|
||||
listSettings: ListViewSettings = {
|
||||
columns: ['name', 'unit', 'tax', 'rate'],
|
||||
};
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['name', 'unit', 'tax', 'rate'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
Action,
|
||||
@ -15,7 +15,7 @@ export class JournalEntry extends Doc {
|
||||
accounts: Doc[] = [];
|
||||
|
||||
getPosting() {
|
||||
const entries = new LedgerPosting({ reference: this });
|
||||
const entries = new LedgerPosting({ reference: this }, this.frappe);
|
||||
|
||||
for (const row of this.accounts) {
|
||||
const debit = row.debit as Money;
|
||||
@ -32,11 +32,11 @@ export class JournalEntry extends Doc {
|
||||
return entries;
|
||||
}
|
||||
|
||||
beforeUpdate() {
|
||||
async beforeUpdate() {
|
||||
this.getPosting().validateEntries();
|
||||
}
|
||||
|
||||
beforeInsert() {
|
||||
async beforeInsert() {
|
||||
this.getPosting().validateEntries();
|
||||
}
|
||||
|
||||
@ -56,44 +56,48 @@ export class JournalEntry extends Doc {
|
||||
numberSeries: () => ({ referenceType: 'JournalEntry' }),
|
||||
};
|
||||
|
||||
static actions: Action[] = [getLedgerLinkAction()];
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return [getLedgerLinkAction(frappe)];
|
||||
}
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
formRoute: (name) => `/edit/JournalEntry/${name}`,
|
||||
columns: [
|
||||
'date',
|
||||
{
|
||||
label: frappe.t`Status`,
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
render(doc) {
|
||||
let status = 'Draft';
|
||||
let color = 'gray';
|
||||
if (doc.submitted) {
|
||||
color = 'green';
|
||||
status = 'Submitted';
|
||||
}
|
||||
static getListViewSettings(frappe: Frappe): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/JournalEntry/${name}`,
|
||||
columns: [
|
||||
'date',
|
||||
{
|
||||
label: frappe.t`Status`,
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
render(doc) {
|
||||
let status = 'Draft';
|
||||
let color = 'gray';
|
||||
if (doc.submitted) {
|
||||
color = 'green';
|
||||
status = 'Submitted';
|
||||
}
|
||||
|
||||
if (doc.cancelled) {
|
||||
color = 'red';
|
||||
status = 'Cancelled';
|
||||
}
|
||||
if (doc.cancelled) {
|
||||
color = 'red';
|
||||
status = 'Cancelled';
|
||||
}
|
||||
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
};
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`Entry ID`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'name',
|
||||
getValue(doc) {
|
||||
return doc.name as string;
|
||||
{
|
||||
label: frappe.t`Entry ID`,
|
||||
fieldtype: 'Data',
|
||||
fieldname: 'name',
|
||||
getValue(doc) {
|
||||
return doc.name as string;
|
||||
},
|
||||
},
|
||||
},
|
||||
'entryType',
|
||||
'referenceNumber',
|
||||
],
|
||||
};
|
||||
'entryType',
|
||||
'referenceNumber',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { FiltersMap, FormulaMap } from 'frappe/model/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
@ -9,7 +8,7 @@ export class JournalEntryAccount extends Doc {
|
||||
|
||||
const otherTypeValue = this.get(otherType) as Money;
|
||||
if (!otherTypeValue.isZero()) {
|
||||
return frappe.pesa(0);
|
||||
return this.frappe.pesa(0);
|
||||
}
|
||||
|
||||
const totalType = this.parentdoc!.getSum('accounts', type, false) as Money;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
Action,
|
||||
@ -29,15 +29,17 @@ export class Party extends Doc {
|
||||
schemaName: 'SalesInvoice' | 'PurchaseInvoice'
|
||||
) {
|
||||
const outstandingAmounts = (
|
||||
await frappe.db.getAllRaw(schemaName, {
|
||||
await this.frappe.db.getAllRaw(schemaName, {
|
||||
fields: ['outstandingAmount', 'party'],
|
||||
filters: { submitted: true },
|
||||
})
|
||||
).filter(({ party }) => party === this.name);
|
||||
|
||||
const totalOutstanding = outstandingAmounts
|
||||
.map(({ outstandingAmount }) => frappe.pesa(outstandingAmount as number))
|
||||
.reduce((a, b) => a.add(b), frappe.pesa(0));
|
||||
.map(({ outstandingAmount }) =>
|
||||
this.frappe.pesa(outstandingAmount as number)
|
||||
)
|
||||
.reduce((a, b) => a.add(b), this.frappe.pesa(0));
|
||||
|
||||
await this.set('outstandingAmount', totalOutstanding);
|
||||
await this.update();
|
||||
@ -55,10 +57,11 @@ export class Party extends Doc {
|
||||
accountName = 'Creditors';
|
||||
}
|
||||
|
||||
const accountExists = await frappe.db.exists('Account', accountName);
|
||||
const accountExists = await this.frappe.db.exists('Account', accountName);
|
||||
return accountExists ? accountName : '';
|
||||
},
|
||||
currency: async () => frappe.singles.AccountingSettings!.currency as string,
|
||||
currency: async () =>
|
||||
this.frappe.singles.AccountingSettings!.currency as string,
|
||||
addressDisplay: async () => {
|
||||
const address = this.address as string | undefined;
|
||||
if (address) {
|
||||
@ -85,80 +88,84 @@ export class Party extends Doc {
|
||||
},
|
||||
};
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
columns: ['name', 'phone', 'outstandingAmount'],
|
||||
};
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return {
|
||||
columns: ['name', 'phone', 'outstandingAmount'],
|
||||
};
|
||||
}
|
||||
|
||||
static actions: Action[] = [
|
||||
{
|
||||
label: frappe.t`Create Bill`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Customer',
|
||||
action: async (partyDoc, router) => {
|
||||
const doc = await frappe.doc.getEmptyDoc('PurchaseInvoice');
|
||||
router.push({
|
||||
path: `/edit/PurchaseInvoice/${doc.name}`,
|
||||
query: {
|
||||
doctype: 'PurchaseInvoice',
|
||||
values: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return [
|
||||
{
|
||||
label: frappe.t`Create Bill`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Customer',
|
||||
action: async (partyDoc, router) => {
|
||||
const doc = await frappe.doc.getEmptyDoc('PurchaseInvoice');
|
||||
router.push({
|
||||
path: `/edit/PurchaseInvoice/${doc.name}`,
|
||||
query: {
|
||||
doctype: 'PurchaseInvoice',
|
||||
values: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`View Bills`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Customer',
|
||||
action: async (partyDoc, router) => {
|
||||
router.push({
|
||||
name: 'ListView',
|
||||
params: {
|
||||
doctype: 'PurchaseInvoice',
|
||||
filters: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
{
|
||||
label: frappe.t`View Bills`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Customer',
|
||||
action: async (partyDoc, router) => {
|
||||
router.push({
|
||||
name: 'ListView',
|
||||
params: {
|
||||
doctype: 'PurchaseInvoice',
|
||||
filters: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`Create Invoice`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Supplier',
|
||||
action: async (partyDoc, router) => {
|
||||
const doc = await frappe.doc.getEmptyDoc('SalesInvoice');
|
||||
router.push({
|
||||
path: `/edit/SalesInvoice/${doc.name}`,
|
||||
query: {
|
||||
doctype: 'SalesInvoice',
|
||||
values: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
{
|
||||
label: frappe.t`Create Invoice`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Supplier',
|
||||
action: async (partyDoc, router) => {
|
||||
const doc = await frappe.doc.getEmptyDoc('SalesInvoice');
|
||||
router.push({
|
||||
path: `/edit/SalesInvoice/${doc.name}`,
|
||||
query: {
|
||||
doctype: 'SalesInvoice',
|
||||
values: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
label: frappe.t`View Invoices`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Supplier',
|
||||
action: async (partyDoc, router) => {
|
||||
router.push({
|
||||
name: 'ListView',
|
||||
params: {
|
||||
doctype: 'SalesInvoice',
|
||||
filters: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
{
|
||||
label: frappe.t`View Invoices`,
|
||||
condition: (doc: Doc) =>
|
||||
!doc.isNew && (doc.role as PartyRole) !== 'Supplier',
|
||||
action: async (partyDoc, router) => {
|
||||
router.push({
|
||||
name: 'ListView',
|
||||
params: {
|
||||
doctype: 'SalesInvoice',
|
||||
filters: {
|
||||
// @ts-ignore
|
||||
party: partyDoc.name!,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
];
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { LedgerPosting } from 'accounting/ledgerPosting';
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import { DocValue } from 'frappe/core/types';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import {
|
||||
@ -21,6 +21,8 @@ import { PaymentMethod, PaymentType } from './types';
|
||||
|
||||
export class Payment extends Doc {
|
||||
party?: string;
|
||||
amount?: Money;
|
||||
writeoff?: Money;
|
||||
|
||||
async change({ changed }: { changed: string }) {
|
||||
switch (changed) {
|
||||
@ -48,7 +50,10 @@ export class Payment extends Doc {
|
||||
}
|
||||
|
||||
const schemaName = referenceType as string;
|
||||
const doc = await frappe.doc.getDoc(schemaName, referenceName as string);
|
||||
const doc = await this.frappe.doc.getDoc(
|
||||
schemaName,
|
||||
referenceName as string
|
||||
);
|
||||
|
||||
let party;
|
||||
let paymentType: PaymentType;
|
||||
@ -66,7 +71,7 @@ export class Payment extends Doc {
|
||||
}
|
||||
|
||||
updateAmountOnReferenceUpdate() {
|
||||
this.amount = frappe.pesa(0);
|
||||
this.amount = this.frappe.pesa(0);
|
||||
for (const paymentReference of this.for as Doc[]) {
|
||||
this.amount = (this.amount as Money).add(
|
||||
paymentReference.amount as Money
|
||||
@ -93,7 +98,7 @@ export class Payment extends Doc {
|
||||
if (this.paymentAccount !== this.account || !this.account) {
|
||||
return;
|
||||
}
|
||||
throw new frappe.errors.ValidationError(
|
||||
throw new this.frappe.errors.ValidationError(
|
||||
`To Account and From Account can't be the same: ${this.account}`
|
||||
);
|
||||
}
|
||||
@ -106,7 +111,7 @@ export class Payment extends Doc {
|
||||
|
||||
const referenceAmountTotal = forReferences
|
||||
.map(({ amount }) => amount as Money)
|
||||
.reduce((a, b) => a.add(b), frappe.pesa(0));
|
||||
.reduce((a, b) => a.add(b), this.frappe.pesa(0));
|
||||
|
||||
if (
|
||||
(this.amount as Money)
|
||||
@ -116,20 +121,20 @@ export class Payment extends Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
const writeoff = frappe.format(this.writeoff, 'Currency');
|
||||
const payment = frappe.format(this.amount, 'Currency');
|
||||
const refAmount = frappe.format(referenceAmountTotal, 'Currency');
|
||||
const writeoff = this.frappe.format(this.writeoff!, 'Currency');
|
||||
const payment = this.frappe.format(this.amount!, 'Currency');
|
||||
const refAmount = this.frappe.format(referenceAmountTotal, 'Currency');
|
||||
|
||||
if ((this.writeoff as Money).gt(0)) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Amount: ${payment} and writeoff: ${writeoff}
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Amount: ${payment} and writeoff: ${writeoff}
|
||||
is less than the total amount allocated to
|
||||
references: ${refAmount}.`
|
||||
);
|
||||
}
|
||||
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Amount: ${payment} is less than the total
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Amount: ${payment} is less than the total
|
||||
amount allocated to references: ${refAmount}.`
|
||||
);
|
||||
}
|
||||
@ -139,9 +144,9 @@ export class Payment extends Doc {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!frappe.singles.AccountingSettings!.writeOffAccount) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Write Off Account not set.
|
||||
if (!this.frappe.singles.AccountingSettings!.writeOffAccount) {
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Write Off Account not set.
|
||||
Please set Write Off Account in General Settings`
|
||||
);
|
||||
}
|
||||
@ -152,10 +157,13 @@ export class Payment extends Doc {
|
||||
const paymentAccount = this.paymentAccount as string;
|
||||
const amount = this.amount as Money;
|
||||
const writeoff = this.writeoff as Money;
|
||||
const entries = new LedgerPosting({
|
||||
reference: this,
|
||||
party: this.party!,
|
||||
});
|
||||
const entries = new LedgerPosting(
|
||||
{
|
||||
reference: this,
|
||||
party: this.party!,
|
||||
},
|
||||
this.frappe
|
||||
);
|
||||
|
||||
await entries.debit(paymentAccount as string, amount.sub(writeoff));
|
||||
await entries.credit(account as string, amount.sub(writeoff));
|
||||
@ -164,11 +172,14 @@ export class Payment extends Doc {
|
||||
return [entries];
|
||||
}
|
||||
|
||||
const writeoffEntry = new LedgerPosting({
|
||||
reference: this,
|
||||
party: this.party!,
|
||||
});
|
||||
const writeOffAccount = frappe.singles.AccountingSettings!
|
||||
const writeoffEntry = new LedgerPosting(
|
||||
{
|
||||
reference: this,
|
||||
party: this.party!,
|
||||
},
|
||||
this.frappe
|
||||
);
|
||||
const writeOffAccount = this.frappe.singles.AccountingSettings!
|
||||
.writeOffAccount as string;
|
||||
|
||||
if (this.paymentType === 'Pay') {
|
||||
@ -196,7 +207,7 @@ export class Payment extends Doc {
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
const referenceDoc = await frappe.doc.getDoc(
|
||||
const referenceDoc = await this.frappe.doc.getDoc(
|
||||
row.referenceType as string,
|
||||
row.referenceName as string
|
||||
);
|
||||
@ -210,26 +221,30 @@ export class Payment extends Doc {
|
||||
}
|
||||
|
||||
if (amount.lte(0) || amount.gt(outstandingAmount)) {
|
||||
let message = frappe.t`Payment amount: ${frappe.format(
|
||||
this.amount,
|
||||
let message = this.frappe.t`Payment amount: ${this.frappe.format(
|
||||
this.amount!,
|
||||
'Currency'
|
||||
)} should be less than Outstanding amount: ${frappe.format(
|
||||
)} should be less than Outstanding amount: ${this.frappe.format(
|
||||
outstandingAmount,
|
||||
'Currency'
|
||||
)}.`;
|
||||
|
||||
if (amount.lte(0)) {
|
||||
const amt = frappe.format(this.amount, 'Currency');
|
||||
message = frappe.t`Payment amount: ${amt} should be greater than 0.`;
|
||||
const amt = this.frappe.format(this.amount!, 'Currency');
|
||||
message = this.frappe
|
||||
.t`Payment amount: ${amt} should be greater than 0.`;
|
||||
}
|
||||
|
||||
throw new frappe.errors.ValidationError(message);
|
||||
throw new ValidationError(message);
|
||||
} else {
|
||||
// update outstanding amounts in invoice and party
|
||||
const newOutstanding = outstandingAmount.sub(amount);
|
||||
await referenceDoc.set('outstandingAmount', newOutstanding);
|
||||
await referenceDoc.update();
|
||||
const party = (await frappe.doc.getDoc('Party', this.party!)) as Party;
|
||||
const party = (await this.frappe.doc.getDoc(
|
||||
'Party',
|
||||
this.party!
|
||||
)) as Party;
|
||||
|
||||
await party.updateOutstandingAmount();
|
||||
}
|
||||
@ -254,7 +269,7 @@ export class Payment extends Doc {
|
||||
async updateReferenceOutstandingAmount() {
|
||||
await (this.for as Doc[]).forEach(
|
||||
async ({ amount, referenceType, referenceName }) => {
|
||||
const refDoc = await frappe.doc.getDoc(
|
||||
const refDoc = await this.frappe.doc.getDoc(
|
||||
referenceType as string,
|
||||
referenceName as string
|
||||
);
|
||||
@ -289,7 +304,7 @@ export class Payment extends Doc {
|
||||
amount: async (value: DocValue) => {
|
||||
if ((value as Money).isNegative()) {
|
||||
throw new ValidationError(
|
||||
frappe.t`Payment amount cannot be less than zero.`
|
||||
this.frappe.t`Payment amount cannot be less than zero.`
|
||||
);
|
||||
}
|
||||
|
||||
@ -297,14 +312,14 @@ export class Payment extends Doc {
|
||||
const amount = this.getSum('for', 'amount', false);
|
||||
|
||||
if ((value as Money).gt(amount)) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Payment amount cannot
|
||||
exceed ${frappe.format(amount, 'Currency')}.`
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Payment amount cannot
|
||||
exceed ${this.frappe.format(amount, 'Currency')}.`
|
||||
);
|
||||
} else if ((value as Money).isZero()) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe.t`Payment amount cannot
|
||||
be ${frappe.format(value, 'Currency')}.`
|
||||
throw new ValidationError(
|
||||
this.frappe.t`Payment amount cannot
|
||||
be ${this.frappe.format(value, 'Currency')}.`
|
||||
);
|
||||
}
|
||||
},
|
||||
@ -353,36 +368,40 @@ export class Payment extends Doc {
|
||||
},
|
||||
};
|
||||
|
||||
static actions: Action[] = [getLedgerLinkAction()];
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return [getLedgerLinkAction(frappe)];
|
||||
}
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
columns: [
|
||||
'party',
|
||||
{
|
||||
label: frappe.t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
render(doc) {
|
||||
let status = 'Draft';
|
||||
let color = 'gray';
|
||||
if (doc.submitted === 1) {
|
||||
color = 'green';
|
||||
status = 'Submitted';
|
||||
}
|
||||
if (doc.cancelled === 1) {
|
||||
color = 'red';
|
||||
status = 'Cancelled';
|
||||
}
|
||||
static getListViewSettings(frappe: Frappe): ListViewSettings {
|
||||
return {
|
||||
columns: [
|
||||
'party',
|
||||
{
|
||||
label: frappe.t`Status`,
|
||||
fieldname: 'status',
|
||||
fieldtype: 'Select',
|
||||
size: 'small',
|
||||
render(doc) {
|
||||
let status = 'Draft';
|
||||
let color = 'gray';
|
||||
if (doc.submitted === 1) {
|
||||
color = 'green';
|
||||
status = 'Submitted';
|
||||
}
|
||||
if (doc.cancelled === 1) {
|
||||
color = 'red';
|
||||
status = 'Cancelled';
|
||||
}
|
||||
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
};
|
||||
return {
|
||||
template: `<Badge class="text-xs" color="${color}">${status}</Badge>`,
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
'paymentType',
|
||||
'date',
|
||||
'amount',
|
||||
],
|
||||
};
|
||||
'paymentType',
|
||||
'date',
|
||||
'amount',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,3 @@
|
||||
import frappe from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { FiltersMap, FormulaMap } from 'frappe/model/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
@ -16,7 +15,7 @@ export class PaymentFor extends Doc {
|
||||
return outstandingAmount;
|
||||
}
|
||||
|
||||
return frappe.pesa(0);
|
||||
return this.frappe.pesa(0);
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { LedgerPosting } from 'accounting/ledgerPosting';
|
||||
import { Frappe } from 'frappe';
|
||||
import { Action, ListViewSettings } from 'frappe/model/types';
|
||||
import {
|
||||
getTransactionActions,
|
||||
@ -11,10 +12,13 @@ export class PurchaseInvoice extends Invoice {
|
||||
items?: PurchaseInvoiceItem[];
|
||||
|
||||
async getPosting() {
|
||||
const entries: LedgerPosting = new LedgerPosting({
|
||||
reference: this,
|
||||
party: this.party,
|
||||
});
|
||||
const entries: LedgerPosting = new LedgerPosting(
|
||||
{
|
||||
reference: this,
|
||||
party: this.party,
|
||||
},
|
||||
this.frappe
|
||||
);
|
||||
|
||||
await entries.credit(this.account!, this.baseGrandTotal!);
|
||||
|
||||
@ -32,17 +36,21 @@ export class PurchaseInvoice extends Invoice {
|
||||
return entries;
|
||||
}
|
||||
|
||||
static actions: Action[] = getTransactionActions('PurchaseInvoice');
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return getTransactionActions('PurchaseInvoice', frappe);
|
||||
}
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
],
|
||||
};
|
||||
static getListViewSettings(frappe: Frappe): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/PurchaseInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(frappe),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { LedgerPosting } from 'accounting/ledgerPosting';
|
||||
import { Frappe } from 'frappe';
|
||||
import { Action, ListViewSettings } from 'frappe/model/types';
|
||||
import {
|
||||
getTransactionActions,
|
||||
@ -11,10 +12,13 @@ export class SalesInvoice extends Invoice {
|
||||
items?: SalesInvoiceItem[];
|
||||
|
||||
async getPosting() {
|
||||
const entries: LedgerPosting = new LedgerPosting({
|
||||
reference: this,
|
||||
party: this.party,
|
||||
});
|
||||
const entries: LedgerPosting = new LedgerPosting(
|
||||
{
|
||||
reference: this,
|
||||
party: this.party,
|
||||
},
|
||||
this.frappe
|
||||
);
|
||||
await entries.debit(this.account!, this.baseGrandTotal!);
|
||||
|
||||
for (const item of this.items!) {
|
||||
@ -30,17 +34,21 @@ export class SalesInvoice extends Invoice {
|
||||
return entries;
|
||||
}
|
||||
|
||||
static actions: Action[] = getTransactionActions('SalesInvoice');
|
||||
static getActions(frappe: Frappe): Action[] {
|
||||
return getTransactionActions('SalesInvoice', frappe);
|
||||
}
|
||||
|
||||
static listSettings: ListViewSettings = {
|
||||
formRoute: (name) => `/edit/SalesInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
],
|
||||
};
|
||||
static getListViewSettings(frappe: Frappe): ListViewSettings {
|
||||
return {
|
||||
formRoute: (name) => `/edit/SalesInvoice/${name}`,
|
||||
columns: [
|
||||
'party',
|
||||
'name',
|
||||
getTransactionStatusColumn(frappe),
|
||||
'date',
|
||||
'grandTotal',
|
||||
'outstandingAmount',
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import frappe from 'frappe';
|
||||
import { t } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { FormulaMap, ListsMap } from 'frappe/model/types';
|
||||
import { DateTime } from 'luxon';
|
||||
@ -6,7 +6,7 @@ import countryInfo from '../../../fixtures/countryInfo.json';
|
||||
|
||||
export function getCOAList() {
|
||||
return [
|
||||
{ name: frappe.t`Standard Chart of Accounts`, countryCode: '' },
|
||||
{ name: t`Standard Chart of Accounts`, countryCode: '' },
|
||||
|
||||
{ countryCode: 'ae', name: 'U.A.E - Chart of Accounts' },
|
||||
{
|
||||
|
@ -2,5 +2,7 @@ import Doc from 'frappe/model/doc';
|
||||
import { ListViewSettings } from 'frappe/model/types';
|
||||
|
||||
export class Tax extends Doc {
|
||||
static listSettings: ListViewSettings = { columns: ['name'] };
|
||||
static getListViewSettings(): ListViewSettings {
|
||||
return { columns: ['name'] };
|
||||
}
|
||||
}
|
||||
|
@ -1,12 +1,11 @@
|
||||
import { openQuickEdit } from '@/utils';
|
||||
import frappe from 'frappe';
|
||||
import { Frappe } from 'frappe';
|
||||
import Doc from 'frappe/model/doc';
|
||||
import { Action, ColumnConfig } from 'frappe/model/types';
|
||||
import Money from 'pesa/dist/types/src/money';
|
||||
import { Router } from 'vue-router';
|
||||
import { InvoiceStatus } from './types';
|
||||
|
||||
export function getLedgerLinkAction(): Action {
|
||||
export function getLedgerLinkAction(frappe: Frappe): Action {
|
||||
return {
|
||||
label: frappe.t`Ledger Entries`,
|
||||
condition: (doc: Doc) => !!doc.submitted,
|
||||
@ -26,7 +25,10 @@ export function getLedgerLinkAction(): Action {
|
||||
};
|
||||
}
|
||||
|
||||
export function getTransactionActions(schemaName: string): Action[] {
|
||||
export function getTransactionActions(
|
||||
schemaName: string,
|
||||
frappe: Frappe
|
||||
): Action[] {
|
||||
return [
|
||||
{
|
||||
label: frappe.t`Make Payment`,
|
||||
@ -42,6 +44,7 @@ export function getTransactionActions(schemaName: string): Action[] {
|
||||
const paymentType = isSales ? 'Receive' : 'Pay';
|
||||
const hideAccountField = isSales ? 'account' : 'paymentAccount';
|
||||
|
||||
const { openQuickEdit } = await import('../src/utils');
|
||||
await openQuickEdit({
|
||||
schemaName: 'Payment',
|
||||
name: payment.name as string,
|
||||
@ -69,11 +72,11 @@ export function getTransactionActions(schemaName: string): Action[] {
|
||||
router.push({ path: `/print/${doc.doctype}/${doc.name}` });
|
||||
},
|
||||
},
|
||||
getLedgerLinkAction(),
|
||||
getLedgerLinkAction(frappe),
|
||||
];
|
||||
}
|
||||
|
||||
export function getTransactionStatusColumn(): ColumnConfig {
|
||||
export function getTransactionStatusColumn(frappe: Frappe): ColumnConfig {
|
||||
const statusMap = {
|
||||
Unpaid: frappe.t`Unpaid`,
|
||||
Paid: frappe.t`Paid`,
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { ModelMap } from 'frappe/model/types';
|
||||
import { Account } from './baseModels/Account/Account';
|
||||
import { AccountingLedgerEntry } from './baseModels/AccountingLedgerEntry/AccountingLedgerEntry';
|
||||
import { AccountingSettings } from './baseModels/AccountingSettings/AccountingSettings';
|
||||
@ -34,9 +35,11 @@ export default {
|
||||
SetupWizard,
|
||||
Tax,
|
||||
TaxSummary,
|
||||
};
|
||||
} as ModelMap;
|
||||
|
||||
export async function getRegionalModels(countryCode: string) {
|
||||
export async function getRegionalModels(
|
||||
countryCode: string
|
||||
): Promise<ModelMap> {
|
||||
if (countryCode !== 'in') {
|
||||
return {};
|
||||
}
|
||||
|
@ -3,7 +3,7 @@ import { Party as BaseParty } from 'models/baseModels/Party/Party';
|
||||
import { GSTType } from './types';
|
||||
|
||||
export class Party extends BaseParty {
|
||||
beforeInsert() {
|
||||
async beforeInsert() {
|
||||
const gstin = this.get('gstin') as string | undefined;
|
||||
const gstType = this.get('gstType') as GSTType;
|
||||
|
||||
|
@ -1 +1,31 @@
|
||||
export type InvoiceStatus = 'Draft' | 'Unpaid' | 'Cancelled' | 'Paid';
|
||||
export enum ModelNameEnum {
|
||||
Account = 'Account',
|
||||
AccountingLedgerEntry = 'AccountingLedgerEntry',
|
||||
AccountingSettings = 'AccountingSettings',
|
||||
Address = 'Address',
|
||||
Color = 'Color',
|
||||
CompanySettings = 'CompanySettings',
|
||||
Currency = 'Currency',
|
||||
GetStarted = 'GetStarted',
|
||||
Item = 'Item',
|
||||
JournalEntry = 'JournalEntry',
|
||||
JournalEntryAccount = 'JournalEntryAccount',
|
||||
NumberSeries = 'NumberSeries',
|
||||
Party = 'Party',
|
||||
Payment = 'Payment',
|
||||
PaymentFor = 'PaymentFor',
|
||||
PrintSettings = 'PrintSettings',
|
||||
PurchaseInvoice = 'PurchaseInvoice',
|
||||
PurchaseInvoiceItem = 'PurchaseInvoiceItem',
|
||||
SalesInvoice = 'SalesInvoice',
|
||||
SalesInvoiceItem = 'SalesInvoiceItem',
|
||||
SalesInvoiceSettings = 'SalesInvoiceSettings',
|
||||
SetupWizard = 'SetupWizard',
|
||||
Tax = 'Tax',
|
||||
TaxDetail = 'TaxDetail',
|
||||
TaxSummary = 'TaxSummary',
|
||||
PatchRun = 'PatchRun',
|
||||
SingleValue = 'SingleValue',
|
||||
SystemSettings = 'SystemSettings',
|
||||
}
|
||||
|
@ -149,8 +149,5 @@
|
||||
"accountType",
|
||||
"isGroup"
|
||||
],
|
||||
"keywordFields": ["name", "rootType", "accountType"],
|
||||
"treeSettings": {
|
||||
"parentField": "parentAccount"
|
||||
}
|
||||
"keywordFields": ["name", "rootType", "accountType"]
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ function deepFreeze(schemaMap: SchemaMap) {
|
||||
}
|
||||
|
||||
export function addMetaFields(schemaMap: SchemaMap): SchemaMap {
|
||||
const metaSchemaMap = getMapFromList(metaSchemas, 'name');
|
||||
const metaSchemaMap = getMapFromList(cloneDeep(metaSchemas), 'name');
|
||||
|
||||
const base = metaSchemaMap.base;
|
||||
const tree = getCombined(metaSchemaMap.tree, base);
|
||||
@ -115,13 +115,13 @@ function addNameField(schemaMap: SchemaMap) {
|
||||
}
|
||||
|
||||
function getCoreSchemas(): SchemaMap {
|
||||
const rawSchemaMap = getMapFromList(coreSchemas, 'name');
|
||||
const rawSchemaMap = getMapFromList(cloneDeep(coreSchemas), 'name');
|
||||
const coreSchemaMap = getAbstractCombinedSchemas(rawSchemaMap);
|
||||
return cleanSchemas(coreSchemaMap);
|
||||
}
|
||||
|
||||
function getAppSchemas(countryCode: string): SchemaMap {
|
||||
const appSchemaMap = getMapFromList(appSchemas, 'name');
|
||||
const appSchemaMap = getMapFromList(cloneDeep(appSchemas), 'name');
|
||||
const regionalSchemaMap = getRegionalSchemaMap(countryCode);
|
||||
const combinedSchemas = getRegionalCombinedSchemas(
|
||||
appSchemaMap,
|
||||
@ -225,7 +225,7 @@ export function getRegionalCombinedSchemas(
|
||||
}
|
||||
|
||||
function getRegionalSchemaMap(countryCode: string): SchemaStubMap {
|
||||
const countrySchemas = regionalSchemas[countryCode] as
|
||||
const countrySchemas = cloneDeep(regionalSchemas[countryCode]) as
|
||||
| SchemaStub[]
|
||||
| undefined;
|
||||
if (countrySchemas === undefined) {
|
||||
|
@ -68,7 +68,6 @@ export type Field =
|
||||
| DynamicLinkField
|
||||
| NumberField;
|
||||
|
||||
export type TreeSettings = { parentField: string };
|
||||
export type Naming = 'autoincrement' | 'random' | 'numberSeries'
|
||||
|
||||
export interface Schema {
|
||||
@ -84,7 +83,6 @@ export interface Schema {
|
||||
isSubmittable?: boolean; // For transactional types, values considered only after submit
|
||||
keywordFields?: string[]; // Used to get fields that are to be used for search.
|
||||
quickEditFields?: string[]; // Used to get fields for the quickEditForm
|
||||
treeSettings?: TreeSettings; // Used to determine root nodes
|
||||
inlineEditDisplayField?:string;// Display field if inline editable
|
||||
naming?: Naming; // Used for assigning name, default is 'random' else 'numberSeries' if present
|
||||
removeFields?: string[]; // Used by the builder to remove fields.
|
||||
|
@ -1,26 +0,0 @@
|
||||
import Store from 'electron-store';
|
||||
import frappe from 'frappe';
|
||||
|
||||
const config = new Store();
|
||||
export default config;
|
||||
|
||||
export enum ConfigKeys {
|
||||
Files = 'files',
|
||||
LastSelectedFilePath = 'lastSelectedFilePath',
|
||||
Language = 'language',
|
||||
DeviceId = 'deviceId',
|
||||
Telemetry = 'telemetry',
|
||||
OpenCount = 'openCount',
|
||||
}
|
||||
|
||||
export enum TelemetrySetting {
|
||||
allow = 'allow',
|
||||
dontLogUsage = 'dontLogUsage',
|
||||
dontLogAnything = 'dontLogAnything',
|
||||
}
|
||||
|
||||
export interface ConfigFile {
|
||||
id: string;
|
||||
companyName: string;
|
||||
filePath: string;
|
||||
}
|
@ -4,8 +4,8 @@ import Doc from 'frappe/model/doc';
|
||||
import { isNameAutoSet } from 'frappe/model/naming';
|
||||
import { FieldType, FieldTypeEnum } from 'schemas/types';
|
||||
import { parseCSV } from '../utils/csvParser';
|
||||
import telemetry from './telemetry/telemetry';
|
||||
import { Noun, Verb } from './telemetry/types';
|
||||
import telemetry from '../frappe/telemetry/telemetry';
|
||||
import { Noun, Verb } from '../frappe/telemetry/types';
|
||||
|
||||
export const importable = [
|
||||
'SalesInvoice',
|
||||
|
@ -9,9 +9,9 @@ import {
|
||||
} from 'frappe/utils/errors';
|
||||
import { ErrorLog } from 'frappe/utils/types';
|
||||
import { IPC_ACTIONS, IPC_MESSAGES } from 'utils/messages';
|
||||
import config, { ConfigKeys, TelemetrySetting } from './config';
|
||||
import telemetry from './telemetry/telemetry';
|
||||
import { showMessageDialog, showToast } from './utils';
|
||||
import telemetry from '../frappe/telemetry/telemetry';
|
||||
import config, { ConfigKeys, TelemetrySetting } from '../utils/config';
|
||||
import { showMessageDialog, showToast } from './utils.js';
|
||||
|
||||
function getCanLog(): boolean {
|
||||
const telemetrySetting = config.get(ConfigKeys.Telemetry);
|
||||
|
3
src/initFyo.ts
Normal file
3
src/initFyo.ts
Normal file
@ -0,0 +1,3 @@
|
||||
import { Frappe } from 'frappe';
|
||||
|
||||
export const fyo = new Frappe({ isTest: false, isElectron: true });
|
@ -1,6 +1,7 @@
|
||||
import frappe from 'frappe';
|
||||
import { createNumberSeries } from 'frappe/model/naming';
|
||||
import { DEFAULT_SERIES_START } from 'frappe/utils/consts';
|
||||
import { getValueMapFromList } from 'utils';
|
||||
import { fyo } from './initFyo';
|
||||
|
||||
export default async function postStart() {
|
||||
await createDefaultNumberSeries();
|
||||
@ -9,23 +10,28 @@ export default async function postStart() {
|
||||
}
|
||||
|
||||
async function createDefaultNumberSeries() {
|
||||
await createNumberSeries('SINV-', 'SalesInvoice');
|
||||
await createNumberSeries('PINV-', 'PurchaseInvoice');
|
||||
await createNumberSeries('PAY-', 'Payment');
|
||||
await createNumberSeries('JV-', 'JournalEntry');
|
||||
await createNumberSeries('SINV-', 'SalesInvoice', DEFAULT_SERIES_START, fyo);
|
||||
await createNumberSeries(
|
||||
'PINV-',
|
||||
'PurchaseInvoice',
|
||||
DEFAULT_SERIES_START,
|
||||
fyo
|
||||
);
|
||||
await createNumberSeries('PAY-', 'Payment', DEFAULT_SERIES_START, fyo);
|
||||
await createNumberSeries('JV-', 'JournalEntry', DEFAULT_SERIES_START, fyo);
|
||||
}
|
||||
|
||||
async function setSingles() {
|
||||
await frappe.doc.getSingle('AccountingSettings');
|
||||
await frappe.doc.getSingle('GetStarted');
|
||||
await fyo.doc.getSingle('AccountingSettings');
|
||||
await fyo.doc.getSingle('GetStarted');
|
||||
}
|
||||
|
||||
async function setCurrencySymbols() {
|
||||
const currencies = (await frappe.db.getAll('Currency', {
|
||||
const currencies = (await fyo.db.getAll('Currency', {
|
||||
fields: ['name', 'symbol'],
|
||||
})) as { name: string; symbol: string }[];
|
||||
|
||||
frappe.currencySymbols = getValueMapFromList(
|
||||
fyo.currencySymbols = getValueMapFromList(
|
||||
currencies,
|
||||
'name',
|
||||
'symbol'
|
||||
|
@ -1,12 +1,13 @@
|
||||
import config, { ConfigKeys } from '@/config';
|
||||
import frappe from 'frappe';
|
||||
import { ConfigKeys } from 'frappe/core/types';
|
||||
|
||||
export function incrementOpenCount() {
|
||||
let openCount = config.get(ConfigKeys.OpenCount);
|
||||
let openCount = frappe.config.get(ConfigKeys.OpenCount);
|
||||
if (typeof openCount !== 'number') {
|
||||
openCount = 1;
|
||||
} else {
|
||||
openCount += 1;
|
||||
}
|
||||
|
||||
config.set(ConfigKeys.OpenCount, openCount);
|
||||
frappe.config.set(ConfigKeys.OpenCount, openCount);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { handleError } from '@/errorHandling';
|
||||
import { IPC_CHANNELS, IPC_MESSAGES } from 'utils/messages';
|
||||
import telemetry from '@/telemetry/telemetry';
|
||||
import telemetry from 'frappe/telemetry/telemetry';
|
||||
import { showToast } from '@/utils';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import frappe from 'frappe';
|
||||
|
@ -10,8 +10,8 @@ import QuickEditForm from '@/pages/QuickEditForm.vue';
|
||||
import Report from '@/pages/Report.vue';
|
||||
import Settings from '@/pages/Settings/Settings.vue';
|
||||
import { createRouter, createWebHistory } from 'vue-router';
|
||||
import telemetry from './telemetry/telemetry';
|
||||
import { NounEnum, Verb } from './telemetry/types';
|
||||
import telemetry from '../frappe/telemetry/telemetry';
|
||||
import { NounEnum, Verb } from '../frappe/telemetry/types';
|
||||
|
||||
const routes = [
|
||||
{
|
||||
|
@ -1,129 +0,0 @@
|
||||
import config, { ConfigFile, ConfigKeys, TelemetrySetting } from '@/config';
|
||||
import { ipcRenderer } from 'electron';
|
||||
import frappe, { t } from 'frappe';
|
||||
import { IPC_ACTIONS } from 'utils/messages';
|
||||
import { DoctypeName } from '../../models/types';
|
||||
import { Count, UniqueId } from './types';
|
||||
|
||||
export function getId(): string {
|
||||
let id: string = '';
|
||||
|
||||
for (let i = 0; i < 4; i++) {
|
||||
id += Math.random().toString(36).slice(2, 9);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export function getCountry(): string {
|
||||
// @ts-ignore
|
||||
return frappe.AccountingSettings?.country ?? '';
|
||||
}
|
||||
|
||||
export function getLanguage(): string {
|
||||
return config.get('language') as string;
|
||||
}
|
||||
|
||||
export async function getCounts(): Promise<Count> {
|
||||
const interestingDocs = [
|
||||
DoctypeName.Payment,
|
||||
DoctypeName.PaymentFor,
|
||||
DoctypeName.SalesInvoice,
|
||||
DoctypeName.SalesInvoiceItem,
|
||||
DoctypeName.PurchaseInvoice,
|
||||
DoctypeName.PurchaseInvoiceItem,
|
||||
DoctypeName.JournalEntry,
|
||||
DoctypeName.JournalEntryAccount,
|
||||
DoctypeName.Party,
|
||||
DoctypeName.Account,
|
||||
DoctypeName.Tax,
|
||||
];
|
||||
|
||||
const countMap: Count = {};
|
||||
// @ts-ignore
|
||||
if (frappe.db === undefined) {
|
||||
return countMap;
|
||||
}
|
||||
|
||||
type CountResponse = { 'count(*)': number }[];
|
||||
for (const name of interestingDocs) {
|
||||
const count: number = (await frappe.db.getAll(name)).length;
|
||||
countMap[name] = count;
|
||||
}
|
||||
|
||||
return countMap;
|
||||
}
|
||||
|
||||
export function getDeviceId(): UniqueId {
|
||||
let deviceId = config.get(ConfigKeys.DeviceId) as string | undefined;
|
||||
if (deviceId === undefined) {
|
||||
deviceId = getId();
|
||||
config.set(ConfigKeys.DeviceId, deviceId);
|
||||
}
|
||||
|
||||
return deviceId;
|
||||
}
|
||||
|
||||
export function getInstanceId(): UniqueId {
|
||||
const files = config.get(ConfigKeys.Files) as ConfigFile[];
|
||||
|
||||
// @ts-ignore
|
||||
const companyName = frappe.AccountingSettings?.companyName;
|
||||
if (companyName === undefined) {
|
||||
return '';
|
||||
}
|
||||
|
||||
const file = files.find((f) => f.companyName === companyName);
|
||||
|
||||
if (file === undefined) {
|
||||
return addNewFile(companyName, files);
|
||||
}
|
||||
|
||||
if (file.id === undefined) {
|
||||
return setInstanceId(companyName, files);
|
||||
}
|
||||
|
||||
return file.id;
|
||||
}
|
||||
|
||||
function addNewFile(companyName: string, files: ConfigFile[]): UniqueId {
|
||||
const newFile: ConfigFile = {
|
||||
companyName,
|
||||
filePath: config.get(ConfigKeys.LastSelectedFilePath, '') as string,
|
||||
id: getId(),
|
||||
};
|
||||
|
||||
files.push(newFile);
|
||||
config.set(ConfigKeys.Files, files);
|
||||
return newFile.id;
|
||||
}
|
||||
|
||||
function setInstanceId(companyName: string, files: ConfigFile[]): UniqueId {
|
||||
let id = '';
|
||||
for (const file of files) {
|
||||
if (file.id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
file.id = getId();
|
||||
if (file.companyName === companyName) {
|
||||
id = file.id;
|
||||
}
|
||||
}
|
||||
|
||||
config.set(ConfigKeys.Files, files);
|
||||
return id;
|
||||
}
|
||||
|
||||
export async function getCreds() {
|
||||
const creds = await ipcRenderer.invoke(IPC_ACTIONS.GET_CREDS);
|
||||
const url: string = creds?.telemetryUrl ?? '';
|
||||
const token: string = creds?.tokenString ?? '';
|
||||
return { url, token };
|
||||
}
|
||||
|
||||
export const getTelemetryOptions = () => ({
|
||||
[TelemetrySetting.allow]: t`Allow Telemetry`,
|
||||
[TelemetrySetting.dontLogUsage]: t`Don't Log Usage`,
|
||||
[TelemetrySetting.dontLogAnything]: t`Don't Log Anything`,
|
||||
});
|
@ -44,6 +44,6 @@
|
||||
|
||||
"scripts/**/*.ts",
|
||||
"utils/csvParser.ts"
|
||||
],
|
||||
, "utils/config.ts" ],
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
5
utils/auth/types.ts
Normal file
5
utils/auth/types.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export type TelemetryCreds = { url: string; token: string };
|
||||
|
||||
export abstract class AuthDemuxBase {
|
||||
abstract getTelemetryCreds(): Promise<TelemetryCreds>
|
||||
}
|
4
utils/config.ts
Normal file
4
utils/config.ts
Normal file
@ -0,0 +1,4 @@
|
||||
import Store from 'electron-store';
|
||||
|
||||
const config = new Store();
|
||||
export default config;
|
Loading…
Reference in New Issue
Block a user