mirror of
https://github.com/frappe/books.git
synced 2024-11-13 00:46:28 +00:00
- Custom doc for Report Filters
- frappe.showModal - Custom textarea rows
This commit is contained in:
parent
eddcaa084c
commit
79b99055e0
526
index.js
526
index.js
@ -1,261 +1,285 @@
|
||||
const Observable = require('./utils/observable');
|
||||
|
||||
module.exports = {
|
||||
async init() {
|
||||
if (this._initialized) return;
|
||||
this.initConfig();
|
||||
this.initGlobals();
|
||||
this.docs = new Observable();
|
||||
this.events = new Observable();
|
||||
this._initialized = true;
|
||||
},
|
||||
async init() {
|
||||
if (this._initialized) return;
|
||||
this.initConfig();
|
||||
this.initGlobals();
|
||||
this.docs = new Observable();
|
||||
this.events = new Observable();
|
||||
this._initialized = true;
|
||||
},
|
||||
|
||||
initConfig() {
|
||||
this.config = {
|
||||
serverURL: '',
|
||||
backend: 'sqlite',
|
||||
port: 8000
|
||||
};
|
||||
},
|
||||
initConfig() {
|
||||
this.config = {
|
||||
serverURL: '',
|
||||
backend: 'sqlite',
|
||||
port: 8000
|
||||
};
|
||||
},
|
||||
|
||||
initGlobals() {
|
||||
this.metaCache = {};
|
||||
this.models = {};
|
||||
this.forms = {};
|
||||
this.views = {};
|
||||
this.flags = {};
|
||||
this.methods = {};
|
||||
// temp params while calling routes
|
||||
this.params = {};
|
||||
},
|
||||
initGlobals() {
|
||||
this.metaCache = {};
|
||||
this.models = {};
|
||||
this.forms = {};
|
||||
this.views = {};
|
||||
this.flags = {};
|
||||
this.methods = {};
|
||||
// temp params while calling routes
|
||||
this.params = {};
|
||||
},
|
||||
|
||||
registerLibs(common) {
|
||||
// add standard libs and utils to frappe
|
||||
common.initLibs(this);
|
||||
},
|
||||
registerLibs(common) {
|
||||
// add standard libs and utils to frappe
|
||||
common.initLibs(this);
|
||||
},
|
||||
|
||||
registerModels(models, type) {
|
||||
// register models from app/models/index.js
|
||||
const toAdd = Object.assign({}, models.models);
|
||||
registerModels(models, type) {
|
||||
// register models from app/models/index.js
|
||||
const toAdd = Object.assign({}, models.models);
|
||||
|
||||
// post process based on type
|
||||
if (models[type]) {
|
||||
models[type](toAdd);
|
||||
}
|
||||
|
||||
Object.assign(this.models, toAdd);
|
||||
},
|
||||
|
||||
registerView(view, name, module) {
|
||||
if (!this.views[view]) this.views[view] = {};
|
||||
this.views[view][name] = module;
|
||||
},
|
||||
|
||||
registerMethod({method, handler}) {
|
||||
this.methods[method] = handler;
|
||||
if (this.app) {
|
||||
// add to router if client-server
|
||||
this.app.post(`/api/method/${method}`, this.asyncHandler(async function(request, response) {
|
||||
let data = await handler(request.body);
|
||||
if (data === undefined) {
|
||||
data = {}
|
||||
}
|
||||
return response.json(data);
|
||||
}));
|
||||
}
|
||||
},
|
||||
|
||||
async call({method, args}) {
|
||||
if (this.isServer) {
|
||||
if (this.methods[method]) {
|
||||
return await this.methods[method](args);
|
||||
} else {
|
||||
throw `${method} not found`;
|
||||
}
|
||||
}
|
||||
|
||||
let url = `/api/method/${method}`;
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(args || {})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
addToCache(doc) {
|
||||
if (!this.docs) return;
|
||||
|
||||
// add to `docs` cache
|
||||
if (doc.doctype && doc.name) {
|
||||
if (!this.docs[doc.doctype]) {
|
||||
this.docs[doc.doctype] = {};
|
||||
}
|
||||
this.docs[doc.doctype][doc.name] = doc;
|
||||
|
||||
// singles available as first level objects too
|
||||
if (doc.doctype === doc.name) {
|
||||
this[doc.name] = doc;
|
||||
}
|
||||
|
||||
// propogate change to `docs`
|
||||
doc.on('change', params => {
|
||||
this.docs.trigger('change', params);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isDirty(doctype, name) {
|
||||
return (this.docs && this.docs[doctype] && this.docs[doctype][name]
|
||||
&& this.docs[doctype][name]._dirty) || false;
|
||||
},
|
||||
|
||||
getDocFromCache(doctype, name) {
|
||||
if (this.docs && this.docs[doctype] && this.docs[doctype][name]) {
|
||||
return this.docs[doctype][name];
|
||||
}
|
||||
},
|
||||
|
||||
getMeta(doctype) {
|
||||
if (!this.metaCache[doctype]) {
|
||||
let model = this.models[doctype];
|
||||
if (!model) {
|
||||
throw `${doctype} is not a registered doctype`;
|
||||
}
|
||||
let metaClass = model.metaClass || this.BaseMeta;
|
||||
this.metaCache[doctype] = new metaClass(model);
|
||||
}
|
||||
|
||||
return this.metaCache[doctype];
|
||||
},
|
||||
|
||||
async getDoc(doctype, name) {
|
||||
let doc = this.getDocFromCache(doctype, name);
|
||||
if (!doc) {
|
||||
doc = new (this.getDocumentClass(doctype))({doctype:doctype, name: name});
|
||||
await doc.load();
|
||||
this.addToCache(doc);
|
||||
}
|
||||
return doc;
|
||||
},
|
||||
|
||||
getDocumentClass(doctype) {
|
||||
const meta = this.getMeta(doctype);
|
||||
return meta.documentClass || this.BaseDocument;
|
||||
},
|
||||
|
||||
async getSingle(doctype) {
|
||||
return await this.getDoc(doctype, doctype);
|
||||
},
|
||||
|
||||
async getDuplicate(doc) {
|
||||
const newDoc = await this.getNewDoc(doc.doctype);
|
||||
for (let field of this.getMeta(doc.doctype).getValidFields()) {
|
||||
if (['name', 'submitted'].includes(field.fieldname)) continue;
|
||||
if (field.fieldtype === 'Table') {
|
||||
newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => {
|
||||
let newd = Object.assign({}, d);
|
||||
newd.name = '';
|
||||
return newd;
|
||||
});
|
||||
} else {
|
||||
newDoc[field.fieldname] = doc[field.fieldname];
|
||||
}
|
||||
}
|
||||
return newDoc;
|
||||
},
|
||||
|
||||
async getNewDoc(doctype) {
|
||||
let doc = this.newDoc({doctype: doctype});
|
||||
doc._notInserted = true;
|
||||
doc.name = this.getRandomString();
|
||||
this.addToCache(doc);
|
||||
return doc;
|
||||
},
|
||||
|
||||
newDoc(data) {
|
||||
let doc = new (this.getDocumentClass(data.doctype))(data);
|
||||
doc.setDefaults();
|
||||
return doc;
|
||||
},
|
||||
|
||||
async insert(data) {
|
||||
return await (this.newDoc(data)).insert();
|
||||
},
|
||||
|
||||
async syncDoc(data) {
|
||||
let doc;
|
||||
if (await this.db.exists(data.doctype, data.name)) {
|
||||
doc = await this.getDoc(data.doctype, data.name);
|
||||
Object.assign(doc, data);
|
||||
await doc.update();
|
||||
} else {
|
||||
doc = this.newDoc(data);
|
||||
await doc.insert();
|
||||
}
|
||||
},
|
||||
|
||||
// only for client side
|
||||
async login(email, password) {
|
||||
if (email === 'Administrator') {
|
||||
this.session = {
|
||||
user: 'Administrator'
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await fetch(this.getServerURL() + '/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
const res = await response.json();
|
||||
|
||||
this.session = {
|
||||
user: email,
|
||||
token: res.token
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
async signup(email, fullName, password) {
|
||||
let response = await fetch(this.getServerURL() + '/api/signup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, fullName, password })
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
getServerURL() {
|
||||
return this.config.serverURL || '';
|
||||
},
|
||||
|
||||
close() {
|
||||
this.db.close();
|
||||
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
}
|
||||
// post process based on type
|
||||
if (models[type]) {
|
||||
models[type](toAdd);
|
||||
}
|
||||
|
||||
Object.assign(this.models, toAdd);
|
||||
},
|
||||
|
||||
registerView(view, name, module) {
|
||||
if (!this.views[view]) this.views[view] = {};
|
||||
this.views[view][name] = module;
|
||||
},
|
||||
|
||||
registerMethod({ method, handler }) {
|
||||
this.methods[method] = handler;
|
||||
if (this.app) {
|
||||
// add to router if client-server
|
||||
this.app.post(
|
||||
`/api/method/${method}`,
|
||||
this.asyncHandler(async function(request, response) {
|
||||
let data = await handler(request.body);
|
||||
if (data === undefined) {
|
||||
data = {};
|
||||
}
|
||||
return response.json(data);
|
||||
})
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
async call({ method, args }) {
|
||||
if (this.isServer) {
|
||||
if (this.methods[method]) {
|
||||
return await this.methods[method](args);
|
||||
} else {
|
||||
throw `${method} not found`;
|
||||
}
|
||||
}
|
||||
|
||||
let url = `/api/method/${method}`;
|
||||
let response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(args || {})
|
||||
});
|
||||
return await response.json();
|
||||
},
|
||||
|
||||
addToCache(doc) {
|
||||
if (!this.docs) return;
|
||||
|
||||
// add to `docs` cache
|
||||
if (doc.doctype && doc.name) {
|
||||
if (!this.docs[doc.doctype]) {
|
||||
this.docs[doc.doctype] = {};
|
||||
}
|
||||
this.docs[doc.doctype][doc.name] = doc;
|
||||
|
||||
// singles available as first level objects too
|
||||
if (doc.doctype === doc.name) {
|
||||
this[doc.name] = doc;
|
||||
}
|
||||
|
||||
// propogate change to `docs`
|
||||
doc.on('change', params => {
|
||||
this.docs.trigger('change', params);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
isDirty(doctype, name) {
|
||||
return (
|
||||
(this.docs &&
|
||||
this.docs[doctype] &&
|
||||
this.docs[doctype][name] &&
|
||||
this.docs[doctype][name]._dirty) ||
|
||||
false
|
||||
);
|
||||
},
|
||||
|
||||
getDocFromCache(doctype, name) {
|
||||
if (this.docs && this.docs[doctype] && this.docs[doctype][name]) {
|
||||
return this.docs[doctype][name];
|
||||
}
|
||||
},
|
||||
|
||||
getMeta(doctype) {
|
||||
if (!this.metaCache[doctype]) {
|
||||
let model = this.models[doctype];
|
||||
if (!model) {
|
||||
throw `${doctype} is not a registered doctype`;
|
||||
}
|
||||
let metaClass = model.metaClass || this.BaseMeta;
|
||||
this.metaCache[doctype] = new metaClass(model);
|
||||
}
|
||||
|
||||
return this.metaCache[doctype];
|
||||
},
|
||||
|
||||
async getDoc(doctype, name) {
|
||||
let doc = this.getDocFromCache(doctype, name);
|
||||
if (!doc) {
|
||||
doc = new (this.getDocumentClass(doctype))({
|
||||
doctype: doctype,
|
||||
name: name
|
||||
});
|
||||
await doc.load();
|
||||
this.addToCache(doc);
|
||||
}
|
||||
return doc;
|
||||
},
|
||||
|
||||
getDocumentClass(doctype) {
|
||||
const meta = this.getMeta(doctype);
|
||||
return meta.documentClass || this.BaseDocument;
|
||||
},
|
||||
|
||||
async getSingle(doctype) {
|
||||
return await this.getDoc(doctype, doctype);
|
||||
},
|
||||
|
||||
async getDuplicate(doc) {
|
||||
const newDoc = await this.getNewDoc(doc.doctype);
|
||||
for (let field of this.getMeta(doc.doctype).getValidFields()) {
|
||||
if (['name', 'submitted'].includes(field.fieldname)) continue;
|
||||
if (field.fieldtype === 'Table') {
|
||||
newDoc[field.fieldname] = (doc[field.fieldname] || []).map(d => {
|
||||
let newd = Object.assign({}, d);
|
||||
newd.name = '';
|
||||
return newd;
|
||||
});
|
||||
} else {
|
||||
newDoc[field.fieldname] = doc[field.fieldname];
|
||||
}
|
||||
}
|
||||
return newDoc;
|
||||
},
|
||||
|
||||
async getNewDoc(doctype) {
|
||||
let doc = this.newDoc({ doctype: doctype });
|
||||
doc._notInserted = true;
|
||||
doc.name = this.getRandomString();
|
||||
this.addToCache(doc);
|
||||
return doc;
|
||||
},
|
||||
|
||||
async newCustomDoc(fields) {
|
||||
let doc = new this.BaseDocument({ isCustom: 1, fields });
|
||||
doc._notInserted = true;
|
||||
doc.name = this.getRandomString();
|
||||
this.addToCache(doc);
|
||||
return doc;
|
||||
},
|
||||
|
||||
createMeta(fields) {
|
||||
let meta = new this.BaseMeta({ isCustom: 1, fields });
|
||||
return meta;
|
||||
},
|
||||
|
||||
newDoc(data) {
|
||||
let doc = new (this.getDocumentClass(data.doctype))(data);
|
||||
doc.setDefaults();
|
||||
return doc;
|
||||
},
|
||||
|
||||
async insert(data) {
|
||||
return await this.newDoc(data).insert();
|
||||
},
|
||||
|
||||
async syncDoc(data) {
|
||||
let doc;
|
||||
if (await this.db.exists(data.doctype, data.name)) {
|
||||
doc = await this.getDoc(data.doctype, data.name);
|
||||
Object.assign(doc, data);
|
||||
await doc.update();
|
||||
} else {
|
||||
doc = this.newDoc(data);
|
||||
await doc.insert();
|
||||
}
|
||||
},
|
||||
|
||||
// only for client side
|
||||
async login(email, password) {
|
||||
if (email === 'Administrator') {
|
||||
this.session = {
|
||||
user: 'Administrator'
|
||||
};
|
||||
return;
|
||||
}
|
||||
|
||||
let response = await fetch(this.getServerURL() + '/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, password })
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
const res = await response.json();
|
||||
|
||||
this.session = {
|
||||
user: email,
|
||||
token: res.token
|
||||
};
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
async signup(email, fullName, password) {
|
||||
let response = await fetch(this.getServerURL() + '/api/signup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ email, fullName, password })
|
||||
});
|
||||
|
||||
if (response.status === 200) {
|
||||
return await response.json();
|
||||
}
|
||||
|
||||
return response;
|
||||
},
|
||||
|
||||
getServerURL() {
|
||||
return this.config.serverURL || '';
|
||||
},
|
||||
|
||||
close() {
|
||||
this.db.close();
|
||||
|
||||
if (this.server) {
|
||||
this.server.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -3,354 +3,375 @@ const Observable = require('frappejs/utils/observable');
|
||||
const naming = require('./naming');
|
||||
|
||||
module.exports = class BaseDocument extends Observable {
|
||||
constructor(data) {
|
||||
super();
|
||||
this.fetchValuesCache = {};
|
||||
this.flags = {};
|
||||
this.setup();
|
||||
Object.assign(this, data);
|
||||
constructor(data) {
|
||||
super();
|
||||
this.fetchValuesCache = {};
|
||||
this.flags = {};
|
||||
this.setup();
|
||||
Object.assign(this, data);
|
||||
|
||||
// clear fetch-values cache
|
||||
frappe.db.on('change', (params) => this.fetchValuesCache[`${params.doctype}:${params.name}`] = {});
|
||||
// clear fetch-values cache
|
||||
frappe.db.on(
|
||||
'change',
|
||||
params => (this.fetchValuesCache[`${params.doctype}:${params.name}`] = {})
|
||||
);
|
||||
}
|
||||
|
||||
setup() {
|
||||
// add listeners
|
||||
}
|
||||
|
||||
get meta() {
|
||||
if (this.isCustom) {
|
||||
this._meta = frappe.createMeta(this.fields);
|
||||
}
|
||||
if (!this._meta) {
|
||||
this._meta = frappe.getMeta(this.doctype);
|
||||
}
|
||||
return this._meta;
|
||||
}
|
||||
|
||||
async getSettings() {
|
||||
if (!this._settings) {
|
||||
this._settings = await frappe.getSingle(this.meta.settings);
|
||||
}
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
// set value and trigger change
|
||||
async set(fieldname, value) {
|
||||
if (typeof fieldname === 'object') {
|
||||
const valueDict = fieldname;
|
||||
for (let fieldname in valueDict) {
|
||||
await this.set(fieldname, valueDict[fieldname]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
setup() {
|
||||
// add listeners
|
||||
if (this[fieldname] !== value) {
|
||||
this._dirty = true;
|
||||
this[fieldname] = await this.validateField(fieldname, value);
|
||||
await this.applyChange(fieldname);
|
||||
}
|
||||
}
|
||||
|
||||
get meta() {
|
||||
if (!this._meta) {
|
||||
this._meta = frappe.getMeta(this.doctype);
|
||||
async applyChange(fieldname) {
|
||||
if (await this.applyFormula()) {
|
||||
// multiple changes
|
||||
await this.trigger('change', {
|
||||
doc: this
|
||||
});
|
||||
} else {
|
||||
// no other change, trigger control refresh
|
||||
await this.trigger('change', {
|
||||
doc: this,
|
||||
fieldname: fieldname
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setDefaults() {
|
||||
for (let field of this.meta.fields) {
|
||||
if (
|
||||
this[field.fieldname] === null ||
|
||||
this[field.fieldname] === undefined
|
||||
) {
|
||||
let defaultValue = null;
|
||||
|
||||
if (field.fieldtype === 'Table') {
|
||||
defaultValue = [];
|
||||
}
|
||||
return this._meta;
|
||||
}
|
||||
|
||||
async getSettings() {
|
||||
if (!this._settings) {
|
||||
this._settings = await frappe.getSingle(this.meta.settings);
|
||||
}
|
||||
return this._settings;
|
||||
}
|
||||
|
||||
// set value and trigger change
|
||||
async set(fieldname, value) {
|
||||
if (typeof fieldname === 'object') {
|
||||
const valueDict = fieldname;
|
||||
for (let fieldname in valueDict) {
|
||||
await this.set(fieldname, valueDict[fieldname]);
|
||||
}
|
||||
return;
|
||||
if (field.default) {
|
||||
defaultValue = field.default;
|
||||
}
|
||||
|
||||
if (this[fieldname] !== value) {
|
||||
this._dirty = true;
|
||||
this[fieldname] = await this.validateField(fieldname, value);
|
||||
await this.applyChange(fieldname);
|
||||
}
|
||||
this[field.fieldname] = defaultValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async applyChange(fieldname) {
|
||||
if (await this.applyFormula()) {
|
||||
// multiple changes
|
||||
await this.trigger('change', {
|
||||
doc: this
|
||||
});
|
||||
} else {
|
||||
// no other change, trigger control refresh
|
||||
await this.trigger('change', {
|
||||
doc: this,
|
||||
fieldname: fieldname
|
||||
});
|
||||
}
|
||||
setKeywords() {
|
||||
let keywords = [];
|
||||
for (let fieldname of this.meta.getKeywordFields()) {
|
||||
keywords.push(this[fieldname]);
|
||||
}
|
||||
this.keywords = keywords.join(', ');
|
||||
}
|
||||
|
||||
setDefaults() {
|
||||
for (let field of this.meta.fields) {
|
||||
if (this[field.fieldname] === null || this[field.fieldname] === undefined) {
|
||||
|
||||
let defaultValue = null;
|
||||
|
||||
if (field.fieldtype === 'Table') {
|
||||
defaultValue = [];
|
||||
}
|
||||
if (field.default) {
|
||||
defaultValue = field.default;
|
||||
}
|
||||
|
||||
this[field.fieldname] = defaultValue;
|
||||
}
|
||||
}
|
||||
append(key, document) {
|
||||
if (!this[key]) {
|
||||
this[key] = [];
|
||||
}
|
||||
this[key].push(this.initDoc(document));
|
||||
}
|
||||
|
||||
setKeywords() {
|
||||
let keywords = [];
|
||||
for (let fieldname of this.meta.getKeywordFields()) {
|
||||
keywords.push(this[fieldname]);
|
||||
}
|
||||
this.keywords = keywords.join(', ');
|
||||
initDoc(data) {
|
||||
if (data.prototype instanceof Document) {
|
||||
return data;
|
||||
} else {
|
||||
return new Document(data);
|
||||
}
|
||||
}
|
||||
|
||||
append(key, document) {
|
||||
if (!this[key]) {
|
||||
this[key] = [];
|
||||
}
|
||||
this[key].push(this.initDoc(document));
|
||||
async validateField(key, value) {
|
||||
let field = this.meta.getField(key);
|
||||
if (field && field.fieldtype == 'Select') {
|
||||
return this.meta.validateSelect(field, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
initDoc(data) {
|
||||
if (data.prototype instanceof Document) {
|
||||
return data;
|
||||
} else {
|
||||
return new Document(data);
|
||||
}
|
||||
getValidDict() {
|
||||
let data = {};
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
data[field.fieldname] = this[field.fieldname];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
async validateField(key, value) {
|
||||
let field = this.meta.getField(key);
|
||||
if (field && field.fieldtype == 'Select') {
|
||||
return this.meta.validateSelect(field, value);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
getFullDict() {
|
||||
let data = this.getValidDict();
|
||||
return data;
|
||||
}
|
||||
|
||||
getValidDict() {
|
||||
let data = {};
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
data[field.fieldname] = this[field.fieldname];
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
getFullDict() {
|
||||
let data = this.getValidDict();
|
||||
return data;
|
||||
}
|
||||
|
||||
setStandardValues() {
|
||||
// set standard values on server-side only
|
||||
if (frappe.isServer) {
|
||||
let now = (new Date()).toISOString();
|
||||
if (!this.submitted) {
|
||||
this.submitted = 0;
|
||||
}
|
||||
|
||||
if (!this.owner) {
|
||||
this.owner = frappe.session.user;
|
||||
}
|
||||
|
||||
if (!this.creation) {
|
||||
this.creation = now;
|
||||
}
|
||||
|
||||
if (!this.modifiedBy) {
|
||||
this.modifiedBy = frappe.session.user;
|
||||
}
|
||||
this.modified = now;
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
let data = await frappe.db.get(this.doctype, this.name);
|
||||
if (data.name) {
|
||||
this.syncValues(data);
|
||||
if (this.meta.isSingle) {
|
||||
this.setDefaults();
|
||||
}
|
||||
} else {
|
||||
throw new frappe.errors.NotFound(`Not Found: ${this.doctype} ${this.name}`);
|
||||
}
|
||||
}
|
||||
|
||||
syncValues(data) {
|
||||
this.clearValues();
|
||||
Object.assign(this, data);
|
||||
this._dirty = false;
|
||||
this.trigger('change', {
|
||||
doc: this
|
||||
});
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
if (this[field.fieldname]) {
|
||||
delete this[field.fieldname];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setChildIdx() {
|
||||
// renumber children
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
if (field.fieldtype === 'Table') {
|
||||
for (let i = 0; i < (this[field.fieldname] || []).length; i++) {
|
||||
this[field.fieldname][i].idx = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compareWithCurrentDoc() {
|
||||
if (frappe.isServer && !this.isNew()) {
|
||||
let currentDoc = await frappe.db.get(this.doctype, this.name);
|
||||
|
||||
// check for conflict
|
||||
if (currentDoc && this.modified != currentDoc.modified) {
|
||||
throw new frappe.errors.Conflict(frappe._('Document {0} {1} has been modified after loading', [this.doctype, this.name]));
|
||||
}
|
||||
|
||||
if (this.submitted && !this.meta.isSubmittable) {
|
||||
throw new frappe.errors.ValidationError(frappe._('Document type {1} is not submittable', [this.doctype]));
|
||||
}
|
||||
|
||||
// set submit action flag
|
||||
if (this.submitted && !currentDoc.submitted) {
|
||||
this.flags.submitAction = true;
|
||||
}
|
||||
|
||||
if (currentDoc.submitted && !this.submitted) {
|
||||
this.flags.revertAction = true;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
async applyFormula() {
|
||||
if (!this.meta.hasFormula()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let doc = this;
|
||||
|
||||
// children
|
||||
for (let tablefield of this.meta.getTableFields()) {
|
||||
let formulaFields = frappe.getMeta(tablefield.childtype).getFormulaFields();
|
||||
if (formulaFields.length) {
|
||||
|
||||
// for each row
|
||||
for (let row of this[tablefield.fieldname]) {
|
||||
for (let field of formulaFields) {
|
||||
if (shouldApplyFormula(field, row)) {
|
||||
const val = await field.formula(row, doc);
|
||||
if (val !== false && val !== undefined) {
|
||||
row[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parent
|
||||
for (let field of this.meta.getFormulaFields()) {
|
||||
if (shouldApplyFormula(field, doc)) {
|
||||
const val = await field.formula(doc);
|
||||
if (val !== false && val !== undefined) {
|
||||
doc[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
function shouldApplyFormula(field, doc) {
|
||||
if (field.readOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!frappe.isServer || frappe.isElectron) {
|
||||
if (doc[field.fieldname] == null || doc[field.fieldname] == '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async commit() {
|
||||
// re-run triggers
|
||||
this.setStandardValues();
|
||||
this.setKeywords();
|
||||
this.setChildIdx();
|
||||
await this.applyFormula();
|
||||
await this.trigger('validate');
|
||||
}
|
||||
|
||||
async insert() {
|
||||
await naming.setName(this);
|
||||
await this.commit();
|
||||
await this.trigger('beforeInsert');
|
||||
|
||||
const data = await frappe.db.insert(this.doctype, this.getValidDict());
|
||||
this.syncValues(data);
|
||||
|
||||
await this.trigger('afterInsert');
|
||||
await this.trigger('afterSave');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.compareWithCurrentDoc();
|
||||
await this.commit();
|
||||
await this.trigger('beforeUpdate');
|
||||
|
||||
// before submit
|
||||
if (this.flags.submitAction) await this.trigger('beforeSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('beforeRevert');
|
||||
|
||||
const data = await frappe.db.update(this.doctype, this.getValidDict());
|
||||
this.syncValues(data);
|
||||
|
||||
await this.trigger('afterUpdate');
|
||||
await this.trigger('afterSave');
|
||||
|
||||
// after submit
|
||||
if (this.flags.submitAction) await this.trigger('afterSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('afterRevert');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
await this.trigger('beforeDelete');
|
||||
await frappe.db.delete(this.doctype, this.name);
|
||||
await this.trigger('afterDelete');
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.submitted = 1;
|
||||
this.update();
|
||||
}
|
||||
|
||||
async revert() {
|
||||
setStandardValues() {
|
||||
// set standard values on server-side only
|
||||
if (frappe.isServer) {
|
||||
let now = new Date().toISOString();
|
||||
if (!this.submitted) {
|
||||
this.submitted = 0;
|
||||
this.update();
|
||||
}
|
||||
}
|
||||
|
||||
// trigger methods on the class if they match
|
||||
// with the trigger name
|
||||
async trigger(event, params) {
|
||||
if (this[event]) {
|
||||
await this[event](params);
|
||||
if (!this.owner) {
|
||||
this.owner = frappe.session.user;
|
||||
}
|
||||
|
||||
if (!this.creation) {
|
||||
this.creation = now;
|
||||
}
|
||||
|
||||
if (!this.modifiedBy) {
|
||||
this.modifiedBy = frappe.session.user;
|
||||
}
|
||||
this.modified = now;
|
||||
}
|
||||
}
|
||||
|
||||
async load() {
|
||||
let data = await frappe.db.get(this.doctype, this.name);
|
||||
if (data.name) {
|
||||
this.syncValues(data);
|
||||
if (this.meta.isSingle) {
|
||||
this.setDefaults();
|
||||
}
|
||||
} else {
|
||||
throw new frappe.errors.NotFound(
|
||||
`Not Found: ${this.doctype} ${this.name}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
syncValues(data) {
|
||||
this.clearValues();
|
||||
Object.assign(this, data);
|
||||
this._dirty = false;
|
||||
this.trigger('change', {
|
||||
doc: this
|
||||
});
|
||||
}
|
||||
|
||||
clearValues() {
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
if (this[field.fieldname]) {
|
||||
delete this[field.fieldname];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setChildIdx() {
|
||||
// renumber children
|
||||
for (let field of this.meta.getValidFields()) {
|
||||
if (field.fieldtype === 'Table') {
|
||||
for (let i = 0; i < (this[field.fieldname] || []).length; i++) {
|
||||
this[field.fieldname][i].idx = i;
|
||||
}
|
||||
await super.trigger(event, params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async compareWithCurrentDoc() {
|
||||
if (frappe.isServer && !this.isNew()) {
|
||||
let currentDoc = await frappe.db.get(this.doctype, this.name);
|
||||
|
||||
// check for conflict
|
||||
if (currentDoc && this.modified != currentDoc.modified) {
|
||||
throw new frappe.errors.Conflict(
|
||||
frappe._('Document {0} {1} has been modified after loading', [
|
||||
this.doctype,
|
||||
this.name
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
if (this.submitted && !this.meta.isSubmittable) {
|
||||
throw new frappe.errors.ValidationError(
|
||||
frappe._('Document type {1} is not submittable', [this.doctype])
|
||||
);
|
||||
}
|
||||
|
||||
// set submit action flag
|
||||
if (this.submitted && !currentDoc.submitted) {
|
||||
this.flags.submitAction = true;
|
||||
}
|
||||
|
||||
if (currentDoc.submitted && !this.submitted) {
|
||||
this.flags.revertAction = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async applyFormula() {
|
||||
if (!this.meta.hasFormula()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// helper functions
|
||||
getSum(tablefield, childfield) {
|
||||
return this[tablefield].map(d => (d[childfield] || 0)).reduce((a, b) => a + b, 0);
|
||||
}
|
||||
let doc = this;
|
||||
|
||||
async getFrom(doctype, name, fieldname) {
|
||||
if (!name) return '';
|
||||
let _values = this.fetchValuesCache[`${doctype}:${name}`] || (this.fetchValuesCache[`${doctype}:${name}`] = {});
|
||||
if (!_values[fieldname]) {
|
||||
_values[fieldname] = await frappe.db.getValue(doctype, name, fieldname);
|
||||
// children
|
||||
for (let tablefield of this.meta.getTableFields()) {
|
||||
let formulaFields = frappe
|
||||
.getMeta(tablefield.childtype)
|
||||
.getFormulaFields();
|
||||
if (formulaFields.length) {
|
||||
// for each row
|
||||
for (let row of this[tablefield.fieldname]) {
|
||||
for (let field of formulaFields) {
|
||||
if (shouldApplyFormula(field, row)) {
|
||||
const val = await field.formula(row, doc);
|
||||
if (val !== false && val !== undefined) {
|
||||
row[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return _values[fieldname];
|
||||
}
|
||||
}
|
||||
|
||||
isNew() {
|
||||
return this._notInserted;
|
||||
// parent
|
||||
for (let field of this.meta.getFormulaFields()) {
|
||||
if (shouldApplyFormula(field, doc)) {
|
||||
const val = await field.formula(doc);
|
||||
if (val !== false && val !== undefined) {
|
||||
doc[field.fieldname] = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return true;
|
||||
|
||||
function shouldApplyFormula(field, doc) {
|
||||
if (field.readOnly) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!frappe.isServer || frappe.isElectron) {
|
||||
if (doc[field.fieldname] == null || doc[field.fieldname] == '') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async commit() {
|
||||
// re-run triggers
|
||||
this.setStandardValues();
|
||||
this.setKeywords();
|
||||
this.setChildIdx();
|
||||
await this.applyFormula();
|
||||
await this.trigger('validate');
|
||||
}
|
||||
|
||||
async insert() {
|
||||
await naming.setName(this);
|
||||
await this.commit();
|
||||
await this.trigger('beforeInsert');
|
||||
|
||||
const data = await frappe.db.insert(this.doctype, this.getValidDict());
|
||||
this.syncValues(data);
|
||||
|
||||
await this.trigger('afterInsert');
|
||||
await this.trigger('afterSave');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async update() {
|
||||
await this.compareWithCurrentDoc();
|
||||
await this.commit();
|
||||
await this.trigger('beforeUpdate');
|
||||
|
||||
// before submit
|
||||
if (this.flags.submitAction) await this.trigger('beforeSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('beforeRevert');
|
||||
|
||||
const data = await frappe.db.update(this.doctype, this.getValidDict());
|
||||
this.syncValues(data);
|
||||
|
||||
await this.trigger('afterUpdate');
|
||||
await this.trigger('afterSave');
|
||||
|
||||
// after submit
|
||||
if (this.flags.submitAction) await this.trigger('afterSubmit');
|
||||
if (this.flags.revertAction) await this.trigger('afterRevert');
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
async delete() {
|
||||
await this.trigger('beforeDelete');
|
||||
await frappe.db.delete(this.doctype, this.name);
|
||||
await this.trigger('afterDelete');
|
||||
}
|
||||
|
||||
async submit() {
|
||||
this.submitted = 1;
|
||||
this.update();
|
||||
}
|
||||
|
||||
async revert() {
|
||||
this.submitted = 0;
|
||||
this.update();
|
||||
}
|
||||
|
||||
// trigger methods on the class if they match
|
||||
// with the trigger name
|
||||
async trigger(event, params) {
|
||||
if (this[event]) {
|
||||
await this[event](params);
|
||||
}
|
||||
await super.trigger(event, params);
|
||||
}
|
||||
|
||||
// helper functions
|
||||
getSum(tablefield, childfield) {
|
||||
return this[tablefield]
|
||||
.map(d => d[childfield] || 0)
|
||||
.reduce((a, b) => a + b, 0);
|
||||
}
|
||||
|
||||
async getFrom(doctype, name, fieldname) {
|
||||
if (!name) return '';
|
||||
let _values =
|
||||
this.fetchValuesCache[`${doctype}:${name}`] ||
|
||||
(this.fetchValuesCache[`${doctype}:${name}`] = {});
|
||||
if (!_values[fieldname]) {
|
||||
_values[fieldname] = await frappe.db.getValue(doctype, name, fieldname);
|
||||
}
|
||||
return _values[fieldname];
|
||||
}
|
||||
|
||||
isNew() {
|
||||
return this._notInserted;
|
||||
}
|
||||
};
|
||||
|
@ -1,8 +1,8 @@
|
||||
import ModalContainer from './ModalContainer';
|
||||
import frappe from 'frappejs';
|
||||
|
||||
const Plugin = {
|
||||
install (Vue) {
|
||||
|
||||
install(Vue) {
|
||||
this.event = new Vue();
|
||||
|
||||
Vue.prototype.$modal = {
|
||||
@ -13,13 +13,15 @@ const Plugin = {
|
||||
hide(id) {
|
||||
Plugin.event.$emit('hide', id);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
frappe.showModal = Vue.prototype.$modal.show;
|
||||
|
||||
// create modal container
|
||||
const div = document.createElement('div');
|
||||
document.body.appendChild(div);
|
||||
new Vue({ render: h => h(ModalContainer) }).$mount(div);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default Plugin;
|
||||
export default Plugin;
|
||||
|
@ -1,24 +1,24 @@
|
||||
<script>
|
||||
import Base from './Base';
|
||||
export default {
|
||||
extends: Base,
|
||||
methods: {
|
||||
getInputTag() {
|
||||
return 'textarea';
|
||||
},
|
||||
getInputAttrs() {
|
||||
return {
|
||||
id: this.id,
|
||||
required: this.docfield.required,
|
||||
rows: 3,
|
||||
disabled: this.disabled
|
||||
};
|
||||
},
|
||||
getDomProps() {
|
||||
return {
|
||||
value: this.value
|
||||
}
|
||||
}
|
||||
extends: Base,
|
||||
methods: {
|
||||
getInputTag() {
|
||||
return 'textarea';
|
||||
},
|
||||
getInputAttrs() {
|
||||
return {
|
||||
id: this.id,
|
||||
required: this.docfield.required,
|
||||
rows: this.docfield.rows || 3,
|
||||
disabled: this.disabled
|
||||
};
|
||||
},
|
||||
getDomProps() {
|
||||
return {
|
||||
value: this.value
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
@ -1,24 +1,24 @@
|
||||
<template>
|
||||
<div class="row pb-4">
|
||||
<frappe-control
|
||||
class="col-3"
|
||||
v-for="docfield in filters"
|
||||
:key="docfield.fieldname"
|
||||
:docfield="docfield"
|
||||
:value="$data.filterValues[docfield.fieldname]"
|
||||
:doc="$data.filterValues"
|
||||
@change="updateValue(docfield.fieldname, $event)"
|
||||
/>
|
||||
<div class="col-3" v-for="docfield in filterFields" :key="docfield.fieldname">
|
||||
<frappe-control
|
||||
v-if="shouldRenderField(docfield)"
|
||||
:docfield="docfield"
|
||||
:value="$data.filterValues[docfield.fieldname]"
|
||||
:doc="filterDoc"
|
||||
@change="updateValue(docfield.fieldname, $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script>
|
||||
import FrappeControl from 'frappejs/ui/components/controls/FrappeControl';
|
||||
|
||||
export default {
|
||||
props: ['filters', 'filterDefaults'],
|
||||
props: ['filterFields', 'filterDoc', 'filterDefaults'],
|
||||
data() {
|
||||
const filterValues = {};
|
||||
for (let filter of this.filters) {
|
||||
for (let filter of this.filterFields) {
|
||||
filterValues[filter.fieldname] =
|
||||
this.filterDefaults[filter.fieldname] || null;
|
||||
}
|
||||
@ -34,7 +34,26 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
shouldRenderField(field) {
|
||||
let hidden;
|
||||
try {
|
||||
hidden = Boolean(field.hidden(this.filterDoc));
|
||||
} catch (e) {
|
||||
hidden = Boolean(field.hidden) || false;
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (field.fieldname === 'name' && !this.doc.isNew()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
updateValue(fieldname, value) {
|
||||
this.filterDoc.set(fieldname, value);
|
||||
this.filterValues[fieldname] = value;
|
||||
this.$emit('change', this.filterValues);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user