mirror of
https://github.com/frappe/books.git
synced 2024-11-14 01:14:03 +00:00
9424b131be
- utils is frontend safe
332 lines
7.8 KiB
JavaScript
332 lines
7.8 KiB
JavaScript
import frappe from 'frappe';
|
|
import { validTypes } from 'frappe/backends/helpers';
|
|
import { ValueError } from 'frappe/utils/errors';
|
|
import { indicators as indicatorColor } from '../../src/colors';
|
|
import { t } from '../utils/translation';
|
|
import Document from './document';
|
|
import model from './index';
|
|
|
|
export default class Meta extends Document {
|
|
constructor(data) {
|
|
if (data.basedOn) {
|
|
let config = frappe.models[data.basedOn];
|
|
Object.assign(data, config, {
|
|
name: data.name,
|
|
label: data.label,
|
|
filters: data.filters,
|
|
});
|
|
}
|
|
super(data);
|
|
this.setDefaultIndicators();
|
|
if (this.setupMeta) {
|
|
this.setupMeta();
|
|
}
|
|
if (!this.titleField) {
|
|
this.titleField = 'name';
|
|
}
|
|
}
|
|
|
|
setValues(data) {
|
|
Object.assign(this, data);
|
|
this.processFields();
|
|
}
|
|
|
|
processFields() {
|
|
// add name field
|
|
if (!this.fields.find((df) => df.fieldname === 'name') && !this.isSingle) {
|
|
this.fields = [
|
|
{
|
|
label: t`ID`,
|
|
fieldname: 'name',
|
|
fieldtype: 'Data',
|
|
required: 1,
|
|
readOnly: 1,
|
|
},
|
|
].concat(this.fields);
|
|
}
|
|
|
|
this.fields = this.fields.map((df) => {
|
|
// name field is always required
|
|
if (df.fieldname === 'name') {
|
|
df.required = 1;
|
|
}
|
|
|
|
return df;
|
|
});
|
|
}
|
|
|
|
hasField(fieldname) {
|
|
return this.getField(fieldname) ? true : false;
|
|
}
|
|
|
|
getField(fieldname) {
|
|
if (!this._field_map) {
|
|
this._field_map = {};
|
|
for (let field of this.fields) {
|
|
this._field_map[field.fieldname] = field;
|
|
}
|
|
}
|
|
return this._field_map[fieldname];
|
|
}
|
|
|
|
/**
|
|
* Get fields filtered by filters
|
|
* @param {Object} filters
|
|
*
|
|
* Usage:
|
|
* meta = frappe.getMeta('ToDo')
|
|
* dataFields = meta.getFieldsWith({ fieldtype: 'Data' })
|
|
*/
|
|
getFieldsWith(filters) {
|
|
return this.fields.filter((df) => {
|
|
let match = true;
|
|
for (const key in filters) {
|
|
const value = filters[key];
|
|
match = df[key] === value;
|
|
}
|
|
return match;
|
|
});
|
|
}
|
|
|
|
getLabel(fieldname) {
|
|
let df = this.getField(fieldname);
|
|
return df.getLabel || df.label;
|
|
}
|
|
|
|
getTableFields() {
|
|
if (this._tableFields === undefined) {
|
|
this._tableFields = this.fields.filter(
|
|
(field) => field.fieldtype === 'Table'
|
|
);
|
|
}
|
|
return this._tableFields;
|
|
}
|
|
|
|
getFormulaFields() {
|
|
if (this._formulaFields === undefined) {
|
|
this._formulaFields = this.fields.filter((field) => field.formula);
|
|
}
|
|
return this._formulaFields;
|
|
}
|
|
|
|
hasFormula() {
|
|
if (this._hasFormula === undefined) {
|
|
this._hasFormula = false;
|
|
if (this.getFormulaFields().length) {
|
|
this._hasFormula = true;
|
|
} else {
|
|
for (let tablefield of this.getTableFields()) {
|
|
if (frappe.getMeta(tablefield.childtype).getFormulaFields().length) {
|
|
this._hasFormula = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return this._hasFormula;
|
|
}
|
|
|
|
getBaseDocType() {
|
|
return this.basedOn || this.name;
|
|
}
|
|
|
|
async set(fieldname, value) {
|
|
this[fieldname] = value;
|
|
await this.trigger(fieldname);
|
|
}
|
|
|
|
get(fieldname) {
|
|
return this[fieldname];
|
|
}
|
|
|
|
getValidFields({ withChildren = true } = {}) {
|
|
if (!this._validFields) {
|
|
this._validFields = [];
|
|
this._validFieldsWithChildren = [];
|
|
|
|
const _add = (field) => {
|
|
this._validFields.push(field);
|
|
this._validFieldsWithChildren.push(field);
|
|
};
|
|
|
|
// fields validation
|
|
this.fields.forEach((df, i) => {
|
|
if (!df.fieldname) {
|
|
throw new ValidationError(
|
|
`DocType ${this.name}: "fieldname" is required for field at index ${i}`
|
|
);
|
|
}
|
|
if (!df.fieldtype) {
|
|
throw new ValidationError(
|
|
`DocType ${this.name}: "fieldtype" is required for field "${df.fieldname}"`
|
|
);
|
|
}
|
|
});
|
|
|
|
const doctypeFields = this.fields.map((field) => field.fieldname);
|
|
|
|
// standard fields
|
|
for (let field of model.commonFields) {
|
|
if (
|
|
validTypes.includes(field.fieldtype) &&
|
|
!doctypeFields.includes(field.fieldname)
|
|
) {
|
|
_add(field);
|
|
}
|
|
}
|
|
|
|
if (this.isSubmittable) {
|
|
_add({
|
|
fieldtype: 'Check',
|
|
fieldname: 'submitted',
|
|
label: t`Submitted`,
|
|
});
|
|
}
|
|
|
|
if (this.isChild) {
|
|
// child fields
|
|
for (let field of model.childFields) {
|
|
if (
|
|
validTypes.includes(field.fieldtype) &&
|
|
!doctypeFields.includes(field.fieldname)
|
|
) {
|
|
_add(field);
|
|
}
|
|
}
|
|
} else {
|
|
// parent fields
|
|
for (let field of model.parentFields) {
|
|
if (
|
|
validTypes.includes(field.fieldtype) &&
|
|
!doctypeFields.includes(field.fieldname)
|
|
) {
|
|
_add(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (this.isTree) {
|
|
// tree fields
|
|
for (let field of model.treeFields) {
|
|
if (
|
|
validTypes.includes(field.fieldtype) &&
|
|
!doctypeFields.includes(field.fieldname)
|
|
) {
|
|
_add(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
// doctype fields
|
|
for (let field of this.fields) {
|
|
const include = validTypes.includes(field.fieldtype);
|
|
|
|
if (include) {
|
|
_add(field);
|
|
}
|
|
|
|
// include tables if (withChildren = True)
|
|
if (!include && field.fieldtype === 'Table') {
|
|
this._validFieldsWithChildren.push(field);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (withChildren) {
|
|
return this._validFieldsWithChildren;
|
|
} else {
|
|
return this._validFields;
|
|
}
|
|
}
|
|
|
|
getKeywordFields() {
|
|
if (!this._keywordFields) {
|
|
this._keywordFields = this.keywordFields;
|
|
if (!(this._keywordFields && this._keywordFields.length && this.fields)) {
|
|
this._keywordFields = this.fields
|
|
.filter((field) => field.fieldtype !== 'Table' && field.required)
|
|
.map((field) => field.fieldname);
|
|
}
|
|
if (!(this._keywordFields && this._keywordFields.length)) {
|
|
this._keywordFields = ['name'];
|
|
}
|
|
}
|
|
return this._keywordFields;
|
|
}
|
|
|
|
getQuickEditFields() {
|
|
if (this.quickEditFields) {
|
|
return this.quickEditFields.map((fieldname) => this.getField(fieldname));
|
|
}
|
|
return this.getFieldsWith({ required: 1 });
|
|
}
|
|
|
|
validateSelect(field, value) {
|
|
let options = field.options;
|
|
if (!options) return;
|
|
if (!field.required && value == null) {
|
|
return;
|
|
}
|
|
|
|
let validValues = options;
|
|
|
|
if (typeof options === 'string') {
|
|
// values given as string
|
|
validValues = options.split('\n');
|
|
}
|
|
|
|
if (typeof options[0] === 'object') {
|
|
// options as array of {label, value} pairs
|
|
validValues = options.map((o) => o.value);
|
|
}
|
|
|
|
if (!validValues.includes(value)) {
|
|
throw new ValueError(
|
|
// prettier-ignore
|
|
`DocType ${this.name}: Invalid value "${value}" for "${field.label}". Must be one of ${options.join(', ')}`
|
|
);
|
|
}
|
|
return value;
|
|
}
|
|
|
|
async trigger(event, params = {}) {
|
|
Object.assign(params, {
|
|
doc: this,
|
|
name: event,
|
|
});
|
|
|
|
await super.trigger(event, params);
|
|
}
|
|
|
|
setDefaultIndicators() {
|
|
if (!this.indicators) {
|
|
if (this.isSubmittable) {
|
|
this.indicators = {
|
|
key: 'submitted',
|
|
colors: {
|
|
0: indicatorColor.GRAY,
|
|
1: indicatorColor.BLUE,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
}
|
|
|
|
getIndicatorColor(doc) {
|
|
if (frappe.isDirty(this.name, doc.name)) {
|
|
return indicatorColor.ORANGE;
|
|
} else {
|
|
if (this.indicators) {
|
|
let value = doc[this.indicators.key];
|
|
if (value) {
|
|
return this.indicators.colors[value] || indicatorColor.GRAY;
|
|
} else {
|
|
return indicatorColor.GRAY;
|
|
}
|
|
} else {
|
|
return indicatorColor.GRAY;
|
|
}
|
|
}
|
|
}
|
|
}
|