mirror of
https://github.com/frappe/books.git
synced 2025-01-03 15:17:30 +00:00
incr: complete dbHandler.ts
This commit is contained in:
parent
af0d897020
commit
a39d7ed555
149
frappe/core/converter.ts
Normal file
149
frappe/core/converter.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import frappe from 'frappe';
|
||||||
|
import Doc from 'frappe/model/doc';
|
||||||
|
import Money from 'pesa/dist/types/src/money';
|
||||||
|
import { FieldType, FieldTypeEnum, RawValue } from 'schemas/types';
|
||||||
|
import { DatabaseHandler } from './dbHandler';
|
||||||
|
import { DocValue, DocValueMap, RawValueMap } from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* # Converter
|
||||||
|
*
|
||||||
|
* Basically converts serializable RawValues from the db to DocValues used
|
||||||
|
* by the frontend and vice versa.
|
||||||
|
*
|
||||||
|
* ## Value Conversion
|
||||||
|
* It exposes two static methods: `toRawValue` and `toDocValue` that can be
|
||||||
|
* used elsewhere given the fieldtype.
|
||||||
|
*
|
||||||
|
* ## Map Conversion
|
||||||
|
* Two methods `toDocValueMap` and `toRawValueMap` are exposed but should be
|
||||||
|
* used only from the `dbHandler`.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export class Converter {
|
||||||
|
db: DatabaseHandler;
|
||||||
|
|
||||||
|
constructor(db: DatabaseHandler) {
|
||||||
|
this.db = db;
|
||||||
|
}
|
||||||
|
|
||||||
|
toDocValueMap(
|
||||||
|
schemaName: string,
|
||||||
|
rawValueMap: RawValueMap | RawValueMap[]
|
||||||
|
): DocValueMap | DocValueMap[] {
|
||||||
|
if (Array.isArray(rawValueMap)) {
|
||||||
|
return rawValueMap.map((dv) => this.#toDocValueMap(schemaName, dv));
|
||||||
|
} else {
|
||||||
|
return this.#toDocValueMap(schemaName, rawValueMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toRawValueMap(
|
||||||
|
schemaName: string,
|
||||||
|
docValueMap: DocValueMap | DocValueMap[]
|
||||||
|
): RawValueMap | RawValueMap[] {
|
||||||
|
if (Array.isArray(docValueMap)) {
|
||||||
|
return docValueMap.map((dv) => this.#toRawValueMap(schemaName, dv));
|
||||||
|
} else {
|
||||||
|
return this.#toRawValueMap(schemaName, docValueMap);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static toDocValue(value: RawValue, fieldtype: FieldType): DocValue {
|
||||||
|
switch (fieldtype) {
|
||||||
|
case FieldTypeEnum.Currency:
|
||||||
|
return frappe.pesa((value ?? 0) as string | number);
|
||||||
|
case FieldTypeEnum.Date:
|
||||||
|
return new Date(value as string);
|
||||||
|
case FieldTypeEnum.Datetime:
|
||||||
|
return new Date(value as string);
|
||||||
|
case FieldTypeEnum.Int:
|
||||||
|
return +(value as string | number);
|
||||||
|
case FieldTypeEnum.Float:
|
||||||
|
return +(value as string | number);
|
||||||
|
case FieldTypeEnum.Check:
|
||||||
|
return Boolean(value as number);
|
||||||
|
default:
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static toRawValue(value: DocValue, fieldtype: FieldType): RawValue {
|
||||||
|
switch (fieldtype) {
|
||||||
|
case FieldTypeEnum.Currency:
|
||||||
|
return (value as Money).store;
|
||||||
|
case FieldTypeEnum.Date:
|
||||||
|
return (value as Date).toISOString().split('T')[0];
|
||||||
|
case FieldTypeEnum.Datetime:
|
||||||
|
return (value as Date).toISOString();
|
||||||
|
case FieldTypeEnum.Int: {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return parseInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Math.floor(value as number);
|
||||||
|
}
|
||||||
|
case FieldTypeEnum.Float: {
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
return parseFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return value as number;
|
||||||
|
}
|
||||||
|
case FieldTypeEnum.Check:
|
||||||
|
return Number(value);
|
||||||
|
default:
|
||||||
|
return String(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#toDocValueMap(schemaName: string, rawValueMap: RawValueMap): DocValueMap {
|
||||||
|
const fieldValueMap = this.db.fieldValueMap[schemaName];
|
||||||
|
const docValueMap: DocValueMap = {};
|
||||||
|
|
||||||
|
for (const fieldname in rawValueMap) {
|
||||||
|
const field = fieldValueMap[fieldname];
|
||||||
|
const rawValue = rawValueMap[fieldname];
|
||||||
|
|
||||||
|
if (Array.isArray(rawValue)) {
|
||||||
|
docValueMap[fieldname] = rawValue.map((rv) =>
|
||||||
|
this.#toDocValueMap(schemaName, rv)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
docValueMap[fieldname] = Converter.toDocValue(
|
||||||
|
rawValue,
|
||||||
|
field.fieldtype
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return docValueMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
#toRawValueMap(schemaName: string, docValueMap: DocValueMap): RawValueMap {
|
||||||
|
const fieldValueMap = this.db.fieldValueMap[schemaName];
|
||||||
|
const rawValueMap: RawValueMap = {};
|
||||||
|
|
||||||
|
for (const fieldname in docValueMap) {
|
||||||
|
const field = fieldValueMap[fieldname];
|
||||||
|
const docValue = docValueMap[fieldname];
|
||||||
|
|
||||||
|
if (Array.isArray(docValue)) {
|
||||||
|
rawValueMap[fieldname] = docValue.map((value) => {
|
||||||
|
if (value instanceof Doc) {
|
||||||
|
return this.#toRawValueMap(schemaName, value.getValidDict());
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.#toRawValueMap(schemaName, value as DocValueMap);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
rawValueMap[fieldname] = Converter.toRawValue(
|
||||||
|
docValue,
|
||||||
|
field.fieldtype
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return rawValueMap;
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,9 @@
|
|||||||
import { Frappe } from 'frappe';
|
import { Frappe } from 'frappe';
|
||||||
import { DatabaseDemux } from 'frappe/demux/db';
|
import { DatabaseDemux } from 'frappe/demux/db';
|
||||||
import Money from 'pesa/dist/types/src/money';
|
import { Field, RawValue, SchemaMap } from 'schemas/types';
|
||||||
import { FieldType, FieldTypeEnum, RawValue, SchemaMap } from 'schemas/types';
|
import { getMapFromList } from 'utils';
|
||||||
import { DatabaseBase, DatabaseDemuxBase, GetAllOptions } from 'utils/db/types';
|
import { DatabaseBase, DatabaseDemuxBase, GetAllOptions } from 'utils/db/types';
|
||||||
|
import { Converter } from './converter';
|
||||||
import {
|
import {
|
||||||
DatabaseDemuxConstructor,
|
DatabaseDemuxConstructor,
|
||||||
DocValue,
|
DocValue,
|
||||||
@ -18,12 +19,15 @@ type Cashflow = { inflow: number; outflow: number; 'month-year': string }[];
|
|||||||
|
|
||||||
export class DatabaseHandler extends DatabaseBase {
|
export class DatabaseHandler extends DatabaseBase {
|
||||||
#frappe: Frappe;
|
#frappe: Frappe;
|
||||||
|
converter: Converter;
|
||||||
#demux: DatabaseDemuxBase;
|
#demux: DatabaseDemuxBase;
|
||||||
schemaMap: Readonly<SchemaMap> = {};
|
schemaMap: Readonly<SchemaMap> = {};
|
||||||
|
fieldValueMap: Record<string, Record<string, Field>> = {};
|
||||||
|
|
||||||
constructor(frappe: Frappe, Demux?: DatabaseDemuxConstructor) {
|
constructor(frappe: Frappe, Demux?: DatabaseDemuxConstructor) {
|
||||||
super();
|
super();
|
||||||
this.#frappe = frappe;
|
this.#frappe = frappe;
|
||||||
|
this.converter = new Converter(this);
|
||||||
|
|
||||||
if (Demux !== undefined) {
|
if (Demux !== undefined) {
|
||||||
this.#demux = new Demux(frappe.isElectron);
|
this.#demux = new Demux(frappe.isElectron);
|
||||||
@ -42,13 +46,18 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
|
|
||||||
async init() {
|
async init() {
|
||||||
this.schemaMap = (await this.#demux.getSchemaMap()) as Readonly<SchemaMap>;
|
this.schemaMap = (await this.#demux.getSchemaMap()) as Readonly<SchemaMap>;
|
||||||
|
|
||||||
|
for (const schemaName in this.schemaMap) {
|
||||||
|
const fields = this.schemaMap[schemaName]!.fields!;
|
||||||
|
this.fieldValueMap[schemaName] = getMapFromList(fields, 'fieldname');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async insert(
|
async insert(
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
docValueMap: DocValueMap
|
docValueMap: DocValueMap
|
||||||
): Promise<DocValueMap> {
|
): Promise<DocValueMap> {
|
||||||
let rawValueMap = this.#toRawValueMap(
|
let rawValueMap = this.converter.toRawValueMap(
|
||||||
schemaName,
|
schemaName,
|
||||||
docValueMap
|
docValueMap
|
||||||
) as RawValueMap;
|
) as RawValueMap;
|
||||||
@ -57,7 +66,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
schemaName,
|
schemaName,
|
||||||
rawValueMap
|
rawValueMap
|
||||||
)) as RawValueMap;
|
)) as RawValueMap;
|
||||||
return this.#toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read
|
// Read
|
||||||
@ -72,7 +81,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
name,
|
name,
|
||||||
fields
|
fields
|
||||||
)) as RawValueMap;
|
)) as RawValueMap;
|
||||||
return this.#toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
return this.converter.toDocValueMap(schemaName, rawValueMap) as DocValueMap;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(
|
async getAll(
|
||||||
@ -80,7 +89,10 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
options: GetAllOptions = {}
|
options: GetAllOptions = {}
|
||||||
): Promise<DocValueMap[]> {
|
): Promise<DocValueMap[]> {
|
||||||
const rawValueMap = await this.#getAll(schemaName, options);
|
const rawValueMap = await this.#getAll(schemaName, options);
|
||||||
return this.#toDocValueMap(schemaName, rawValueMap) as DocValueMap[];
|
return this.converter.toDocValueMap(
|
||||||
|
schemaName,
|
||||||
|
rawValueMap
|
||||||
|
) as DocValueMap[];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllRaw(
|
async getAllRaw(
|
||||||
@ -90,25 +102,6 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
return await this.#getAll(schemaName, options);
|
return await this.#getAll(schemaName, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async count(
|
|
||||||
schemaName: string,
|
|
||||||
options: GetAllOptions = {}
|
|
||||||
): Promise<number> {
|
|
||||||
const rawValueMap = await this.#getAll(schemaName, options);
|
|
||||||
return rawValueMap.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
async #getAll(
|
|
||||||
schemaName: string,
|
|
||||||
options: GetAllOptions = {}
|
|
||||||
): Promise<RawValueMap[]> {
|
|
||||||
return (await this.#demux.call(
|
|
||||||
'getAll',
|
|
||||||
schemaName,
|
|
||||||
options
|
|
||||||
)) as RawValueMap[];
|
|
||||||
}
|
|
||||||
|
|
||||||
async getSingleValues(
|
async getSingleValues(
|
||||||
...fieldnames: ({ fieldname: string; parent?: string } | string)[]
|
...fieldnames: ({ fieldname: string; parent?: string } | string)[]
|
||||||
): Promise<SingleValue<DocValue>> {
|
): Promise<SingleValue<DocValue>> {
|
||||||
@ -117,8 +110,27 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
...fieldnames
|
...fieldnames
|
||||||
)) as SingleValue<RawValue>;
|
)) as SingleValue<RawValue>;
|
||||||
|
|
||||||
// TODO: Complete this
|
const docSingleValue: SingleValue<DocValue> = [];
|
||||||
throw new Error('Not implemented');
|
for (const sv of rawSingleValue) {
|
||||||
|
const fieldtype = this.fieldValueMap[sv.parent][sv.fieldname].fieldtype;
|
||||||
|
const value = Converter.toDocValue(sv.value, fieldtype);
|
||||||
|
|
||||||
|
docSingleValue.push({
|
||||||
|
value,
|
||||||
|
parent: sv.parent,
|
||||||
|
fieldname: sv.fieldname,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return docSingleValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
async count(
|
||||||
|
schemaName: string,
|
||||||
|
options: GetAllOptions = {}
|
||||||
|
): Promise<number> {
|
||||||
|
const rawValueMap = await this.#getAll(schemaName, options);
|
||||||
|
return rawValueMap.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update
|
// Update
|
||||||
@ -131,7 +143,7 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async update(schemaName: string, docValueMap: DocValueMap): Promise<void> {
|
async update(schemaName: string, docValueMap: DocValueMap): Promise<void> {
|
||||||
const rawValueMap = this.#toRawValueMap(schemaName, docValueMap);
|
const rawValueMap = this.converter.toRawValueMap(schemaName, docValueMap);
|
||||||
await this.#demux.call('update', schemaName, rawValueMap);
|
await this.#demux.call('update', schemaName, rawValueMap);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -153,7 +165,8 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
* Bespoke function
|
* Bespoke function
|
||||||
*
|
*
|
||||||
* These are functions to run custom queries that are too complex for
|
* These are functions to run custom queries that are too complex for
|
||||||
* DatabaseCore and require use of knex or raw queries.
|
* 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
|
* The query logic for these is in backend/database/bespoke.ts
|
||||||
*/
|
*/
|
||||||
@ -186,66 +199,17 @@ export class DatabaseHandler extends DatabaseBase {
|
|||||||
)) as Cashflow;
|
)) as Cashflow;
|
||||||
}
|
}
|
||||||
|
|
||||||
#toDocValueMap(
|
/**
|
||||||
|
* Internal methods
|
||||||
|
*/
|
||||||
|
async #getAll(
|
||||||
schemaName: string,
|
schemaName: string,
|
||||||
rawValueMap: RawValueMap | RawValueMap[]
|
options: GetAllOptions = {}
|
||||||
): DocValueMap | DocValueMap[] {
|
): Promise<RawValueMap[]> {
|
||||||
// TODO: Complete this
|
return (await this.#demux.call(
|
||||||
throw new Error('Not implemented');
|
'getAll',
|
||||||
}
|
schemaName,
|
||||||
#toRawValueMap(
|
options
|
||||||
schemaName: string,
|
)) as RawValueMap[];
|
||||||
docValueMap: DocValueMap | DocValueMap[]
|
|
||||||
): RawValueMap | RawValueMap[] {
|
|
||||||
// TODO: Complete this
|
|
||||||
throw new Error('Not implemented');
|
|
||||||
}
|
|
||||||
|
|
||||||
#toDocValue(value: RawValue, fieldtype: FieldType): DocValue {
|
|
||||||
switch (fieldtype) {
|
|
||||||
case FieldTypeEnum.Currency:
|
|
||||||
return this.#frappe.pesa((value ?? 0) as string | number);
|
|
||||||
case FieldTypeEnum.Date:
|
|
||||||
return new Date(value as string);
|
|
||||||
case FieldTypeEnum.Datetime:
|
|
||||||
return new Date(value as string);
|
|
||||||
case FieldTypeEnum.Int:
|
|
||||||
return +(value as string | number);
|
|
||||||
case FieldTypeEnum.Float:
|
|
||||||
return +(value as string | number);
|
|
||||||
case FieldTypeEnum.Check:
|
|
||||||
return Boolean(value as number);
|
|
||||||
default:
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#toRawValue(value: DocValue, fieldtype: FieldType): RawValue {
|
|
||||||
switch (fieldtype) {
|
|
||||||
case FieldTypeEnum.Currency:
|
|
||||||
return (value as Money).store;
|
|
||||||
case FieldTypeEnum.Date:
|
|
||||||
return (value as Date).toISOString().split('T')[0];
|
|
||||||
case FieldTypeEnum.Datetime:
|
|
||||||
return (value as Date).toISOString();
|
|
||||||
case FieldTypeEnum.Int: {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return parseInt(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Math.floor(value as number);
|
|
||||||
}
|
|
||||||
case FieldTypeEnum.Float: {
|
|
||||||
if (typeof value === 'string') {
|
|
||||||
return parseFloat(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
return value as number;
|
|
||||||
}
|
|
||||||
case FieldTypeEnum.Check:
|
|
||||||
return Number(value);
|
|
||||||
default:
|
|
||||||
return String(value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
* backend process and the the frontend db class (dbHandler.ts).
|
* backend process and the the frontend db class (dbHandler.ts).
|
||||||
*
|
*
|
||||||
* DatabaseBase is an abstract class so that the function signatures
|
* DatabaseBase is an abstract class so that the function signatures
|
||||||
* match on both ends.
|
* match on both ends i.e. DatabaseCore and DatabaseHandler.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { SchemaMap } from 'schemas/types';
|
import { SchemaMap } from 'schemas/types';
|
||||||
|
Loading…
Reference in New Issue
Block a user