2
0
mirror of https://github.com/frappe/books.git synced 2024-11-15 01:44:04 +00:00
books/fyo/core/dbHandler.ts

380 lines
9.6 KiB
TypeScript
Raw Normal View History

import { SingleValue } from 'backend/database/types';
import { Fyo } from 'fyo';
import { DatabaseDemux } from 'fyo/demux/db';
import { ValueError } from 'fyo/utils/errors';
import Observable from 'fyo/utils/observable';
2022-05-19 09:05:19 +00:00
import { translateSchema } from 'fyo/utils/translation';
2022-04-18 05:07:36 +00:00
import { Field, RawValue, SchemaMap } from 'schemas/types';
import { getMapFromList } from 'utils';
import {
Cashflow,
DatabaseBase,
DatabaseDemuxBase,
GetAllOptions,
IncomeExpense,
QueryFilter,
TopExpenses,
TotalCreditAndDebit,
TotalOutstanding,
} from 'utils/db/types';
2022-05-19 09:05:19 +00:00
import { schemaTranslateables } from 'utils/translationHelpers';
import { LanguageMap } from 'utils/types';
2022-04-18 05:07:36 +00:00
import { Converter } from './converter';
import {
DatabaseDemuxConstructor,
DocValue,
DocValueMap,
RawValueMap,
} from './types';
import { ReturnDocItem } from 'models/inventory/types';
2023-08-22 12:59:37 +00:00
import { Money } from 'pesa';
2022-03-22 09:28:36 +00:00
type FieldMap = Record<string, Record<string, Field>>;
2022-03-31 09:04:30 +00:00
export class DatabaseHandler extends DatabaseBase {
/* eslint-disable @typescript-eslint/no-floating-promises */
#fyo: Fyo;
2022-04-18 05:07:36 +00:00
converter: Converter;
#demux: DatabaseDemuxBase;
dbPath?: string;
2022-05-19 09:05:19 +00:00
#schemaMap: SchemaMap = {};
#fieldMap: FieldMap = {};
observer: Observable<never> = new Observable();
2022-03-22 09:28:36 +00:00
constructor(fyo: Fyo, Demux?: DatabaseDemuxConstructor) {
2022-03-31 09:04:30 +00:00
super();
this.#fyo = fyo;
this.converter = new Converter(this, this.#fyo);
if (Demux !== undefined) {
this.#demux = new Demux(fyo.isElectron);
} else {
this.#demux = new DatabaseDemux(fyo.isElectron);
}
2022-03-22 09:28:36 +00:00
}
2022-05-19 09:05:19 +00:00
get schemaMap(): Readonly<SchemaMap> {
return this.#schemaMap;
}
get fieldMap(): Readonly<FieldMap> {
return this.#fieldMap;
}
2022-04-22 11:02:03 +00:00
get isConnected() {
return !!this.dbPath;
}
async createNewDatabase(dbPath: string, countryCode: string) {
countryCode = await this.#demux.createNewDatabase(dbPath, countryCode);
await this.init();
this.dbPath = dbPath;
return countryCode;
2022-03-22 09:28:36 +00:00
}
2022-03-31 09:04:30 +00:00
async connectToDatabase(dbPath: string, countryCode?: string) {
countryCode = await this.#demux.connectToDatabase(dbPath, countryCode);
await this.init();
this.dbPath = dbPath;
return countryCode;
2022-03-22 09:28:36 +00:00
}
2022-03-31 09:04:30 +00:00
async init() {
this.#schemaMap = await this.#demux.getSchemaMap();
this.#setFieldMap();
this.observer = new Observable();
2022-03-31 09:04:30 +00:00
}
2022-05-19 09:05:19 +00:00
async translateSchemaMap(languageMap?: LanguageMap) {
if (languageMap) {
translateSchema(this.#schemaMap, languageMap, schemaTranslateables);
} else {
this.#schemaMap = await this.#demux.getSchemaMap();
this.#setFieldMap();
2022-05-19 09:05:19 +00:00
}
}
2022-07-30 11:03:09 +00:00
async purgeCache() {
await this.close();
2022-04-22 11:02:03 +00:00
this.dbPath = undefined;
2022-05-19 09:05:19 +00:00
this.#schemaMap = {};
this.#fieldMap = {};
2022-04-22 11:02:03 +00:00
}
2022-03-31 09:04:30 +00:00
async insert(
schemaName: string,
docValueMap: DocValueMap
): Promise<DocValueMap> {
2022-04-18 05:07:36 +00:00
let rawValueMap = this.converter.toRawValueMap(
2022-03-31 09:04:30 +00:00
schemaName,
docValueMap
) as RawValueMap;
rawValueMap = (await this.#demux.call(
'insert',
schemaName,
rawValueMap
)) as RawValueMap;
this.observer.trigger(`insert:${schemaName}`, docValueMap);
2022-04-18 05:07:36 +00:00
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
2022-03-31 09:04:30 +00:00
}
// Read
async get(
schemaName: string,
name: string,
fields?: string | string[]
): Promise<DocValueMap> {
const rawValueMap = (await this.#demux.call(
'get',
schemaName,
name,
fields
)) as RawValueMap;
this.observer.trigger(`get:${schemaName}`, { name, fields });
2022-04-18 05:07:36 +00:00
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
2022-03-31 09:04:30 +00:00
}
async getAll(
schemaName: string,
options: GetAllOptions = {}
2022-03-31 09:04:30 +00:00
): Promise<DocValueMap[]> {
const rawValueMap = await this.#getAll(schemaName, options);
this.observer.trigger(`getAll:${schemaName}`, options);
2022-04-18 05:07:36 +00:00
return this.converter.toDocValueMap(
schemaName,
rawValueMap
) as DocValueMap[];
}
async getAllRaw(
schemaName: string,
options: GetAllOptions = {}
2022-05-12 10:04:37 +00:00
): Promise<RawValueMap[]> {
const all = await this.#getAll(schemaName, options);
this.observer.trigger(`getAllRaw:${schemaName}`, options);
return all;
}
2022-03-31 09:04:30 +00:00
async getSingleValues(
...fieldnames: ({ fieldname: string; parent?: string } | string)[]
2022-03-31 11:43:20 +00:00
): Promise<SingleValue<DocValue>> {
const rawSingleValue = (await this.#demux.call(
'getSingleValues',
...fieldnames
)) as SingleValue<RawValue>;
2022-04-18 05:07:36 +00:00
const docSingleValue: SingleValue<DocValue> = [];
for (const sv of rawSingleValue) {
const field = this.fieldMap[sv.parent][sv.fieldname];
const value = Converter.toDocValue(sv.value, field, this.#fyo);
2022-04-18 05:07:36 +00:00
docSingleValue.push({
value,
parent: sv.parent,
fieldname: sv.fieldname,
});
}
this.observer.trigger(`getSingleValues`, fieldnames);
2022-04-18 05:07:36 +00:00
return docSingleValue;
}
async count(
schemaName: string,
options: GetAllOptions = {}
): Promise<number> {
const rawValueMap = await this.#getAll(schemaName, options);
const count = rawValueMap.length;
this.observer.trigger(`count:${schemaName}`, options);
return count;
2022-03-31 09:04:30 +00:00
}
// Update
async rename(
schemaName: string,
oldName: string,
newName: string
): Promise<void> {
await this.#demux.call('rename', schemaName, oldName, newName);
this.observer.trigger(`rename:${schemaName}`, { oldName, newName });
2022-03-31 09:04:30 +00:00
}
async update(schemaName: string, docValueMap: DocValueMap): Promise<void> {
2022-04-18 05:07:36 +00:00
const rawValueMap = this.converter.toRawValueMap(schemaName, docValueMap);
2022-03-31 09:04:30 +00:00
await this.#demux.call('update', schemaName, rawValueMap);
this.observer.trigger(`update:${schemaName}`, docValueMap);
2022-03-31 09:04:30 +00:00
}
// Delete
async delete(schemaName: string, name: string): Promise<void> {
await this.#demux.call('delete', schemaName, name);
this.observer.trigger(`delete:${schemaName}`, name);
2022-03-31 09:04:30 +00:00
}
async deleteAll(schemaName: string, filters: QueryFilter): Promise<number> {
const count = (await this.#demux.call(
'deleteAll',
schemaName,
filters
)) as number;
this.observer.trigger(`deleteAll:${schemaName}`, filters);
return count;
}
2022-03-31 09:04:30 +00:00
// Other
async exists(schemaName: string, name?: string): Promise<boolean> {
const doesExist = (await this.#demux.call(
'exists',
schemaName,
name
)) as boolean;
this.observer.trigger(`exists:${schemaName}`, name);
return doesExist;
}
2022-03-31 09:04:30 +00:00
async close(): Promise<void> {
await this.#demux.call('close');
}
/**
* Bespoke function
*
* These are functions to run custom queries that are too complex for
2022-04-18 05:07:36 +00:00
* DatabaseCore and require use of knex or raw queries. The output
* of these is not converted to DocValue and is used as is (RawValue).
*
* The query logic for these is in backend/database/bespoke.ts
*/
async getLastInserted(schemaName: string): Promise<number> {
if (this.schemaMap[schemaName]?.naming !== 'autoincrement') {
throw new ValueError(
`invalid schema, ${schemaName} does not have autoincrement naming`
);
}
return (await this.#demux.callBespoke(
'getLastInserted',
schemaName
)) as number;
}
async getTopExpenses(fromDate: string, toDate: string): Promise<TopExpenses> {
return (await this.#demux.callBespoke(
'getTopExpenses',
fromDate,
toDate
)) as TopExpenses;
}
async getTotalOutstanding(
schemaName: string,
fromDate: string,
toDate: string
): Promise<TotalOutstanding> {
return (await this.#demux.callBespoke(
'getTotalOutstanding',
schemaName,
fromDate,
toDate
)) as TotalOutstanding;
}
async getCashflow(fromDate: string, toDate: string): Promise<Cashflow> {
return (await this.#demux.callBespoke(
'getCashflow',
fromDate,
toDate
)) as Cashflow;
}
async getIncomeAndExpenses(
fromDate: string,
toDate: string
): Promise<IncomeExpense> {
return (await this.#demux.callBespoke(
'getIncomeAndExpenses',
fromDate,
toDate
)) as IncomeExpense;
}
async getTotalCreditAndDebit(): Promise<TotalCreditAndDebit[]> {
return (await this.#demux.callBespoke(
'getTotalCreditAndDebit'
)) as TotalCreditAndDebit[];
}
2022-11-03 10:53:34 +00:00
async getStockQuantity(
item: string,
location?: string,
fromDate?: string,
toDate?: string,
2023-04-25 07:07:29 +00:00
batch?: string,
2023-05-04 10:45:12 +00:00
serialNumbers?: string[]
2022-11-03 10:53:34 +00:00
): Promise<number | null> {
return (await this.#demux.callBespoke(
'getStockQuantity',
item,
location,
fromDate,
toDate,
2023-04-25 07:07:29 +00:00
batch,
2023-05-04 10:45:12 +00:00
serialNumbers
2022-11-03 10:53:34 +00:00
)) as number | null;
}
2023-07-01 07:31:00 +00:00
async getReturnBalanceItemsQty(
schemaName: string,
docName: string
): Promise<Record<string, ReturnDocItem> | undefined> {
2023-07-01 07:31:00 +00:00
return (await this.#demux.callBespoke(
'getReturnBalanceItemsQty',
schemaName,
docName
)) as Promise<Record<string, ReturnDocItem> | undefined>;
2023-07-01 07:31:00 +00:00
}
2023-08-22 12:59:37 +00:00
async getPOSTransactedAmount(
fromDate: Date
): Promise<Record<string, Money> | undefined> {
return (await this.#demux.callBespoke(
'getPOSTransactedAmount',
fromDate
)) as Promise<Record<string, Money> | undefined>;
}
2022-04-18 05:07:36 +00:00
/**
* Internal methods
*/
async #getAll(
2022-03-31 09:04:30 +00:00
schemaName: string,
2022-04-18 05:07:36 +00:00
options: GetAllOptions = {}
): Promise<RawValueMap[]> {
return (await this.#demux.call(
'getAll',
schemaName,
options
)) as RawValueMap[];
2022-03-31 11:43:20 +00:00
}
#setFieldMap() {
this.#fieldMap = Object.values(this.schemaMap).reduce((acc, sch) => {
if (!sch?.name) {
return acc;
}
acc[sch?.name] = getMapFromList(sch?.fields, 'fieldname');
return acc;
}, {} as FieldMap);
}
2022-03-22 09:28:36 +00:00
}