import { DocValue } from 'fyo/core/types'; import { Doc } from 'fyo/model/doc'; import type { FormulaMap, HiddenMap, ListsMap, RequiredMap, ValidationMap, } from 'fyo/model/types'; import { ValueError } from 'fyo/utils/errors'; import { camelCase } from 'lodash'; import { ModelNameEnum } from 'models/types'; import type { FieldType } from 'schemas/types'; import { FieldTypeEnum } from 'schemas/types'; import type { CustomForm } from './CustomForm'; export class CustomField extends Doc { parentdoc?: CustomForm; label?: string; fieldname?: string; fieldtype?: FieldType; isRequired?: boolean; section?: string; tab?: string; options?: string; target?: string; references?: string; get parentSchema() { return this.parentdoc?.parentSchema ?? null; } get parentFields() { return this.parentdoc?.parentFields; } formulas: FormulaMap = { fieldname: { formula: () => { if (!this.label?.length) { return; } return camelCase(this.label); }, dependsOn: ['label'], }, }; hidden: HiddenMap = { options: () => this.fieldtype !== 'Select' && this.fieldtype !== 'AutoComplete' && this.fieldtype !== 'Color', target: () => this.fieldtype !== 'Link' && this.fieldtype !== 'Table', references: () => this.fieldtype !== 'DynamicLink', }; validations: ValidationMap = { label: (value) => { if (typeof value !== 'string') { return; } const fieldname = camelCase(value); (this.validations.fieldname as (value: DocValue) => void)(fieldname); }, fieldname: (value) => { if (typeof value !== 'string') { return; } const field = this.parentFields?.[value]; if (field && !field.isCustom) { throw new ValueError( this.fyo.t`Fieldname ${value} already exists for ${this.parentdoc! .name!}` ); } const cf = this.parentdoc?.customFields?.find( (cf) => cf.fieldname === value ); if (cf) { throw new ValueError( this.fyo.t`Fieldname ${value} already used for Custom Field ${ (cf.idx ?? 0) + 1 }` ); } }, }; static lists: ListsMap = { target: (doc) => { const schemaMap = doc?.fyo.schemaMap ?? {}; return Object.values(schemaMap) .filter( (s) => !s?.isSingle && ![ ModelNameEnum.PatchRun, ModelNameEnum.SingleValue, ModelNameEnum.CustomField, ModelNameEnum.CustomForm, ModelNameEnum.SetupWizard, ].includes(s?.name as ModelNameEnum) ) .map((s) => ({ label: s?.label ?? '', value: s?.name ?? '', })); }, references: (doc) => { if (!(doc instanceof CustomField)) { return []; } const referenceType: string[] = [ FieldTypeEnum.AutoComplete, FieldTypeEnum.Data, FieldTypeEnum.Text, FieldTypeEnum.Select, ]; const customFields = doc.parentdoc?.customFields ?.filter( (cf) => cf.fieldname && cf.label && referenceType.includes(cf.fieldtype ?? '') ) ?.map((cf) => ({ value: cf.fieldname!, label: cf.label! })) ?? []; const schemaFields = doc.parentSchema?.fields .filter( (f) => f.fieldname && f.label && referenceType.includes(f.fieldtype) ) .map((f) => ({ value: f.fieldname, label: f.label })) ?? []; return [customFields, schemaFields].flat(); }, }; required: RequiredMap = { options: () => this.fieldtype === 'Select' || this.fieldtype === 'AutoComplete', target: () => this.fieldtype === 'Link' || this.fieldtype === 'Table', references: () => this.fieldtype === 'DynamicLink', default: () => !!this.isRequired, }; }