2022-04-24 06:48:44 +00:00
|
|
|
import { Doc } from 'fyo/model/doc';
|
2022-04-19 05:59:36 +00:00
|
|
|
import { DocMap, ModelMap, SinglesMap } from 'fyo/model/types';
|
|
|
|
import { coreModels } from 'fyo/models';
|
2022-05-26 10:11:15 +00:00
|
|
|
import { NotFoundError, ValueError } from 'fyo/utils/errors';
|
2022-04-19 05:59:36 +00:00
|
|
|
import Observable from 'fyo/utils/observable';
|
2022-04-22 11:02:03 +00:00
|
|
|
import { Schema } from 'schemas/types';
|
2022-04-21 13:08:36 +00:00
|
|
|
import { getRandomString } from 'utils';
|
2022-04-19 05:59:36 +00:00
|
|
|
import { Fyo } from '..';
|
2022-10-13 11:40:28 +00:00
|
|
|
import { DocValueMap, RawValueMap } from './types';
|
2022-03-22 09:28:36 +00:00
|
|
|
|
|
|
|
export class DocHandler {
|
2022-04-19 05:59:36 +00:00
|
|
|
fyo: Fyo;
|
2022-04-27 18:39:34 +00:00
|
|
|
models: ModelMap = {};
|
2022-04-18 08:01:41 +00:00
|
|
|
singles: SinglesMap = {};
|
2022-05-03 17:48:50 +00:00
|
|
|
docs: Observable<DocMap | undefined> = new Observable();
|
2022-04-27 18:39:34 +00:00
|
|
|
observer: Observable<never> = new Observable();
|
2023-02-20 05:52:48 +00:00
|
|
|
#temporaryNameCounters: Record<string, number>;
|
2022-03-22 09:28:36 +00:00
|
|
|
|
2022-04-19 05:59:36 +00:00
|
|
|
constructor(fyo: Fyo) {
|
|
|
|
this.fyo = fyo;
|
2023-02-20 05:52:48 +00:00
|
|
|
this.#temporaryNameCounters = {};
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
init() {
|
|
|
|
this.models = {};
|
2022-04-22 11:02:03 +00:00
|
|
|
this.singles = {};
|
2022-03-22 09:28:36 +00:00
|
|
|
this.docs = new Observable();
|
2022-04-27 18:39:34 +00:00
|
|
|
this.observer = new Observable();
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|
|
|
|
|
2023-06-21 10:38:39 +00:00
|
|
|
purgeCache() {
|
2022-04-22 11:02:03 +00:00
|
|
|
this.init();
|
|
|
|
}
|
|
|
|
|
2022-04-18 06:42:56 +00:00
|
|
|
registerModels(models: ModelMap, regionalModels: ModelMap = {}) {
|
2022-04-19 05:59:36 +00:00
|
|
|
for (const schemaName in this.fyo.db.schemaMap) {
|
2022-04-18 06:42:56 +00:00
|
|
|
if (coreModels[schemaName] !== undefined) {
|
|
|
|
this.models[schemaName] = coreModels[schemaName];
|
|
|
|
} else if (regionalModels[schemaName] !== undefined) {
|
|
|
|
this.models[schemaName] = regionalModels[schemaName];
|
|
|
|
} else if (models[schemaName] !== undefined) {
|
|
|
|
this.models[schemaName] = models[schemaName];
|
|
|
|
} else {
|
|
|
|
this.models[schemaName] = Doc;
|
|
|
|
}
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Doc Operations
|
|
|
|
*/
|
|
|
|
|
|
|
|
async getDoc(
|
2022-04-07 06:20:14 +00:00
|
|
|
schemaName: string,
|
2022-05-26 10:11:15 +00:00
|
|
|
name?: string,
|
2022-03-22 09:28:36 +00:00
|
|
|
options = { skipDocumentCache: false }
|
|
|
|
) {
|
2022-05-26 10:11:15 +00:00
|
|
|
if (name === undefined) {
|
|
|
|
name = schemaName;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (name === schemaName && !this.fyo.schemaMap[schemaName]?.isSingle) {
|
|
|
|
throw new ValueError(`${schemaName} is not a Single Schema`);
|
|
|
|
}
|
|
|
|
|
2022-04-07 06:20:14 +00:00
|
|
|
let doc: Doc | undefined;
|
2022-03-22 09:28:36 +00:00
|
|
|
if (!options?.skipDocumentCache) {
|
2022-05-03 17:48:50 +00:00
|
|
|
doc = this.#getFromCache(schemaName, name);
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (doc) {
|
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
2022-10-07 18:41:50 +00:00
|
|
|
doc = this.getNewDoc(schemaName, { name }, false);
|
2022-03-22 09:28:36 +00:00
|
|
|
await doc.load();
|
2023-04-17 04:29:12 +00:00
|
|
|
this.#addToCache(doc);
|
2022-03-22 09:28:36 +00:00
|
|
|
|
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
2022-04-22 11:02:03 +00:00
|
|
|
getNewDoc(
|
|
|
|
schemaName: string,
|
2022-10-13 11:40:28 +00:00
|
|
|
data: DocValueMap | RawValueMap = {},
|
2023-06-21 10:38:39 +00:00
|
|
|
cacheDoc = true,
|
2022-04-22 11:02:03 +00:00
|
|
|
schema?: Schema,
|
2022-10-13 11:40:28 +00:00
|
|
|
Model?: typeof Doc,
|
2023-06-21 10:38:39 +00:00
|
|
|
isRawValueMap = true
|
2022-04-22 11:02:03 +00:00
|
|
|
): Doc {
|
2022-04-22 12:01:04 +00:00
|
|
|
if (!this.models[schemaName] && Model) {
|
|
|
|
this.models[schemaName] = Model;
|
|
|
|
}
|
|
|
|
|
|
|
|
Model ??= this.models[schemaName];
|
2022-04-22 11:02:03 +00:00
|
|
|
schema ??= this.fyo.schemaMap[schemaName];
|
2022-04-24 06:48:44 +00:00
|
|
|
|
2022-04-07 06:20:14 +00:00
|
|
|
if (schema === undefined) {
|
2022-05-04 08:19:40 +00:00
|
|
|
throw new NotFoundError(`Schema not found for ${schemaName}`);
|
2022-04-07 06:20:14 +00:00
|
|
|
}
|
|
|
|
|
2022-10-13 11:40:28 +00:00
|
|
|
const doc = new Model!(schema, data, this.fyo, isRawValueMap);
|
2023-02-21 06:21:32 +00:00
|
|
|
doc.name ??= this.getTemporaryName(schema);
|
2022-04-24 06:48:44 +00:00
|
|
|
if (cacheDoc) {
|
2023-04-17 04:29:12 +00:00
|
|
|
this.#addToCache(doc);
|
2022-04-24 06:48:44 +00:00
|
|
|
}
|
|
|
|
|
2022-03-22 09:28:36 +00:00
|
|
|
return doc;
|
|
|
|
}
|
|
|
|
|
2023-03-20 07:46:53 +00:00
|
|
|
isTemporaryName(name: string, schema: Schema): boolean {
|
|
|
|
const label = schema.label ?? schema.name;
|
|
|
|
const template = this.fyo.t`New ${label} `;
|
|
|
|
return name.includes(template);
|
|
|
|
}
|
|
|
|
|
2023-02-21 06:21:32 +00:00
|
|
|
getTemporaryName(schema: Schema): string {
|
2023-02-20 05:52:48 +00:00
|
|
|
if (schema.naming === 'random') {
|
|
|
|
return getRandomString();
|
|
|
|
}
|
|
|
|
|
|
|
|
this.#temporaryNameCounters[schema.name] ??= 1;
|
|
|
|
|
|
|
|
const idx = this.#temporaryNameCounters[schema.name];
|
|
|
|
this.#temporaryNameCounters[schema.name] = idx + 1;
|
2023-03-20 07:46:53 +00:00
|
|
|
const label = schema.label ?? schema.name;
|
2023-02-20 05:52:48 +00:00
|
|
|
|
2023-03-20 07:46:53 +00:00
|
|
|
return this.fyo.t`New ${label} ${String(idx).padStart(2, '0')}`;
|
2023-02-20 05:52:48 +00:00
|
|
|
}
|
|
|
|
|
2022-05-03 17:48:50 +00:00
|
|
|
/**
|
|
|
|
* Cache operations
|
|
|
|
*/
|
|
|
|
|
2023-04-17 04:29:12 +00:00
|
|
|
#addToCache(doc: Doc) {
|
2022-05-03 17:48:50 +00:00
|
|
|
if (!doc.name) {
|
2022-03-22 09:28:36 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2022-05-03 17:48:50 +00:00
|
|
|
const name = doc.name;
|
|
|
|
const schemaName = doc.schemaName;
|
|
|
|
|
|
|
|
if (!this.docs[schemaName]) {
|
|
|
|
this.docs.set(schemaName, {});
|
2022-05-04 08:19:40 +00:00
|
|
|
this.#setCacheUpdationListeners(schemaName);
|
2022-05-03 17:48:50 +00:00
|
|
|
}
|
|
|
|
|
2023-04-17 04:29:12 +00:00
|
|
|
this.docs.get(schemaName)![name] = doc;
|
2022-05-03 17:48:50 +00:00
|
|
|
|
|
|
|
// singles available as first level objects too
|
|
|
|
if (schemaName === doc.name) {
|
|
|
|
this.singles[name] = doc;
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|
2022-04-01 09:35:51 +00:00
|
|
|
|
2022-05-03 17:48:50 +00:00
|
|
|
// propagate change to `docs`
|
|
|
|
doc.on('change', (params: unknown) => {
|
2023-06-22 08:52:54 +00:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-floating-promises
|
2023-06-21 10:38:39 +00:00
|
|
|
this.docs.trigger('change', params);
|
2022-05-03 17:48:50 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
doc.on('afterSync', () => {
|
2023-04-14 07:57:44 +00:00
|
|
|
if (doc.name === name && this.#cacheHas(schemaName, name)) {
|
2022-05-03 17:48:50 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-17 04:29:12 +00:00
|
|
|
this.removeFromCache(doc.schemaName, name);
|
|
|
|
this.#addToCache(doc);
|
2022-05-03 17:48:50 +00:00
|
|
|
});
|
2022-05-04 08:19:40 +00:00
|
|
|
}
|
2022-05-03 17:48:50 +00:00
|
|
|
|
2022-05-04 08:19:40 +00:00
|
|
|
#setCacheUpdationListeners(schemaName: string) {
|
2023-06-22 08:52:54 +00:00
|
|
|
this.fyo.db.observer.on(`delete:${schemaName}`, (name) => {
|
|
|
|
if (typeof name !== 'string') {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-04-17 04:29:12 +00:00
|
|
|
this.removeFromCache(schemaName, name);
|
2022-05-03 17:48:50 +00:00
|
|
|
});
|
2022-05-04 08:19:40 +00:00
|
|
|
|
2023-06-22 08:52:54 +00:00
|
|
|
this.fyo.db.observer.on(`rename:${schemaName}`, (names) => {
|
|
|
|
const { oldName } = names as { oldName: string };
|
|
|
|
const doc = this.#getFromCache(schemaName, oldName);
|
|
|
|
if (doc === undefined) {
|
|
|
|
return;
|
2022-05-04 08:19:40 +00:00
|
|
|
}
|
2023-06-22 08:52:54 +00:00
|
|
|
|
|
|
|
this.removeFromCache(schemaName, oldName);
|
|
|
|
this.#addToCache(doc);
|
|
|
|
});
|
2022-05-03 17:48:50 +00:00
|
|
|
}
|
|
|
|
|
2023-04-17 04:29:12 +00:00
|
|
|
removeFromCache(schemaName: string, name: string) {
|
2022-05-03 17:48:50 +00:00
|
|
|
const docMap = this.docs.get(schemaName);
|
|
|
|
delete docMap?.[name];
|
|
|
|
}
|
|
|
|
|
|
|
|
#getFromCache(schemaName: string, name: string): Doc | undefined {
|
|
|
|
const docMap = this.docs.get(schemaName);
|
|
|
|
return docMap?.[name];
|
2022-04-07 06:20:14 +00:00
|
|
|
}
|
2023-04-14 07:57:44 +00:00
|
|
|
|
|
|
|
#cacheHas(schemaName: string, name: string): boolean {
|
|
|
|
return !!this.#getFromCache(schemaName, name);
|
|
|
|
}
|
2022-03-22 09:28:36 +00:00
|
|
|
}
|