mirror of
https://github.com/frappe/books.git
synced 2024-12-23 11:29:03 +00:00
File (#80)
File doctype - Upload files using multer - uploadFiles API in http.js - Link File to doctype with name and field - Refactor File component for fullpaths - frappe.db.setValue(s) API
This commit is contained in:
parent
1aabf7ef40
commit
375325d917
@ -2,499 +2,513 @@ const frappe = require('frappejs');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class Database extends Observable {
|
||||
constructor() {
|
||||
super();
|
||||
this.initTypeMap();
|
||||
}
|
||||
constructor() {
|
||||
super();
|
||||
this.initTypeMap();
|
||||
}
|
||||
|
||||
async connect() {
|
||||
// this.conn
|
||||
}
|
||||
async connect() {
|
||||
// this.conn
|
||||
}
|
||||
|
||||
close() {
|
||||
this.conn.close();
|
||||
}
|
||||
close() {
|
||||
this.conn.close();
|
||||
}
|
||||
|
||||
async migrate() {
|
||||
for (let doctype in frappe.models) {
|
||||
// check if controller module
|
||||
let meta = frappe.getMeta(doctype);
|
||||
if (!meta.isSingle) {
|
||||
if (await this.tableExists(doctype)) {
|
||||
await this.alterTable(doctype);
|
||||
} else {
|
||||
await this.createTable(doctype);
|
||||
}
|
||||
}
|
||||
}
|
||||
await this.commit();
|
||||
}
|
||||
|
||||
async createTable(doctype, newName=null) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
let columns = [];
|
||||
let indexes = [];
|
||||
|
||||
for (let field of meta.getValidFields({ withChildren: false })) {
|
||||
if (this.typeMap[field.fieldtype]) {
|
||||
this.updateColumnDefinition(field, columns, indexes);
|
||||
}
|
||||
}
|
||||
|
||||
return await this.runCreateTableQuery(newName || doctype, columns, indexes);
|
||||
}
|
||||
|
||||
async tableExists(table) {
|
||||
// return true if table exists
|
||||
}
|
||||
|
||||
async runCreateTableQuery(doctype, columns, indexes) {
|
||||
// override
|
||||
}
|
||||
|
||||
updateColumnDefinition(field, columns, indexes) {
|
||||
// return `${df.fieldname} ${this.typeMap[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}`
|
||||
}
|
||||
|
||||
async alterTable(doctype) {
|
||||
// get columns
|
||||
let diff = await this.getColumnDiff(doctype);
|
||||
let newForeignKeys = await this.getNewForeignKeys(doctype);
|
||||
|
||||
if (diff.added.length) {
|
||||
await this.addColumns(doctype, diff.added);
|
||||
}
|
||||
|
||||
if (diff.removed.length) {
|
||||
await this.removeColumns(doctype, diff.removed);
|
||||
}
|
||||
|
||||
if (newForeignKeys.length) {
|
||||
await this.addForeignKeys(doctype, newForeignKeys);
|
||||
}
|
||||
}
|
||||
|
||||
async getColumnDiff(doctype) {
|
||||
const tableColumns = await this.getTableColumns(doctype);
|
||||
const validFields = frappe.getMeta(doctype).getValidFields({ withChildren: false });
|
||||
const diff = { added: [], removed: [] };
|
||||
|
||||
for (let field of validFields) {
|
||||
if (!tableColumns.includes(field.fieldname) && this.typeMap[field.fieldtype]) {
|
||||
diff.added.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
const validFieldNames = validFields.map(field => field.fieldname);
|
||||
for (let column of tableColumns) {
|
||||
if (!validFieldNames.includes(column)) {
|
||||
diff.removed.push(column);
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
async addColumns(doctype, added) {
|
||||
for (let field of added) {
|
||||
await this.runAddColumnQuery(doctype, field);
|
||||
}
|
||||
}
|
||||
|
||||
async removeColumns(doctype, removed) {
|
||||
for (let column of removed) {
|
||||
await this.runRemoveColumnQuery(doctype, column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getNewForeignKeys(doctype) {
|
||||
let foreignKeys = await this.getForeignKeys(doctype);
|
||||
let newForeignKeys = [];
|
||||
let meta = frappe.getMeta(doctype);
|
||||
for (let field of meta.getValidFields({ withChildren: false})) {
|
||||
if (field.fieldtype==='Link' && !foreignKeys.includes(field.fieldname)) {
|
||||
newForeignKeys.push(field);
|
||||
}
|
||||
}
|
||||
return newForeignKeys;
|
||||
}
|
||||
|
||||
async addForeignKeys(doctype, newForeignKeys) {
|
||||
for (let field of newForeignKeys) {
|
||||
this.addForeignKey(doctype, field);
|
||||
}
|
||||
}
|
||||
|
||||
async getForeignKey(doctype, field) {
|
||||
|
||||
}
|
||||
|
||||
async getTableColumns(doctype) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async runAddColumnQuery(doctype, field) {
|
||||
// alter table {doctype} add column ({column_def});
|
||||
}
|
||||
|
||||
async get(doctype, name=null, fields = '*') {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
let doc;
|
||||
if (meta.isSingle) {
|
||||
doc = await this.getSingle(doctype);
|
||||
doc.name = doctype;
|
||||
async migrate() {
|
||||
for (let doctype in frappe.models) {
|
||||
// check if controller module
|
||||
let meta = frappe.getMeta(doctype);
|
||||
if (!meta.isSingle) {
|
||||
if (await this.tableExists(doctype)) {
|
||||
await this.alterTable(doctype);
|
||||
} else {
|
||||
if (!name) {
|
||||
throw new frappe.errors.ValueError('name is mandatory');
|
||||
}
|
||||
doc = await this.getOne(doctype, name, fields);
|
||||
await this.createTable(doctype);
|
||||
}
|
||||
await this.loadChildren(doc, meta);
|
||||
return doc;
|
||||
}
|
||||
}
|
||||
await this.commit();
|
||||
}
|
||||
|
||||
async createTable(doctype, newName = null) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
let columns = [];
|
||||
let indexes = [];
|
||||
|
||||
for (let field of meta.getValidFields({ withChildren: false })) {
|
||||
if (this.typeMap[field.fieldtype]) {
|
||||
this.updateColumnDefinition(field, columns, indexes);
|
||||
}
|
||||
}
|
||||
|
||||
async loadChildren(doc, meta) {
|
||||
// load children
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
doc[field.fieldname] = await this.getAll({
|
||||
doctype: field.childtype,
|
||||
fields: ["*"],
|
||||
filters: { parent: doc.name },
|
||||
orderBy: 'idx',
|
||||
order: 'asc'
|
||||
});
|
||||
return await this.runCreateTableQuery(newName || doctype, columns, indexes);
|
||||
}
|
||||
|
||||
async tableExists(table) {
|
||||
// return true if table exists
|
||||
}
|
||||
|
||||
async runCreateTableQuery(doctype, columns, indexes) {
|
||||
// override
|
||||
}
|
||||
|
||||
updateColumnDefinition(field, columns, indexes) {
|
||||
// return `${df.fieldname} ${this.typeMap[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}`
|
||||
}
|
||||
|
||||
async alterTable(doctype) {
|
||||
// get columns
|
||||
let diff = await this.getColumnDiff(doctype);
|
||||
let newForeignKeys = await this.getNewForeignKeys(doctype);
|
||||
|
||||
if (diff.added.length) {
|
||||
await this.addColumns(doctype, diff.added);
|
||||
}
|
||||
|
||||
if (diff.removed.length) {
|
||||
await this.removeColumns(doctype, diff.removed);
|
||||
}
|
||||
|
||||
if (newForeignKeys.length) {
|
||||
await this.addForeignKeys(doctype, newForeignKeys);
|
||||
}
|
||||
}
|
||||
|
||||
async getColumnDiff(doctype) {
|
||||
const tableColumns = await this.getTableColumns(doctype);
|
||||
const validFields = frappe.getMeta(doctype).getValidFields({ withChildren: false });
|
||||
const diff = { added: [], removed: [] };
|
||||
|
||||
for (let field of validFields) {
|
||||
if (!tableColumns.includes(field.fieldname) && this.typeMap[field.fieldtype]) {
|
||||
diff.added.push(field);
|
||||
}
|
||||
}
|
||||
|
||||
const validFieldNames = validFields.map(field => field.fieldname);
|
||||
for (let column of tableColumns) {
|
||||
if (!validFieldNames.includes(column)) {
|
||||
diff.removed.push(column);
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
async addColumns(doctype, added) {
|
||||
for (let field of added) {
|
||||
await this.runAddColumnQuery(doctype, field);
|
||||
}
|
||||
}
|
||||
|
||||
async removeColumns(doctype, removed) {
|
||||
for (let column of removed) {
|
||||
await this.runRemoveColumnQuery(doctype, column);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getNewForeignKeys(doctype) {
|
||||
let foreignKeys = await this.getForeignKeys(doctype);
|
||||
let newForeignKeys = [];
|
||||
let meta = frappe.getMeta(doctype);
|
||||
for (let field of meta.getValidFields({ withChildren: false })) {
|
||||
if (field.fieldtype === 'Link' && !foreignKeys.includes(field.fieldname)) {
|
||||
newForeignKeys.push(field);
|
||||
}
|
||||
}
|
||||
return newForeignKeys;
|
||||
}
|
||||
|
||||
async addForeignKeys(doctype, newForeignKeys) {
|
||||
for (let field of newForeignKeys) {
|
||||
this.addForeignKey(doctype, field);
|
||||
}
|
||||
}
|
||||
|
||||
async getForeignKey(doctype, field) {
|
||||
|
||||
}
|
||||
|
||||
async getTableColumns(doctype) {
|
||||
return [];
|
||||
}
|
||||
|
||||
async runAddColumnQuery(doctype, field) {
|
||||
// alter table {doctype} add column ({column_def});
|
||||
}
|
||||
|
||||
async get(doctype, name = null, fields = '*') {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
let doc;
|
||||
if (meta.isSingle) {
|
||||
doc = await this.getSingle(doctype);
|
||||
doc.name = doctype;
|
||||
} else {
|
||||
if (!name) {
|
||||
throw new 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
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
doc[field.fieldname] = await this.getAll({
|
||||
doctype: field.childtype,
|
||||
fields: ["*"],
|
||||
filters: { parent: doc.name },
|
||||
orderBy: 'idx',
|
||||
order: 'asc'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getSingle(doctype) {
|
||||
let values = await this.getAll({
|
||||
doctype: 'SingleValue',
|
||||
fields: ['fieldname', 'value'],
|
||||
filters: { parent: doctype },
|
||||
orderBy: 'fieldname',
|
||||
order: 'asc'
|
||||
});
|
||||
let doc = {};
|
||||
for (let row of values) {
|
||||
doc[row.fieldname] = row.value;
|
||||
}
|
||||
return doc;
|
||||
}
|
||||
|
||||
async getOne(doctype, name, fields = '*') {
|
||||
// select {fields} form {doctype} where name = ?
|
||||
}
|
||||
|
||||
prepareFields(fields) {
|
||||
if (fields instanceof Array) {
|
||||
fields = fields.join(", ");
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
triggerChange(doctype, name) {
|
||||
this.trigger(`change:${doctype}`, { name: name }, 500);
|
||||
this.trigger(`change`, { doctype: name, name: name }, 500);
|
||||
}
|
||||
|
||||
async insert(doctype, doc) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
|
||||
// insert parent
|
||||
if (meta.isSingle) {
|
||||
await this.updateSingle(meta, doc, doctype);
|
||||
} else {
|
||||
await this.insertOne(doctype, doc);
|
||||
}
|
||||
|
||||
// insert children
|
||||
await this.insertChildren(meta, doc, doctype);
|
||||
|
||||
this.triggerChange(doctype, doc.name);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
async insertChildren(meta, doc, doctype) {
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
let idx = 0;
|
||||
for (let child of (doc[field.fieldname] || [])) {
|
||||
this.prepareChild(doctype, doc.name, child, field, idx);
|
||||
await this.insertOne(field.childtype, child);
|
||||
idx++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async insertOne(doctype, doc) {
|
||||
// insert into {doctype} ({fields}) values ({values})
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
|
||||
// update parent
|
||||
if (meta.isSingle) {
|
||||
await this.updateSingle(meta, doc, doctype);
|
||||
} else {
|
||||
await this.updateOne(doctype, doc);
|
||||
}
|
||||
|
||||
// insert or update children
|
||||
await this.updateChildren(meta, doc, doctype);
|
||||
|
||||
this.triggerChange(doctype, doc.name);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
async updateChildren(meta, doc, doctype) {
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
// first key is "parent" - for SQL params
|
||||
let added = [doc.name];
|
||||
for (let child of (doc[field.fieldname] || [])) {
|
||||
this.prepareChild(doctype, doc.name, child, field, added.length - 1);
|
||||
if (await this.exists(field.childtype, child.name)) {
|
||||
await this.updateOne(field.childtype, child);
|
||||
}
|
||||
}
|
||||
|
||||
async getSingle(doctype) {
|
||||
let values = await this.getAll({
|
||||
doctype: 'SingleValue',
|
||||
fields: ['fieldname', 'value'],
|
||||
filters: { parent: doctype },
|
||||
orderBy: 'fieldname',
|
||||
order: 'asc'
|
||||
});
|
||||
let doc = {};
|
||||
for (let row of values) {
|
||||
doc[row.fieldname] = row.value;
|
||||
else {
|
||||
await this.insertOne(field.childtype, child);
|
||||
}
|
||||
return doc;
|
||||
added.push(child.name);
|
||||
}
|
||||
await this.runDeleteOtherChildren(field, added);
|
||||
}
|
||||
}
|
||||
|
||||
async updateOne(doctype, doc) {
|
||||
// update {doctype} set {field=value} where name=?
|
||||
}
|
||||
|
||||
async runDeleteOtherChildren(field, added) {
|
||||
// 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: 'SingleValue',
|
||||
parent: doctype,
|
||||
fieldname: field.fieldname,
|
||||
value: value
|
||||
})
|
||||
await singleValue.insert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSingleValues(name) {
|
||||
// await frappe.db.run('delete from SingleValue where parent=?', name)
|
||||
}
|
||||
|
||||
prepareChild(parenttype, parent, child, field, idx) {
|
||||
if (!child.name) {
|
||||
child.name = frappe.getRandomString();
|
||||
}
|
||||
child.parent = parent;
|
||||
child.parenttype = parenttype;
|
||||
child.parentfield = field.fieldname;
|
||||
child.idx = idx;
|
||||
}
|
||||
|
||||
getKeys(doctype) {
|
||||
return frappe.getMeta(doctype).getValidFields({ withChildren: false });
|
||||
}
|
||||
|
||||
getFormattedValues(fields, doc) {
|
||||
let values = fields.map(field => {
|
||||
let value = doc[field.fieldname];
|
||||
return this.getFormattedValue(field, value);
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
getFormattedValue(field, value) {
|
||||
if (value instanceof Date) {
|
||||
if (field.fieldtype === 'Date') {
|
||||
// date
|
||||
return value.toISOString().substr(0, 10);
|
||||
} else {
|
||||
// datetime
|
||||
return value.toISOString();
|
||||
}
|
||||
} else if (field.fieldtype === 'Link' && !value) {
|
||||
// empty value must be null to satisfy
|
||||
// foreign key constraint
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
for (const name of names) {
|
||||
await this.delete(doctype, name);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
await this.deleteOne(doctype, name);
|
||||
|
||||
// delete children
|
||||
let tableFields = frappe.getMeta(doctype).getTableFields();
|
||||
for (let field of tableFields) {
|
||||
await this.deleteChildren(field.childtype, name);
|
||||
}
|
||||
|
||||
async getOne(doctype, name, fields = '*') {
|
||||
// select {fields} form {doctype} where name = ?
|
||||
this.triggerChange(doctype, name);
|
||||
}
|
||||
|
||||
async deleteOne(doctype, name) {
|
||||
// delete from {doctype} where name = ?
|
||||
}
|
||||
|
||||
async deleteChildren(parenttype, parent) {
|
||||
// delete from {parenttype} where parent = ?
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.getValue(doctype, name)) ? true : false;
|
||||
}
|
||||
|
||||
async getValue(doctype, filters, fieldname = 'name') {
|
||||
if (typeof filters === 'string') {
|
||||
filters = { name: filters };
|
||||
}
|
||||
|
||||
prepareFields(fields) {
|
||||
if (fields instanceof Array) {
|
||||
fields = fields.join(", ");
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
let row = await this.getAll({
|
||||
doctype: doctype,
|
||||
fields: [fieldname],
|
||||
filters: filters,
|
||||
start: 0,
|
||||
limit: 1,
|
||||
orderBy: 'name',
|
||||
order: 'asc'
|
||||
});
|
||||
return row.length ? row[0][fieldname] : null;
|
||||
}
|
||||
|
||||
triggerChange(doctype, name) {
|
||||
this.trigger(`change:${doctype}`, {name:name}, 500);
|
||||
this.trigger(`change`, {doctype:name, name:name}, 500);
|
||||
}
|
||||
async setValue(doctype, name, fieldname, value) {
|
||||
return await this.setValues(doctype, name, {
|
||||
[fieldname]: value
|
||||
});
|
||||
}
|
||||
|
||||
async insert(doctype, doc) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
async setValues(doctype, name, fieldValuePair) {
|
||||
//
|
||||
}
|
||||
|
||||
// insert parent
|
||||
if (meta.isSingle) {
|
||||
await this.updateSingle(meta, doc, doctype);
|
||||
} else {
|
||||
await this.insertOne(doctype, doc);
|
||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', order = 'desc' } = {}) {
|
||||
// select {fields} from {doctype} where {filters} order by {orderBy} {order} limit {start} {limit}
|
||||
}
|
||||
|
||||
getFilterConditions(filters) {
|
||||
// {"status": "Open"} => `status = "Open"`
|
||||
|
||||
// {"status": "Open", "name": ["like", "apple%"]}
|
||||
// => `status="Open" and name like "apple%"
|
||||
|
||||
// {"date": [">=", "2017-09-09", "<=", "2017-11-01"]}
|
||||
// => `date >= 2017-09-09 and date <= 2017-11-01`
|
||||
|
||||
let filtersArray = [];
|
||||
|
||||
for (let key in filters) {
|
||||
let value = filters[key];
|
||||
let field = key;
|
||||
let operator = '=';
|
||||
let comparisonValue = value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
operator = value[0];
|
||||
comparisonValue = value[1];
|
||||
operator = operator.toLowerCase();
|
||||
|
||||
if (operator === 'includes') {
|
||||
operator = 'like';
|
||||
}
|
||||
|
||||
// insert children
|
||||
await this.insertChildren(meta, doc, doctype);
|
||||
|
||||
this.triggerChange(doctype, doc.name);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
|
||||
async insertChildren(meta, doc, doctype) {
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
let idx = 0;
|
||||
for (let child of (doc[field.fieldname] || [])) {
|
||||
this.prepareChild(doctype, doc.name, child, field, idx);
|
||||
await this.insertOne(field.childtype, child);
|
||||
idx++;
|
||||
}
|
||||
if (['like', 'includes'].includes(operator) && !comparisonValue.includes('%')) {
|
||||
comparisonValue = `%${comparisonValue}%`;
|
||||
}
|
||||
}
|
||||
|
||||
filtersArray.push([field, operator, comparisonValue]);
|
||||
|
||||
if (Array.isArray(value) && value.length > 2) {
|
||||
// multiple conditions
|
||||
let operator = value[2];
|
||||
let comparisonValue = value[3];
|
||||
filtersArray.push([field, operator, comparisonValue]);
|
||||
}
|
||||
}
|
||||
|
||||
async insertOne(doctype, doc) {
|
||||
// insert into {doctype} ({fields}) values ({values})
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
let meta = frappe.getMeta(doctype);
|
||||
|
||||
// update parent
|
||||
if (meta.isSingle) {
|
||||
await this.updateSingle(meta, doc, doctype);
|
||||
} else {
|
||||
await this.updateOne(doctype, doc);
|
||||
}
|
||||
|
||||
// insert or update children
|
||||
await this.updateChildren(meta, doc, doctype);
|
||||
|
||||
this.triggerChange(doctype, doc.name);
|
||||
|
||||
return doc;
|
||||
}
|
||||
|
||||
async updateChildren(meta, doc, doctype) {
|
||||
let tableFields = meta.getTableFields();
|
||||
for (let field of tableFields) {
|
||||
// first key is "parent" - for SQL params
|
||||
let added = [doc.name];
|
||||
for (let child of (doc[field.fieldname] || [])) {
|
||||
this.prepareChild(doctype, doc.name, child, field, added.length - 1);
|
||||
if (await this.exists(field.childtype, child.name)) {
|
||||
await this.updateOne(field.childtype, child);
|
||||
}
|
||||
else {
|
||||
await this.insertOne(field.childtype, child);
|
||||
}
|
||||
added.push(child.name);
|
||||
}
|
||||
await this.runDeleteOtherChildren(field, added);
|
||||
}
|
||||
}
|
||||
|
||||
async updateOne(doctype, doc) {
|
||||
// update {doctype} set {field=value} where name=?
|
||||
}
|
||||
|
||||
async runDeleteOtherChildren(field, added) {
|
||||
// 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: 'SingleValue',
|
||||
parent: doctype,
|
||||
fieldname: field.fieldname,
|
||||
value: value
|
||||
})
|
||||
await singleValue.insert();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async deleteSingleValues(name) {
|
||||
// await frappe.db.run('delete from SingleValue where parent=?', name)
|
||||
}
|
||||
|
||||
prepareChild(parenttype, parent, child, field, idx) {
|
||||
if (!child.name) {
|
||||
child.name = frappe.getRandomString();
|
||||
}
|
||||
child.parent = parent;
|
||||
child.parenttype = parenttype;
|
||||
child.parentfield = field.fieldname;
|
||||
child.idx = idx;
|
||||
}
|
||||
|
||||
getKeys(doctype) {
|
||||
return frappe.getMeta(doctype).getValidFields({ withChildren: false });
|
||||
}
|
||||
|
||||
getFormattedValues(fields, doc) {
|
||||
let values = fields.map(field => {
|
||||
let value = doc[field.fieldname];
|
||||
if (value instanceof Date) {
|
||||
if (field.fieldtype==='Date') {
|
||||
// date
|
||||
return value.toISOString().substr(0, 10);
|
||||
} else {
|
||||
// datetime
|
||||
return value.toISOString();
|
||||
}
|
||||
} else if (field.fieldtype === 'Link' && !value) {
|
||||
// empty value must be null to satisfy
|
||||
// foreign key constraint
|
||||
return null;
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
});
|
||||
return values;
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
for (const name of names) {
|
||||
await this.delete(doctype, name);
|
||||
}
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
await this.deleteOne(doctype, name);
|
||||
|
||||
// delete children
|
||||
let tableFields = frappe.getMeta(doctype).getTableFields();
|
||||
for (let field of tableFields) {
|
||||
await this.deleteChildren(field.childtype, name);
|
||||
}
|
||||
|
||||
this.triggerChange(doctype, name);
|
||||
}
|
||||
|
||||
async deleteOne(doctype, name) {
|
||||
// delete from {doctype} where name = ?
|
||||
}
|
||||
|
||||
async deleteChildren(parenttype, parent) {
|
||||
// delete from {parenttype} where parent = ?
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.getValue(doctype, name)) ? true : false;
|
||||
}
|
||||
|
||||
async getValue(doctype, filters, fieldname = 'name') {
|
||||
if (typeof filters === 'string') {
|
||||
filters = { name: filters };
|
||||
}
|
||||
|
||||
let row = await this.getAll({
|
||||
doctype: doctype,
|
||||
fields: [fieldname],
|
||||
filters: filters,
|
||||
start: 0,
|
||||
limit: 1,
|
||||
orderBy: 'name',
|
||||
order: 'asc'
|
||||
});
|
||||
return row.length ? row[0][fieldname] : null;
|
||||
}
|
||||
|
||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', order = 'desc' } = {}) {
|
||||
// select {fields} from {doctype} where {filters} order by {orderBy} {order} limit {start} {limit}
|
||||
}
|
||||
|
||||
getFilterConditions(filters) {
|
||||
// {"status": "Open"} => `status = "Open"`
|
||||
|
||||
// {"status": "Open", "name": ["like", "apple%"]}
|
||||
// => `status="Open" and name like "apple%"
|
||||
|
||||
// {"date": [">=", "2017-09-09", "<=", "2017-11-01"]}
|
||||
// => `date >= 2017-09-09 and date <= 2017-11-01`
|
||||
|
||||
let filtersArray = [];
|
||||
|
||||
for (let key in filters) {
|
||||
let value = filters[key];
|
||||
let field = key;
|
||||
let operator = '=';
|
||||
let comparisonValue = value;
|
||||
|
||||
if (Array.isArray(value)) {
|
||||
operator = value[0];
|
||||
comparisonValue = value[1];
|
||||
operator = operator.toLowerCase();
|
||||
|
||||
if (operator === 'includes') {
|
||||
operator = 'like';
|
||||
}
|
||||
|
||||
if (['like', 'includes'].includes(operator) && !comparisonValue.includes('%')) {
|
||||
comparisonValue = `%${comparisonValue}%`;
|
||||
}
|
||||
}
|
||||
|
||||
filtersArray.push([field, operator, comparisonValue]);
|
||||
|
||||
if (Array.isArray(value) && value.length > 2) {
|
||||
// multiple conditions
|
||||
let operator = value[2];
|
||||
let comparisonValue = value[3];
|
||||
filtersArray.push([field, operator, comparisonValue]);
|
||||
}
|
||||
}
|
||||
|
||||
let conditions = filtersArray.map(filter => {
|
||||
const [field, operator, comparisonValue] = filter;
|
||||
|
||||
let placeholder = Array.isArray(comparisonValue) ?
|
||||
comparisonValue.map(v => '?').join(', ') :
|
||||
'?';
|
||||
|
||||
return `ifnull(${field}, '') ${operator} (${placeholder})`;
|
||||
});
|
||||
|
||||
let values = filtersArray.reduce((acc, filter) => {
|
||||
const comparisonValue = filter[2];
|
||||
if (Array.isArray(comparisonValue)) {
|
||||
acc = acc.concat(comparisonValue);
|
||||
} else {
|
||||
acc.push(comparisonValue);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
conditions: conditions.length ? conditions.join(" and ") : "",
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
async run(query, params) {
|
||||
// run query
|
||||
}
|
||||
|
||||
async sql(query, params) {
|
||||
// run sql
|
||||
}
|
||||
|
||||
async commit() {
|
||||
// commit
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': 'text'
|
||||
, 'Currency': 'real'
|
||||
, 'Int': 'integer'
|
||||
, 'Float': 'real'
|
||||
, 'Percent': 'real'
|
||||
, 'Check': 'integer'
|
||||
, 'Small Text': 'text'
|
||||
, 'Long Text': 'text'
|
||||
, 'Code': 'text'
|
||||
, 'Text Editor': 'text'
|
||||
, 'Date': 'text'
|
||||
, 'Datetime': 'text'
|
||||
, 'Time': 'text'
|
||||
, 'Text': 'text'
|
||||
, 'Data': 'text'
|
||||
, 'Link': 'text'
|
||||
, 'DynamicLink': 'text'
|
||||
, 'Password': 'text'
|
||||
, 'Select': 'text'
|
||||
, 'Read Only': 'text'
|
||||
, 'File': 'text'
|
||||
, 'Attach': 'text'
|
||||
, 'Attach Image': 'text'
|
||||
, 'Signature': 'text'
|
||||
, 'Color': 'text'
|
||||
, 'Barcode': 'text'
|
||||
, 'Geolocation': 'text'
|
||||
}
|
||||
let conditions = filtersArray.map(filter => {
|
||||
const [field, operator, comparisonValue] = filter;
|
||||
|
||||
let placeholder = Array.isArray(comparisonValue) ?
|
||||
comparisonValue.map(v => '?').join(', ') :
|
||||
'?';
|
||||
|
||||
return `ifnull(${field}, '') ${operator} (${placeholder})`;
|
||||
});
|
||||
|
||||
let values = filtersArray.reduce((acc, filter) => {
|
||||
const comparisonValue = filter[2];
|
||||
if (Array.isArray(comparisonValue)) {
|
||||
acc = acc.concat(comparisonValue);
|
||||
} else {
|
||||
acc.push(comparisonValue);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return {
|
||||
conditions: conditions.length ? conditions.join(" and ") : "",
|
||||
values
|
||||
};
|
||||
}
|
||||
|
||||
async run(query, params) {
|
||||
// run query
|
||||
}
|
||||
|
||||
async sql(query, params) {
|
||||
// run sql
|
||||
}
|
||||
|
||||
async commit() {
|
||||
// commit
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': 'text'
|
||||
, 'Currency': 'real'
|
||||
, 'Int': 'integer'
|
||||
, 'Float': 'real'
|
||||
, 'Percent': 'real'
|
||||
, 'Check': 'integer'
|
||||
, 'Small Text': 'text'
|
||||
, 'Long Text': 'text'
|
||||
, 'Code': 'text'
|
||||
, 'Text Editor': 'text'
|
||||
, 'Date': 'text'
|
||||
, 'Datetime': 'text'
|
||||
, 'Time': 'text'
|
||||
, 'Text': 'text'
|
||||
, 'Data': 'text'
|
||||
, 'Link': 'text'
|
||||
, 'DynamicLink': 'text'
|
||||
, 'Password': 'text'
|
||||
, 'Select': 'text'
|
||||
, 'Read Only': 'text'
|
||||
, 'File': 'text'
|
||||
, 'Attach': 'text'
|
||||
, 'Attach Image': 'text'
|
||||
, 'Signature': 'text'
|
||||
, 'Color': 'text'
|
||||
, 'Barcode': 'text'
|
||||
, 'Geolocation': 'text'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
333
backends/http.js
333
backends/http.js
@ -2,157 +2,220 @@ const frappe = require('frappejs');
|
||||
const Observable = require('frappejs/utils/observable');
|
||||
|
||||
module.exports = class HTTPClient extends Observable {
|
||||
constructor({ server, protocol = 'http' }) {
|
||||
super();
|
||||
constructor({ server, protocol = 'http' }) {
|
||||
super();
|
||||
|
||||
this.server = server;
|
||||
this.protocol = protocol;
|
||||
frappe.config.serverURL = this.getURL();
|
||||
this.server = server;
|
||||
this.protocol = protocol;
|
||||
frappe.config.serverURL = this.getURL();
|
||||
|
||||
// if the backend is http, then always client!
|
||||
frappe.isServer = false;
|
||||
// if the backend is http, then always client!
|
||||
frappe.isServer = false;
|
||||
|
||||
this.initTypeMap();
|
||||
this.initTypeMap();
|
||||
}
|
||||
|
||||
connect() {
|
||||
|
||||
}
|
||||
|
||||
async insert(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let filesToUpload = this.getFilesToUpload(doc);
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
|
||||
const responseDoc = await this.fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(doc)
|
||||
});
|
||||
|
||||
await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);
|
||||
|
||||
return responseDoc;
|
||||
}
|
||||
|
||||
async get(doctype, name) {
|
||||
name = encodeURIComponent(name);
|
||||
let url = this.getURL('/api/resource', doctype, name);
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders()
|
||||
})
|
||||
}
|
||||
|
||||
async getAll({ doctype, fields, filters, start, limit, sortBy, order }) {
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
|
||||
url = url + '?' + frappe.getQueryString({
|
||||
fields: JSON.stringify(fields),
|
||||
filters: JSON.stringify(filters),
|
||||
start: start,
|
||||
limit: limit,
|
||||
sortBy: sortBy,
|
||||
order: order
|
||||
});
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let filesToUpload = this.getFilesToUpload(doc);
|
||||
let url = this.getURL('/api/resource', doctype, doc.name);
|
||||
|
||||
const responseDoc = await this.fetch(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(doc)
|
||||
});
|
||||
|
||||
await this.uploadFilesAndUpdateDoc(filesToUpload, doctype, responseDoc);
|
||||
|
||||
return responseDoc;
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
let url = this.getURL('/api/resource', doctype, name);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(names)
|
||||
});
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.getValue(doctype, name, 'name')) ? true : false;
|
||||
}
|
||||
|
||||
async getValue(doctype, name, fieldname) {
|
||||
let url = this.getURL('/api/resource', doctype, name, fieldname);
|
||||
|
||||
return (await this.fetch(url, {
|
||||
method: 'GET',
|
||||
})).value;
|
||||
}
|
||||
|
||||
async fetch(url, args) {
|
||||
args.headers = this.getHeaders();
|
||||
let response = await frappe.fetch(url, args);
|
||||
let data = await response.json();
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw Error(data.error);
|
||||
}
|
||||
|
||||
connect() {
|
||||
return data;
|
||||
}
|
||||
|
||||
}
|
||||
getFilesToUpload(doc) {
|
||||
const meta = frappe.getMeta(doc.doctype);
|
||||
const fileFields = meta.getFieldsWith({ fieldtype: 'File' });
|
||||
const filesToUpload = [];
|
||||
|
||||
async insert(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
return await this.fetch(url, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify(doc)
|
||||
})
|
||||
}
|
||||
|
||||
async get(doctype, name) {
|
||||
let url = this.getURL('/api/resource', doctype, name);
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
headers: this.getHeaders()
|
||||
})
|
||||
}
|
||||
|
||||
async getAll({ doctype, fields, filters, start, limit, sort_by, order }) {
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
|
||||
url = url + "?" + frappe.getQueryString({
|
||||
fields: JSON.stringify(fields),
|
||||
filters: JSON.stringify(filters),
|
||||
start: start,
|
||||
limit: limit,
|
||||
sort_by: sort_by,
|
||||
order: order
|
||||
});
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'GET',
|
||||
});
|
||||
}
|
||||
|
||||
async update(doctype, doc) {
|
||||
doc.doctype = doctype;
|
||||
let url = this.getURL('/api/resource', doctype, doc.name);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'PUT',
|
||||
body: JSON.stringify(doc)
|
||||
});
|
||||
}
|
||||
|
||||
async delete(doctype, name) {
|
||||
let url = this.getURL('/api/resource', doctype, name);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
});
|
||||
}
|
||||
|
||||
async deleteMany(doctype, names) {
|
||||
let url = this.getURL('/api/resource', doctype);
|
||||
|
||||
return await this.fetch(url, {
|
||||
method: 'DELETE',
|
||||
body: JSON.stringify(names)
|
||||
});
|
||||
}
|
||||
|
||||
async exists(doctype, name) {
|
||||
return (await this.getValue(doctype, name, 'name')) ? true : false;
|
||||
}
|
||||
|
||||
async getValue(doctype, name, fieldname) {
|
||||
let url = this.getURL('/api/resource', doctype, name, fieldname);
|
||||
|
||||
return (await this.fetch(url, {
|
||||
method: 'GET',
|
||||
})).value;
|
||||
}
|
||||
|
||||
async fetch(url, args) {
|
||||
args.headers = this.getHeaders();
|
||||
let response = await frappe.fetch(url, args);
|
||||
let data = await response.json();
|
||||
|
||||
if (response.status !== 200) {
|
||||
throw Error(data.error);
|
||||
if (fileFields.length > 0) {
|
||||
fileFields.forEach(df => {
|
||||
const files = doc[df.fieldname] || [];
|
||||
if (files.length) {
|
||||
filesToUpload.push({
|
||||
fieldname: df.fieldname,
|
||||
files: files
|
||||
})
|
||||
}
|
||||
|
||||
return data;
|
||||
delete doc[df.fieldname];
|
||||
});
|
||||
}
|
||||
|
||||
getURL(...parts) {
|
||||
return this.protocol + '://' + this.server + (parts || []).join('/');
|
||||
return filesToUpload;
|
||||
}
|
||||
|
||||
async uploadFilesAndUpdateDoc(filesToUpload, doctype, doc) {
|
||||
if (filesToUpload.length > 0) {
|
||||
// upload files
|
||||
for (const fileToUpload of filesToUpload) {
|
||||
const files = await this.uploadFiles(fileToUpload.files, doctype, doc.name, fileToUpload.fieldname);
|
||||
doc[fileToUpload.fieldname] = files[0].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async uploadFiles(fileList, doctype, name, fieldname) {
|
||||
let url = this.getURL('/api/upload', doctype, name, fieldname);
|
||||
|
||||
let formData = new FormData();
|
||||
for (const file of fileList) {
|
||||
formData.append('files', file, file.name);
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (frappe.session && frappe.session.token) {
|
||||
headers.token = frappe.session.token;
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
let response = await frappe.fetch(url, {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
});
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': true
|
||||
, 'Currency': true
|
||||
, 'Int': true
|
||||
, 'Float': true
|
||||
, 'Percent': true
|
||||
, 'Check': true
|
||||
, 'Small Text': true
|
||||
, 'Long Text': true
|
||||
, 'Code': true
|
||||
, 'Text Editor': true
|
||||
, 'Date': true
|
||||
, 'Datetime': true
|
||||
, 'Time': true
|
||||
, 'Text': true
|
||||
, 'Data': true
|
||||
, 'Link': true
|
||||
, 'DynamicLink': true
|
||||
, 'Password': true
|
||||
, 'Select': true
|
||||
, 'Read Only': true
|
||||
, 'File': true
|
||||
, 'Attach': true
|
||||
, 'Attach Image': true
|
||||
, 'Signature': true
|
||||
, 'Color': true
|
||||
, 'Barcode': true
|
||||
, 'Geolocation': true
|
||||
}
|
||||
const data = await response.json();
|
||||
if (response.status !== 200) {
|
||||
throw Error(data.error);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
close() {
|
||||
getURL(...parts) {
|
||||
return this.protocol + '://' + this.server + (parts || []).join('/');
|
||||
}
|
||||
|
||||
getHeaders() {
|
||||
const headers = {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
if (frappe.session && frappe.session.token) {
|
||||
headers.token = frappe.session.token;
|
||||
};
|
||||
return headers;
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': true
|
||||
, 'Currency': true
|
||||
, 'Int': true
|
||||
, 'Float': true
|
||||
, 'Percent': true
|
||||
, 'Check': true
|
||||
, 'Small Text': true
|
||||
, 'Long Text': true
|
||||
, 'Code': true
|
||||
, 'Text Editor': true
|
||||
, 'Date': true
|
||||
, 'Datetime': true
|
||||
, 'Time': true
|
||||
, 'Text': true
|
||||
, 'Data': true
|
||||
, 'Link': true
|
||||
, 'DynamicLink': true
|
||||
, 'Password': true
|
||||
, 'Select': true
|
||||
, 'Read Only': true
|
||||
, 'File': true
|
||||
, 'Attach': true
|
||||
, 'Attach Image': true
|
||||
, 'Signature': true
|
||||
, 'Color': true
|
||||
, 'Barcode': true
|
||||
, 'Geolocation': true
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -4,244 +4,268 @@ const Database = require('./database');
|
||||
const debug = false;
|
||||
|
||||
module.exports = class sqliteDatabase extends Database {
|
||||
constructor({ dbPath }) {
|
||||
super();
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
constructor({ dbPath }) {
|
||||
super();
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
|
||||
connect(dbPath) {
|
||||
if (dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
connect(dbPath) {
|
||||
if (dbPath) {
|
||||
this.dbPath = dbPath;
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.conn = new sqlite3.Database(this.dbPath, () => {
|
||||
if (debug) {
|
||||
this.conn.on('trace', (trace) => console.log(trace));
|
||||
}
|
||||
return new Promise(resolve => {
|
||||
this.conn = new sqlite3.Database(this.dbPath, () => {
|
||||
if (debug) {
|
||||
this.conn.on('trace', (trace) => console.log(trace));
|
||||
}
|
||||
this.run('PRAGMA foreign_keys=ON').then(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
this.run('PRAGMA foreign_keys=ON').then(resolve);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async tableExists(table) {
|
||||
const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`);
|
||||
return (name && name.length) ? true : false;
|
||||
}
|
||||
async tableExists(table) {
|
||||
const name = await this.sql(`SELECT name FROM sqlite_master WHERE type='table' AND name='${table}'`);
|
||||
return (name && name.length) ? true : false;
|
||||
}
|
||||
|
||||
async addForeignKeys(doctype, newForeignKeys) {
|
||||
await this.run('PRAGMA foreign_keys=OFF');
|
||||
await this.run('BEGIN TRANSACTION');
|
||||
async addForeignKeys(doctype, newForeignKeys) {
|
||||
await this.run('PRAGMA foreign_keys=OFF');
|
||||
await this.run('BEGIN TRANSACTION');
|
||||
|
||||
const tempName = 'TEMP' + doctype
|
||||
const tempName = 'TEMP' + doctype
|
||||
|
||||
// create temp table
|
||||
await this.createTable(doctype, tempName);
|
||||
// create temp table
|
||||
await this.createTable(doctype, tempName);
|
||||
|
||||
const columns = (await this.getTableColumns(tempName)).join(', ');
|
||||
const columns = (await this.getTableColumns(tempName)).join(', ');
|
||||
|
||||
// copy from old to new table
|
||||
await this.run(`INSERT INTO ${tempName} (${columns}) SELECT ${columns} from ${doctype}`);
|
||||
// copy from old to new table
|
||||
await this.run(`INSERT INTO ${tempName} (${columns}) SELECT ${columns} from ${doctype}`);
|
||||
|
||||
// drop old table
|
||||
await this.run(`DROP TABLE ${doctype}`);
|
||||
// drop old table
|
||||
await this.run(`DROP TABLE ${doctype}`);
|
||||
|
||||
// rename new table
|
||||
await this.run(`ALTER TABLE ${tempName} RENAME TO ${doctype}`);
|
||||
// rename new table
|
||||
await this.run(`ALTER TABLE ${tempName} RENAME TO ${doctype}`);
|
||||
|
||||
await this.run('COMMIT');
|
||||
await this.run('PRAGMA foreign_keys=ON');
|
||||
}
|
||||
await this.run('COMMIT');
|
||||
await this.run('PRAGMA foreign_keys=ON');
|
||||
}
|
||||
|
||||
removeColumns() {
|
||||
// pass
|
||||
}
|
||||
removeColumns() {
|
||||
// pass
|
||||
}
|
||||
|
||||
async runCreateTableQuery(doctype, columns, indexes) {
|
||||
const query = `CREATE TABLE IF NOT EXISTS ${doctype} (
|
||||
async runCreateTableQuery(doctype, columns, indexes) {
|
||||
const query = `CREATE TABLE IF NOT EXISTS ${doctype} (
|
||||
${columns.join(", ")} ${indexes.length ? (", " + indexes.join(", ")) : ''})`;
|
||||
|
||||
return await this.run(query);
|
||||
return await this.run(query);
|
||||
}
|
||||
|
||||
updateColumnDefinition(field, columns, indexes) {
|
||||
let def = this.getColumnDefinition(field);
|
||||
|
||||
columns.push(def);
|
||||
|
||||
if (field.fieldtype === 'Link' && field.target) {
|
||||
indexes.push(`FOREIGN KEY (${field.fieldname}) REFERENCES ${field.target} ON UPDATE CASCADE ON DELETE RESTRICT`);
|
||||
}
|
||||
}
|
||||
|
||||
updateColumnDefinition(field, columns, indexes) {
|
||||
let def = this.getColumnDefinition(field);
|
||||
getColumnDefinition(field) {
|
||||
let def = [
|
||||
field.fieldname,
|
||||
this.typeMap[field.fieldtype],
|
||||
field.fieldname === 'name' ? 'PRIMARY KEY NOT NULL' : '',
|
||||
field.required ? 'NOT NULL' : '',
|
||||
field.default ? `DEFAULT ${field.default}` : ''
|
||||
].join(' ');
|
||||
|
||||
columns.push(def);
|
||||
return def;
|
||||
}
|
||||
|
||||
if (field.fieldtype==='Link' && field.target) {
|
||||
indexes.push(`FOREIGN KEY (${field.fieldname}) REFERENCES ${field.target} ON UPDATE CASCADE ON DELETE RESTRICT`);
|
||||
}
|
||||
}
|
||||
async getTableColumns(doctype) {
|
||||
return (await this.sql(`PRAGMA table_info(${doctype})`)).map(d => d.name);
|
||||
}
|
||||
|
||||
getColumnDefinition(field) {
|
||||
let def = [
|
||||
field.fieldname,
|
||||
this.typeMap[field.fieldtype],
|
||||
field.fieldname === 'name' ? 'PRIMARY KEY NOT NULL' : '',
|
||||
field.required ? 'NOT NULL' : '',
|
||||
field.default ? `DEFAULT ${field.default}` : ''
|
||||
].join(' ');
|
||||
async getForeignKeys(doctype) {
|
||||
return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map(d => d.from);
|
||||
}
|
||||
|
||||
return def;
|
||||
}
|
||||
async runAddColumnQuery(doctype, field, values) {
|
||||
await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.getColumnDefinition(field)}`, values);
|
||||
}
|
||||
|
||||
async getTableColumns(doctype) {
|
||||
return (await this.sql(`PRAGMA table_info(${doctype})`)).map(d => d.name);
|
||||
}
|
||||
|
||||
async getForeignKeys(doctype) {
|
||||
return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map(d => d.from);
|
||||
}
|
||||
|
||||
async runAddColumnQuery(doctype, field, values) {
|
||||
await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.getColumnDefinition(field)}`, values);
|
||||
}
|
||||
|
||||
getOne(doctype, name, fields = '*') {
|
||||
fields = this.prepareFields(fields);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.get(`select ${fields} from ${doctype}
|
||||
getOne(doctype, name, fields = '*') {
|
||||
fields = this.prepareFields(fields);
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.get(`select ${fields} from ${doctype}
|
||||
where name = ?`, name,
|
||||
(err, row) => {
|
||||
resolve(row || {});
|
||||
});
|
||||
(err, row) => {
|
||||
resolve(row || {});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async insertOne(doctype, doc) {
|
||||
let fields = this.getKeys(doctype);
|
||||
let placeholders = fields.map(d => '?').join(', ');
|
||||
|
||||
if (!doc.name) {
|
||||
doc.name = frappe.getRandomString();
|
||||
}
|
||||
|
||||
async insertOne(doctype, doc) {
|
||||
let fields = this.getKeys(doctype);
|
||||
let placeholders = fields.map(d => '?').join(', ');
|
||||
|
||||
if (!doc.name) {
|
||||
doc.name = frappe.getRandomString();
|
||||
}
|
||||
|
||||
return await this.run(`insert into ${doctype}
|
||||
return await this.run(`insert into ${doctype}
|
||||
(${fields.map(field => field.fieldname).join(", ")})
|
||||
values (${placeholders})`, this.getFormattedValues(fields, doc));
|
||||
}
|
||||
}
|
||||
|
||||
async updateOne(doctype, doc) {
|
||||
let fields = this.getKeys(doctype);
|
||||
let assigns = fields.map(field => `${field.fieldname} = ?`);
|
||||
let values = this.getFormattedValues(fields, doc);
|
||||
async updateOne(doctype, doc) {
|
||||
let fields = this.getKeys(doctype);
|
||||
let assigns = fields.map(field => `${field.fieldname} = ?`);
|
||||
let values = this.getFormattedValues(fields, doc);
|
||||
|
||||
// additional name for where clause
|
||||
values.push(doc.name);
|
||||
// additional name for where clause
|
||||
values.push(doc.name);
|
||||
|
||||
return await this.run(`update ${doctype}
|
||||
return await this.run(`update ${doctype}
|
||||
set ${assigns.join(", ")} where name=?`, values);
|
||||
}
|
||||
}
|
||||
|
||||
async runDeleteOtherChildren(field, added) {
|
||||
// delete other children
|
||||
// `delete from doctype where parent = ? and name not in (?, ?, ?)}`
|
||||
await this.run(`delete from ${field.childtype}
|
||||
async runDeleteOtherChildren(field, added) {
|
||||
// delete other children
|
||||
// `delete from doctype where parent = ? and name not in (?, ?, ?)}`
|
||||
await this.run(`delete from ${field.childtype}
|
||||
where
|
||||
parent = ? and
|
||||
name not in (${added.slice(1).map(d => '?').join(', ')})`, added);
|
||||
}
|
||||
|
||||
async deleteOne(doctype, name) {
|
||||
return await this.run(`delete from ${doctype} where name=?`, name);
|
||||
}
|
||||
|
||||
async deleteChildren(parenttype, parent) {
|
||||
await this.run(`delete from ${parenttype} where parent=?`, parent);
|
||||
}
|
||||
|
||||
async deleteSingleValues(name) {
|
||||
await frappe.db.run('delete from SingleValue where parent=?', name)
|
||||
}
|
||||
|
||||
async setValues(doctype, name, fieldValuePair) {
|
||||
const meta = frappe.getMeta(doctype);
|
||||
const validFields = this.getKeys(doctype);
|
||||
const validFieldnames = validFields.map(df => df.fieldname);
|
||||
const fieldsToUpdate = Object.keys(fieldValuePair)
|
||||
.filter(fieldname => validFieldnames.includes(fieldname))
|
||||
|
||||
// assignment part of query
|
||||
const assigns = fieldsToUpdate.map(fieldname => `${fieldname} = ?`);
|
||||
|
||||
// values
|
||||
const values = fieldsToUpdate.map(fieldname => {
|
||||
const field = meta.getField(fieldname);
|
||||
const value = fieldValuePair[fieldname];
|
||||
return this.getFormattedValue(field, value);
|
||||
});
|
||||
|
||||
// additional name for where clause
|
||||
values.push(name);
|
||||
|
||||
return await this.run(`update ${doctype}
|
||||
set ${assigns.join(', ')} where name=?`, values);
|
||||
}
|
||||
|
||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) {
|
||||
if (!fields) {
|
||||
fields = frappe.getMeta(doctype).getKeywordFields();
|
||||
}
|
||||
if (typeof fields === 'string') {
|
||||
fields = [fields];
|
||||
}
|
||||
|
||||
async deleteOne(doctype, name) {
|
||||
return await this.run(`delete from ${doctype} where name=?`, name);
|
||||
}
|
||||
|
||||
async deleteChildren(parenttype, parent) {
|
||||
await this.run(`delete from ${parenttype} where parent=?`, parent);
|
||||
}
|
||||
|
||||
async deleteSingleValues(name) {
|
||||
await frappe.db.run('delete from SingleValue where parent=?', name)
|
||||
}
|
||||
|
||||
getAll({ doctype, fields, filters, start, limit, orderBy = 'modified', groupBy, order = 'desc' } = {}) {
|
||||
if (!fields) {
|
||||
fields = frappe.getMeta(doctype).getKeywordFields();
|
||||
}
|
||||
if (typeof fields === 'string') {
|
||||
fields = [fields];
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let conditions = this.getFilterConditions(filters);
|
||||
let query = `select ${fields.join(", ")}
|
||||
return new Promise((resolve, reject) => {
|
||||
let conditions = this.getFilterConditions(filters);
|
||||
let query = `select ${fields.join(", ")}
|
||||
from ${doctype}
|
||||
${conditions.conditions ? "where" : ""} ${conditions.conditions}
|
||||
${groupBy ? ("group by " + groupBy.join(', ')) : ""}
|
||||
${orderBy ? ("order by " + orderBy) : ""} ${orderBy ? (order || "asc") : ""}
|
||||
${limit ? ("limit " + limit) : ""} ${start ? ("offset " + start) : ""}`;
|
||||
|
||||
this.conn.all(query, conditions.values,
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
this.conn.all(query, conditions.values,
|
||||
(err, rows) => {
|
||||
if (err) {
|
||||
console.error(err);
|
||||
reject(err);
|
||||
} else {
|
||||
resolve(rows);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
run(query, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.run(query, params, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
sql(query, params) {
|
||||
return new Promise((resolve) => {
|
||||
this.conn.all(query, params, (err, rows) => {
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async commit() {
|
||||
try {
|
||||
await this.run('commit');
|
||||
} catch (e) {
|
||||
if (e.errno !== 1) {
|
||||
throw e;
|
||||
}
|
||||
run(query, params) {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.conn.run(query, params, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': 'text'
|
||||
, 'Currency': 'real'
|
||||
, 'Int': 'integer'
|
||||
, 'Float': 'real'
|
||||
, 'Percent': 'real'
|
||||
, 'Check': 'integer'
|
||||
, 'Small Text': 'text'
|
||||
, 'Long Text': 'text'
|
||||
, 'Code': 'text'
|
||||
, 'Text Editor': 'text'
|
||||
, 'Date': 'text'
|
||||
, 'Datetime': 'text'
|
||||
, 'Time': 'text'
|
||||
, 'Text': 'text'
|
||||
, 'Data': 'text'
|
||||
, 'Link': 'text'
|
||||
, 'DynamicLink': 'text'
|
||||
, 'Password': 'text'
|
||||
, 'Select': 'text'
|
||||
, 'Read Only': 'text'
|
||||
, 'File': 'text'
|
||||
, 'Attach': 'text'
|
||||
, 'Attach Image': 'text'
|
||||
, 'Signature': 'text'
|
||||
, 'Color': 'text'
|
||||
, 'Barcode': 'text'
|
||||
, 'Geolocation': 'text'
|
||||
}
|
||||
sql(query, params) {
|
||||
return new Promise((resolve) => {
|
||||
this.conn.all(query, params, (err, rows) => {
|
||||
resolve(rows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async commit() {
|
||||
try {
|
||||
await this.run('commit');
|
||||
} catch (e) {
|
||||
if (e.errno !== 1) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
initTypeMap() {
|
||||
this.typeMap = {
|
||||
'Autocomplete': 'text'
|
||||
, 'Currency': 'real'
|
||||
, 'Int': 'integer'
|
||||
, 'Float': 'real'
|
||||
, 'Percent': 'real'
|
||||
, 'Check': 'integer'
|
||||
, 'Small Text': 'text'
|
||||
, 'Long Text': 'text'
|
||||
, 'Code': 'text'
|
||||
, 'Text Editor': 'text'
|
||||
, 'Date': 'text'
|
||||
, 'Datetime': 'text'
|
||||
, 'Time': 'text'
|
||||
, 'Text': 'text'
|
||||
, 'Data': 'text'
|
||||
, 'Link': 'text'
|
||||
, 'DynamicLink': 'text'
|
||||
, 'Password': 'text'
|
||||
, 'Select': 'text'
|
||||
, 'Read Only': 'text'
|
||||
, 'File': 'text'
|
||||
, 'Attach': 'text'
|
||||
, 'Attach Image': 'text'
|
||||
, 'Signature': 'text'
|
||||
, 'Color': 'text'
|
||||
, 'Barcode': 'text'
|
||||
, 'Geolocation': 'text'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -29,6 +29,25 @@ module.exports = class BaseMeta extends BaseDocument {
|
||||
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) {
|
||||
return this.getField(fieldname).label;
|
||||
}
|
||||
|
67
models/doctype/File/File.js
Normal file
67
models/doctype/File/File.js
Normal file
@ -0,0 +1,67 @@
|
||||
module.exports = {
|
||||
name: 'File',
|
||||
doctype: 'DocType',
|
||||
isSingle: 0,
|
||||
keywordFields: [
|
||||
'name',
|
||||
'filename'
|
||||
],
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'name',
|
||||
label: 'File Path',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
},
|
||||
{
|
||||
fieldname: 'filename',
|
||||
label: 'File Name',
|
||||
fieldtype: 'Data',
|
||||
required: 1,
|
||||
},
|
||||
{
|
||||
fieldname: 'mimetype',
|
||||
label: 'MIME Type',
|
||||
fieldtype: 'Data',
|
||||
},
|
||||
{
|
||||
fieldname: 'size',
|
||||
label: 'File Size',
|
||||
fieldtype: 'Int',
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceDoctype',
|
||||
label: 'Reference DocType',
|
||||
fieldtype: 'Data',
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceName',
|
||||
label: 'Reference Name',
|
||||
fieldtype: 'Data',
|
||||
},
|
||||
{
|
||||
fieldname: 'referenceField',
|
||||
label: 'Reference Field',
|
||||
fieldtype: 'Data',
|
||||
},
|
||||
],
|
||||
layout: [
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['filename'] },
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['mimetype'] },
|
||||
{ fields: ['size'] },
|
||||
]
|
||||
},
|
||||
{
|
||||
columns: [
|
||||
{ fields: ['referenceDoctype'] },
|
||||
{ fields: ['referenceName'] },
|
||||
]
|
||||
},
|
||||
]
|
||||
}
|
@ -1,66 +1,66 @@
|
||||
const indicatorColor = require('frappejs/ui/constants/indicators');
|
||||
const { BLUE, GREEN } = require('frappejs/ui/constants/indicators');
|
||||
|
||||
module.exports = {
|
||||
name: "ToDo",
|
||||
label: "To Do",
|
||||
naming: "autoincrement",
|
||||
pageSettings: {
|
||||
hideTitle: true
|
||||
name: 'ToDo',
|
||||
label: 'To Do',
|
||||
naming: 'autoincrement',
|
||||
pageSettings: {
|
||||
hideTitle: true
|
||||
},
|
||||
isSingle: 0,
|
||||
keywordFields: [
|
||||
'subject',
|
||||
'description'
|
||||
],
|
||||
titleField: 'subject',
|
||||
indicators: {
|
||||
key: 'status',
|
||||
colors: {
|
||||
Open: BLUE,
|
||||
Closed: GREEN
|
||||
}
|
||||
},
|
||||
fields: [
|
||||
{
|
||||
fieldname: 'subject',
|
||||
label: 'Subject',
|
||||
fieldtype: 'Data',
|
||||
required: 1
|
||||
},
|
||||
"isSingle": 0,
|
||||
"keywordFields": [
|
||||
"subject",
|
||||
"description"
|
||||
],
|
||||
titleField: 'subject',
|
||||
indicators: {
|
||||
key: 'status',
|
||||
colors: {
|
||||
Open: indicatorColor.BLUE,
|
||||
Closed: indicatorColor.GREEN
|
||||
}
|
||||
{
|
||||
fieldname: 'status',
|
||||
label: 'Status',
|
||||
fieldtype: 'Select',
|
||||
options: [
|
||||
'Open',
|
||||
'Closed'
|
||||
],
|
||||
default: 'Open',
|
||||
required: 1
|
||||
},
|
||||
"fields": [
|
||||
{
|
||||
"fieldname": "subject",
|
||||
"label": "Subject",
|
||||
"fieldtype": "Data",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "status",
|
||||
"label": "Status",
|
||||
"fieldtype": "Select",
|
||||
"options": [
|
||||
"Open",
|
||||
"Closed"
|
||||
],
|
||||
"default": "Open",
|
||||
"required": 1
|
||||
},
|
||||
{
|
||||
"fieldname": "description",
|
||||
"label": "Description",
|
||||
"fieldtype": "Text"
|
||||
}
|
||||
],
|
||||
{
|
||||
fieldname: 'description',
|
||||
label: 'Description',
|
||||
fieldtype: 'Text'
|
||||
}
|
||||
],
|
||||
|
||||
links: [
|
||||
{
|
||||
label: 'Close',
|
||||
condition: (form) => form.doc.status !== 'Closed',
|
||||
action: async (form) => {
|
||||
await form.doc.set('status', 'Closed');
|
||||
await form.doc.update();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Re-Open',
|
||||
condition: (form) => form.doc.status !== 'Open',
|
||||
action: async (form) => {
|
||||
await form.doc.set('status', 'Open');
|
||||
await form.doc.update();
|
||||
}
|
||||
}
|
||||
]
|
||||
links: [
|
||||
{
|
||||
label: 'Close',
|
||||
condition: (form) => form.doc.status !== 'Closed',
|
||||
action: async (form) => {
|
||||
await form.doc.set('status', 'Closed');
|
||||
await form.doc.update();
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Re-Open',
|
||||
condition: (form) => form.doc.status !== 'Open',
|
||||
action: async (form) => {
|
||||
await form.doc.set('status', 'Open');
|
||||
await form.doc.update();
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
@ -11,6 +11,7 @@ module.exports = {
|
||||
SystemSettings: require('./doctype/SystemSettings/SystemSettings.js'),
|
||||
ToDo: require('./doctype/ToDo/ToDo.js'),
|
||||
User: require('./doctype/User/User.js'),
|
||||
UserRole: require('./doctype/UserRole/UserRole.js')
|
||||
UserRole: require('./doctype/UserRole/UserRole.js'),
|
||||
File: require('./doctype/File/File.js'),
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,7 @@
|
||||
"luxon": "^1.0.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"morgan": "^1.9.0",
|
||||
"multer": "^1.3.1",
|
||||
"mysql": "^2.15.0",
|
||||
"node-fetch": "^1.7.3",
|
||||
"node-sass": "^4.7.2",
|
||||
|
@ -18,12 +18,15 @@ const auth = require('./../auth/auth')();
|
||||
const morgan = require('morgan');
|
||||
const { addWebpackMiddleware } = require('../webpack/serve');
|
||||
const { getAppConfig } = require('../webpack/utils');
|
||||
const appConfig = getAppConfig();
|
||||
|
||||
frappe.conf = getAppConfig();
|
||||
|
||||
require.extensions['.html'] = function (module, filename) {
|
||||
module.exports = fs.readFileSync(filename, 'utf8');
|
||||
};
|
||||
|
||||
process.env.NODE_ENV = 'development';
|
||||
|
||||
module.exports = {
|
||||
async start({backend, connectionParams, models, authConfig=null}) {
|
||||
await this.init();
|
||||
@ -39,9 +42,8 @@ module.exports = {
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
for (let staticPath of [appConfig.distPath, appConfig.staticPath]) {
|
||||
app.use(express.static(staticPath));
|
||||
}
|
||||
app.use(express.static(frappe.conf.distPath));
|
||||
app.use('/static', express.static(frappe.conf.staticPath))
|
||||
|
||||
app.use(morgan('tiny'));
|
||||
|
||||
@ -65,7 +67,7 @@ module.exports = {
|
||||
addWebpackMiddleware(app);
|
||||
}
|
||||
|
||||
frappe.config.port = appConfig.dev.devServerPort
|
||||
frappe.config.port = frappe.conf.dev.devServerPort;
|
||||
|
||||
// listen
|
||||
server.listen(frappe.config.port, () => {
|
||||
|
@ -1,4 +1,6 @@
|
||||
const frappe = require('frappejs');
|
||||
const path = require('path');
|
||||
const multer = require('multer');
|
||||
|
||||
module.exports = {
|
||||
setup(app) {
|
||||
@ -43,6 +45,45 @@ module.exports = {
|
||||
return response.json(doc.getValidDict());
|
||||
}));
|
||||
|
||||
const upload = multer({
|
||||
storage: multer.diskStorage({
|
||||
destination: (req, file, cb) => {
|
||||
cb(null, frappe.conf.staticPath)
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const filename = file.originalname.split('.')[0];
|
||||
const extension = path.extname(file.originalname);
|
||||
const now = Date.now();
|
||||
cb(null, filename + '-' + now + extension);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
app.post('/api/upload/:doctype/:name/:fieldname', upload.array('files', 10), frappe.asyncHandler(async function(request, response) {
|
||||
const files = request.files;
|
||||
const { doctype, name, fieldname } = request.params;
|
||||
|
||||
let fileDocs = [];
|
||||
for (let file of files) {
|
||||
const doc = frappe.newDoc({
|
||||
doctype: 'File',
|
||||
name: path.join('/', file.path),
|
||||
filename: file.originalname,
|
||||
mimetype: file.mimetype,
|
||||
size: file.size,
|
||||
referenceDoctype: doctype,
|
||||
referenceName: name,
|
||||
referenceFieldname: fieldname
|
||||
});
|
||||
await doc.insert();
|
||||
|
||||
await frappe.db.setValue(doctype, name, fieldname, doc.name);
|
||||
|
||||
fileDocs.push(doc.getValidDict());
|
||||
}
|
||||
|
||||
return response.json(fileDocs);
|
||||
}));
|
||||
|
||||
// get document
|
||||
app.get('/api/resource/:doctype/:name', frappe.asyncHandler(async function(request, response) {
|
||||
|
@ -1,6 +1,6 @@
|
||||
<template>
|
||||
<form :class="['frappe-form-layout', { 'was-validated': invalid }]">
|
||||
<div class="row" v-if="layoutConfig"
|
||||
<div class="form-row" v-if="layoutConfig"
|
||||
v-for="(section, i) in layoutConfig.sections" :key="i"
|
||||
v-show="showSection(i)"
|
||||
>
|
||||
|
@ -3,7 +3,7 @@
|
||||
<list-actions
|
||||
:doctype="doctype"
|
||||
:showDelete="checkList.length"
|
||||
@new="newDoc"
|
||||
@new="$emit('newDoc')"
|
||||
@delete="deleteCheckedItems"
|
||||
/>
|
||||
<ul class="list-group">
|
||||
@ -60,18 +60,18 @@ export default {
|
||||
this.updateList();
|
||||
},
|
||||
methods: {
|
||||
async newDoc() {
|
||||
let doc = await frappe.getNewDoc(this.doctype);
|
||||
this.$router.push(`/edit/${this.doctype}/${doc.name}`);
|
||||
},
|
||||
async updateList(query=null) {
|
||||
let filters = null
|
||||
async updateList(query = null) {
|
||||
let filters = null;
|
||||
if (query) {
|
||||
filters = {
|
||||
keywords : ['like', query]
|
||||
}
|
||||
keywords: ['like', query]
|
||||
};
|
||||
}
|
||||
const indicatorField = this.hasIndicator ? this.meta.indicators.key : null;
|
||||
|
||||
const indicatorField = this.hasIndicator
|
||||
? this.meta.indicators.key
|
||||
: null;
|
||||
|
||||
const fields = [
|
||||
'name',
|
||||
indicatorField,
|
||||
@ -89,7 +89,7 @@ export default {
|
||||
},
|
||||
openForm(name) {
|
||||
this.activeItem = name;
|
||||
this.$router.push(`/edit/${this.doctype}/${name}`);
|
||||
this.$emit('openForm', name);
|
||||
},
|
||||
async deleteCheckedItems() {
|
||||
await frappe.db.deleteMany(this.doctype, this.checkList);
|
||||
@ -112,7 +112,7 @@ export default {
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../../styles/variables";
|
||||
@import '../../styles/variables';
|
||||
|
||||
.list-group-item {
|
||||
border-left: none;
|
||||
|
@ -3,38 +3,55 @@ import Base from './Base';
|
||||
|
||||
export default {
|
||||
extends: Base,
|
||||
computed: {
|
||||
inputClass() {
|
||||
return ['d-none'];
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
getWrapperElement(h) {
|
||||
let fileName = this.docfield.placeholder || this._('Choose a file..');
|
||||
let filePath = null;
|
||||
|
||||
if (this.$refs.input && this.$refs.input.files.length) {
|
||||
if (this.value && typeof this.value === 'string') {
|
||||
filePath = this.value;
|
||||
}
|
||||
else if (this.$refs.input && this.$refs.input.files.length) {
|
||||
fileName = this.$refs.input.files[0].name;
|
||||
}
|
||||
|
||||
const fileButton = h('button', {
|
||||
class: ['btn btn-outline-secondary btn-block'],
|
||||
domProps: {
|
||||
textContent: fileName
|
||||
},
|
||||
const fileLink = h('a', {
|
||||
attrs: {
|
||||
type: 'button'
|
||||
href: filePath,
|
||||
target: '_blank'
|
||||
},
|
||||
on: {
|
||||
click: () => this.$refs.input.click()
|
||||
domProps: {
|
||||
textContent: this._('View File')
|
||||
}
|
||||
});
|
||||
|
||||
return h('div', {
|
||||
const helpText = h('small', {
|
||||
class: 'form-text text-muted'
|
||||
}, [fileLink]);
|
||||
|
||||
const fileNameLabel = h('label', {
|
||||
class: ['custom-file-label'],
|
||||
domProps: {
|
||||
textContent: filePath || fileName
|
||||
}
|
||||
});
|
||||
|
||||
const fileInputWrapper = h('div', {
|
||||
class: ['custom-file']
|
||||
},
|
||||
[this.getInputElement(h), fileNameLabel, filePath ? helpText : null]
|
||||
);
|
||||
|
||||
return h(
|
||||
'div',
|
||||
{
|
||||
class: ['form-group', ...this.wrapperClass],
|
||||
attrs: {
|
||||
'data-fieldname': this.docfield.fieldname
|
||||
}
|
||||
}, [this.getLabelElement(h), this.getInputElement(h), fileButton]);
|
||||
},
|
||||
[this.getLabelElement(h), fileInputWrapper]
|
||||
);
|
||||
},
|
||||
getInputAttrs() {
|
||||
return {
|
||||
@ -48,6 +65,9 @@ export default {
|
||||
accept: (this.docfield.filetypes || []).join(',')
|
||||
};
|
||||
},
|
||||
getInputClass() {
|
||||
return 'custom-file-input';
|
||||
},
|
||||
getInputListeners() {
|
||||
return {
|
||||
change: e => {
|
||||
|
@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div class="frappe-list-form row no-gutters">
|
||||
<div class="col-4 border-right">
|
||||
<frappe-list :doctype="doctype" :key="doctype" />
|
||||
<frappe-list :doctype="doctype" :key="doctype" @newDoc="openNewDoc" @openForm="openForm" />
|
||||
</div>
|
||||
<div class="col-8">
|
||||
<frappe-form v-if="name" :key="doctype + name" :doctype="doctype" :name="name" @save="onSave" />
|
||||
@ -13,22 +13,30 @@ import List from '../components/List/List';
|
||||
import Form from '../components/Form/Form';
|
||||
|
||||
export default {
|
||||
props: ['doctype', 'name'],
|
||||
components: {
|
||||
FrappeList: List,
|
||||
FrappeForm: Form
|
||||
props: ['doctype', 'name'],
|
||||
components: {
|
||||
FrappeList: List,
|
||||
FrappeForm: Form
|
||||
},
|
||||
methods: {
|
||||
onSave(doc) {
|
||||
if (doc.name !== this.$route.params.name) {
|
||||
this.$router.push(`/edit/${doc.doctype}/${doc.name}`);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onSave(doc) {
|
||||
if (doc.name !== this.$route.params.name) {
|
||||
this.$router.push(`/edit/${doc.doctype}/${doc.name}`);
|
||||
}
|
||||
}
|
||||
openForm(name) {
|
||||
name = encodeURIComponent(name);
|
||||
this.$router.push(`/edit/${this.doctype}/${name}`);
|
||||
},
|
||||
async openNewDoc() {
|
||||
let doc = await frappe.getNewDoc(this.doctype);
|
||||
this.$router.push(`/edit/${this.doctype}/${doc.name}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.frappe-list-form {
|
||||
min-height: calc(100vh - 4rem);
|
||||
min-height: calc(100vh - 4rem);
|
||||
}
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user