2
0
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:
18alantom 2022-04-07 11:50:14 +05:30
parent cdb039d308
commit e0e74817be
9 changed files with 163 additions and 220 deletions

View File

@ -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[];
} }

View File

@ -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'
); );

View File

@ -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 {}
} }

View File

@ -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);
} }

View File

@ -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 = {};

View File

@ -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>;

View File

@ -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;

View File

@ -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
); );

View File

@ -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>;