mirror of
https://github.com/frappe/books.git
synced 2025-02-08 23:18:31 +00:00
incr: update docHandler to not use meta
This commit is contained in:
parent
cdb039d308
commit
e0e74817be
@ -11,6 +11,7 @@ import {
|
|||||||
Field,
|
Field,
|
||||||
FieldTypeEnum,
|
FieldTypeEnum,
|
||||||
RawValue,
|
RawValue,
|
||||||
|
Schema,
|
||||||
SchemaMap,
|
SchemaMap,
|
||||||
TargetField,
|
TargetField,
|
||||||
} from '../../schemas/types';
|
} from '../../schemas/types';
|
||||||
@ -92,7 +93,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async migrate() {
|
async migrate() {
|
||||||
for (const schemaName in this.schemaMap) {
|
for (const schemaName in this.schemaMap) {
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
if (schema.isSingle) {
|
if (schema.isSingle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -109,7 +110,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async exists(schemaName: string, name?: string): Promise<boolean> {
|
async exists(schemaName: string, name?: string): Promise<boolean> {
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
if (schema.isSingle) {
|
if (schema.isSingle) {
|
||||||
return this.#singleExists(schemaName);
|
return this.#singleExists(schemaName);
|
||||||
}
|
}
|
||||||
@ -134,7 +135,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
fieldValueMap: FieldValueMap
|
fieldValueMap: FieldValueMap
|
||||||
): Promise<FieldValueMap> {
|
): Promise<FieldValueMap> {
|
||||||
// insert parent
|
// insert parent
|
||||||
if (this.schemaMap[schemaName].isSingle) {
|
if (this.schemaMap[schemaName]!.isSingle) {
|
||||||
await this.#updateSingleValues(schemaName, fieldValueMap);
|
await this.#updateSingleValues(schemaName, fieldValueMap);
|
||||||
} else {
|
} else {
|
||||||
await this.#insertOne(schemaName, fieldValueMap);
|
await this.#insertOne(schemaName, fieldValueMap);
|
||||||
@ -150,7 +151,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
name: string = '',
|
name: string = '',
|
||||||
fields?: string | string[]
|
fields?: string | string[]
|
||||||
): Promise<FieldValueMap> {
|
): Promise<FieldValueMap> {
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
if (!schema.isSingle && !name) {
|
if (!schema.isSingle && !name) {
|
||||||
throw new ValueError('name is mandatory');
|
throw new ValueError('name is mandatory');
|
||||||
}
|
}
|
||||||
@ -204,7 +205,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
schemaName: string,
|
schemaName: string,
|
||||||
options: GetAllOptions = {}
|
options: GetAllOptions = {}
|
||||||
): Promise<FieldValueMap[]> {
|
): Promise<FieldValueMap[]> {
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
const hasCreated = !!schema.fields.find((f) => f.fieldname === 'created');
|
const hasCreated = !!schema.fields.find((f) => f.fieldname === 'created');
|
||||||
const {
|
const {
|
||||||
fields = ['name', ...(schema.keywordFields ?? [])],
|
fields = ['name', ...(schema.keywordFields ?? [])],
|
||||||
@ -279,7 +280,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async update(schemaName: string, fieldValueMap: FieldValueMap) {
|
async update(schemaName: string, fieldValueMap: FieldValueMap) {
|
||||||
// update parent
|
// update parent
|
||||||
if (this.schemaMap[schemaName].isSingle) {
|
if (this.schemaMap[schemaName]!.isSingle) {
|
||||||
await this.#updateSingleValues(schemaName, fieldValueMap);
|
await this.#updateSingleValues(schemaName, fieldValueMap);
|
||||||
} else {
|
} else {
|
||||||
await this.#updateOne(schemaName, fieldValueMap);
|
await this.#updateOne(schemaName, fieldValueMap);
|
||||||
@ -290,7 +291,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async delete(schemaName: string, name: string) {
|
async delete(schemaName: string, name: string) {
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
if (schema.isSingle) {
|
if (schema.isSingle) {
|
||||||
await this.#deleteSingle(schemaName, name);
|
await this.#deleteSingle(schemaName, name);
|
||||||
return;
|
return;
|
||||||
@ -462,7 +463,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async #getColumnDiff(schemaName: string): Promise<ColumnDiff> {
|
async #getColumnDiff(schemaName: string): Promise<ColumnDiff> {
|
||||||
const tableColumns = await this.#getTableColumns(schemaName);
|
const tableColumns = await this.#getTableColumns(schemaName);
|
||||||
const validFields = this.schemaMap[schemaName].fields;
|
const validFields = this.schemaMap[schemaName]!.fields;
|
||||||
const diff: ColumnDiff = { added: [], removed: [] };
|
const diff: ColumnDiff = { added: [], removed: [] };
|
||||||
|
|
||||||
for (const field of validFields) {
|
for (const field of validFields) {
|
||||||
@ -485,7 +486,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
async #getNewForeignKeys(schemaName: string): Promise<Field[]> {
|
async #getNewForeignKeys(schemaName: string): Promise<Field[]> {
|
||||||
const foreignKeys = await this.#getForeignKeys(schemaName);
|
const foreignKeys = await this.#getForeignKeys(schemaName);
|
||||||
const newForeignKeys: Field[] = [];
|
const newForeignKeys: Field[] = [];
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
for (const field of schema.fields) {
|
for (const field of schema.fields) {
|
||||||
if (
|
if (
|
||||||
field.fieldtype === 'Link' &&
|
field.fieldtype === 'Link' &&
|
||||||
@ -534,7 +535,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
(field as TargetField).target
|
(field as TargetField).target
|
||||||
) {
|
) {
|
||||||
const targetSchemaName = (field as TargetField).target as string;
|
const targetSchemaName = (field as TargetField).target as string;
|
||||||
const schema = this.schemaMap[targetSchemaName];
|
const schema = this.schemaMap[targetSchemaName] as Schema;
|
||||||
table
|
table
|
||||||
.foreign(field.fieldname)
|
.foreign(field.fieldname)
|
||||||
.references('name')
|
.references('name')
|
||||||
@ -568,7 +569,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async #createTable(schemaName: string, tableName?: string) {
|
async #createTable(schemaName: string, tableName?: string) {
|
||||||
tableName ??= schemaName;
|
tableName ??= schemaName;
|
||||||
const fields = this.schemaMap[schemaName].fields;
|
const fields = this.schemaMap[schemaName]!.fields;
|
||||||
return await this.#runCreateTableQuery(tableName, fields);
|
return await this.#runCreateTableQuery(tableName, fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -587,15 +588,15 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
.select('fieldname')
|
.select('fieldname')
|
||||||
).map(({ fieldname }) => fieldname);
|
).map(({ fieldname }) => fieldname);
|
||||||
|
|
||||||
return this.schemaMap[singleSchemaName].fields
|
return this.schemaMap[singleSchemaName]!.fields.map(
|
||||||
.map(({ fieldname, default: value }) => ({
|
({ fieldname, default: value }) => ({
|
||||||
fieldname,
|
fieldname,
|
||||||
value: value as RawValue | undefined,
|
value: value as RawValue | undefined,
|
||||||
}))
|
})
|
||||||
.filter(
|
).filter(
|
||||||
({ fieldname, value }) =>
|
({ fieldname, value }) =>
|
||||||
!existingFields.includes(fieldname) && value !== undefined
|
!existingFields.includes(fieldname) && value !== undefined
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async #deleteOne(schemaName: string, name: string) {
|
async #deleteOne(schemaName: string, name: string) {
|
||||||
@ -710,7 +711,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Non Table Fields
|
// Non Table Fields
|
||||||
const fields = this.schemaMap[schemaName].fields.filter(
|
const fields = this.schemaMap[schemaName]!.fields.filter(
|
||||||
(f) => f.fieldtype !== FieldTypeEnum.Table
|
(f) => f.fieldtype !== FieldTypeEnum.Table
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -726,7 +727,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
singleSchemaName: string,
|
singleSchemaName: string,
|
||||||
fieldValueMap: FieldValueMap
|
fieldValueMap: FieldValueMap
|
||||||
) {
|
) {
|
||||||
const fields = this.schemaMap[singleSchemaName].fields;
|
const fields = this.schemaMap[singleSchemaName]!.fields;
|
||||||
|
|
||||||
for (const field of fields) {
|
for (const field of fields) {
|
||||||
const value = fieldValueMap[field.fieldname] as RawValue | undefined;
|
const value = fieldValueMap[field.fieldname] as RawValue | undefined;
|
||||||
@ -779,7 +780,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
|
|
||||||
async #initializeSingles() {
|
async #initializeSingles() {
|
||||||
const singleSchemaNames = Object.keys(this.schemaMap).filter(
|
const singleSchemaNames = Object.keys(this.schemaMap).filter(
|
||||||
(n) => this.schemaMap[n].isSingle
|
(n) => this.schemaMap[n]!.isSingle
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const schemaName of singleSchemaNames) {
|
for (const schemaName of singleSchemaNames) {
|
||||||
@ -788,7 +789,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const fields = this.schemaMap[schemaName].fields;
|
const fields = this.schemaMap[schemaName]!.fields;
|
||||||
if (fields.every((f) => f.default === undefined)) {
|
if (fields.every((f) => f.default === undefined)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -815,7 +816,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
async #updateOne(schemaName: string, fieldValueMap: FieldValueMap) {
|
async #updateOne(schemaName: string, fieldValueMap: FieldValueMap) {
|
||||||
const updateMap = { ...fieldValueMap };
|
const updateMap = { ...fieldValueMap };
|
||||||
delete updateMap.name;
|
delete updateMap.name;
|
||||||
const schema = this.schemaMap[schemaName];
|
const schema = this.schemaMap[schemaName] as Schema;
|
||||||
for (const { fieldname, fieldtype } of schema.fields) {
|
for (const { fieldname, fieldtype } of schema.fields) {
|
||||||
if (fieldtype !== FieldTypeEnum.Table) {
|
if (fieldtype !== FieldTypeEnum.Table) {
|
||||||
continue;
|
continue;
|
||||||
@ -868,7 +869,7 @@ export default class DatabaseCore extends DatabaseBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#getTableFields(schemaName: string): TargetField[] {
|
#getTableFields(schemaName: string): TargetField[] {
|
||||||
return this.schemaMap[schemaName].fields.filter(
|
return this.schemaMap[schemaName]!.fields.filter(
|
||||||
(f) => f.fieldtype === FieldTypeEnum.Table
|
(f) => f.fieldtype === FieldTypeEnum.Table
|
||||||
) as TargetField[];
|
) as TargetField[];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as assert from 'assert';
|
import * as assert from 'assert';
|
||||||
import 'mocha';
|
import 'mocha';
|
||||||
import { FieldTypeEnum, RawValue } from 'schemas/types';
|
import { FieldTypeEnum, RawValue, Schema } from 'schemas/types';
|
||||||
import { getMapFromList, getValueMapFromList, sleep } from 'utils';
|
import { getMapFromList, getValueMapFromList, sleep } from 'utils';
|
||||||
import { getDefaultMetaFieldValueMap, sqliteTypeMap } from '../../helpers';
|
import { getDefaultMetaFieldValueMap, sqliteTypeMap } from '../../helpers';
|
||||||
import DatabaseCore from '../core';
|
import DatabaseCore from '../core';
|
||||||
@ -72,7 +72,7 @@ describe('DatabaseCore: Migrate and Check Db', function () {
|
|||||||
specify('Post Migrate TableInfo', async function () {
|
specify('Post Migrate TableInfo', async function () {
|
||||||
await db.migrate();
|
await db.migrate();
|
||||||
for (const schemaName in schemaMap) {
|
for (const schemaName in schemaMap) {
|
||||||
const schema = schemaMap[schemaName];
|
const schema = schemaMap[schemaName] as Schema;
|
||||||
const fieldMap = getMapFromList(schema.fields, 'fieldname');
|
const fieldMap = getMapFromList(schema.fields, 'fieldname');
|
||||||
const columns: SqliteTableInfo[] = await db.knex!.raw(
|
const columns: SqliteTableInfo[] = await db.knex!.raw(
|
||||||
'pragma table_info(??)',
|
'pragma table_info(??)',
|
||||||
@ -176,7 +176,7 @@ describe('DatabaseCore: CRUD', function () {
|
|||||||
'select * from SingleValue'
|
'select * from SingleValue'
|
||||||
);
|
);
|
||||||
const defaultMap = getValueMapFromList(
|
const defaultMap = getValueMapFromList(
|
||||||
schemaMap.SystemSettings.fields,
|
(schemaMap.SystemSettings as Schema).fields,
|
||||||
'fieldname',
|
'fieldname',
|
||||||
'default'
|
'default'
|
||||||
);
|
);
|
||||||
|
@ -1,25 +1,15 @@
|
|||||||
import { Field, Model } from '@/types/model';
|
|
||||||
import Doc from 'frappe/model/doc';
|
import Doc from 'frappe/model/doc';
|
||||||
import Meta from 'frappe/model/meta';
|
import { DocMap, ModelMap } from 'frappe/model/types';
|
||||||
import { getDuplicates, getRandomString } from 'frappe/utils';
|
import { getRandomString } from 'frappe/utils';
|
||||||
import Observable from 'frappe/utils/observable';
|
import Observable from 'frappe/utils/observable';
|
||||||
import { Frappe } from '..';
|
import { Frappe } from '..';
|
||||||
import { DocValue } from './types';
|
import { DocValue, DocValueMap } from './types';
|
||||||
|
|
||||||
type DocMap = Record<string, Doc | undefined>;
|
|
||||||
type MetaMap = Record<string, Meta | undefined>;
|
|
||||||
interface DocData {
|
|
||||||
doctype: string;
|
|
||||||
name?: string;
|
|
||||||
[key: string]: unknown;
|
|
||||||
}
|
|
||||||
|
|
||||||
export class DocHandler {
|
export class DocHandler {
|
||||||
frappe: Frappe;
|
frappe: Frappe;
|
||||||
singles: DocMap = {};
|
singles: DocMap = {};
|
||||||
metaCache: MetaMap = {};
|
docs: Observable<DocMap> = new Observable();
|
||||||
docs?: Observable<Doc>;
|
models: ModelMap = {};
|
||||||
models: Record<string, Model | undefined> = {};
|
|
||||||
|
|
||||||
constructor(frappe: Frappe) {
|
constructor(frappe: Frappe) {
|
||||||
this.frappe = frappe;
|
this.frappe = frappe;
|
||||||
@ -27,124 +17,80 @@ export class DocHandler {
|
|||||||
|
|
||||||
init() {
|
init() {
|
||||||
this.models = {};
|
this.models = {};
|
||||||
this.metaCache = {};
|
|
||||||
this.docs = new Observable();
|
this.docs = new Observable();
|
||||||
}
|
}
|
||||||
|
|
||||||
registerModels(models: Record<string, Model>) {
|
registerModels(models: ModelMap) {
|
||||||
for (const doctype in models) {
|
for (const schemaName in models) {
|
||||||
const metaDefinition = models[doctype];
|
this.models[schemaName] = models[schemaName];
|
||||||
if (!metaDefinition.name) {
|
|
||||||
throw new Error(`Name is mandatory for ${doctype}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (metaDefinition.name !== doctype) {
|
|
||||||
throw new Error(
|
|
||||||
`Model name mismatch for ${doctype}: ${metaDefinition.name}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const fieldnames = (metaDefinition.fields || [])
|
|
||||||
.map((df) => df.fieldname)
|
|
||||||
.sort();
|
|
||||||
|
|
||||||
const duplicateFieldnames = getDuplicates(fieldnames);
|
|
||||||
if (duplicateFieldnames.length > 0) {
|
|
||||||
throw new Error(
|
|
||||||
`Duplicate fields in ${doctype}: ${duplicateFieldnames.join(', ')}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.models[doctype] = metaDefinition;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getModels(filterFunction: (name: Model) => boolean): Model[] {
|
|
||||||
const models: Model[] = [];
|
|
||||||
for (const doctype in this.models) {
|
|
||||||
models.push(this.models[doctype]!);
|
|
||||||
}
|
|
||||||
return filterFunction ? models.filter(filterFunction) : models;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cache operations
|
* Cache operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
addToCache(doc: Doc) {
|
addToCache(doc: Doc) {
|
||||||
if (!this.docs) return;
|
if (!this.docs) {
|
||||||
|
|
||||||
// add to `docs` cache
|
|
||||||
const name = doc.name as string | undefined;
|
|
||||||
const doctype = doc.doctype as string | undefined;
|
|
||||||
|
|
||||||
if (!doctype || !name) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.docs[doctype]) {
|
// add to `docs` cache
|
||||||
this.docs[doctype] = {};
|
const name = doc.name;
|
||||||
|
const schemaName = doc.schemaName;
|
||||||
|
|
||||||
|
if (!name) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
(this.docs[doctype] as DocMap)[name] = doc;
|
if (!this.docs[schemaName]) {
|
||||||
|
this.docs[schemaName] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
(this.docs[schemaName] as DocMap)[name] = doc;
|
||||||
|
|
||||||
// singles available as first level objects too
|
// singles available as first level objects too
|
||||||
if (doctype === doc.name) {
|
if (schemaName === doc.name) {
|
||||||
this.singles[name] = doc;
|
this.singles[name] = doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
// propogate change to `docs`
|
// propagate change to `docs`
|
||||||
doc.on('change', (params: unknown) => {
|
doc.on('change', (params: unknown) => {
|
||||||
this.docs!.trigger('change', params);
|
this.docs!.trigger('change', params);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
removeFromCache(doctype: string, name: string) {
|
removeFromCache(schemaName: string, name: string) {
|
||||||
const docMap = this.docs?.[doctype] as DocMap | undefined;
|
const docMap = this.docs[schemaName] as DocMap | undefined;
|
||||||
const doc = docMap?.[name];
|
delete docMap?.[name];
|
||||||
|
}
|
||||||
|
|
||||||
if (doc) {
|
getFromCache(schemaName: string, name: string): Doc | undefined {
|
||||||
delete docMap[name];
|
const docMap = this.docs[schemaName] as DocMap | undefined;
|
||||||
} else {
|
return docMap?.[name];
|
||||||
console.warn(`Document ${doctype} ${name} does not exist`);
|
}
|
||||||
|
|
||||||
|
getCachedValue(
|
||||||
|
schemaName: string,
|
||||||
|
name: string,
|
||||||
|
fieldname: string
|
||||||
|
): DocValue | Doc[] | undefined {
|
||||||
|
const docMap = this.docs[schemaName] as DocMap;
|
||||||
|
const doc = docMap[name];
|
||||||
|
if (doc === undefined) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return doc.get(fieldname);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDocFromCache(schemaName: string, name: string): Doc | undefined {
|
isDirty(schemaName: string, name: string): boolean {
|
||||||
const doc = (this.docs?.[schemaName] as DocMap)?.[name];
|
const doc = (this.docs?.[schemaName] as DocMap)?.[name];
|
||||||
return doc;
|
|
||||||
}
|
|
||||||
|
|
||||||
isDirty(doctype: string, name: string) {
|
|
||||||
const doc = (this.docs?.[doctype] as DocMap)?.[name];
|
|
||||||
if (doc === undefined) {
|
if (doc === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return !!doc._dirty;
|
return doc.dirty;
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Meta Operations
|
|
||||||
*/
|
|
||||||
|
|
||||||
getMeta(doctype: string): Meta {
|
|
||||||
const meta = this.metaCache[doctype];
|
|
||||||
if (meta) {
|
|
||||||
return meta;
|
|
||||||
}
|
|
||||||
|
|
||||||
const model = this.models?.[doctype];
|
|
||||||
if (!model) {
|
|
||||||
throw new Error(`${doctype} is not a registered doctype`);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.metaCache[doctype] = new this.frappe.Meta!(model);
|
|
||||||
return this.metaCache[doctype]!;
|
|
||||||
}
|
|
||||||
|
|
||||||
createMeta(fields: Field[]) {
|
|
||||||
return new this.frappe.Meta!({ isCustom: 1, fields });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -152,73 +98,47 @@ export class DocHandler {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
async getDoc(
|
async getDoc(
|
||||||
doctype: string,
|
schemaName: string,
|
||||||
name: string,
|
name: string,
|
||||||
options = { skipDocumentCache: false }
|
options = { skipDocumentCache: false }
|
||||||
) {
|
) {
|
||||||
let doc = null;
|
let doc: Doc | undefined;
|
||||||
if (!options?.skipDocumentCache) {
|
if (!options?.skipDocumentCache) {
|
||||||
doc = this.getDocFromCache(doctype, name);
|
doc = this.getFromCache(schemaName, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doc) {
|
if (doc) {
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DocClass = this.getDocumentClass(doctype);
|
doc = this.getNewDoc(schemaName, { name });
|
||||||
doc = new DocClass({
|
|
||||||
doctype: doctype,
|
|
||||||
name: name,
|
|
||||||
});
|
|
||||||
|
|
||||||
await doc.load();
|
await doc.load();
|
||||||
this.addToCache(doc);
|
this.addToCache(doc);
|
||||||
|
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDocumentClass(doctype: string): typeof Doc {
|
getModel(schemaName: string): typeof Doc {
|
||||||
const meta = this.getMeta(doctype);
|
const Model = this.models[schemaName];
|
||||||
let documentClass = this.frappe.Document!;
|
if (Model === undefined) {
|
||||||
if (meta && meta.documentClass) {
|
return Doc;
|
||||||
documentClass = meta.documentClass as typeof Doc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return documentClass;
|
return Model;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSingle(doctype: string) {
|
async getSingle(schemaName: string) {
|
||||||
return await this.getDoc(doctype, doctype);
|
return await this.getDoc(schemaName, schemaName);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDuplicate(doc: Doc) {
|
async getDuplicate(doc: Doc): Promise<Doc> {
|
||||||
const doctype = doc.doctype as string;
|
const newDoc = await doc.duplicate(false);
|
||||||
const newDoc = await this.getEmptyDoc(doctype);
|
delete newDoc.name;
|
||||||
const meta = this.getMeta(doctype);
|
|
||||||
|
|
||||||
const fields = meta.getValidFields() as Field[];
|
|
||||||
|
|
||||||
for (const field of fields) {
|
|
||||||
if (['name', 'submitted'].includes(field.fieldname)) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
newDoc[field.fieldname] = doc[field.fieldname];
|
|
||||||
if (field.fieldtype === 'Table') {
|
|
||||||
const value = (doc[field.fieldname] as DocData[]) || [];
|
|
||||||
newDoc[field.fieldname] = value.map((d) => {
|
|
||||||
const childData = Object.assign({}, d);
|
|
||||||
childData.name = '';
|
|
||||||
return childData;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return newDoc;
|
return newDoc;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEmptyDoc(doctype: string, cacheDoc: boolean = true): Doc {
|
getEmptyDoc(schemaName: string, cacheDoc: boolean = true): Doc {
|
||||||
const doc = this.getNewDoc({ doctype });
|
const doc = this.getNewDoc(schemaName);
|
||||||
doc._notInserted = true;
|
|
||||||
doc.name = getRandomString();
|
doc.name = getRandomString();
|
||||||
|
|
||||||
if (cacheDoc) {
|
if (cacheDoc) {
|
||||||
@ -228,34 +148,33 @@ export class DocHandler {
|
|||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
getNewDoc(data: DocData): Doc {
|
getNewDoc(schemaName: string, data: DocValueMap = {}): Doc {
|
||||||
const DocClass = this.getDocumentClass(data.doctype);
|
const Model = this.getModel(schemaName);
|
||||||
const doc = new DocClass(data);
|
const schema = this.frappe.schemaMap[schemaName];
|
||||||
|
if (schema === undefined) {
|
||||||
|
throw new Error(`Schema not found for ${schemaName}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = new Model(schema, data);
|
||||||
doc.setDefaults();
|
doc.setDefaults();
|
||||||
return doc;
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
async syncDoc(data: DocData) {
|
async syncDoc(schemaName: string, data: DocValueMap) {
|
||||||
let doc;
|
const name = data.name as string | undefined;
|
||||||
const { doctype, name } = data;
|
if (name === undefined) {
|
||||||
if (!doctype || !name) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const docExists = await this.frappe.db.exists(doctype, name);
|
const docExists = await this.frappe.db.exists(schemaName, name);
|
||||||
if (docExists) {
|
if (!docExists) {
|
||||||
doc = await this.getDoc(doctype, name);
|
const doc = this.getNewDoc(schemaName, data);
|
||||||
Object.assign(doc, data);
|
await doc.insert;
|
||||||
await doc.update();
|
return;
|
||||||
} else {
|
|
||||||
doc = this.getNewDoc(data);
|
|
||||||
await doc.insert();
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
getCachedValue(
|
const doc = await this.getDoc(schemaName, name);
|
||||||
schemaName: string,
|
await doc.setMultiple(data);
|
||||||
name: string,
|
await doc.update();
|
||||||
fieldname: string
|
}
|
||||||
): DocValue {}
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import { ErrorLog } from '@/errorHandling';
|
import { ErrorLog } from '@/errorHandling';
|
||||||
import Doc from 'frappe/model/doc';
|
|
||||||
import { getMoneyMaker, MoneyMaker } from 'pesa';
|
import { getMoneyMaker, MoneyMaker } from 'pesa';
|
||||||
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 { ModelMap } from './model/types';
|
||||||
|
import coreModels from './models';
|
||||||
import {
|
import {
|
||||||
DEFAULT_DISPLAY_PRECISION,
|
DEFAULT_DISPLAY_PRECISION,
|
||||||
DEFAULT_INTERNAL_PRECISION,
|
DEFAULT_INTERNAL_PRECISION,
|
||||||
@ -13,6 +14,14 @@ import * as errors from './utils/errors';
|
|||||||
import { format } from './utils/format';
|
import { format } from './utils/format';
|
||||||
import { t, T } from './utils/translation';
|
import { t, T } from './utils/translation';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Terminology
|
||||||
|
* - Schema: object that defines shape of the data in the database
|
||||||
|
* - Model: the controller class that extends the Doc class or the Doc
|
||||||
|
* class itself.
|
||||||
|
* - Doc: instance of a Model, i.e. what has the data.
|
||||||
|
*/
|
||||||
|
|
||||||
export class Frappe {
|
export class Frappe {
|
||||||
t = t;
|
t = t;
|
||||||
T = T;
|
T = T;
|
||||||
@ -28,8 +37,6 @@ export class Frappe {
|
|||||||
doc: DocHandler;
|
doc: DocHandler;
|
||||||
db: DatabaseHandler;
|
db: DatabaseHandler;
|
||||||
|
|
||||||
Doc?: typeof Doc;
|
|
||||||
|
|
||||||
_initialized: boolean = false;
|
_initialized: boolean = false;
|
||||||
|
|
||||||
errorLog?: ErrorLog[];
|
errorLog?: ErrorLog[];
|
||||||
@ -38,8 +45,9 @@ export class Frappe {
|
|||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.auth = new AuthHandler(this);
|
this.auth = new AuthHandler(this);
|
||||||
this.doc = new DocHandler(this);
|
|
||||||
this.db = new DatabaseHandler(this);
|
this.db = new DatabaseHandler(this);
|
||||||
|
this.doc = new DocHandler(this);
|
||||||
|
|
||||||
this.pesa = getMoneyMaker({
|
this.pesa = getMoneyMaker({
|
||||||
currency: 'XXX',
|
currency: 'XXX',
|
||||||
precision: DEFAULT_INTERNAL_PRECISION,
|
precision: DEFAULT_INTERNAL_PRECISION,
|
||||||
@ -60,13 +68,17 @@ export class Frappe {
|
|||||||
return this.doc.models;
|
return this.doc.models;
|
||||||
}
|
}
|
||||||
|
|
||||||
async initializeAndRegister(customModels = {}, force = false) {
|
get schemaMap() {
|
||||||
|
return this.db.schemaMap;
|
||||||
|
}
|
||||||
|
|
||||||
|
async initializeAndRegister(
|
||||||
|
customModels: ModelMap = {},
|
||||||
|
force: boolean = false
|
||||||
|
) {
|
||||||
await this.init(force);
|
await this.init(force);
|
||||||
|
|
||||||
this.Doc = (await import('frappe/model/doc')).default;
|
this.doc.registerModels(coreModels as ModelMap);
|
||||||
|
|
||||||
const coreModels = await import('frappe/models');
|
|
||||||
this.doc.registerModels(coreModels.default as Record<string, Model>);
|
|
||||||
this.doc.registerModels(customModels);
|
this.doc.registerModels(customModels);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,6 +78,10 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
) as TargetField[];
|
) as TargetField[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get dirty() {
|
||||||
|
return this._dirty;
|
||||||
|
}
|
||||||
|
|
||||||
_setInitialValues(data: DocValueMap) {
|
_setInitialValues(data: DocValueMap) {
|
||||||
for (const fieldname in data) {
|
for (const fieldname in data) {
|
||||||
const value = data[fieldname];
|
const value = data[fieldname];
|
||||||
@ -222,7 +226,7 @@ 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];
|
const schema = frappe.db.schemaMap[childSchemaName.target] as Schema;
|
||||||
const childDoc = new Doc(schema, data as DocValueMap);
|
const childDoc = new Doc(schema, data as DocValueMap);
|
||||||
childDoc.setDefaults();
|
childDoc.setDefaults();
|
||||||
return childDoc;
|
return childDoc;
|
||||||
@ -417,13 +421,13 @@ 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.doctype} ${this.name} has been modified after loading`
|
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.doctype} is not submittable`
|
frappe.t`Document type ${this.schemaName} is not submittable`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -651,7 +655,7 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
return frappe.doc.getCachedValue(schemaName, name, fieldname);
|
return frappe.doc.getCachedValue(schemaName, name, fieldname);
|
||||||
}
|
}
|
||||||
|
|
||||||
async duplicate() {
|
async duplicate(shouldInsert: boolean = true): Promise<Doc> {
|
||||||
const updateMap: DocValueMap = {};
|
const updateMap: DocValueMap = {};
|
||||||
const docValueMap = this.getValidDict();
|
const docValueMap = this.getValidDict();
|
||||||
const fieldnames = this.schema.fields.map((f) => f.fieldname);
|
const fieldnames = this.schema.fields.map((f) => f.fieldname);
|
||||||
@ -680,7 +684,12 @@ export default class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
|
|
||||||
const doc = frappe.doc.getEmptyDoc(this.schemaName, false);
|
const doc = frappe.doc.getEmptyDoc(this.schemaName, false);
|
||||||
await doc.setMultiple(updateMap);
|
await doc.setMultiple(updateMap);
|
||||||
await doc.insert();
|
|
||||||
|
if (shouldInsert) {
|
||||||
|
await doc.insert();
|
||||||
|
}
|
||||||
|
|
||||||
|
return doc;
|
||||||
}
|
}
|
||||||
|
|
||||||
formulas: FormulaMap = {};
|
formulas: FormulaMap = {};
|
||||||
|
@ -27,5 +27,7 @@ export type DependsOnMap = Record<string, string[]>
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Should add this for hidden too
|
* Should add this for hidden too
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
export type ModelMap = Record< string, typeof Doc | undefined>
|
||||||
|
export type DocMap = Record<string, Doc | undefined>;
|
@ -31,7 +31,7 @@ function deepFreeze(schemaMap: SchemaMap) {
|
|||||||
Object.freeze(schemaMap[schemaName][key]);
|
Object.freeze(schemaMap[schemaName][key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const field of schemaMap[schemaName].fields ?? []) {
|
for (const field of schemaMap[schemaName]?.fields ?? []) {
|
||||||
Object.freeze(field);
|
Object.freeze(field);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -47,7 +47,7 @@ export function addMetaFields(schemaMap: SchemaMap): SchemaMap {
|
|||||||
const submittableTree = getCombined(tree, metaSchemaMap.submittable);
|
const submittableTree = getCombined(tree, metaSchemaMap.submittable);
|
||||||
|
|
||||||
for (const name in schemaMap) {
|
for (const name in schemaMap) {
|
||||||
const schema = schemaMap[name];
|
const schema = schemaMap[name] as Schema;
|
||||||
if (schema.isSingle) {
|
if (schema.isSingle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -71,7 +71,7 @@ export function addMetaFields(schemaMap: SchemaMap): SchemaMap {
|
|||||||
|
|
||||||
function addNameField(schemaMap: SchemaMap) {
|
function addNameField(schemaMap: SchemaMap) {
|
||||||
for (const name in schemaMap) {
|
for (const name in schemaMap) {
|
||||||
const schema = schemaMap[name];
|
const schema = schemaMap[name] as Schema;
|
||||||
if (schema.isSingle) {
|
if (schema.isSingle) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -104,7 +104,7 @@ function getAppSchemas(countryCode: string): SchemaMap {
|
|||||||
|
|
||||||
export function cleanSchemas(schemaMap: SchemaMap): SchemaMap {
|
export function cleanSchemas(schemaMap: SchemaMap): SchemaMap {
|
||||||
for (const name in schemaMap) {
|
for (const name in schemaMap) {
|
||||||
const schema = schemaMap[name];
|
const schema = schemaMap[name] as Schema;
|
||||||
if (schema.isAbstract && !schema.extends) {
|
if (schema.isAbstract && !schema.extends) {
|
||||||
delete schemaMap[name];
|
delete schemaMap[name];
|
||||||
continue;
|
continue;
|
||||||
|
@ -86,7 +86,7 @@ describe('Schema Builder', function () {
|
|||||||
);
|
);
|
||||||
describe('Abstract Combined Schemas', function () {
|
describe('Abstract Combined Schemas', function () {
|
||||||
specify('Meta Properties', function () {
|
specify('Meta Properties', function () {
|
||||||
assert.strictEqual(abstractCombined.Customer.extends, undefined);
|
assert.strictEqual(abstractCombined.Customer!.extends, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
specify('Abstract Schema Existance', function () {
|
specify('Abstract Schema Existance', function () {
|
||||||
@ -94,11 +94,11 @@ describe('Schema Builder', function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
specify('Field Counts', function () {
|
specify('Field Counts', function () {
|
||||||
assert.strictEqual(abstractCombined.Customer.fields?.length, 10);
|
assert.strictEqual(abstractCombined.Customer!.fields?.length, 10);
|
||||||
});
|
});
|
||||||
|
|
||||||
specify('Quick Edit Field Counts', function () {
|
specify('Quick Edit Field Counts', function () {
|
||||||
assert.strictEqual(abstractCombined.Customer.quickEditFields?.length, 7);
|
assert.strictEqual(abstractCombined.Customer!.quickEditFields?.length, 7);
|
||||||
});
|
});
|
||||||
|
|
||||||
specify('Schema Equality with App Schemas', function () {
|
specify('Schema Equality with App Schemas', function () {
|
||||||
@ -127,7 +127,7 @@ describe('Schema Builder', function () {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
everyFieldExists(
|
everyFieldExists(
|
||||||
regionalSchemaMap.Party.quickEditFields ?? [],
|
regionalSchemaMap.Party.quickEditFields ?? [],
|
||||||
abstractCombined.Customer
|
abstractCombined.Customer!
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -152,14 +152,14 @@ describe('Schema Builder', function () {
|
|||||||
describe('Final Schemas', function () {
|
describe('Final Schemas', function () {
|
||||||
specify('Schema Field Existance', function () {
|
specify('Schema Field Existance', function () {
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
everyFieldExists(baseFieldNames, finalSchemas.Customer),
|
everyFieldExists(baseFieldNames, finalSchemas.Customer!),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
someFieldExists(
|
someFieldExists(
|
||||||
subtract(allFieldNames, baseFieldNames),
|
subtract(allFieldNames, baseFieldNames),
|
||||||
finalSchemas.Customer
|
finalSchemas.Customer!
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@ -167,7 +167,7 @@ describe('Schema Builder', function () {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
everyFieldExists(
|
everyFieldExists(
|
||||||
[...baseFieldNames, ...submittableFieldNames],
|
[...baseFieldNames, ...submittableFieldNames],
|
||||||
finalSchemas.JournalEntry
|
finalSchemas.JournalEntry!
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -175,20 +175,20 @@ describe('Schema Builder', function () {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
someFieldExists(
|
someFieldExists(
|
||||||
subtract(allFieldNames, baseFieldNames, submittableFieldNames),
|
subtract(allFieldNames, baseFieldNames, submittableFieldNames),
|
||||||
finalSchemas.JournalEntry
|
finalSchemas.JournalEntry!
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
everyFieldExists(childFieldNames, finalSchemas.JournalEntryAccount),
|
everyFieldExists(childFieldNames, finalSchemas.JournalEntryAccount!),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
|
|
||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
someFieldExists(
|
someFieldExists(
|
||||||
subtract(allFieldNames, childFieldNames),
|
subtract(allFieldNames, childFieldNames),
|
||||||
finalSchemas.JournalEntryAccount
|
finalSchemas.JournalEntryAccount!
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
@ -196,7 +196,7 @@ describe('Schema Builder', function () {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
everyFieldExists(
|
everyFieldExists(
|
||||||
[...treeFieldNames, ...baseFieldNames],
|
[...treeFieldNames, ...baseFieldNames],
|
||||||
finalSchemas.Account
|
finalSchemas.Account!
|
||||||
),
|
),
|
||||||
true
|
true
|
||||||
);
|
);
|
||||||
@ -204,7 +204,7 @@ describe('Schema Builder', function () {
|
|||||||
assert.strictEqual(
|
assert.strictEqual(
|
||||||
someFieldExists(
|
someFieldExists(
|
||||||
subtract(allFieldNames, treeFieldNames, baseFieldNames),
|
subtract(allFieldNames, treeFieldNames, baseFieldNames),
|
||||||
finalSchemas.Account
|
finalSchemas.Account!
|
||||||
),
|
),
|
||||||
false
|
false
|
||||||
);
|
);
|
||||||
|
@ -128,5 +128,5 @@ export interface Schema {
|
|||||||
export interface SchemaStub extends Partial<Schema> {
|
export interface SchemaStub extends Partial<Schema> {
|
||||||
name: string;
|
name: string;
|
||||||
}
|
}
|
||||||
export type SchemaMap = Record<string, Schema>;
|
export type SchemaMap = Record<string, Schema | undefined>;
|
||||||
export type SchemaStubMap = Record<string, SchemaStub>;
|
export type SchemaStubMap = Record<string, SchemaStub>;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user