2
0
mirror of https://github.com/frappe/books.git synced 2025-01-11 02:36:14 +00:00

added singles

This commit is contained in:
Rushabh Mehta 2018-02-12 17:31:31 +05:30
parent 95cbd8ea61
commit 92355be627
23 changed files with 308 additions and 80 deletions

View File

@ -17,12 +17,14 @@ module.exports = class Database {
for (let doctype in frappe.modules) { for (let doctype in frappe.modules) {
// check if controller module // check if controller module
if (frappe.modules[doctype].Meta) { if (frappe.modules[doctype].Meta) {
let meta = frappe.getMeta(doctype);
if (!meta.isSingle) {
if (await this.tableExists(doctype)) { if (await this.tableExists(doctype)) {
await this.alterTable(doctype); await this.alterTable(doctype);
} else { } else {
await this.createTable(doctype); await this.createTable(doctype);
} }
}
} }
} }
await this.commit(); await this.commit();
@ -33,9 +35,9 @@ module.exports = class Database {
let columns = []; let columns = [];
let values = []; let values = [];
for (let df of meta.getValidFields({ with_children: false })) { for (let field of meta.getValidFields({ withChildren: false })) {
if (this.type_map[df.fieldtype]) { if (this.type_map[field.fieldtype]) {
columns.push(this.getColumnDefinition(df)); columns.push(this.getColumnDefinition(field));
} }
} }
@ -60,7 +62,7 @@ module.exports = class Database {
let meta = frappe.getMeta(doctype); let meta = frappe.getMeta(doctype);
let values = []; let values = [];
for (let field of meta.getValidFields({ with_children: false })) { for (let field of meta.getValidFields({ withChildren: false })) {
if (!tableColumns.includes(field.fieldname) && this.type_map[field.fieldtype]) { if (!tableColumns.includes(field.fieldname) && this.type_map[field.fieldtype]) {
values = [] values = []
if (field.default) { if (field.default) {
@ -79,12 +81,25 @@ module.exports = class Database {
// alter table {doctype} add column ({column_def}); // alter table {doctype} add column ({column_def});
} }
async get(doctype, name, fields = '*') { async get(doctype, name=null, fields = '*') {
// load parent let meta = frappe.getMeta(doctype);
let doc = await this.getOne(doctype, name, fields); let doc;
if (meta.isSingle) {
doc = await this.getSingle(doctype);
doc.name = doctype;
} else {
if (!name) {
throw frappe.errors.ValueError('name is mandatory');
}
doc = await this.getOne(doctype, name, fields);
}
await this.loadChildren(doc, meta);
return doc;
}
async loadChildren(doc, meta) {
// load children // load children
let tableFields = frappe.getMeta(doctype).getTableFields(); let tableFields = meta.getTableFields();
for (let field of tableFields) { for (let field of tableFields) {
doc[field.fieldname] = await this.getAll({ doc[field.fieldname] = await this.getAll({
doctype: field.childtype, doctype: field.childtype,
@ -94,6 +109,20 @@ module.exports = class Database {
order: 'asc' order: 'asc'
}); });
} }
}
async getSingle(doctype) {
let values = await this.getAll({
doctype: 'Single Value',
fields: ['fieldname', 'value'],
filters: { parent: doctype },
order_by: 'fieldname',
order: 'asc'
});
let doc = {};
for (let row of values) {
doc[row.fieldname] = row.value;
}
return doc; return doc;
} }
@ -109,11 +138,23 @@ module.exports = class Database {
} }
async insert(doctype, doc) { async insert(doctype, doc) {
let meta = frappe.getMeta(doctype);
// insert parent // insert parent
if (meta.isSingle) {
await this.updateSingle(meta, doc, doctype);
} else {
await this.insertOne(doctype, doc); await this.insertOne(doctype, doc);
}
// insert children // insert children
let tableFields = frappe.getMeta(doctype).getTableFields(); await this.insertChildren(meta, doc, doctype);
return doc;
}
async insertChildren(meta, doc, doctype) {
let tableFields = meta.getTableFields();
for (let field of tableFields) { for (let field of tableFields) {
let idx = 0; let idx = 0;
for (let child of (doc[field.fieldname] || [])) { for (let child of (doc[field.fieldname] || [])) {
@ -122,8 +163,6 @@ module.exports = class Database {
idx++; idx++;
} }
} }
return doc;
} }
async insertOne(doctype, doc) { async insertOne(doctype, doc) {
@ -131,28 +170,37 @@ module.exports = class Database {
} }
async update(doctype, doc) { async update(doctype, doc) {
let meta = frappe.getMeta(doctype);
// update parent // update parent
if (meta.isSingle) {
await this.updateSingle(meta, doc, doctype);
} else {
await this.updateOne(doctype, doc); await this.updateOne(doctype, doc);
}
// insert or update children // insert or update children
let tableFields = frappe.getMeta(doctype).getTableFields(); await this.updateChildren(meta, doc, doctype);
for (let field of tableFields) { return doc;
}
async updateChildren(meta, doc, doctype) {
let tableFields = meta.getTableFields();
for (let field of tableFields) {
// first key is "parent" - for SQL params // first key is "parent" - for SQL params
let added = [doc.name]; let added = [doc.name];
for (let child of (doc[field.fieldname] || [])) { for (let child of (doc[field.fieldname] || [])) {
this.prepareChild(doctype, doc.name, child, field, added.length - 1); this.prepareChild(doctype, doc.name, child, field, added.length - 1);
if (await this.exists(field.childtype, child.name)) { if (await this.exists(field.childtype, child.name)) {
await this.updateOne(field.childtype, child); await this.updateOne(field.childtype, child);
} else { }
else {
await this.insertOne(field.childtype, child); await this.insertOne(field.childtype, child);
} }
added.push(child.name); added.push(child.name);
} }
await this.runDeleteOtherChildren(field, added); await this.runDeleteOtherChildren(field, added);
} }
return doc;
} }
async updateOne(doctype, doc) { async updateOne(doctype, doc) {
@ -163,6 +211,26 @@ module.exports = class Database {
// delete from doctype where parent = ? and name not in (?, ?, ?) // delete from doctype where parent = ? and name not in (?, ?, ?)
} }
async updateSingle(meta, doc, doctype) {
await this.deleteSingleValues();
for (let field of meta.getValidFields({withChildren: false})) {
let value = doc[field.fieldname];
if (value) {
let singleValue = frappe.newDoc({
doctype: 'Single Value',
parent: doctype,
fieldname: field.fieldname,
value: value
})
await singleValue.insert();
}
}
}
async deleteSingleValues(name) {
// await frappe.db.run('delete from single_value where parent=?', name)
}
prepareChild(parenttype, parent, child, field, idx) { prepareChild(parenttype, parent, child, field, idx) {
if (!child.name) { if (!child.name) {
child.name = frappe.getRandomName(); child.name = frappe.getRandomName();
@ -174,7 +242,7 @@ module.exports = class Database {
} }
getKeys(doctype) { getKeys(doctype) {
return frappe.getMeta(doctype).getValidFields({ with_children: false }); return frappe.getMeta(doctype).getValidFields({ withChildren: false });
} }
getFormattedValues(fields, doc) { getFormattedValues(fields, doc) {

View File

@ -97,7 +97,11 @@ module.exports = class sqliteDatabase extends Database {
} }
async deleteChildren(parenttype, parent) { async deleteChildren(parenttype, parent) {
await this.run(`delete from ${parent} where parent=?`, parent); await this.run(`delete from ${parenttype} where parent=?`, parent);
}
async deleteSingleValues(name) {
await frappe.db.run('delete from single_value where parent=?', name)
} }
getAll({ doctype, fields, filters, start, limit, order_by = 'modified', order = 'desc' } = {}) { getAll({ doctype, fields, filters, start, limit, order_by = 'modified', order = 'desc' } = {}) {
@ -106,12 +110,13 @@ module.exports = class sqliteDatabase extends Database {
} }
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let conditions = this.getFilterConditions(filters); let conditions = this.getFilterConditions(filters);
let query = `select ${fields.join(", ")}
this.conn.all(`select ${fields.join(", ")}
from ${frappe.slug(doctype)} from ${frappe.slug(doctype)}
${conditions.conditions ? "where" : ""} ${conditions.conditions} ${conditions.conditions ? "where" : ""} ${conditions.conditions}
${order_by ? ("order by " + order_by) : ""} ${order_by ? (order || "asc") : ""} ${order_by ? ("order by " + order_by) : ""} ${order_by ? (order || "asc") : ""}
${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`, conditions.values, ${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`;
this.conn.all(query, conditions.values,
(err, rows) => { (err, rows) => {
if (err) { if (err) {
reject(err); reject(err);

View File

@ -59,17 +59,23 @@ module.exports = {
async getDoc(doctype, name) { async getDoc(doctype, name) {
let doc = this.getDocFromCache(doctype, name); let doc = this.getDocFromCache(doctype, name);
if (!doc) { if (!doc) {
let controller_class = this.getControllerClass(doctype); let controllerClass = this.getControllerClass(doctype);
doc = new controller_class({doctype:doctype, name: name}); doc = new controllerClass({doctype:doctype, name: name});
await doc.load(); await doc.load();
this.addToCache(doc); this.addToCache(doc);
} }
return doc; return doc;
}, },
async getSingle(doctype) {
return await this.getDoc(doctype, doctype);
},
newDoc(data) { newDoc(data) {
let controller_class = this.getControllerClass(data.doctype); let controllerClass = this.getControllerClass(data.doctype);
return new controller_class(data); let doc = new controllerClass(data);
doc.setDefaults();
return doc;
}, },
getControllerClass(doctype) { getControllerClass(doctype) {
@ -83,8 +89,8 @@ module.exports = {
async getNewDoc(doctype) { async getNewDoc(doctype) {
let doc = this.newDoc({doctype: doctype}); let doc = this.newDoc({doctype: doctype});
doc.setName();
doc._notInserted = true; doc._notInserted = true;
doc.name = this.getRandomName();
this.addToCache(doc); this.addToCache(doc);
return doc; return doc;
}, },

View File

@ -9,9 +9,9 @@ module.exports = {
fs.writeFileSync(`./models/doctype/${utils.slug(name)}/${utils.slug(name)}.json`, `{ fs.writeFileSync(`./models/doctype/${utils.slug(name)}/${utils.slug(name)}.json`, `{
"name": "${name}", "name": "${name}",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 0, "isChild": 0,
"keyword_fields": [], "keywordFields": [],
"fields": [ "fields": [
{ {
"fieldname": "name", "fieldname": "name",

View File

@ -23,6 +23,20 @@ module.exports = class BaseDocument {
this.handlers[key].push(method || key); this.handlers[key].push(method || key);
} }
get meta() {
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;
}
get(fieldname) { get(fieldname) {
return this[fieldname]; return this[fieldname];
} }
@ -39,7 +53,22 @@ module.exports = class BaseDocument {
} }
} }
setName() { async setName() {
// name === doctype for Single
if (this.meta.isSingle) {
this.name = this.meta.name;
return;
}
if (this.meta.settings) {
const number_series = (await this.getSettings()).number_series;
console.log(1, number_series);
if(number_series) {
this.name = await frappe.model.getSeriesNext(number_series);
console.log(2, this.name);
}
}
// assign a random name by default // assign a random name by default
// override this to set a name // override this to set a name
if (!this.name) { if (!this.name) {
@ -47,6 +76,14 @@ module.exports = class BaseDocument {
} }
} }
setDefaults() {
for (let field of this.meta.fields) {
if (!this[field.fieldname] && field.default) {
this[field.fieldname] = field.default;
}
}
}
setKeywords() { setKeywords() {
let keywords = []; let keywords = [];
for (let fieldname of this.meta.getKeywordFields()) { for (let fieldname of this.meta.getKeywordFields()) {
@ -55,13 +92,6 @@ module.exports = class BaseDocument {
this.keywords = keywords.join(', '); this.keywords = keywords.join(', ');
} }
get meta() {
if (!this._meta) {
this._meta = frappe.getMeta(this.doctype);
}
return this._meta;
}
append(key, document) { append(key, document) {
if (!this[key]) { if (!this[key]) {
this[key] = []; this[key] = [];
@ -170,7 +200,7 @@ module.exports = class BaseDocument {
async commit() { async commit() {
// re-run triggers // re-run triggers
this.setName(); await this.setName();
this.setStandardValues(); this.setStandardValues();
this.setKeywords(); this.setKeywords();
this.setChildIdx(); this.setChildIdx();

View File

@ -1,7 +1,7 @@
const frappe = require('frappejs'); const frappe = require('frappejs');
module.exports = { module.exports = {
async get_series_next(prefix) { async getSeriesNext(prefix) {
let series; let series;
try { try {
series = await frappe.getDoc('Number Series', prefix); series = await frappe.getDoc('Number Series', prefix);

View File

@ -70,15 +70,15 @@ module.exports = class BaseMeta extends BaseDocument {
return this[fieldname]; return this[fieldname];
} }
getValidFields({ with_children = true } = {}) { getValidFields({ withChildren = true } = {}) {
if (!this._valid_fields) { if (!this._valid_fields) {
this._valid_fields = []; this._valid_fields = [];
this._valid_fields_with_children = []; this._valid_fields_withChildren = [];
const _add = (field) => { const _add = (field) => {
this._valid_fields.push(field); this._valid_fields.push(field);
this._valid_fields_with_children.push(field); this._valid_fields_withChildren.push(field);
} }
const doctype_fields = this.fields.map((field) => field.fieldname); const doctype_fields = this.fields.map((field) => field.fieldname);
@ -90,7 +90,7 @@ module.exports = class BaseMeta extends BaseDocument {
} }
} }
if (this.is_child) { if (this.isChild) {
// child fields // child fields
for (let field of frappe.model.child_fields) { for (let field of frappe.model.child_fields) {
if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) { if (frappe.db.type_map[field.fieldtype] && !doctype_fields.includes(field.fieldname)) {
@ -114,22 +114,31 @@ module.exports = class BaseMeta extends BaseDocument {
_add(field); _add(field);
} }
// include tables if (with_children = True) // include tables if (withChildren = True)
if (!include && field.fieldtype === 'Table') { if (!include && field.fieldtype === 'Table') {
this._valid_fields_with_children.push(field); this._valid_fields_withChildren.push(field);
} }
} }
} }
if (with_children) { if (withChildren) {
return this._valid_fields_with_children; return this._valid_fields_withChildren;
} else { } else {
return this._valid_fields; return this._valid_fields;
} }
} }
getKeywordFields() { getKeywordFields() {
return this.keyword_fields || this.meta.fields.filter(field => field.required).map(field => field.fieldname); if (!this._keywordFields) {
this._keywordFields = this.keywordFields;
if (!(this._keywordFields && this._keywordFields.length && this.fields)) {
this._keywordFields = this.fields.filter(field => field.required).map(field => field.fieldname);
}
if (!(this._keywordFields && this._keywordFields.length)) {
this._keywordFields = ['name']
}
}
return this._keywordFields;
} }
validate_select(field, value) { validate_select(field, value) {

View File

@ -8,10 +8,16 @@ class NumberSeriesMeta extends BaseMeta {
} }
class NumberSeries extends BaseDocument { class NumberSeries extends BaseDocument {
async next() { setup() {
this.addHandler('validate');
}
validate() {
if (this.current===null || this.current===undefined) { if (this.current===null || this.current===undefined) {
this.current = 0; this.current = 0;
} }
}
async next() {
this.validate();
this.current++; this.current++;
await this.update(); await this.update();
return this.current; return this.current;

View File

@ -1,9 +1,9 @@
{ {
"name": "Number Series", "name": "Number Series",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 0, "isChild": 0,
"keyword_fields": [], "keywordFields": [],
"fields": [ "fields": [
{ {
"fieldname": "name", "fieldname": "name",

View File

@ -1,9 +1,9 @@
{ {
"name": "Role", "name": "Role",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 0, "isChild": 0,
"keyword_fields": [], "keywordFields": [],
"fields": [ "fields": [
{ {
"fieldname": "name", "fieldname": "name",

View File

@ -1,9 +1,9 @@
{ {
"name": "Session", "name": "Session",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 0, "isChild": 0,
"keyword_fields": [], "keywordFields": [],
"fields": [ "fields": [
{ {
"fieldname": "username", "fieldname": "username",

View File

@ -0,0 +1,16 @@
const BaseMeta = require('frappejs/model/meta');
const BaseDocument = require('frappejs/model/document');
class SingleValueMeta extends BaseMeta {
setupMeta() {
Object.assign(this, require('./single_value.json'));
}
}
class SingleValue extends BaseDocument {
}
module.exports = {
Document: SingleValue,
Meta: SingleValueMeta
};

View File

@ -0,0 +1,27 @@
{
"name": "Single Value",
"doctype": "DocType",
"isSingle": 0,
"isChild": 0,
"keywordFields": [],
"fields": [
{
"fieldname": "parent",
"label": "Parent",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "fieldname",
"label": "Fieldname",
"fieldtype": "Data",
"required": 1
},
{
"fieldname": "value",
"label": "Value",
"fieldtype": "Data",
"required": 1
}
]
}

View File

@ -0,0 +1,16 @@
const BaseMeta = require('frappejs/model/meta');
const BaseDocument = require('frappejs/model/document');
class SystemSettingsMeta extends BaseMeta {
setupMeta() {
Object.assign(this, require('./system_settings.json'));
}
}
class SystemSettings extends BaseDocument {
}
module.exports = {
Document: SystemSettings,
Meta: SystemSettingsMeta
};

View File

@ -0,0 +1,21 @@
{
"name": "System Settings",
"doctype": "DocType",
"isSingle": 1,
"isChild": 0,
"keywordFields": [],
"fields": [
{
"fieldname": "dateFormat",
"label": "Date Format",
"fieldtype": "Select",
"options": [
"dd/mm/yyyy",
"mm/dd/yyyy",
"dd-mm-yyyy",
"mm-dd-yyyy"
],
"required": 1
}
]
}

View File

@ -2,8 +2,8 @@
"autoname": "hash", "autoname": "hash",
"name": "ToDo", "name": "ToDo",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"keyword_fields": [ "keywordFields": [
"subject", "subject",
"description" "description"
], ],

View File

@ -1,9 +1,9 @@
{ {
"name": "User", "name": "User",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 0, "isChild": 0,
"keyword_fields": [ "keywordFields": [
"name", "name",
"full_name" "full_name"
], ],

View File

@ -1,9 +1,9 @@
{ {
"name": "User Role", "name": "User Role",
"doctype": "DocType", "doctype": "DocType",
"is_single": 0, "isSingle": 0,
"is_child": 1, "isChild": 1,
"keyword_fields": [], "keywordFields": [],
"fields": [ "fields": [
{ {
"fieldname": "role", "fieldname": "role",

View File

@ -4,7 +4,7 @@ module.exports = {
setup(app) { setup(app) {
// get list // get list
app.get('/api/resource/:doctype', frappe.async_handler(async function(request, response) { app.get('/api/resource/:doctype', frappe.async_handler(async function(request, response) {
for (key of ['fields', 'filters']) { for (let key of ['fields', 'filters']) {
if (request.query[key]) { if (request.query[key]) {
request.query[key] = JSON.parse(request.query[key]); request.query[key] = JSON.parse(request.query[key]);
} }

View File

@ -9,7 +9,7 @@ describe('Meta', () => {
it('should get init from json file', () => { it('should get init from json file', () => {
let todo = frappe.getMeta('ToDo'); let todo = frappe.getMeta('ToDo');
assert.equal(todo.is_single, 0); assert.equal(todo.isSingle, 0);
}); });
it('should get fields from meta', () => { it('should get fields from meta', () => {

View File

@ -9,6 +9,6 @@ describe('Models', () => {
it('should get todo json', () => { it('should get todo json', () => {
let todo = frappe.getMeta('todo'); let todo = frappe.getMeta('todo');
assert.equal(todo.is_single, 0); assert.equal(todo.isSingle, 0);
}); });
}); });

View File

@ -9,8 +9,8 @@ describe('Number Series', () => {
it('should start a series and get next value', async () => { it('should start a series and get next value', async () => {
frappe.db.delete('Number Series', 'test-series-') frappe.db.delete('Number Series', 'test-series-')
assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-1'); assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-1');
assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-2'); assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-2');
assert.equal(await frappe.model.get_series_next('test-series-'), 'test-series-3'); assert.equal(await frappe.model.getSeriesNext('test-series-'), 'test-series-3');
}); });
}); });

24
tests/test_single.js Normal file
View File

@ -0,0 +1,24 @@
const assert = require('assert');
const frappe = require('frappejs');
const helpers = require('./helpers');
describe('Single Documents', () => {
before(async function() {
await helpers.init_sqlite();
});
it('should set a single value', async () => {
let systemSettings = await frappe.getSingle('System Settings');
systemSettings.dateFormat = 'dd/mm/yyyy';
await systemSettings.update();
systemSettings = await frappe.getSingle('System Settings');
assert.equal(systemSettings.dateFormat, 'dd/mm/yyyy');
systemSettings.dateFormat = 'mm/dd/yyyy';
await systemSettings.update();
systemSettings = await frappe.getSingle('System Settings');
assert.equal(systemSettings.dateFormat, 'mm/dd/yyyy');
});
});