mirror of
https://github.com/frappe/books.git
synced 2025-02-08 23:18:31 +00:00
Merge pull request #705 from frappe/custom-fields
feat: form customization
This commit is contained in:
commit
adfd59d1a9
@ -11,10 +11,11 @@ import patches from '../patches';
|
|||||||
import { BespokeQueries } from './bespoke';
|
import { BespokeQueries } from './bespoke';
|
||||||
import DatabaseCore from './core';
|
import DatabaseCore from './core';
|
||||||
import { runPatches } from './runPatch';
|
import { runPatches } from './runPatch';
|
||||||
import { BespokeFunction, Patch } from './types';
|
import { BespokeFunction, Patch, RawCustomField } from './types';
|
||||||
|
|
||||||
export class DatabaseManager extends DatabaseDemuxBase {
|
export class DatabaseManager extends DatabaseDemuxBase {
|
||||||
db?: DatabaseCore;
|
db?: DatabaseCore;
|
||||||
|
rawCustomFields: RawCustomField[] = [];
|
||||||
|
|
||||||
get #isInitialized(): boolean {
|
get #isInitialized(): boolean {
|
||||||
return this.db !== undefined && this.db.knex !== undefined;
|
return this.db !== undefined && this.db.knex !== undefined;
|
||||||
@ -22,10 +23,10 @@ export class DatabaseManager extends DatabaseDemuxBase {
|
|||||||
|
|
||||||
getSchemaMap() {
|
getSchemaMap() {
|
||||||
if (this.#isInitialized) {
|
if (this.#isInitialized) {
|
||||||
return this.db?.schemaMap ?? getSchemas();
|
return this.db?.schemaMap ?? getSchemas('-', this.rawCustomFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
return getSchemas();
|
return getSchemas('-', this.rawCustomFields);
|
||||||
}
|
}
|
||||||
|
|
||||||
async createNewDatabase(dbPath: string, countryCode: string) {
|
async createNewDatabase(dbPath: string, countryCode: string) {
|
||||||
@ -43,11 +44,20 @@ export class DatabaseManager extends DatabaseDemuxBase {
|
|||||||
countryCode ??= await DatabaseCore.getCountryCode(dbPath);
|
countryCode ??= await DatabaseCore.getCountryCode(dbPath);
|
||||||
this.db = new DatabaseCore(dbPath);
|
this.db = new DatabaseCore(dbPath);
|
||||||
await this.db.connect();
|
await this.db.connect();
|
||||||
const schemaMap = getSchemas(countryCode);
|
await this.setRawCustomFields();
|
||||||
|
const schemaMap = getSchemas(countryCode, this.rawCustomFields);
|
||||||
this.db.setSchemaMap(schemaMap);
|
this.db.setSchemaMap(schemaMap);
|
||||||
return countryCode;
|
return countryCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async setRawCustomFields() {
|
||||||
|
try {
|
||||||
|
this.rawCustomFields = (await this.db?.knex?.(
|
||||||
|
'CustomField'
|
||||||
|
)) as RawCustomField[];
|
||||||
|
} catch {}
|
||||||
|
}
|
||||||
|
|
||||||
async #migrate(): Promise<void> {
|
async #migrate(): Promise<void> {
|
||||||
if (!this.#isInitialized) {
|
if (!this.#isInitialized) {
|
||||||
return;
|
return;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { Field, RawValue } from '../../schemas/types';
|
import type { Field, FieldType, RawValue } from '../../schemas/types';
|
||||||
import DatabaseCore from './core';
|
import type DatabaseCore from './core';
|
||||||
import { DatabaseManager } from './manager';
|
import type { DatabaseManager } from './manager';
|
||||||
|
|
||||||
export interface GetQueryBuilderOptions {
|
export interface GetQueryBuilderOptions {
|
||||||
offset?: number;
|
offset?: number;
|
||||||
@ -80,3 +80,16 @@ export type SingleValue<T> = {
|
|||||||
parent: string;
|
parent: string;
|
||||||
value: T;
|
value: T;
|
||||||
}[];
|
}[];
|
||||||
|
|
||||||
|
export type RawCustomField = {
|
||||||
|
parent: string;
|
||||||
|
label: string;
|
||||||
|
fieldname: string;
|
||||||
|
fieldtype: FieldType;
|
||||||
|
isRequired?: boolean;
|
||||||
|
section?: string;
|
||||||
|
tab?: string;
|
||||||
|
options?: string;
|
||||||
|
target?: string;
|
||||||
|
references?: string;
|
||||||
|
};
|
||||||
|
@ -751,7 +751,7 @@ export class Doc extends Observable<DocValue | Doc[]> {
|
|||||||
if (dbValues && docModified !== dbModified) {
|
if (dbValues && docModified !== dbModified) {
|
||||||
throw new ConflictError(
|
throw new ConflictError(
|
||||||
this.fyo
|
this.fyo
|
||||||
.t`${this.schema.label} ${this.name} has been modified after loading` +
|
.t`${this.schema.label} ${this.name} has been modified after loading please reload entry.` +
|
||||||
` ${dbModified}, ${docModified}`
|
` ${dbModified}, ${docModified}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,15 @@
|
|||||||
import { Fyo } from 'fyo';
|
import type { Fyo } from 'fyo';
|
||||||
import { DocValue, DocValueMap } from 'fyo/core/types';
|
import type { DocValue, DocValueMap } from 'fyo/core/types';
|
||||||
import type SystemSettings from 'fyo/models/SystemSettings';
|
import type SystemSettings from 'fyo/models/SystemSettings';
|
||||||
import { FieldType, Schema, SelectOption } from 'schemas/types';
|
import type { FieldType, Schema, SelectOption } from 'schemas/types';
|
||||||
import { QueryFilter } from 'utils/db/types';
|
import type { QueryFilter } from 'utils/db/types';
|
||||||
import { RouteLocationRaw, Router } from 'vue-router';
|
import type { RouteLocationRaw, Router } from 'vue-router';
|
||||||
import { Doc } from './doc';
|
import type { Doc } from './doc';
|
||||||
import type { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
import type { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
||||||
import type { Defaults } from 'models/baseModels/Defaults/Defaults';
|
import type { Defaults } from 'models/baseModels/Defaults/Defaults';
|
||||||
import type { PrintSettings } from 'models/baseModels/PrintSettings/PrintSettings';
|
import type { PrintSettings } from 'models/baseModels/PrintSettings/PrintSettings';
|
||||||
import type { InventorySettings } from 'models/inventory/InventorySettings';
|
import type { InventorySettings } from 'models/inventory/InventorySettings';
|
||||||
import { Misc } from 'models/baseModels/Misc';
|
import type { Misc } from 'models/baseModels/Misc';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The functions below are used for dynamic evaluation
|
* The functions below are used for dynamic evaluation
|
||||||
|
154
fyo/models/CustomField.ts
Normal file
154
fyo/models/CustomField.ts
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
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',
|
||||||
|
};
|
||||||
|
}
|
78
fyo/models/CustomForm.ts
Normal file
78
fyo/models/CustomForm.ts
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
import { Doc } from 'fyo/model/doc';
|
||||||
|
import { HiddenMap, ListsMap } from 'fyo/model/types';
|
||||||
|
import { ValidationError } from 'fyo/utils/errors';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
import { Field } from 'schemas/types';
|
||||||
|
import { getMapFromList } from 'utils/index';
|
||||||
|
import { CustomField } from './CustomField';
|
||||||
|
|
||||||
|
export class CustomForm extends Doc {
|
||||||
|
name?: string;
|
||||||
|
customFields?: CustomField[];
|
||||||
|
|
||||||
|
get parentSchema() {
|
||||||
|
return this.fyo.schemaMap[this.name ?? ''] ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
get parentFields(): Record<string, Field> {
|
||||||
|
const fields = this.parentSchema?.fields;
|
||||||
|
if (!fields) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return getMapFromList(fields, 'fieldname');
|
||||||
|
}
|
||||||
|
|
||||||
|
static lists: ListsMap = {
|
||||||
|
name: (doc) =>
|
||||||
|
Object.values(doc?.fyo.schemaMap ?? {})
|
||||||
|
.filter((s) => {
|
||||||
|
if (!s || !s.label || !s.name) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (s.isSingle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ![
|
||||||
|
ModelNameEnum.PatchRun,
|
||||||
|
ModelNameEnum.SingleValue,
|
||||||
|
ModelNameEnum.CustomField,
|
||||||
|
ModelNameEnum.CustomForm,
|
||||||
|
ModelNameEnum.SetupWizard,
|
||||||
|
].includes(s.name as ModelNameEnum);
|
||||||
|
})
|
||||||
|
.map((s) => ({
|
||||||
|
value: s!.name,
|
||||||
|
label: s!.label,
|
||||||
|
})),
|
||||||
|
};
|
||||||
|
|
||||||
|
hidden: HiddenMap = { customFields: () => !this.name };
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/require-await
|
||||||
|
override async validate(): Promise<void> {
|
||||||
|
for (const row of this.customFields ?? []) {
|
||||||
|
if (row.fieldtype === 'Select' || row.fieldtype === 'AutoComplete') {
|
||||||
|
this.validateOptions(row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validateOptions(row: CustomField) {
|
||||||
|
const optionString = row.options ?? '';
|
||||||
|
const options = optionString
|
||||||
|
.split('\n')
|
||||||
|
.map((s) => s.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
if (options.length > 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new ValidationError(
|
||||||
|
`At least two options need to be set for the selected fieldtype`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,8 +1,12 @@
|
|||||||
import { ModelMap } from 'fyo/model/types';
|
import { ModelMap } from 'fyo/model/types';
|
||||||
import NumberSeries from './NumberSeries';
|
import NumberSeries from './NumberSeries';
|
||||||
import SystemSettings from './SystemSettings';
|
import SystemSettings from './SystemSettings';
|
||||||
|
import { CustomField } from './CustomField';
|
||||||
|
import { CustomForm } from './CustomForm';
|
||||||
|
|
||||||
export const coreModels = {
|
export const coreModels = {
|
||||||
NumberSeries,
|
NumberSeries,
|
||||||
SystemSettings,
|
SystemSettings,
|
||||||
|
CustomForm,
|
||||||
|
CustomField,
|
||||||
} as ModelMap;
|
} as ModelMap;
|
||||||
|
@ -17,7 +17,7 @@ test('Fyo Init', async (t) => {
|
|||||||
test('Fyo Docs', async (t) => {
|
test('Fyo Docs', async (t) => {
|
||||||
const countryCode = 'in';
|
const countryCode = 'in';
|
||||||
const fyo = getTestFyo();
|
const fyo = getTestFyo();
|
||||||
const schemaMap = getSchemas(countryCode);
|
const schemaMap = getSchemas(countryCode, []);
|
||||||
const regionalModels = await getRegionalModels(countryCode);
|
const regionalModels = await getRegionalModels(countryCode);
|
||||||
await fyo.db.createNewDatabase(':memory:', countryCode);
|
await fyo.db.createNewDatabase(':memory:', countryCode);
|
||||||
await fyo.initializeAndRegister(models, regionalModels);
|
await fyo.initializeAndRegister(models, regionalModels);
|
||||||
|
@ -15,6 +15,7 @@ export class AccountingSettings extends Doc {
|
|||||||
enableDiscounting?: boolean;
|
enableDiscounting?: boolean;
|
||||||
enableInventory?: boolean;
|
enableInventory?: boolean;
|
||||||
enablePriceList?: boolean;
|
enablePriceList?: boolean;
|
||||||
|
enableFormCustomization?: boolean;
|
||||||
|
|
||||||
static filters: FiltersMap = {
|
static filters: FiltersMap = {
|
||||||
writeOffAccount: () => ({
|
writeOffAccount: () => ({
|
||||||
|
@ -44,6 +44,8 @@ export enum ModelNameEnum {
|
|||||||
PurchaseReceipt = 'PurchaseReceipt',
|
PurchaseReceipt = 'PurchaseReceipt',
|
||||||
PurchaseReceiptItem = 'PurchaseReceiptItem',
|
PurchaseReceiptItem = 'PurchaseReceiptItem',
|
||||||
Location = 'Location',
|
Location = 'Location',
|
||||||
|
CustomForm = 'CustomForm',
|
||||||
|
CustomField = 'CustomField'
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelName = keyof typeof ModelNameEnum;
|
export type ModelName = keyof typeof ModelNameEnum;
|
||||||
|
@ -86,6 +86,13 @@
|
|||||||
"default": false,
|
"default": false,
|
||||||
"section": "Features"
|
"section": "Features"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "enableFormCustomization",
|
||||||
|
"label": "Enable Form Customization",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false,
|
||||||
|
"section": "Features"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"fieldname": "fiscalYearStart",
|
"fieldname": "fiscalYearStart",
|
||||||
"label": "Fiscal Year Start Date",
|
"label": "Fiscal Year Start Date",
|
||||||
|
137
schemas/core/CustomField.json
Normal file
137
schemas/core/CustomField.json
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
{
|
||||||
|
"name": "CustomField",
|
||||||
|
"label": "Custom Field",
|
||||||
|
"isChild": true,
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "label",
|
||||||
|
"label": "Label",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fieldname",
|
||||||
|
"label": "Fieldname",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "fieldtype",
|
||||||
|
"label": "Fieldtype",
|
||||||
|
"fieldtype": "Select",
|
||||||
|
"options": [
|
||||||
|
{
|
||||||
|
"label": "Data",
|
||||||
|
"value": "Data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Select",
|
||||||
|
"value": "Select"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Link",
|
||||||
|
"value": "Link"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Date",
|
||||||
|
"value": "Date"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Date Time",
|
||||||
|
"value": "Datetime"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Table",
|
||||||
|
"value": "Table"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Autocomplete",
|
||||||
|
"value": "AutoComplete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Check",
|
||||||
|
"value": "Check"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Attach Image",
|
||||||
|
"value": "AttachImage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Dynamic Link",
|
||||||
|
"value": "DynamicLink"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Int",
|
||||||
|
"value": "Int"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Float",
|
||||||
|
"value": "Float"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Currency",
|
||||||
|
"value": "Currency"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Text",
|
||||||
|
"value": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Color",
|
||||||
|
"value": "Color"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"label": "Attachment",
|
||||||
|
"value": "Attachment"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"default": "Data",
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "isRequired",
|
||||||
|
"label": "Is Required",
|
||||||
|
"fieldtype": "Check",
|
||||||
|
"default": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "section",
|
||||||
|
"label": "Form Section",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"default": "Default"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "tab",
|
||||||
|
"label": "Form Tab",
|
||||||
|
"fieldtype": "Data",
|
||||||
|
"default": "Custom"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "options",
|
||||||
|
"label": "Options",
|
||||||
|
"fieldtype": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "target",
|
||||||
|
"label": "Target",
|
||||||
|
"fieldtype": "AutoComplete"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "references",
|
||||||
|
"label": "References",
|
||||||
|
"fieldtype": "AutoComplete"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"tableFields": ["label", "fieldname", "fieldtype", "isRequired"],
|
||||||
|
"quickEditFields": [
|
||||||
|
"label",
|
||||||
|
"fieldname",
|
||||||
|
"fieldtype",
|
||||||
|
"isRequired",
|
||||||
|
"options",
|
||||||
|
"target",
|
||||||
|
"references",
|
||||||
|
"section",
|
||||||
|
"tab"
|
||||||
|
]
|
||||||
|
}
|
22
schemas/core/CustomForm.json
Normal file
22
schemas/core/CustomForm.json
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "CustomForm",
|
||||||
|
"label": "Custom Form",
|
||||||
|
"naming": "manual",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldname": "name",
|
||||||
|
"fieldtype": "AutoComplete",
|
||||||
|
"label": "Form Type",
|
||||||
|
"options": [],
|
||||||
|
"required": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldname": "customFields",
|
||||||
|
"label": "Custom Fields",
|
||||||
|
"fieldtype": "Table",
|
||||||
|
"target": "CustomField",
|
||||||
|
"required": true,
|
||||||
|
"edit": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
102
schemas/index.ts
102
schemas/index.ts
@ -1,8 +1,19 @@
|
|||||||
|
import { RawCustomField } from 'backend/database/types';
|
||||||
import { cloneDeep } from 'lodash';
|
import { cloneDeep } from 'lodash';
|
||||||
import { getListFromMap, getMapFromList } from 'utils';
|
import { getListFromMap, getMapFromList } from 'utils';
|
||||||
import regionalSchemas from './regional';
|
import regionalSchemas from './regional';
|
||||||
import { appSchemas, coreSchemas, metaSchemas } from './schemas';
|
import { appSchemas, coreSchemas, metaSchemas } from './schemas';
|
||||||
import { Field, Schema, SchemaMap, SchemaStub, SchemaStubMap } from './types';
|
import type {
|
||||||
|
DynamicLinkField,
|
||||||
|
Field,
|
||||||
|
OptionField,
|
||||||
|
Schema,
|
||||||
|
SchemaMap,
|
||||||
|
SchemaStub,
|
||||||
|
SchemaStubMap,
|
||||||
|
SelectOption,
|
||||||
|
TargetField,
|
||||||
|
} from './types';
|
||||||
|
|
||||||
const NAME_FIELD = {
|
const NAME_FIELD = {
|
||||||
fieldname: 'name',
|
fieldname: 'name',
|
||||||
@ -12,7 +23,10 @@ const NAME_FIELD = {
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getSchemas(countryCode = '-'): Readonly<SchemaMap> {
|
export function getSchemas(
|
||||||
|
countryCode = '-',
|
||||||
|
rawCustomFields: RawCustomField[]
|
||||||
|
): Readonly<SchemaMap> {
|
||||||
const builtCoreSchemas = getCoreSchemas();
|
const builtCoreSchemas = getCoreSchemas();
|
||||||
const builtAppSchemas = getAppSchemas(countryCode);
|
const builtAppSchemas = getAppSchemas(countryCode);
|
||||||
|
|
||||||
@ -21,6 +35,7 @@ export function getSchemas(countryCode = '-'): Readonly<SchemaMap> {
|
|||||||
schemaMap = removeFields(schemaMap);
|
schemaMap = removeFields(schemaMap);
|
||||||
schemaMap = setSchemaNameOnFields(schemaMap);
|
schemaMap = setSchemaNameOnFields(schemaMap);
|
||||||
|
|
||||||
|
addCustomFields(schemaMap, rawCustomFields);
|
||||||
deepFreeze(schemaMap);
|
deepFreeze(schemaMap);
|
||||||
return schemaMap;
|
return schemaMap;
|
||||||
}
|
}
|
||||||
@ -251,3 +266,86 @@ function getRegionalSchemaMap(countryCode: string): SchemaStubMap {
|
|||||||
|
|
||||||
return getMapFromList(countrySchemas, 'name');
|
return getMapFromList(countrySchemas, 'name');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addCustomFields(
|
||||||
|
schemaMap: SchemaMap,
|
||||||
|
rawCustomFields: RawCustomField[]
|
||||||
|
): void {
|
||||||
|
const fieldMap = getFieldMapFromRawCustomFields(rawCustomFields, schemaMap);
|
||||||
|
for (const schemaName in fieldMap) {
|
||||||
|
const fields = fieldMap[schemaName];
|
||||||
|
schemaMap[schemaName]?.fields.push(...fields);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getFieldMapFromRawCustomFields(
|
||||||
|
rawCustomFields: RawCustomField[],
|
||||||
|
schemaMap: SchemaMap
|
||||||
|
) {
|
||||||
|
const schemaFieldMap: Record<string, Record<string, Field>> = {};
|
||||||
|
|
||||||
|
return rawCustomFields.reduce(
|
||||||
|
(
|
||||||
|
map,
|
||||||
|
{
|
||||||
|
parent,
|
||||||
|
label,
|
||||||
|
fieldname,
|
||||||
|
fieldtype,
|
||||||
|
isRequired,
|
||||||
|
section,
|
||||||
|
tab,
|
||||||
|
options: rawOptions,
|
||||||
|
target,
|
||||||
|
references,
|
||||||
|
}
|
||||||
|
) => {
|
||||||
|
schemaFieldMap[parent] ??= getMapFromList(
|
||||||
|
schemaMap[parent]?.fields ?? [],
|
||||||
|
'fieldname'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!schemaFieldMap[parent] || schemaFieldMap[parent][fieldname]) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
map[parent] ??= [];
|
||||||
|
const options = rawOptions
|
||||||
|
?.split('\n')
|
||||||
|
.map((o) => {
|
||||||
|
const value = o.trim();
|
||||||
|
return { value, label: value } as SelectOption;
|
||||||
|
})
|
||||||
|
.filter((o) => o.label && o.value);
|
||||||
|
|
||||||
|
const field = {
|
||||||
|
label,
|
||||||
|
fieldname,
|
||||||
|
fieldtype,
|
||||||
|
section,
|
||||||
|
tab,
|
||||||
|
isCustom: true,
|
||||||
|
} as Field;
|
||||||
|
|
||||||
|
if (options?.length) {
|
||||||
|
(field as OptionField).options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof isRequired === 'number' || typeof isRequired === 'boolean') {
|
||||||
|
field.required = Boolean(isRequired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof target === 'string') {
|
||||||
|
(field as TargetField).target === 'target';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof references === 'string') {
|
||||||
|
(field as DynamicLinkField).references === 'references';
|
||||||
|
}
|
||||||
|
|
||||||
|
map[parent].push(field);
|
||||||
|
return map;
|
||||||
|
},
|
||||||
|
{} as Record<string, Field[]>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
@ -7,21 +7,6 @@ import Color from './app/Color.json';
|
|||||||
import Currency from './app/Currency.json';
|
import Currency from './app/Currency.json';
|
||||||
import Defaults from './app/Defaults.json';
|
import Defaults from './app/Defaults.json';
|
||||||
import GetStarted from './app/GetStarted.json';
|
import GetStarted from './app/GetStarted.json';
|
||||||
import InventorySettings from './app/inventory/InventorySettings.json';
|
|
||||||
import Location from './app/inventory/Location.json';
|
|
||||||
import PriceList from './app/PriceList.json';
|
|
||||||
import PriceListItem from './app/PriceListItem.json';
|
|
||||||
import PurchaseReceipt from './app/inventory/PurchaseReceipt.json';
|
|
||||||
import PurchaseReceiptItem from './app/inventory/PurchaseReceiptItem.json';
|
|
||||||
import SerialNumber from './app/inventory/SerialNumber.json';
|
|
||||||
import Shipment from './app/inventory/Shipment.json';
|
|
||||||
import ShipmentItem from './app/inventory/ShipmentItem.json';
|
|
||||||
import StockLedgerEntry from './app/inventory/StockLedgerEntry.json';
|
|
||||||
import StockMovement from './app/inventory/StockMovement.json';
|
|
||||||
import StockMovementItem from './app/inventory/StockMovementItem.json';
|
|
||||||
import StockTransfer from './app/inventory/StockTransfer.json';
|
|
||||||
import StockTransferItem from './app/inventory/StockTransferItem.json';
|
|
||||||
import UOMConversionItem from './app/inventory/UOMConversionItem.json';
|
|
||||||
import Invoice from './app/Invoice.json';
|
import Invoice from './app/Invoice.json';
|
||||||
import InvoiceItem from './app/InvoiceItem.json';
|
import InvoiceItem from './app/InvoiceItem.json';
|
||||||
import Item from './app/Item.json';
|
import Item from './app/Item.json';
|
||||||
@ -32,6 +17,8 @@ import NumberSeries from './app/NumberSeries.json';
|
|||||||
import Party from './app/Party.json';
|
import Party from './app/Party.json';
|
||||||
import Payment from './app/Payment.json';
|
import Payment from './app/Payment.json';
|
||||||
import PaymentFor from './app/PaymentFor.json';
|
import PaymentFor from './app/PaymentFor.json';
|
||||||
|
import PriceList from './app/PriceList.json';
|
||||||
|
import PriceListItem from './app/PriceListItem.json';
|
||||||
import PrintSettings from './app/PrintSettings.json';
|
import PrintSettings from './app/PrintSettings.json';
|
||||||
import PrintTemplate from './app/PrintTemplate.json';
|
import PrintTemplate from './app/PrintTemplate.json';
|
||||||
import PurchaseInvoice from './app/PurchaseInvoice.json';
|
import PurchaseInvoice from './app/PurchaseInvoice.json';
|
||||||
@ -43,6 +30,21 @@ import Tax from './app/Tax.json';
|
|||||||
import TaxDetail from './app/TaxDetail.json';
|
import TaxDetail from './app/TaxDetail.json';
|
||||||
import TaxSummary from './app/TaxSummary.json';
|
import TaxSummary from './app/TaxSummary.json';
|
||||||
import UOM from './app/UOM.json';
|
import UOM from './app/UOM.json';
|
||||||
|
import InventorySettings from './app/inventory/InventorySettings.json';
|
||||||
|
import Location from './app/inventory/Location.json';
|
||||||
|
import PurchaseReceipt from './app/inventory/PurchaseReceipt.json';
|
||||||
|
import PurchaseReceiptItem from './app/inventory/PurchaseReceiptItem.json';
|
||||||
|
import SerialNumber from './app/inventory/SerialNumber.json';
|
||||||
|
import Shipment from './app/inventory/Shipment.json';
|
||||||
|
import ShipmentItem from './app/inventory/ShipmentItem.json';
|
||||||
|
import StockLedgerEntry from './app/inventory/StockLedgerEntry.json';
|
||||||
|
import StockMovement from './app/inventory/StockMovement.json';
|
||||||
|
import StockMovementItem from './app/inventory/StockMovementItem.json';
|
||||||
|
import StockTransfer from './app/inventory/StockTransfer.json';
|
||||||
|
import StockTransferItem from './app/inventory/StockTransferItem.json';
|
||||||
|
import UOMConversionItem from './app/inventory/UOMConversionItem.json';
|
||||||
|
import CustomField from './core/CustomField.json';
|
||||||
|
import CustomForm from './core/CustomForm.json';
|
||||||
import PatchRun from './core/PatchRun.json';
|
import PatchRun from './core/PatchRun.json';
|
||||||
import SingleValue from './core/SingleValue.json';
|
import SingleValue from './core/SingleValue.json';
|
||||||
import SystemSettings from './core/SystemSettings.json';
|
import SystemSettings from './core/SystemSettings.json';
|
||||||
@ -124,4 +126,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
|||||||
|
|
||||||
Batch as Schema,
|
Batch as Schema,
|
||||||
SerialNumber as Schema,
|
SerialNumber as Schema,
|
||||||
|
|
||||||
|
CustomForm as Schema,
|
||||||
|
CustomField as Schema,
|
||||||
];
|
];
|
||||||
|
@ -65,7 +65,8 @@ export interface BaseField {
|
|||||||
computed?: boolean; // Computed values are not stored in the database.
|
computed?: boolean; // Computed values are not stored in the database.
|
||||||
section?: string; // UI Facing config, for grouping by sections
|
section?: string; // UI Facing config, for grouping by sections
|
||||||
tab?: string; // UI Facing config, for grouping by tabs
|
tab?: string; // UI Facing config, for grouping by tabs
|
||||||
abstract?: string; // Uused to mark the location of a field in an Abstract schema
|
abstract?: string; // Used to mark the location of a field in an Abstract schema
|
||||||
|
isCustom?: boolean; // Whether the field is a custom field
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SelectOption = { value: string; label: string };
|
export type SelectOption = { value: string; label: string };
|
||||||
|
101
src/pages/CustomizeForm/CustomizeForm.vue
Normal file
101
src/pages/CustomizeForm/CustomizeForm.vue
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<PageHeader :title="t`Customize Form`">
|
||||||
|
<DropdownWithActions :actions="[]" :disabled="false" :title="t`More`" />
|
||||||
|
<Button :title="t`Save Customizations`" type="primary">
|
||||||
|
{{ t`Save` }}
|
||||||
|
</Button>
|
||||||
|
</PageHeader>
|
||||||
|
<div class="flex text-base w-full flex-col">
|
||||||
|
<!-- Select Entry Type -->
|
||||||
|
<div
|
||||||
|
class="
|
||||||
|
h-row-largest
|
||||||
|
flex flex-row
|
||||||
|
justify-start
|
||||||
|
items-center
|
||||||
|
w-full
|
||||||
|
gap-2
|
||||||
|
border-b
|
||||||
|
p-4
|
||||||
|
"
|
||||||
|
>
|
||||||
|
<AutoComplete
|
||||||
|
:df="{
|
||||||
|
fieldname: 'formType',
|
||||||
|
label: t`Form Type`,
|
||||||
|
fieldtype: 'AutoComplete',
|
||||||
|
options: customizableSchemas,
|
||||||
|
}"
|
||||||
|
input-class="bg-transparent text-gray-900 text-base"
|
||||||
|
class="w-40"
|
||||||
|
:border="true"
|
||||||
|
:value="formType"
|
||||||
|
size="small"
|
||||||
|
@change="setEntryType"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<p v-if="errorMessage" class="text-base ms-2 text-red-500">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</p>
|
||||||
|
<p v-else-if="helpMessage" class="text-base ms-2 text-gray-700">
|
||||||
|
{{ helpMessage }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent } from 'vue';
|
||||||
|
|
||||||
|
import DropdownWithActions from 'src/components/DropdownWithActions.vue';
|
||||||
|
import Button from 'src/components/Button.vue';
|
||||||
|
import PageHeader from 'src/components/PageHeader.vue';
|
||||||
|
import AutoComplete from 'src/components/Controls/AutoComplete.vue';
|
||||||
|
import { ModelNameEnum } from 'models/types';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
components: { PageHeader, Button, DropdownWithActions, AutoComplete },
|
||||||
|
data() {
|
||||||
|
return { errorMessage: '', formType: '' };
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
customizableSchemas() {
|
||||||
|
const schemaNames = Object.keys(this.fyo.schemaMap).filter(
|
||||||
|
(schemaName) => {
|
||||||
|
const schema = this.fyo.schemaMap[schemaName];
|
||||||
|
if (!schema) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (schema?.isSingle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ![
|
||||||
|
ModelNameEnum.NumberSeries,
|
||||||
|
ModelNameEnum.SingleValue,
|
||||||
|
ModelNameEnum.SetupWizard,
|
||||||
|
ModelNameEnum.PatchRun,
|
||||||
|
].includes(schemaName as ModelNameEnum);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
return schemaNames.map((sn) => ({
|
||||||
|
value: sn,
|
||||||
|
label: this.fyo.schemaMap[sn]?.label ?? sn,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
helpMessage() {
|
||||||
|
if (!this.formType) {
|
||||||
|
return this.t`Select a form type to customize`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
setEntryType(type: string) {
|
||||||
|
this.formType = type;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
@ -10,6 +10,7 @@ import QuickEditForm from 'src/pages/QuickEditForm.vue';
|
|||||||
import Report from 'src/pages/Report.vue';
|
import Report from 'src/pages/Report.vue';
|
||||||
import Settings from 'src/pages/Settings/Settings.vue';
|
import Settings from 'src/pages/Settings/Settings.vue';
|
||||||
import TemplateBuilder from 'src/pages/TemplateBuilder/TemplateBuilder.vue';
|
import TemplateBuilder from 'src/pages/TemplateBuilder/TemplateBuilder.vue';
|
||||||
|
import CustomizeForm from 'src/pages/CustomizeForm/CustomizeForm.vue';
|
||||||
import type { HistoryState } from 'vue-router';
|
import type { HistoryState } from 'vue-router';
|
||||||
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router';
|
||||||
import { historyState } from './utils/refs';
|
import { historyState } from './utils/refs';
|
||||||
@ -106,6 +107,11 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: TemplateBuilder,
|
component: TemplateBuilder,
|
||||||
props: true,
|
props: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/customize-form',
|
||||||
|
name: 'Customize Form',
|
||||||
|
component: CustomizeForm,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
name: 'Settings',
|
name: 'Settings',
|
||||||
|
@ -217,6 +217,10 @@ function getListViewList(fyo: Fyo): SearchItem[] {
|
|||||||
schemaNames.push(ModelNameEnum.SerialNumber);
|
schemaNames.push(ModelNameEnum.SerialNumber);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (fyo.doc.singles.AccountingSettings?.enableFormCustomization) {
|
||||||
|
schemaNames.push(ModelNameEnum.CustomForm);
|
||||||
|
}
|
||||||
|
|
||||||
if (fyo.store.isDevelopment) {
|
if (fyo.store.isDevelopment) {
|
||||||
schemaNames = Object.keys(fyo.schemaMap) as ModelNameEnum[];
|
schemaNames = Object.keys(fyo.schemaMap) as ModelNameEnum[];
|
||||||
}
|
}
|
||||||
|
@ -284,6 +284,14 @@ function getCompleteSidebar(): SidebarConfig {
|
|||||||
name: 'print-template',
|
name: 'print-template',
|
||||||
route: `/list/PrintTemplate/${t`Print Templates`}`,
|
route: `/list/PrintTemplate/${t`Print Templates`}`,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: t`Customize Form`,
|
||||||
|
name: 'customize-form',
|
||||||
|
// route: `/customize-form`,
|
||||||
|
route: `/list/CustomForm/${t`Customize Form`}`,
|
||||||
|
hidden: () =>
|
||||||
|
!fyo.singles.AccountingSettings?.enableFormCustomization,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: t`Settings`,
|
label: t`Settings`,
|
||||||
name: 'settings',
|
name: 'settings',
|
||||||
|
@ -2,6 +2,7 @@ import { DatabaseManager } from 'backend/database/manager';
|
|||||||
import { config } from 'dotenv';
|
import { config } from 'dotenv';
|
||||||
import { Fyo } from 'fyo';
|
import { Fyo } from 'fyo';
|
||||||
import { DummyAuthDemux } from 'fyo/tests/helpers';
|
import { DummyAuthDemux } from 'fyo/tests/helpers';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import setupInstance from 'src/setup/setupInstance';
|
import setupInstance from 'src/setup/setupInstance';
|
||||||
import { SetupWizardOptions } from 'src/setup/types';
|
import { SetupWizardOptions } from 'src/setup/types';
|
||||||
@ -17,8 +18,12 @@ export function getTestSetupWizardOptions(): SetupWizardOptions {
|
|||||||
email: 'test@testmyfantasy.com',
|
email: 'test@testmyfantasy.com',
|
||||||
bankName: 'Test Bank of Scriptia',
|
bankName: 'Test Bank of Scriptia',
|
||||||
currency: 'INR',
|
currency: 'INR',
|
||||||
fiscalYearStart: getFiscalYear('04-01', true)!.toISOString().split('T')[0],
|
fiscalYearStart: DateTime.fromJSDate(
|
||||||
fiscalYearEnd: getFiscalYear('04-01', false)!.toISOString().split('T')[0],
|
getFiscalYear('04-01', true)!
|
||||||
|
).toISODate(),
|
||||||
|
fiscalYearEnd: DateTime.fromJSDate(
|
||||||
|
getFiscalYear('04-01', false)!
|
||||||
|
).toISODate(),
|
||||||
chartOfAccounts: 'India - Chart of Accounts',
|
chartOfAccounts: 'India - Chart of Accounts',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
import { assertDoesNotThrow } from 'backend/database/tests/helpers';
|
import { assertDoesNotThrow } from 'backend/database/tests/helpers';
|
||||||
|
import { DateTime } from 'luxon';
|
||||||
import setupInstance from 'src/setup/setupInstance';
|
import setupInstance from 'src/setup/setupInstance';
|
||||||
import { SetupWizardOptions } from 'src/setup/types';
|
import { SetupWizardOptions } from 'src/setup/types';
|
||||||
import test from 'tape';
|
import test from 'tape';
|
||||||
@ -39,7 +40,7 @@ test('check setup Singles', async (t) => {
|
|||||||
const optionsValue = setupOptions[field as keyof SetupWizardOptions];
|
const optionsValue = setupOptions[field as keyof SetupWizardOptions];
|
||||||
|
|
||||||
if (dbValue instanceof Date) {
|
if (dbValue instanceof Date) {
|
||||||
dbValue = dbValue.toISOString().split('T')[0];
|
dbValue = DateTime.fromJSDate(dbValue).toISODate();
|
||||||
}
|
}
|
||||||
|
|
||||||
t.equal(
|
t.equal(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user