mirror of
https://github.com/frappe/books.git
synced 2024-12-22 19:09:01 +00:00
incr: build schemas, add some notes
This commit is contained in:
parent
879b677b3b
commit
ef6e8d44ab
@ -71,7 +71,7 @@
|
||||
"fieldname": "hideGetStarted",
|
||||
"label": "Hide Get Started",
|
||||
"fieldtype": "Check",
|
||||
"default": 0,
|
||||
"default": false,
|
||||
"description": "Hides the Get Started section from the sidebar. Change will be visible on restart or refreshing the app."
|
||||
}
|
||||
],
|
||||
|
19
schemas/helpers.ts
Normal file
19
schemas/helpers.ts
Normal file
@ -0,0 +1,19 @@
|
||||
export function getMapFromList<T>(
|
||||
list: T[],
|
||||
name: string = 'name'
|
||||
): Record<string, T> {
|
||||
const acc: Record<string, T> = {};
|
||||
for (const t of list) {
|
||||
const key = t[name] as string | undefined;
|
||||
if (key === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
acc[key] = t;
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
export function getListFromMap<T>(map: Record<string, T>): T[] {
|
||||
return Object.keys(map).map((n) => map[n]);
|
||||
}
|
125
schemas/index.ts
Normal file
125
schemas/index.ts
Normal file
@ -0,0 +1,125 @@
|
||||
import { cloneDeep } from 'lodash';
|
||||
import { getListFromMap, getMapFromList } from './helpers';
|
||||
import regional from './regional';
|
||||
import { appSchemas, coreSchemas } from './schemas';
|
||||
import { Schema, SchemaMap, SchemaStub, SchemaStubMap } from './types';
|
||||
|
||||
export function getSchemas(countryCode: string = '-'): SchemaMap {
|
||||
const builtCoreSchemas = getCoreSchemas();
|
||||
const builtAppSchemas = getAppSchemas(countryCode);
|
||||
|
||||
return Object.assign({}, builtAppSchemas, builtCoreSchemas);
|
||||
}
|
||||
|
||||
function getCoreSchemas(): SchemaMap {
|
||||
const rawSchemaMap = getMapFromList(coreSchemas);
|
||||
const coreSchemaMap = getAbstractCombinedSchemas(rawSchemaMap);
|
||||
return cleanSchemas(coreSchemaMap);
|
||||
}
|
||||
|
||||
function getAppSchemas(countryCode: string): SchemaMap {
|
||||
const combinedSchemas = getRegionalCombinedSchemas(countryCode);
|
||||
const schemaMap = getAbstractCombinedSchemas(combinedSchemas);
|
||||
return cleanSchemas(schemaMap);
|
||||
}
|
||||
|
||||
function cleanSchemas(schemaMap: SchemaMap): SchemaMap {
|
||||
for (const name in schemaMap) {
|
||||
const schema = schemaMap[name];
|
||||
if (schema.isAbstract && !schema.extends) {
|
||||
delete schemaMap[name];
|
||||
continue;
|
||||
}
|
||||
|
||||
delete schema.extends;
|
||||
delete schema.isAbstract;
|
||||
}
|
||||
|
||||
return schemaMap;
|
||||
}
|
||||
|
||||
function getCombined(
|
||||
extendingSchema: SchemaStub,
|
||||
abstractSchema: SchemaStub
|
||||
): SchemaStub {
|
||||
abstractSchema = cloneDeep(abstractSchema);
|
||||
extendingSchema = cloneDeep(extendingSchema);
|
||||
|
||||
const abstractFields = getMapFromList(
|
||||
abstractSchema.fields ?? [],
|
||||
'fieldname'
|
||||
);
|
||||
const extendingFields = getMapFromList(
|
||||
extendingSchema.fields ?? [],
|
||||
'fieldname'
|
||||
);
|
||||
|
||||
const combined = Object.assign(abstractSchema, extendingSchema);
|
||||
|
||||
for (const fieldname in extendingFields) {
|
||||
abstractFields[fieldname] = extendingFields[fieldname];
|
||||
}
|
||||
|
||||
combined.fields = getListFromMap(abstractFields);
|
||||
return combined;
|
||||
}
|
||||
|
||||
function getAbstractCombinedSchemas(schemas: SchemaStubMap): SchemaMap {
|
||||
const abstractSchemaNames: string[] = Object.keys(schemas).filter(
|
||||
(n) => schemas[n].isAbstract
|
||||
);
|
||||
|
||||
const extendingSchemaNames: string[] = Object.keys(schemas).filter((n) =>
|
||||
abstractSchemaNames.includes(schemas[n].extends)
|
||||
);
|
||||
|
||||
const completeSchemas: Schema[] = Object.keys(schemas)
|
||||
.filter(
|
||||
(n) =>
|
||||
!abstractSchemaNames.includes(n) && !extendingSchemaNames.includes(n)
|
||||
)
|
||||
.map((n) => schemas[n] as Schema);
|
||||
|
||||
const schemaMap = getMapFromList(completeSchemas) as SchemaMap;
|
||||
|
||||
for (const name of extendingSchemaNames) {
|
||||
const extendingSchema = schemas[name] as Schema;
|
||||
const abstractSchema = schemas[extendingSchema.extends] as SchemaStub;
|
||||
|
||||
schemaMap[name] = getCombined(extendingSchema, abstractSchema) as Schema;
|
||||
}
|
||||
|
||||
for (const name in abstractSchemaNames) {
|
||||
delete schemaMap[name];
|
||||
}
|
||||
|
||||
return schemaMap;
|
||||
}
|
||||
|
||||
function getRegionalCombinedSchemas(countryCode: string): SchemaStubMap {
|
||||
const regionalSchemaMap = getRegionalSchema(countryCode);
|
||||
const appSchemaMap = getMapFromList(appSchemas);
|
||||
const combined = { ...appSchemaMap };
|
||||
|
||||
for (const name in regionalSchemaMap) {
|
||||
const regionalSchema = regionalSchemaMap[name];
|
||||
|
||||
if (!combined.hasOwnProperty(name)) {
|
||||
combined[name] = regionalSchema;
|
||||
continue;
|
||||
}
|
||||
|
||||
combined[name] = getCombined(regionalSchema, combined[name]);
|
||||
}
|
||||
|
||||
return combined;
|
||||
}
|
||||
|
||||
function getRegionalSchema(countryCode: string): SchemaStubMap {
|
||||
const regionalSchemas = regional[countryCode] as SchemaStub[] | undefined;
|
||||
if (regionalSchemas === undefined) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMapFromList(regionalSchemas);
|
||||
}
|
@ -8,7 +8,6 @@
|
||||
"placeholder": "27AAAAA0000A1Z5"
|
||||
}
|
||||
],
|
||||
"keywordFields": [],
|
||||
"quickEditFields": [
|
||||
"fullname",
|
||||
"email",
|
||||
|
@ -12,7 +12,20 @@
|
||||
"placeholder": "GST Registration",
|
||||
"fieldtype": "Select",
|
||||
"default": "Unregistered",
|
||||
"options": ["Unregistered", "Registered Regular", "Consumer"]
|
||||
"options": [
|
||||
{
|
||||
"value": "Unregistered",
|
||||
"label": "Unregistered"
|
||||
},
|
||||
{
|
||||
"value": "Registered Regular",
|
||||
"label": "Registered Regular"
|
||||
},
|
||||
{
|
||||
"value": "Consumer",
|
||||
"label": "Consumer"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"quickEditFields": [
|
||||
|
6
schemas/regional/in/index.ts
Normal file
6
schemas/regional/in/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { SchemaStub } from '../../types';
|
||||
import AccountingSettings from './AccountingSettings.json';
|
||||
import Address from './Address.json';
|
||||
import Party from './Party.json';
|
||||
|
||||
export default [AccountingSettings, Address, Party] as SchemaStub[];
|
6
schemas/regional/index.ts
Normal file
6
schemas/regional/index.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import IndianSchemas from './in';
|
||||
|
||||
/**
|
||||
* Regional Schemas are exported by country code.
|
||||
*/
|
||||
export default { in: IndianSchemas };
|
@ -28,7 +28,7 @@ import TaxSummary from './app/TaxSummary.json';
|
||||
import PatchRun from './core/PatchRun.json';
|
||||
import SingleValue from './core/SingleValue.json';
|
||||
import SystemSettings from './core/SystemSettings.json';
|
||||
import { Schema } from './types';
|
||||
import { Schema, SchemaStub } from './types';
|
||||
|
||||
export const coreSchemas: Schema[] = [
|
||||
PatchRun as Schema,
|
||||
@ -36,7 +36,7 @@ export const coreSchemas: Schema[] = [
|
||||
SystemSettings as Schema,
|
||||
];
|
||||
|
||||
export const appSchemas: Schema[] = [
|
||||
export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
SetupWizard as Schema,
|
||||
GetStarted as Schema,
|
||||
|
||||
@ -52,8 +52,8 @@ export const appSchemas: Schema[] = [
|
||||
AccountingLedgerEntry as Schema,
|
||||
|
||||
Party as Schema,
|
||||
Supplier as Schema,
|
||||
Customer as Schema,
|
||||
Supplier as SchemaStub,
|
||||
Customer as SchemaStub,
|
||||
|
||||
Address as Schema,
|
||||
Item as Schema,
|
||||
|
@ -1,3 +1,38 @@
|
||||
/**
|
||||
* # Schema
|
||||
*
|
||||
* Main purpose of this is to describe the shape of the Models table in the
|
||||
* database. But there is some irrelevant information in the schemas with
|
||||
* respect to this goal. This is information is allowed as long as it is not
|
||||
* dynamic, which is impossible anyways as the files are data (.json !.js)
|
||||
*
|
||||
* If any field has to have a dynamic value, it should be added to the controller
|
||||
* file by the same name.
|
||||
*
|
||||
*
|
||||
* There are a few types of schemas:
|
||||
* - _Regional_: Schemas that are in the '../regional' subdirectories
|
||||
* these can be of any of the below types.
|
||||
* - _Abstract_: Schemas that are not used as they are but only after they are
|
||||
* extended by Stub schemas. Indentified by the `isAbstract` field
|
||||
* - _Subclass_: Schemas that have an "extends" field on them, the value of which
|
||||
* points to an Abstract schema.
|
||||
* - _Complete_: Schemas which are neither abstract nor stub.
|
||||
*
|
||||
*
|
||||
* ## Final Schema
|
||||
*
|
||||
* This is the schema which is used by the database and app code.
|
||||
*
|
||||
* The order in which a schema is built is:
|
||||
* 1. Build _Regional_ schemas by overriding the fields and other properties of the
|
||||
* non regional variants.
|
||||
* 2. Combine _Subclass_ schemas with _Abstract_ schemas to get complete schemas.
|
||||
*
|
||||
* Note: if a Regional schema is not present as a non regional variant it's used
|
||||
* as it is.
|
||||
*/
|
||||
|
||||
export enum FieldTypeEnum {
|
||||
Data = 'Data',
|
||||
Select = 'Select',
|
||||
@ -19,18 +54,19 @@ export enum FieldTypeEnum {
|
||||
export type FieldType = keyof typeof FieldTypeEnum;
|
||||
export type RawValue = string | number | boolean;
|
||||
|
||||
// prettier-ignore
|
||||
export interface BaseField {
|
||||
fieldname: string;
|
||||
fieldtype: FieldType;
|
||||
label: string;
|
||||
hidden?: boolean;
|
||||
required?: boolean;
|
||||
readOnly?: boolean;
|
||||
description?: string;
|
||||
default?: RawValue;
|
||||
placeholder?: string;
|
||||
groupBy?: string;
|
||||
computed?: boolean;
|
||||
fieldname: string; // Column name in the db
|
||||
fieldtype: FieldType; // UI Descriptive field types that map to column types
|
||||
label: string; // Translateable UI facing name
|
||||
required?: boolean; // Implies Not Null
|
||||
hidden?: boolean; // UI Facing config, whether field is shown in a form
|
||||
readOnly?: boolean; // UI Facing config, whether field is editable
|
||||
description?: string; // UI Facing, translateable, used for inline documentation
|
||||
default?: RawValue; // Default value of a field, should match the db type
|
||||
placeholder?: string; // UI Facing config, form field placeholder
|
||||
groupBy?: string; // UI Facing used in dropdowns fields
|
||||
computed?: boolean; // Indicates whether a value is computed, implies readonly
|
||||
}
|
||||
|
||||
export type SelectOption = { value: string; label: string };
|
||||
@ -42,20 +78,23 @@ export interface OptionField extends BaseField {
|
||||
options: SelectOption[];
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export interface TargetField extends BaseField {
|
||||
fieldtype: FieldTypeEnum.Table | FieldTypeEnum.Link;
|
||||
target: string | string[];
|
||||
target: string | string[]; // Name of the table or group of tables to fetch values
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export interface DynamicLinkField extends BaseField {
|
||||
fieldtype: FieldTypeEnum.DynamicLink;
|
||||
references: string;
|
||||
references: string; // Reference to an option field that links to schema
|
||||
}
|
||||
|
||||
// prettier-ignore
|
||||
export interface NumberField extends BaseField {
|
||||
fieldtype: FieldTypeEnum.Float | FieldTypeEnum.Int;
|
||||
minvalue?: number;
|
||||
maxvalue?: number;
|
||||
minvalue?: number; // UI Facing used to restrict lower bound
|
||||
maxvalue?: number; // UI Facing used to restrict upper bound
|
||||
}
|
||||
|
||||
export type Field =
|
||||
@ -66,16 +105,24 @@ export type Field =
|
||||
| NumberField;
|
||||
|
||||
export type TreeSettings = { parentField: string };
|
||||
|
||||
// prettier-ignore
|
||||
export interface Schema {
|
||||
name: string;
|
||||
label: string;
|
||||
fields: Field[];
|
||||
isTree?: boolean;
|
||||
extends?: string;
|
||||
isChild?: boolean;
|
||||
isSingle?: boolean;
|
||||
isAbstract?: boolean;
|
||||
isSubmittable?: boolean;
|
||||
keywordFields?: string[];
|
||||
treeSettings?: TreeSettings;
|
||||
name: string; // Table PK
|
||||
label: string; // Translateable UI facing name
|
||||
fields: Field[]; // Maps to database columns
|
||||
isTree?: boolean; // Used for nested set, eg for Chart of Accounts
|
||||
extends?: string; // Value points to an Abstract schema. Indicates Subclass schema
|
||||
isChild?: boolean; // Indicates a child table, i.e table with "parent" FK column
|
||||
isSingle?: boolean; // Fields will be values in SingleValue, i.e. an Entity Attr. Value
|
||||
isAbstract?: boolean; // Not entered into db, used to extend a Subclass schema
|
||||
isSubmittable?: boolean; // For transactional types, values considered only after submit
|
||||
keywordFields?: string[]; // Used for fields that are to be used for search.
|
||||
treeSettings?: TreeSettings; // Used to determine root nodes
|
||||
}
|
||||
|
||||
export interface SchemaStub extends Partial<Schema> {
|
||||
name: string;
|
||||
}
|
||||
export type SchemaMap = Record<string, Schema>;
|
||||
export type SchemaStubMap = Record<string, SchemaStub>;
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { ipcRenderer } from 'electron';
|
||||
import frappe from 'frappe';
|
||||
import { createApp } from 'vue';
|
||||
import { getSchemas } from '../schemas';
|
||||
import App from './App';
|
||||
import FeatherIcon from './components/FeatherIcon';
|
||||
import config, { ConfigKeys } from './config';
|
||||
@ -101,3 +102,5 @@ import { setLanguageMap, stringifyCircular } from './utils';
|
||||
handleError(true, error, {}, () => process.exit(1));
|
||||
});
|
||||
})();
|
||||
|
||||
window.gs = getSchemas;
|
||||
|
Loading…
Reference in New Issue
Block a user