mirror of
https://github.com/frappe/books.git
synced 2024-12-22 10:58:59 +00:00
incr: add customization schemas and models
This commit is contained in:
parent
202612b57a
commit
e840b5bb7c
@ -1,15 +1,15 @@
|
||||
import { Fyo } from 'fyo';
|
||||
import { DocValue, DocValueMap } from 'fyo/core/types';
|
||||
import type { Fyo } from 'fyo';
|
||||
import type { DocValue, DocValueMap } from 'fyo/core/types';
|
||||
import type SystemSettings from 'fyo/models/SystemSettings';
|
||||
import { FieldType, Schema, SelectOption } from 'schemas/types';
|
||||
import { QueryFilter } from 'utils/db/types';
|
||||
import { RouteLocationRaw, Router } from 'vue-router';
|
||||
import { Doc } from './doc';
|
||||
import type { FieldType, Schema, SelectOption } from 'schemas/types';
|
||||
import type { QueryFilter } from 'utils/db/types';
|
||||
import type { RouteLocationRaw, Router } from 'vue-router';
|
||||
import type { Doc } from './doc';
|
||||
import type { AccountingSettings } from 'models/baseModels/AccountingSettings/AccountingSettings';
|
||||
import type { Defaults } from 'models/baseModels/Defaults/Defaults';
|
||||
import type { PrintSettings } from 'models/baseModels/PrintSettings/PrintSettings';
|
||||
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
|
||||
|
102
fyo/models/CustomField.ts
Normal file
102
fyo/models/CustomField.ts
Normal file
@ -0,0 +1,102 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import type {
|
||||
FormulaMap,
|
||||
HiddenMap,
|
||||
ListsMap,
|
||||
ValidationMap,
|
||||
} from 'fyo/model/types';
|
||||
import type { FieldType, SelectOption } from 'schemas/types';
|
||||
import type { CustomForm } from './CustomForm';
|
||||
import { ValueError } from 'fyo/utils/errors';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
hidden: HiddenMap = {
|
||||
options: () =>
|
||||
this.fieldtype !== 'Select' &&
|
||||
this.fieldtype !== 'AutoComplete' &&
|
||||
this.fieldtype !== 'Color',
|
||||
target: () => this.fieldtype !== 'Link' && this.fieldtype !== 'Table',
|
||||
references: () => this.fieldtype !== 'DynamicLink',
|
||||
};
|
||||
|
||||
formulas: FormulaMap = {};
|
||||
|
||||
validations: ValidationMap = {
|
||||
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 [];
|
||||
}
|
||||
|
||||
return (doc.parentdoc?.customFields
|
||||
?.map((cf) => ({ value: cf.fieldname, label: cf.label }))
|
||||
.filter((cf) => cf.label && cf.value) ?? []) as SelectOption[];
|
||||
},
|
||||
};
|
||||
}
|
52
fyo/models/CustomForm.ts
Normal file
52
fyo/models/CustomForm.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import { Doc } from 'fyo/model/doc';
|
||||
import { HiddenMap, ListsMap } from 'fyo/model/types';
|
||||
import { ModelNameEnum } from 'models/types';
|
||||
import type { CustomField } from './CustomField';
|
||||
import { getMapFromList } from 'utils/index';
|
||||
import { Field } from 'schemas/types';
|
||||
|
||||
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 };
|
||||
}
|
@ -1,8 +1,12 @@
|
||||
import { ModelMap } from 'fyo/model/types';
|
||||
import NumberSeries from './NumberSeries';
|
||||
import SystemSettings from './SystemSettings';
|
||||
import { CustomField } from './CustomField';
|
||||
import { CustomForm } from './CustomForm';
|
||||
|
||||
export const coreModels = {
|
||||
NumberSeries,
|
||||
SystemSettings,
|
||||
CustomForm,
|
||||
CustomField,
|
||||
} as ModelMap;
|
||||
|
@ -44,6 +44,8 @@ export enum ModelNameEnum {
|
||||
PurchaseReceipt = 'PurchaseReceipt',
|
||||
PurchaseReceiptItem = 'PurchaseReceiptItem',
|
||||
Location = 'Location',
|
||||
CustomForm = 'CustomForm',
|
||||
CustomField = 'CustomField'
|
||||
}
|
||||
|
||||
export type ModelName = keyof typeof ModelNameEnum;
|
||||
|
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"],
|
||||
"quickEditFields": [
|
||||
"label",
|
||||
"fieldname",
|
||||
"fieldtype",
|
||||
"required",
|
||||
"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
|
||||
}
|
||||
]
|
||||
}
|
@ -7,21 +7,6 @@ import Color from './app/Color.json';
|
||||
import Currency from './app/Currency.json';
|
||||
import Defaults from './app/Defaults.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 InvoiceItem from './app/InvoiceItem.json';
|
||||
import Item from './app/Item.json';
|
||||
@ -32,6 +17,8 @@ import NumberSeries from './app/NumberSeries.json';
|
||||
import Party from './app/Party.json';
|
||||
import Payment from './app/Payment.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 PrintTemplate from './app/PrintTemplate.json';
|
||||
import PurchaseInvoice from './app/PurchaseInvoice.json';
|
||||
@ -43,6 +30,21 @@ import Tax from './app/Tax.json';
|
||||
import TaxDetail from './app/TaxDetail.json';
|
||||
import TaxSummary from './app/TaxSummary.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 SingleValue from './core/SingleValue.json';
|
||||
import SystemSettings from './core/SystemSettings.json';
|
||||
@ -124,4 +126,7 @@ export const appSchemas: Schema[] | SchemaStub[] = [
|
||||
|
||||
Batch as Schema,
|
||||
SerialNumber as Schema,
|
||||
|
||||
CustomForm as Schema,
|
||||
CustomField as Schema,
|
||||
];
|
||||
|
@ -49,8 +49,8 @@ type BaseFieldType = Exclude<
|
||||
export type RawValue = string | number | boolean | null;
|
||||
|
||||
export interface BaseField {
|
||||
fieldname: string; // Column name in the db
|
||||
fieldtype: BaseFieldType; // UI Descriptive field types that map to column types
|
||||
fieldname: string; // Column name in the db
|
||||
fieldtype: BaseFieldType; // UI Descriptive field types that map to column types
|
||||
label: string; // Translateable UI facing name
|
||||
schemaName?: string; // Convenient access to schemaName incase just the field is passed
|
||||
required?: boolean; // Implies Not Null
|
||||
@ -61,11 +61,12 @@ export interface BaseField {
|
||||
placeholder?: string; // UI Facing config, form field placeholder
|
||||
groupBy?: string; // UI Facing used in dropdowns fields
|
||||
meta?: boolean; // Field is a meta field, i.e. only for the db, not UI
|
||||
filter?: boolean; // UI Facing config, whether to be used to filter the List.
|
||||
filter?: boolean; // UI Facing config, whether to be used to filter the List.
|
||||
computed?: boolean; // Computed values are not stored in the database.
|
||||
section?: string; // UI Facing config, for grouping by sections
|
||||
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 };
|
||||
|
@ -6,6 +6,43 @@
|
||||
{{ 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">
|
||||
@ -14,8 +51,51 @@ 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 },
|
||||
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>
|
||||
|
Loading…
Reference in New Issue
Block a user