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