UNPKG

flexbiz-server

Version:

Flexible Server

1,338 lines (1,265 loc) 46 kB
const controller = require('../controllers/controller'); const underscore =require('underscore'); const async = require("async"); const {evalute} = require("../libs/utils"); const utils = require("../libs/utils"); const {isSupperAdmin} = require("../libs/utils"); const PostBook = require('../libs/post-book'); const PostSocai = require('../libs/post-socai'); const moment = require("moment"); const numeral = require("numeral"); const fs = require("fs"); const _ = require("lodash") const controllerUtils = require("../controllers/controllerUtils"); const joinModel2 = (arr,id_app, model, joinFields, fn,options={cache:true})=>{ return [...arr].joinModel2(id_app, model, joinFields, fn,options); } const fieldScheme = new global.Schema( { stt:{type:Number,default:0}, stt_col:{type:Number,default:0}, name:{type: String, required: "Yêu cầu nhập mã trường",index:true,lowercase:true,trim:true}, type:{type: String, default:'String',trim:true}, form:{type: String,trim:true},//user for type array or Mixed header:{type: String, required: "Yêu cầu nhập tên trường"}, header2:{type: String}, api_description:{type: String},//Miêu tả field trong tài liệu api sort:Number, unique:Boolean, default:global.Schema.Types.Mixed, lowercase:{type:Boolean,default:false}, uppercase:{type:Boolean,default:false}, required:global.Schema.Types.Mixed, index:{type:Boolean,default:false}, is_tmp:{type:Boolean,default:false}, is_title:{type:Boolean,default:false}, on_view:{type:Boolean,default:true}, maxlength:Number, min:global.Schema.Types.Mixed, max:global.Schema.Types.Mixed, ref_model:{type: String,trim:true}, ref_field:{type:String,trim:true}, ref_label:{type:String,trim:true}, ref_label_as:{type:String,trim:true}, ref_search_fields:{type:String,trim:true}, ref_data_fields:{type:String,trim:true}, ref_condition:{type:String,trim:true}, multiple:Boolean, not_display:global.Schema.Types.Mixed, not_input:global.Schema.Types.Mixed, min_width_display:{type:Number,default:80}, format:{type:String}, align:{type:String}, color:{type:String}, bold:Boolean, html_variant_display:{type:String}, html_component_display:{type:String}, html_component_input:{type:String}, render_on_list:String, render_on_view:String, render_title:String, grid_configs:{type:String,trim:true}, handle_value_changed:{type:String,trim:true}, handle_value_changed_on_list:{type:String,trim:true},//script chạy khi dữ liệu được thay đổi trực tiếp trên grid handle_search_condition:{type:String,trim:true}, tab:{type:String,trim:true}, group:{type:String,trim:true}, help_text:{type:String,trim:true}, line:{type:Number,default:0} } ) const listinfoSchema = new global.Schema({ menu_position:String, code: {type: String, required: true,index:true,lowercase:true,trim:true}, title: {type: String, required: true}, title2: {type: String}, mother_code: {type: String,lowercase:true,trim:true}, api_code: {type: String,lowercase:true,trim:true}, model_code: {type: String,lowercase:true,trim:true}, used_by_3rd_systems: String,//Api được sử dụng bởi phần mềm nào. ví dụ n8n fields:[fieldScheme], postinfos:[global.Schema.Types.Mixed], exfields:global.Schema.Types.Mixed, create_model: {type: Boolean, default: false}, breadcrumbs: {type: String,trim:true}, permistion_code:String, not_add:Boolean, not_edit:Boolean, not_delete:Boolean, not_copy:Boolean, //events handle_cell_clicked:{type: String,trim:true}, expression_check_save:{type:String,trim:true}, on_saved:{type:String,trim:true}, on_deleting:{type:String,trim:true}, on_deleted:{type:String,trim:true}, on_init:{type:String,trim:true}, on_init_update:{type:String,trim:true}, handle_view_server:{type:String,trim:true}, handle_oncreating_server:{type:String,trim:true}, handle_oncreated_server:{type:String,trim:true}, handle_onupdating_server:{type:String,trim:true}, handle_onupdated_server:{type:String,trim:true}, handle_ondeleting_server:{type:String,trim:true}, //condition search_required:Boolean, search_form:{type:String,trim:true}, default_condition:{type:String,trim:true}, require_condition:{type:String,trim:true}, // export_yn:Boolean, sort_by:String, not_need_right:{type: Boolean,default:false},//Có cần phân quyền hay không require_id_app:{type: Boolean,default:true},//Dữ liệu theo công ty hay dữ liệu chung private_data:Boolean, view_tabs:[], allow_users:{type: String,trim:true}, input_users:{type: String,trim:true}, create_menu:Boolean, show_view_tabs_on_grid:Boolean, form_size:{type:String,default:"md",trim:true}, // is_dashboard_item:{type: Boolean, default: false}, dashboard_default:{type: Boolean, default: true}, dashboard_ids:{type: String,trim:true}, dashboard_stt:{type: Number}, // get_other_fields:String, options:{}, status: {type: Boolean, default: true}, date_created: {type: Date, default: Date.now}, date_updated: {type: Date, default: Date.now}, user_created: {type: String, default: ''}, user_updated: {type: String, default: ''} }); if((global.configs||{}).createIndexes){ listinfoSchema.index({code: 1},{unique:true}); listinfoSchema.index({used_by_3rd_systems: 1}); listinfoSchema.index({title: 1}); listinfoSchema.index({title2: 1}); listinfoSchema.index({mother_code: 1}); listinfoSchema.index({api_code: 1}); listinfoSchema.index({code:1,api_code: 1}); listinfoSchema.index({model_code: 1}); listinfoSchema.index({mother_code: 1}); listinfoSchema.index({is_dashboard_item: 1}); listinfoSchema.index({dashboard_ids: 1}); listinfoSchema.index({mother_code: "text",code:"text",title:"text"},{name:"listinfo_index2_text"}); listinfoSchema.index({status: 1}); listinfoSchema.index({user_created: 1,visible_to: 1,visible_to_users: 1}); } const model = global.mongoose.models.listinfo || global.mongoose.model('listinfo', listinfoSchema); const requireFields= model.requireFields ={ id_app:{type:String,required:true}, exfields:global.Schema.Types.Mixed, listinfo_code:String, status: {type: Boolean, default: true}, date_created: {type: Date, default: Date.now}, date_updated: {type: Date, default: Date.now}, user_created: {type: String, default: ''}, user_updated: {type: String, default: ''}, visible_to: {type: Number, default: 0}, visible_to_users: [String], } model.createSchema = (info)=>{ if(!info.create_model) return null; const require_id_app = (info.require_id_app!=false && info.require_id_app!="false"); const _requireFields ={...requireFields} if(!require_id_app){ delete _requireFields.id_app; } let mySchema = new global.Schema(_requireFields,{strict: false}); let require_paths = Object.keys(mySchema.paths); let fields = {}; for(let f of info.fields.filter(f=>f.type!=="Link" && f.type!=="Files" && f.type!=="Action" && f.type!=="Group" && !f.is_tmp && require_paths.indexOf(f.name)<0)){ if(f.unique){ f.index = true; } let _field = Object.assign({},f); if(_field.type=="DateTime" || _field.type=="Time") _field.type= "Date"; if(_field.type=="File" || _field.type=="OtherFile") _field.type= "Mixed"; if(_field.type=="Array") _field.type= "Mixed"; if(_field.type=="Pdf") _field.type= "String"; if(_field.type=="Image") _field.type= "String"; if(_field.type=="String") _field.trim = true; _field.required = false; _field.isDynamic = true; delete _field.name; delete _field.header; delete _field.header2; delete _field.line; delete _field.sort; delete _field.unique; delete _field._id; delete _field.stt; delete _field.not_display; delete _field.not_search; delete _field.format; delete _field.align; delete _field.color; delete _field.form; delete _field.min_width_display; delete _field.html_component_display; delete _field.html_variant_display; delete _field.html_component_input; delete _field.handle_value_changed; delete _field.handle_value_changed_on_list; delete _field.default; delete _field.multiple; delete _field.tab; delete _field.help_text; delete _field.grid_configs; delete _field.render_on_list; delete _field.render_on_view; delete _field.is_tmp; delete _field.is_title; delete _field.on_view; delete _field.handle_view_server; if(_field.type=="String" || _field.type=="Number" ){ _field.maxlength = _field.maxlength || 4000; }else{ delete _field.maxlength } fields[f.name] = _field } try{ mySchema.add(fields); }catch(e){ Logger.error("[listinfo] [createSchema]",e.message); Logger.error(JSON.stringify(fields),null,2); throw e } //create index for all fields if((global.configs||{}).createIndexes){ //Tạo index chứng từ chuyển if(info.fields.find(f=>f.id_ct_chuyen)){ mySchema.index({id_app: 1, id_ct_chuyen:1},{name:"index_id_ct_chuyen"}); } //tạo dynamic index const dynamic_indexes = info.fields.filter(f=>f.index); let indexs = {}; if(require_id_app){ indexs.id_app =1; } dynamic_indexes.forEach(field=>{ indexs[field.name] = 1; }) if(Object.keys(indexs).length>0){ mySchema.index(indexs,{name:"index_dynamic"}); } //Tạo index cho user let index_user = {user_created: 1,visible_to: 1,visible_to_users: 1,status:1} if(require_id_app){ index_user.id_app =1; } if(info.fields.find(f=>f.name=="trang_thai")){ index_user.trang_thai =1; } if(info.fields.find(f=>f.name=="of_user")){ index_user.of_user =1; } if(info.fields.find(f=>f.name=="phu_trach")){ index_user.phu_trach =1; } mySchema.index(index_user); mySchema.index({index_user,...indexs}); //create unique index let uq = info.fields.filter(f=>f.unique); if(uq.length>0){ let indUq = {id_app:1}; if(!require_id_app){ delete indUq.id_app; } uq.forEach((field) => { indUq[field.name] =field.sort || 1; }); mySchema.index(indUq,{unique: true }); } //create text index let api_code = info.api_code || info.code; let index_name = `${api_code}_index_text`; mySchema.index({"$**": "text" },{name:index_name}); /*//create requiere index const requireIndex ={} require_paths.forEach(rq=>{ requireIndex[rq] =1; }) mySchema.index(requireIndex);*/ } return mySchema; } model.updateSchema = async (schema,listinfo_code)=>{ const info = await model.findOne({code:listinfo_code}).lean(); if(!info || !info.fields) return; for(let field of info.fields.filter(f=>f.type!=="Link" && f.type!=="Files" && f.type!=="Action" && f.type!=="Group")){ if(schema.paths[field.name]){ //Nếu là dynamic field thì remove để tạo lại if(schema.paths[field.name].options && schema.paths[field.name].options.isDynamic){ schema.remove(field.name); }else{ continue; } } //Nếu là field tmp thì không thêm vào schema if(field.is_tmp){ continue; } // if(field.toObject) field = field.toObject(); const _field = {...field}; if(_field.type=="DateTime" || _field.type=="Time") _field.type= "Date"; if(_field.type=="File" || _field.type=="OtherFile") _field.type= "Mixed"; if(_field.type=="Array") _field.type= "Mixed"; if(_field.type=="Pdf") _field.type= "String"; if(_field.type=="Image") _field.type= "String"; if(_field.type=="String") _field.trim = true; _field.required = false; _field.isDynamic = true; delete _field.stt_col; delete _field.name; delete _field.header; delete _field.header2; delete _field.line; delete _field.sort; delete _field.unique; delete _field._id; delete _field.stt; delete _field.not_display; delete _field.not_search; delete _field.format; delete _field.align; delete _field.color; delete _field.form; delete _field.min_width_display; delete _field.html_component_display; delete _field.html_variant_display; delete _field.html_component_input; delete _field.handle_value_changed; delete _field.handle_value_changed_on_list; delete _field.default; delete _field.multiple; delete _field.tab; delete _field.help_text; delete _field.grid_configs; delete _field.render_on_list; delete _field.render_on_view; delete _field.is_tmp; delete _field.is_title; delete _field.on_view; delete _field.handle_view_server; if(_field.type=="String" || _field.type=="Number" ){ _field.maxlength = _field.maxlength || 4000; }else{ delete _field.maxlength } //Logger.info(`[listinfo] [updateSchema] [${listinfo_code}] add field:`,field.name); schema.add({[field.name]:_field}); } } model.updateModel = async (info,_newModel)=>{ if(info.toObject) info = info.toObject(); if(!info.create_model) return null; let model_code = info.api_code || info.model_code || info.code; if(!_newModel) _newModel = global.mongoose.models[model_code]; if(!_newModel) return; if(!_newModel.schema){ Logger.error("[listinfo][updateModel] model does not have schema",_newModel); return; } // //Logger.info("[listinfo][updateModel] update model...",model_code); const api_code = info.api_code || info.code; const _newList = global.controllers[api_code.toUpperCase()]; let require_paths = Object.keys(requireFields); let fields = [...info.fields]; //add ref fields let fields_have_ref =fields.filter(f=>f.ref_model && f.ref_model.indexOf("[")<0 && f.ref_field && f.type!=="Array" && f.type!=="Mixed" && f.ref_label) .map(f=>f.ref_label_as || f.ref_label) .filter(f=>!fields.find(gf=>gf.name===f)) .map(f=>{ return { name:f, type:"String" } }) //Logger.info("ref fields",fields_have_ref); fields = fields.concat(fields_have_ref); const fields_can_xu_ly = fields.filter(f=>f.type!=="Link" && f.type!=="Files" && f.type!=="Action" && f.type!=="Group" && require_paths.indexOf(f.name)<0); // for(let field of fields_can_xu_ly){ if(field.toObject) field = field.toObject(); const _field = {...field}; const schemaCode = field.form; if(underscore.has(_newModel.schema.paths,field.name)){ if(field.type==="Array" || field.type==="Mixed"){ //Cập nhật field cho tab detail if(schemaCode){ const schemaOfDetail = _newModel.schema.paths[field.name].schema; if(schemaOfDetail){ await model.updateSchema(schemaOfDetail,schemaCode) } } continue; } //xoá field để cập nhật lại nếu là dynamic field if(_newModel.schema.paths[field.name].options && _newModel.schema.paths[field.name].options.isDynamic){ _newModel.schema.remove(field.name); }else{ continue; } } //Nếu là field tmp thì không thêm vào schema if(field.is_tmp){ continue; } if(_field.type=="DateTime" || _field.type=="Time") _field.type= "Date"; if(_field.type=="File" || _field.type=="OtherFile") _field.type= "Mixed"; if(_field.type=="Array") _field.type= "Mixed"; if(_field.type=="Pdf") _field.type= "String"; if(_field.type=="Image") _field.type= "String"; if(_field.type=="String") _field.trim = true; if(model_code!=info.code){ _field.required = false;//phai luon dat la false neu khong se bi dung giua cac api khac dung chung model } _field.isDynamic = true; delete _field.stt_col; delete _field.name; delete _field.header; delete _field.header2; delete _field.line; delete _field.sort; delete _field.unique; delete _field._id; delete _field.stt; delete _field.not_display; delete _field.not_search; delete _field.format; delete _field.align; delete _field.color; delete _field.form; delete _field.min_width_display; delete _field.html_component_display; delete _field.html_variant_display; delete _field.html_component_input; delete _field.handle_value_changed; delete _field.handle_value_changed_on_list; delete _field.default; delete _field.multiple; delete _field.tab; delete _field.help_text; delete _field.grid_configs; delete _field.render_on_list; delete _field.render_on_view; delete _field.is_tmp; delete _field.is_title; delete _field.on_view; delete _field.handle_view_server; if(_field.type=="String" || _field.type=="Number" ){ _field.maxlength = _field.maxlength || 4000; }else{ delete _field.maxlength } _newModel.schema.add({[field.name]:_field}); //if(!field.unique && field.type!="Array" && field.type!="Mixed" && field.index) _newModel.schema.index({[field.name]:1}); } //create unique index if((global.configs||{}).createIndexes){ //tạo dynamic index try{ await _newModel.collection.dropIndex('index_dynamic'); }catch(e){/* */} const dynamic_indexes = info.fields.filter(f=>f.index); if(dynamic_indexes.length>0){ let indexs = {}; if(_newModel.schema.paths.id_app){ indexs.id_app =1; } dynamic_indexes.forEach(field=>{ indexs[field.name] = 1; }) if(Object.keys(indexs).length>0){ _newModel.schema.index(indexs,{name:"index_dynamic"}); } } //Tạo index chứng từ chuyển if(info.fields.find(f=>f.id_ct_chuyen)){ try{ await _newModel.collection.dropIndex('index_id_ct_chuyen'); }catch(e){/* */} _newModel.schema.index({id_app: 1, id_ct_chuyen:1},{name:"index_id_ct_chuyen"}); } if(_newList && _newList.is_dynamic_list){ let uq = fields.filter(f=>f.unique && f.name!="so_ct");//trường so_ct không được phép unique let indUq; if(uq.length>0){ if(_newModel.schema.paths.id_app){ indUq = {id_app:1}; }else{ indUq = {}; } uq.forEach((field) => { indUq[field.name] =field.sort || 1; }); } if(_newList.options.unique && _newList.options.unique.length>0){ let old_index_unique ={id_app:1}; _newList.options.unique.forEach(u=>{ let field = fields.find(f=>f.name===u); old_index_unique[u]= (field||{}).sort || 1; }); //Logger.info("drop current index unique...",old_index_unique); _newModel.collection.dropIndex(old_index_unique,()=>{ //if(e) return Logger.error("Can't drop index unique",old_index_unique,model_code,e); //create new unique if(indUq){ //Logger.info("create new index unique...",indUq); _newModel.schema.index(indUq,{unique: true }); } }) }else{ //create new unique if(indUq){ //Logger.info("create new index unique...",indUq) _newModel.schema.index(indUq,{unique: true }); } } } } } model.createModel = (info)=>{ if(!info.create_model) return null; let model_code = info.api_code || info.model_code || info.code; //models are not allowed to create new APIs if(global.secu_models.indexOf(model_code)>=0) return null; // let exModel = global.mongoose.models[model_code] if(!exModel){ let model_path = (((global.configs||{}).paths||{}).models || __dirname) + "/" + model_code + ".js" if(fs.existsSync(model_path)){ exModel = require(model_path); } } if(!exModel){ exModel = global.mongoose.model(model_code, model.createSchema(info)); }else{ model.updateModel(info,exModel); } return exModel; } const deletePost = (_listInfo,obj)=>{ return new Promise((resolve,reject)=>{ let bookbackup={}; async.mapSeries((_listInfo.postinfos||[]).filter(info=>info.model && info.script && info.condition),(postInfo,callback)=>{ setImmediate(async ()=>{ let m = global.getModel(postInfo.model); let scriptCondition = postInfo.condition.indexOf("return ")<0?`return ${postInfo.condition}`:postInfo.condition; let condition = evalute(scriptCondition,{master:obj,data:obj,moment}); condition.id_app = obj.id_app; //backup old data bookbackup[postInfo.model] = await m.find(condition).lean(); //delete await m.deleteMany(condition); callback(); }) },async (e)=>{ if(e){ //restore old data for(let key in bookbackup){ if(bookbackup[key].length>0){ await global.getModel(key.toString()).insertMany(bookbackup[key]); } } return reject(e); } resolve(bookbackup) }) }) } const post = (_listInfo,obj)=>{ if(obj.toObject) obj = obj.toObject(); return Promise.all((_listInfo.postinfos||[]).filter(info=>info.model && info.script && info.condition).map(postInfo=>{ return (async ()=>{ let script = `return (async ()=>{ try{ ${postInfo.script} }catch(e){ Logger.error("error post data",e) return {error:e} } })()` let data = await evalute(script,{master:{...obj},data:{...obj},moment,numeral}); if(data && data.error){ throw data.error } //Logger.info("post dynamic",postInfo.model,data); return new Promise((resolve,reject)=>{ if(postInfo.model==="socai"){ const postsocai = new PostSocai(obj, data,null,null,{deleteQuery:{id_ct:"#"}});//không xoá sổ sách vì trước đó đã xoá postsocai.run((e, rs)=>{ if(e) return reject(e); resolve(rs); }); }else{ let m = global.getModel(postInfo.model); const posttdttno = new PostBook(obj, data, m, function(detail, callback) { callback(detail); },{deleteQuery:{id_ct:"#"}});//không xoá sổ sách vì trước đó đã xoá posttdttno.run(function(e, rs) { //Logger.info("posted dynamic",rs,e) if(e) return reject(e); resolve(rs); }) } }) })() })) } const dynamicFinding = (_listInfo,user,condition,next,options={})=>{ if(_listInfo.api_code && _listInfo.api_code!=_listInfo.code){ //don't change condition of original api return next(null,condition); } if(_listInfo.private_data){ condition.user_created = user.email } if(_listInfo.require_condition){ try{ let script = _listInfo.require_condition; if(script.indexOf("return ")<0) script = `return ${script}`; const require_condition = evalute(script,{user,condition,moment,numeral,request:options.req}); if(require_condition && Object.keys(require_condition).length>0){ for(let key in require_condition){ condition[key] = require_condition[key]; } } }catch(e){ Logger.error(e); return next(`Error on require condition: ${e.message}`); } } next(null,condition); } const dynamicCreating = async (listInfo,user,obj,next,options={})=>{ let _listInfo = await model.findOne({_id:listInfo._id}).lean(); if(!_listInfo) return next("Không tìm thấy danh mục này"); for(let key in _listInfo){ listInfo[key] = _listInfo[key]; } const require_id_app = (_listInfo.require_id_app!=false && _listInfo.require_id_app!="false"); const supportUsers = configs.supportUsers || [] if (!require_id_app && (_listInfo.input_users||"").toLowerCase().indexOf(user.email.toLowerCase())<0 && !supportUsers.includes(user.email.toLowerCase()) && !isSupperAdmin(user.email.toLowerCase())) { //Logger.info("support users",supportUsers,user.email.toLowerCase()) return next('Bạn không có quyền thực hiện thao tác này'); } if(_listInfo.handle_oncreating_server && _listInfo.handle_oncreating_server.trim()!==""){ if(_listInfo.handle_oncreating_server.indexOf("next")<0) return next("Script xử lý dữ liệu trước khi tạo yêu cầu gọi function next"); try{ await new Promise((resolve,reject)=>{ async.parallel([ (next)=>{ const func_body = `(async ()=>{ try{ ${_listInfo.handle_oncreating_server} }catch(e){ next(e); } })();` setImmediate(async ()=>{ try{ await evalute(func_body,{obj,user,getLib:global.getLib,getModel:global.getModel,utils,query:utils.query,async,controller,controllerUtils,controllers:global.controllers,joinModel2,next,moment,numeral,request:options.req}) }catch(e){ Logger.error("[dynamicCreating][handle_oncreating_server]",{code:listInfo.code,handle_oncreating_server:_listInfo.handle_oncreating_server,e}) next(e); } }) } ],(e,rs)=>{ if(e){ Logger.error("[dynamicCreating][handle_oncreating_server]",listInfo.code,e,_listInfo.handle_oncreating_server); return reject(e) } resolve(rs); }) }) }catch(e){ Logger.error("[dynamicCreating]",listInfo.code,e); return next(e); } } next(null,obj); } const dynamicCreated = async (_listInfo,user,obj,next,options={})=>{ if(_listInfo.handle_oncreated_server && _listInfo.handle_oncreated_server.trim()!==""){ if(_listInfo.handle_oncreated_server.indexOf("next")<0) return next("Script xử lý dữ liệu sau khi tạo yêu cầu gọi function next"); try{ await new Promise((resolve,reject)=>{ async.parallel([ (next)=>{ const func_body = `(async ()=>{ try{ ${_listInfo.handle_oncreated_server} }catch(e){ next(e); } })();` setImmediate(async ()=>{ try{ await evalute(func_body,{obj,user,getLib:global.getLib,getModel:global.getModel,controller,controllerUtils,controllers:global.controllers,utils,query:utils.query,async,joinModel2,next,moment,numeral,request:options.req}) }catch(e){ Logger.error("error dynamic created",_listInfo.handle_oncreated_server,e) next(e) } }) } ],(e,rs)=>{ if(e){ Logger.error(e); return reject(e) } resolve(rs); }) }) }catch(e){ Logger.error(e); return next(e); } } next(null,obj); } const dynamicUpdating = async (listInfo,user,data,obj,next,options={})=>{ let _listInfo = await model.findOne({_id:listInfo._id}).lean(); if(!_listInfo) return next("Không tìm thấy danh mục này"); for(let key in _listInfo){ listInfo[key] = _listInfo[key]; } const require_id_app = (_listInfo.require_id_app!=false && _listInfo.require_id_app!="false"); const supportUsers = configs.supportUsers || []; if (!require_id_app && (_listInfo.input_users||"").toLowerCase().indexOf(user.email.toLowerCase())<0 && !supportUsers.includes(user.email.toLowerCase()) && !isSupperAdmin(user.email.toLowerCase())) { //Logger.info("support users",supportUsers,user.email.toLowerCase()) return next('Bạn không có quyền thực hiện thao tác này'); } if(_listInfo.handle_onupdating_server && _listInfo.handle_onupdating_server.trim()!==""){ //Logger.info("[listinof] [dynamicUpdating]",_listInfo.code,_listInfo.handle_onupdating_server); if(_listInfo.handle_onupdating_server.indexOf("next")<0) return next("Script xử lý dữ liệu trước khi update yêu cầu gọi function next"); try{ await new Promise((resolve,reject)=>{ async.series([ (next)=>{ const func_body = `(async ()=>{ try{ ${_listInfo.handle_onupdating_server} }catch(e){ next(e); } })();` setImmediate(async ()=>{ try{ await evalute(func_body,{data,obj,user,getLib:global.getLib,getModel:global.getModel,controller,controllerUtils,controllers:global.controllers,utils,query:utils.query,async,joinModel2,next,moment,numeral,request:options.req}) }catch(e){ Logger.error("[listinfo][dynamicUpdating] error dynamic update",_listInfo.handle_onupdating_server,e) next(e) } }) } ],(e,rs)=>{ if(e){ Logger.error("[listinfo][dynamicUpdating]",e); return reject(e) } resolve(rs); }) }) }catch(e){ Logger.error(e); return next(e); } }else{ //Logger.info("[listinof] [dynamicUpdating]",_listInfo.code,"không xử lý updating"); } next(null,data,obj); } const dynamicUpdated = async (_listInfo,user,obj,next,options={})=>{ if(_listInfo.handle_onupdated_server && _listInfo.handle_onupdated_server.trim()!==""){ if(_listInfo.handle_onupdated_server.indexOf("next")<0) return next("Script xử lý dữ liệu sau khi update yêu cầu gọi function next"); try{ await new Promise((resolve,reject)=>{ async.series([ (next)=>{ const func_body = `(async ()=>{ try{ ${_listInfo.handle_onupdated_server} }catch(e){ next(e) } })();` setImmediate(async ()=>{ try{ await evalute(func_body,{obj,user,getLib:global.getLib,getModel:global.getModel,controller,controllerUtils,controllers:global.controllers,utils,query:utils.query,async,joinModel2,next,moment,numeral,request:options.req}) }catch(e){ Logger.error("error dynamic updated",_listInfo.handle_onupdated_server,e) next(e) } }) } ],(e,rs)=>{ if(e){ Logger.error(e); return reject(e) } resolve(rs); }) }) }catch(e){ Logger.error(e); return next(e); } } next(null,obj); } const dynamicDeleting = async (listInfo,user,obj,next,options={})=>{ let _listInfo = await model.findOne({_id:listInfo._id}).lean(); if(!_listInfo) return next("Không tìm thấy danh mục này"); for(let key in _listInfo){ listInfo[key] = _listInfo[key]; } const require_id_app = (_listInfo.require_id_app!=false && _listInfo.require_id_app!="false"); const supportUsers = configs.supportUsers || []; if (!require_id_app && (_listInfo.input_users||"").toLowerCase().indexOf(user.email.toLowerCase())<0 && !supportUsers.includes(user.email) && !isSupperAdmin(user.email.toLowerCase())) { return next('Bạn không có quyền thực hiện thao tác này'); } if(_listInfo.handle_ondeleting_server && _listInfo.handle_ondeleting_server.trim()!==""){ if(_listInfo.handle_ondeleting_server.indexOf("next")<0) return next("Script xử lý dữ liệu trước khi update yêu cầu gọi function next"); try{ await new Promise((resolve,reject)=>{ async.series([ (next)=>{ const func_body = `(async ()=>{ try{ ${_listInfo.handle_ondeleting_server} }catch(e){ next(e); } })();` setImmediate(async ()=>{ try{ await evalute(func_body,{obj,user,getLib:global.getLib,getModel:global.getModel,controller,controllerUtils,controllers:global.controllers,utils,query:utils.query,async,joinModel2,next,moment,numeral,request:options.req}) }catch(e){ Logger.error("error dynamic delete",_listInfo.handle_ondeleting_server,e) next(e) } }) } ],(e,rs)=>{ if(e){ Logger.error(e); return reject(e) } resolve(rs); }) }) }catch(e){ Logger.error(e); return next(e); } } next(null,obj); } const onView = async (control,obj,user,items,next,options={})=>{ /*//chỉ xử lý view trên main code và current code let listinfo_code = options.listinfo_code || obj.api_code || obj.code;//nếu không cung cấp listinfo_code thì sẽ chạy dynamic view của api gốc let co_xu_ly_view = ((!obj.api_code || obj.code===obj.api_code) && control.is_dynamic_list) || (listinfo_code && listinfo_code.toLowerCase()===(obj.code||"").toLowerCase()) if(!co_xu_ly_view){ return next(null,items); }*/ const api_code = (obj.api_code || obj.code).toUpperCase(); if(obj.private_data){ items = items.filter(item=>item.user_created===user.email) } //handle view if(obj.handle_view_server && obj.handle_view_server.trim()!==""){ if(obj.handle_view_server.indexOf("next")<0) return next("Script xử lý view yêu cầu gọi function next"); await new Promise((resolve,reject)=>{ async.parallel([ (next)=>{ setImmediate(async ()=>{ const func_body = `(async ()=>{ try{ ${obj.handle_view_server} }catch(e){ next(e); } })();` try{ Logger.info("[listinfo][dynamicView] [handle view]",obj.code); await evalute(func_body,{items,user,getLib:global.getLib,utils,query:utils.query,next,async,joinModel2,moment,numeral,request:options.req}) }catch(e){ Logger.error("[listinfo][dynamicView] [handle view] error dynamic view",obj.handle_view_serve,e); next(e); } }) } ],(e,rs)=>{ if(e){ Logger.error(e); return reject(e); } resolve(rs); }) }) } // //let fields_have_ref = obj.fields.filter(f=>["Number","String"].indexOf(f.type)>0 && f.ref_model && f.ref_model.indexOf("[")<0 && f.ref_field && f.ref_label); let fields_have_ref = obj.fields.filter(f=>["Number","String"].indexOf(f.type)>0 && f.ref_model && f.ref_field && f.ref_label && ["tinhthanh","xaphuong","quanhuyen","account","currency","templatevoucher"].indexOf(f.ref_model)<0); if(fields_have_ref.length===0){ //console.timeEnd(timer); return next(null,items); } async.map(fields_have_ref, (f,cb)=>{ setImmediate(async ()=>{ let ref_model,where,fields,ref_name_model,ref_field,ref_condition,field_name,ref_label; ref_label = f.ref_label_as || f.ref_label; ref_field = f.ref_field; ref_condition = f.ref_condition; field_name = f.name; try{ ref_name_model= f.ref_model; if(ref_name_model.indexOf("[")>=0 || ref_name_model.indexOf(",")>=0){ try{ if(ref_name_model.indexOf("[")>=0){ ref_model = utils.JSONParser(ref_name_model) }else{ ref_model = ref_name_model.split(","); } }catch(e){ Logger.error("ref_model is not valid",ref_name_model,e); return cb(e); } }else{ if(ref_name_model==="dmkh") ref_name_model = "customer"; if(ref_name_model==="dmtk") ref_name_model = "account"; if(ref_name_model==="dmnt") ref_name_model = "currency"; if(ref_name_model==="dmnhtask") ref_name_model = "group"; ref_model = global.mongoose.models[ref_name_model]; if(!ref_model){ let _listInofOfRef = await model.findOne({code:ref_name_model}).lean(); if(_listInofOfRef && (_listInofOfRef.api_code || _listInofOfRef.model_code)){ let model_code = _listInofOfRef.api_code || _listInofOfRef.model_code; ref_model = global.mongoose.models[model_code]; } if(!ref_model){ let ctrl = global.controllers[ref_name_model.trim().toUpperCase()]; if(ctrl) ref_model = ctrl.getProperty("model"); } } if(!ref_model) ref_model = global.getModel(ref_name_model); } }catch(e){ Logger.error(e); return cb(e); } /*//chỉ xử lý dynamic joinview nếu field có giá trị let items_will_join = items.filter(item=>{ return (item[f.name] || item[f.name]==0) //Chỉ joinview nếu trường đó có giá trị && ( !item[ref_label] //chỉ joinview nếu chưa từng join với ref_model ||!Object.keys(item.__refs||{}).find(k=>k.includes(ref_name_model) && k.includes(f.name)) ) });*/ //Cần xử lý joinview tất cả các field để xoá dữ liệu ref không phù hợp let items_will_join = items; // if(items_will_join.length==0) return cb(); //xác định các fields cần lấy dữ liệu fields = {[ref_label]:f.ref_label}; if(f.ref_model==="trangthai" && f.name=="trang_thai"){ fields.color="color"; } // if(!_.isArray(ref_model)){ if(Object.keys(fields).length>0){ //create where //Logger.info("[listinfo][dynamicView] handle joinview",obj.code,{field_name,ref_name_model,ref_field,ref_label,rows:items_will_join.length}); where = (item)=>{ let w = { [ref_field]:item[field_name] } //other condition let condition; if(ref_condition && ref_condition.indexOf("API")<0){ let func_string = ref_condition; if(func_string.indexOf("return ")<0){ func_string = `return ${func_string}`; } let userInfo = {...user}; try{ condition = evalute(func_string,{master:item,data:item,detail:item,userInfo}); }catch(e){ Logger.error(e,func_string); } } if(condition){ if(ref_name_model==="participant"){ delete condition.groups } //check where of trangthai. Chỉ sử dụng ma_ct và ma_trang_thai if(ref_name_model==="trangthai"){ if(condition.ma_ct){ w.ma_ct = condition.ma_ct } }else{ for(let key in condition){ w.$and = w.$and||[]; w.$and.push({[key]:condition[key]}) } } } //model trạng thái yêu cầu phải có ma_ct if(ref_name_model==="trangthai" && !w.ma_ct){ w.ma_ct = api_code } return w; } //id app let id_app; if(underscore.has(ref_model.schema.paths,"id_app")){ id_app = user.current_id_app; } //Logger.info("[listinfo][dynamicView] [handle join]",obj.code,f.name,ref_name_model,fields); await items_will_join.asyncJoinModel2(id_app,ref_model,{where,fields}); } }else{ if(f.ref_field && f.ref_label){ items_will_join.forEach(item=>{ let ref_item = ref_model.find(r=>r[f.ref_field]==item[f.name]); if(ref_item) item[ref_label] = ref_item[f.ref_label] }) } } cb(); }) },(e)=>{ //console.timeEnd(timer); if(e) return next(e); next(null,items); }) } model.createController = (router,_listInfo)=>{ let api_code = _listInfo.api_code || _listInfo.code; if(!_listInfo.create_model) return null; let sort={},unique=[],_newModel,_newList; _listInfo.fields.filter(f=>f.sort).forEach(f=>{ sort[f.name] = f.sort; }) if(_listInfo.sort_by){ if(_listInfo.sort_by.indexOf("return ")>=0 || _listInfo.sort_by.indexOf("{")===0) { try{ let func_string = _listInfo.sort_by; if(func_string.indexOf("return ")<0){ func_string = "return " + func_string; } sort = evalute(func_string); }catch(e){ Logger.error("[listinfo] error create sort", _listInfo.sort_by,e); } }else{ sort = _listInfo.sort_by.split(",").reduce((sort,b)=>sort[b]=1,{}) } } //tạo danh sách unique. không được phép unique trường số chứng từ _listInfo.fields.filter(f=>f.unique && f.name!="so_ct").forEach(f=>{ unique.push(f.name); }) _newList = global.controllers[api_code.toUpperCase()]; if(!_newList){ if(api_code=="moduleinfo") Logger.info("[listinfo] create new API",api_code); if(Object.keys(sort).length===0) sort.date_created = -1; //create new model const require_id_app = (_listInfo.require_id_app!=false && _listInfo.require_id_app!="false"); //Logger.info("create new api",api_code); _newModel = model.createModel(_listInfo); if(!_newModel) return; _newList = new controller(router,_newModel,api_code.toLowerCase(),{ sort: sort, unique:unique, notNeedRight:_listInfo.not_need_right, require_id_app, onView:(user,items,next)=>{ next(null,items); //onView(_newList,_listInfo,user,items,next,options); }, onCreating:async (user,data,next)=>{ next(null,data); }, onUpdating:async (user,data,obj,next)=>{ next(null,data,obj); }, onDeleting:async (user,obj,next)=>{ next(null,obj); } }) _newList.is_dynamic_list = true; if(_listInfo.fields && _listInfo.postinfos && _listInfo.postinfos.length>0){ //Logger.info("dynamic list",_listInfo.code,"has post") _newList.dynamicDeletePost = async (obj)=>{ return deletePost(_listInfo,obj) } _newList.dynamicPost = async (obj, fn,options)=>{ if(_newList.post){ _newList.post(obj, async function(e){ if(e) return fn(e); try{ let rs = await post(_listInfo,obj); fn(null,rs) }catch(e){ fn(e.message || e.error || e) } },options) }else{ try{ let rs = await post(_listInfo,obj); fn(null,rs) }catch(e){ fn(e.message || e.error || e) } } } } _newList.route(); }else{ if(api_code=="moduleinfo") Logger.info("[listinfo] update API",api_code); //Logger.info("update API",api_code); //update options if(Object.keys(sort).length>0) _newList.sort = _newList.options.sort = sort; //update model _newModel = _newList.model; model.updateModel(_listInfo,_newModel); //set new unique and view if(_newList.is_dynamic_list || !_newList.unique || _newList.unique.length===0 ){ _newList.unique = _newList.options.unique=unique; } if(_listInfo.fields && _listInfo.postinfos && _listInfo.postinfos.length>0){ //chi xu lydynamic post mot lan de tranh trung du lieu if(!_newList.dynamicDeletePost) _newList.dynamicDeletePost = async (obj)=>{ return deletePost(_listInfo,obj) } if(!_newList.dynamicPost) _newList.dynamicPost = async (obj, fn,options)=>{ if(_newList.post){ _newList.post(obj, async function(e){ if(e) return fn(e); try{ let rs = await post(_listInfo,obj); fn(null,rs) }catch(e){ fn(e.message || e.error || e) } },options) }else{ try{ let rs = await post(_listInfo,obj); fn(null,rs) }catch(e){ fn(e.message || e.error || e) } } } }else{ _newList.dynamicPost = undefined _newList.dynamicDeletePost = undefined } } //dynamic View handler _newList.dynamicViews = _newList.dynamicViews ||{}; _newList.dynamicViews[_listInfo.code] = (user,items,next,options)=>{ onView(_newList,_listInfo,user,items,next,options); } _newList.dynamicView = (user,items,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicViews[listinfo_code]){ _newList.dynamicViews[listinfo_code](user,items,next,JSON.parse(JSON.stringify(options))); }else{ next(null,items); } } //dynamic Creating handler _newList.dynamicFindings = _newList.dynamicFindings ||{}; _newList.dynamicFindings[_listInfo.code] = (user,condition,next,options)=>{ dynamicFinding(_listInfo,user,condition,next,options); } _newList.dynamicFinding = (user,condition,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicFindings[listinfo_code]){ _newList.dynamicFindings[listinfo_code](user,condition,next,JSON.parse(JSON.stringify(options))); }else{ next(null,condition); } } //dynamic Creating handler _newList.dynamicCreatings = _newList.dynamicCreatings ||{}; _newList.dynamicCreatings[_listInfo.code] = (user,obj,next,options)=>{ dynamicCreating(_listInfo,user,obj,next,options); } _newList.dynamicCreating = (user,obj,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicCreatings[listinfo_code]){ _newList.dynamicCreatings[listinfo_code](user,obj,next,JSON.parse(JSON.stringify(options))); }else{ next(null,obj); } } //dynamic Created handler _newList.dynamicCreateds = _newList.dynamicCreateds ||{}; _newList.dynamicCreateds[_listInfo.code] = (user,obj,next,options)=>{ dynamicCreated(_listInfo,user,obj,next,options); } _newList.dynamicCreated = (user,obj,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicCreateds[listinfo_code]){ _newList.dynamicCreateds[listinfo_code](user,obj,next,JSON.parse(JSON.stringify(options))); }else{ next(null,obj); } } //dynamic Updating handler _newList.dynamicUpdatings = _newList.dynamicUpdatings ||{}; _newList.dynamicUpdatings[_listInfo.code] = (user,data,obj,next,options)=>{ dynamicUpdating(_listInfo,user,data,obj,next,options); } _newList.dynamicUpdating = (user,data,obj,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code //Logger.info("[listinfo] [dynamicUpdating] chọn hàm xử lý updating...",listinfo_code); if(_newList.dynamicUpdatings[listinfo_code]){ _newList.dynamicUpdatings[listinfo_code](user,data,obj,next,JSON.parse(JSON.stringify(options))); }else{ next(null,data,obj); } } //dynamic Updated handler _newList.dynamicUpdateds = _newList.dynamicUpdateds ||{}; _newList.dynamicUpdateds[_listInfo.code] = (user,obj,next,options)=>{ dynamicUpdated(_listInfo,user,obj,next,options); } _newList.dynamicUpdated = (user,obj,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicUpdateds[listinfo_code]){ _newList.dynamicUpdateds[listinfo_code](user,obj,next,JSON.parse(JSON.stringify(options))); }else{ next(null,obj); } } //dynamic Deleting handler _newList.dynamicDeletings = _newList.dynamicDeletings ||{}; _newList.dynamicDeletings[_listInfo.code] = (user,obj,next,options)=>{ dynamicDeleting(_listInfo,user,obj,next,options); } _newList.dynamicDeleting = (user,obj,next,options={})=>{ let listinfo_code = options.listinfo_code || _listInfo.api_code || _listInfo.code if(_newList.dynamicDeletings[listinfo_code]){ _newList.dynamicDeletings[listinfo_code](user,obj,next,JSON.parse(JSON.stringify(options))); }else{ next(null,obj); } } return _newList; } module.exports = model;