//#import pigeon.js
//#import eventBus.js
//#import Util.js
//#import base64.js
//#import HttpUtil.js
//#import jobs.js
//#import DigestUtil.js
//#import kafkautil.js
//#import @services/dblayer.jsx
//#import @handlers/include/diff.jsx
//#import $owl_task_info:services/modelService.jsx

function trim(s){
    if(s){
        return s.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
    }
    return ""
}

var @projectCodeService = (function (pigeon) {
    var objPrefix = '@projectCode'
    var listPrefix = '@projectCode'

    var spec = @spec

    var formSpecs = @formSpecs

    var flattenedSpecs = @flattenedSpecs

    var idFunc = @idFunc
    var lockFunc = @lockFunc

    var f = {
        /**
         * 添加
         */
        getId: function (data) {
            //默认实现是直接返回一个递增的Id
            //其他实现方式包括从data中获取数据构造出一个Id
            if(data.id){
                return data.id;
            }
            //@idOrig
            if (idFunc) {
                return objPrefix + '_' + idFunc(data)
            }
            else {
                var seq = pigeon.getId(objPrefix)
                return objPrefix + '_' + seq
            }
        },

        getSpec:function(){
            return spec;
        },

        getFormSpec:function(){
            return formSpecs;
        },

        getLock: function (data) {
            //@lockOrig
            if (lockFunc) {
                return lockFunc(data)
            }
            else {
                return data['id']
            }
        },

        lock:function(key){
            pigeon.lock(key);
        },
        unlock:function(key){
            pigeon.unlock(key);
        },

        /**
         * 返回一个树状结构
         * 本函数读进所有的数据进内存，在内存中构造一个树状结构
         * 所以对于大的数据不是很合适
         * 适用范围是中小型电商系统的商品分类数，地区管理等
         *
         */
        getTree(){},

        getAllListName: function (data) {
            return listPrefix + '_all'
        },

        getValue: function (fullkey, data) {
            var path = fullkey.split('.')
            var curData = data
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (typeof(curData) == 'object' && curData != null) {
                    curData = curData[curKey]
                }
                else {
                    return null
                }
            }
            return curData
        },
        deleteValue:function(fullkey,data){
            var path = fullkey.split('.')
            var curData = data
            for (var i = 0; i < path.length-1; i++) {
                var curKey = path[i]
                if (typeof(curData) == 'object' && curData != null) {
                    curData = curData[curKey]
                }
                else {
                    return null
                }
            }
            if(curData){
                var lastKey = path[path.length-1];
                delete curData[lastKey];
            }
        },

        setValue: function (fullkey, value, data) {
            var path = fullkey.split('.')
            var curData = data
            var objPath = [data]
            for (var i = 0; i < path.length; i++) {
                var curKey = path[i]
                if (i == path.length - 1) {
                    curData[curKey] = value
                    return
                }

                var subData = curData[curKey]
                if (!subData) {
                    curData[curKey] = {}
                    subData = curData[curKey]

                }
                curData = subData
            }

        },

        getEnvValue: function (v, env) {
            if (!v) {
                return v
            }
            if (v.indexOf('$') == 0) {
                var fullkey = v.substring(1)
                var newValue =  f.getValue(fullkey, env);
                if(newValue){
                    return newValue;
                }
            }
            return v
        },



        getUniqueObj:function(key,value){
            var pigeonKey = objPrefix+ '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if(obj==null){
                return null;
            }
            var id = obj.id;
            return pigeon.getObject(id);
        },

        isDuplicated: function (id, key, value) {
            var pigeonKey = objPrefix + '_' + key + '_' + DigestUtil.md5(value)
            var obj = pigeon.getObject(pigeonKey);
            if (obj == null) {
                return false
            }
            if (obj.id === id) {
                return false
            }
            return true
        },



        saveUniqueValue: function (objId, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            var obj = {
                id: objId,
                key: key,
                value: value,
                md5: md5,
            }
            pigeon.saveObject(pigeonKey, obj)
        },

        removeUniqueValue: function (id, key, value) {
            var md5 = DigestUtil.md5(value)
            var pigeonKey = objPrefix + '_' + key + '_' + md5
            pigeon.saveObject(pigeonKey, null)
        },

        validate: function (data, env) {
            //TODO:需要实现validate 服务器端
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                var value = f.getValue(field.key, data)
                if(value==null || typeof(value)=='undefined'){
                    value = field.defaultValue;
                    f.setValue(field.key, value, data)
                }
                if (typeof(value) == 'string' && value.indexOf('$') == 0) {
                    value = f.getEnvValue(value, env)
                    f.setValue(field.key, value, data)
                }
                if(((!value) || typeof(value)=='undefined') && field.autoGen=='true'){
                    value =$.getUUID()
                    f.setValue(field.key,value,data);
                }
                if ((value==null || typeof(value)=='undefined') && field.required == 'true') {
                    return {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                }

                //对于unique的字段，检查有没有重复
                if (field.unique === 'true' && value) {
                    if (f.isDuplicated(data.id, field.key, value)) {
                        $.log("duplicated,key=" + field.key + ",value=" + value + ",call removeUniqueValue(data.id, field.key, value)");
                        return {'state': 'err', msg: field.fieldLabel + '不能重复。' + value, code: 'duplicated'}
                    }
                }
            }

            var arrFields = flattenedSpecs.details;
            for(var j=0; j<arrFields.length; j++){
                var arrFieldInfo = arrFields[j];
                var rows = f.getValue(arrFieldInfo.name,data);
                if(rows){
                    for(var k=0; k<rows.length; k++){
                        var row = rows[k];
                        for(var l=0; l<arrFieldInfo.fields.length; l++){
                            var subField = arrFieldInfo.fields[l];
                            var value = row[subField.origKey];
                            if(value==null || typeof value=='undefined'){
                                value = subField.defaultValue;
                                row[subField.origKey] = value;
                            }
                            if (typeof(value) == 'string' && value.indexOf('$') == 0) {
                                value = f.getEnvValue(value, env)
                                row[subField.origKey] = value;
                            }
                            if(((!value) || typeof(value)=='undefined') && subField.autoGen=='true'){
                                value = $.getUUID()
                                row[subField.origKey] = value;
                            }
                            if ((value==null || typeof(value)=='undefined') && subField.required == 'true') {
                                return {'state': 'err', msg: arrFieldInfo.field.fieldLabel + '.' + subField.fieldLabel + '不能为空。出错的行数为：' + k, code: 'required'}
                            }
                        }
                    }
                }
            }
            return {'state': 'ok'}
        },

        saveUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof(value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if (!value && field.required == 'true') {
                        throw {'state': 'err', msg: field.fieldLabel + '不能为空。', code: 'required'}
                    }

                    if(value && typeof(value)=='string'){
                        if (f.isDuplicated(data.id, field.key, value)) {
                            throw {'state': 'err', msg: field.fieldLabel + '不能重复。value=' + value, code: 'duplicated'}
                        }

                        //对于没有重复的数据，保存起来
                        f.saveUniqueValue(data.id, field.key, value)
                    }

                }
            }
        },

        removeUniqueFields: function (data, env) {
            var fields = flattenedSpecs.mainFields
            for (var i = 0; i < fields.length; i++) {
                var field = fields[i]
                //对于unique的字段，检查有没有重复
                if (field.unique === 'true') {
                    var value = f.getValue(field.key, data)
                    if (!value || (typeof(value) == 'string' && value.indexOf('$') == 0)) {
                        value = f.getEnvValue(field.defaultValue, env)
                        if (value) {
                            f.setValue(field.key, value, data)
                        }
                    }
                    if(value && typeof(value)=='string'){
                        f.removeUniqueValue(data.id, field.key, value)
                    }
                }
            }
        },

        addToList: function (data) {
            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.addToList(f.getAllListName(), key, data.id)

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            pigeon.addToList(listName, key, data.id)
            //如果这里有子系统的Id则加入子系统list
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.addToList(listName, key, data.id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.addToList(listName, key, data.id)
            }
        },

        getField:function(fullKey){
            var matchedField=null;
            f.tranverseFields(formSpecs,function(field,ctx){
                if(field.key===fullKey){
                    matchedField = field;
                }
            });
            return matchedField;
        },
        tranverseFields: function (formSpec, callback, ctx) {
            formSpec.fields.forEach(function (field) {
                if (field['_ft'] == 'field') {
                    callback(field, ctx)
                }
                else if (field['_ft'] == 'subform') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                }
                else if (field['_ft'] == 'array') {
                    var context = {parentField: field}
                    f.tranverseFields(field, callback, context)
                }
            })
        },

        normalizeValue: function (value, spec) {
            if (value == null) {
                return null
            }
            switch (spec.fieldType) {
                case 'string':
                    if(typeof value=='number'){
                        value = value + '';
                    }
                    return value + ''
                case 'number':
                    if (isNaN(value)) {
                        return null
                    }
                    else {
                        value = Number(value)
                        return value
                    }
                case 'date':
                    try{
                        if(isNaN(value)){
                            return value;
                        }

                        return new Date(Number(value)).toISOString();
                    }
                    catch(e){
                        return null;
                    }

                case 'choice':
                    return value
                default:
                    return value
            }
        },


        getNormalizedDoc: function (data) {
            var meta = spec['#meta'];
            var indexFields = meta.indexFields;


            var obj = JSON.parse(JSON.stringify(data))

            var newObj = {};
            if(indexFields && indexFields.length>0){
                for(var i=0; i<indexFields.length; i++){
                    var key = indexFields[i];
                    var value = f.getValue(key,obj);
                    if(value){
                        f.setValue(key,value,newObj);
                    }
                }
                newObj['del'] = obj['del'];
                obj = newObj;
            }


            var fields = formSpecs.fields;
            for(var i=0; i<fields.length; i++){
                var field= fields[i];
                 if (field['_ft'] === 'subform' || field['_ft'] === 'array') {
                   var meta = field.meta;
                   if(meta.index==='no'){
                       delete obj[field.key]
                   }
                }
                else if (field['_ft'] === 'field') {
                   if(field.index==='no'){
                       delete obj[field.key]
                   }

                }
            }
            f.tranverseFields(formSpecs, function (field, ctx) {
                if (ctx.parentField && ctx.parentField._ft == 'array') {
                    var items = f.getValue(ctx.parentField.key, obj)
                    if (items) {
                        for (var i = 0; i < items.length; i++) {
                            var item = items[i]
                            if(field.index==='no'){
                                delete item[field.origKey]
                                continue;
                            }
                            var value = item[field.origKey]
                            value = f.normalizeValue(value, field)
                            item[field.origKey] = value
                        }
                    }
                }
                else {
                    if(indexFields && indexFields.length>0){
                        if(indexFields.indexOf(field.key)==-1){
                            f.deleteValue(field.key,obj);
                        }
                    }
                    else if(field.index==='no'){
                        f.deleteValue(field.key,obj);
                    }
                    else{
                        var value = f.getValue(field.key, obj)
                        value = f.normalizeValue(value, field)
                        f.setValue(field.key, value, obj)
                    }

                }
            }, {})

            //专门处理一下owl_modifyTime和owl_createTime
            if(obj.owl_modifyTime){
                obj.owl_modifyTime = new Date(obj.owl_modifyTime).toISOString();
            }
            if(obj.owl_createTime){
                obj.owl_createTime = new Date(obj.owl_createTime).toISOString();
            }
            return obj
        },

        index: function (doc) {
            var data = f.getNormalizedDoc(doc);
            var m = [];
            m.push(data.m);
            if (data['subplatformId']) {
                m.push(data['subplatformId']);
            }
            if (data['shopId']) {
                m.push(data['shopId'])
            }
            if(data['shop_id']){
                m.push(data['shop_id'])
            }
            data._m = m;
            var elasticSearchUrl = $.getEnv('elasticSearchUrl')

            var headers = {'Content-Type': 'application/json;charset=utf-8'}
            var elasticSearchUser = $.getEnv('elasticSearchUser')
            var elasticSearchPass = $.getEnv('elasticSearchPass')
            if (elasticSearchUser && elasticSearchPass) {
                var auth = Base64.encode(elasticSearchUser + ':' + elasticSearchPass)
                var basicAuth = 'Basic ' + auth
                headers['Authorization'] = basicAuth
            }
            var searchUrl = elasticSearchUrl + '/@projectCode/_doc/' + data.id
            var sndTxt = JSON.stringify(data)
            var s = HttpUtils.postRaw(searchUrl, sndTxt, headers)
            var r = JSON.parse(s)
            if (!r.result) {
                $.log(data.id + ',index error:' + JSON.stringify(s))
            }
            else {
                // $.log('index ok...')
            }
        },

        updateParentPath(treeNode){
            /*工具类，用于更新对应的parentPathIds和pathPathNames*/
            var parentId = treeNode.parent_id;
            var parentPathIds = [];
            var parentPathNames = [];
            var nSteps = 0;
            //获得parentId对应的node
            while(parentId && parentId!=='0'){
                nSteps++;
                if(nSteps>15){
                    break;
                }
                var parentNode = f.get(parentId);
                if(parentNode){
                    parentPathIds.push(parentNode.id);
                    parentPathNames.push(parentNode.name);
                    parentId = parentNode.parent_id;
                }
                else{
                    break;
                }
            }


            treeNode.parentPathIds = parentPathIds.reverse();
            treeNode.fullPathIds = treeNode.parentPathIds.concat([treeNode.id]);
            treeNode.parentPathNames = parentPathNames.reverse();
            if(treeNode.parentPathNames ){
                var sep = "";
                var meta = spec['#meta'];
                if(meta && meta.pathSep){
                    sep = meta.pathSep;
                }
                treeNode.fullPathName = treeNode.parentPathNames.join(sep)+sep+treeNode.name;
            }


        },
        add: function (data, env) {
            f.checkData(data);
            env = env || {}
            var ret = f.validate(data, env)
            if(ret.state!='ok'){
                throw JSON.stringify(ret);
            }

            if(data.__force !== 'T'){
                data['owl_createTime'] = new Date().getTime();
                data['owl_modifyTime'] = new Date().getTime();
            }

            data['_v'] = 0
            data['_t'] = spec['_t']
            try {
                var meta = spec['#meta'];


                EventBusService.fire(spec['_t'] + '_add_before', {data: data, env: env})
                data.id = f.getId(data)
                if(meta.isTree){
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                f.saveUniqueFields(data, env)
                pigeon.saveObject(data.id, data)
                f.addToList(data)
                f.index(data)

                if(meta && meta.enableJDBC){
                    var sqls = insertObject(data);
                    // $.log(sqls.join(";\n"));
                }
                // $.log("add,meta.push=" +meta.push + ",id=" + data.id);
                if(meta && meta.push){
                    KafkaUtil.send('owlchange',"add",data.id);
                }
                EventBusService.fire(spec['_t'] + '_add_after', {data: data, env: env})
                return data
            }
            finally {
                pigeon.unlock(f.getLock(data))
            }

        },

        get: function (id,includeDeleted) {
            var meta = spec['#meta'];
            if(meta && meta.push) {
                var cachedObj = appData.getObservableObject(id);
                if (cachedObj) {
                    if (cachedObj.del === 'T' && !includeDeleted) {
                        return null;
                    }
                    return cachedObj;
                }
                var obj = pigeon.getObject(id)
                if(obj){
                    appData.setObservableObject(id,obj);
                }
                else{
                    appData.removeObservableObject(id);
                }

                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            }
            else{
                var obj = pigeon.getObject(id)
                if (obj && obj.del === 'T' && !includeDeleted) {
                    return null;
                }
                return obj;
            }
        },

        getEx:function(id,refresh){
          if(refresh){
              var obj = pigeon.getObject(id);

              if(obj){
                  appData.setObservableObject(id,obj);
              }
              else{
                  appData.removeObservableObject(id);
              }
              if(obj && obj.del==='T'){
                  return null;
              }
              return obj;
          }
          else{
              return f.get(id);
          }
        },

        saveTempData: function (key, data) {
            return pigeon.saveObject(objPrefix + key, data)

        },

        getTempData: function (key) {
            return pigeon.getObject(objPrefix + key)
        },

        getObjects: function (ids) {
            return pigeon.getObjects(ids)
        },

        checkData: function (data, env) {
            var meta = spec['#meta'];
            // 检查数据是否合法
            var checkData = meta.checkData;
            if(checkData){
                if(checkData.maxSize){
                    var s = JSON.stringify(data);
                    if(s.length>checkData.maxSize){
                        throw {code: 'dataTooLarge', msg: '数据太大，超过了' + checkData.maxSize + '字节'}
                    }
                }
            }
        },

        update: function (data, env) {
            f.checkData(data);
            env = env || {}
            var id = data.id

            try {
                var meta = spec['#meta'];
                if(meta.isTree){
                    f.updateParentPath(data);
                }
                pigeon.lock(f.getLock(data))
                var obj = f.get(data.id,true)
                if (!obj) {
                    throw {msg: '对象不存在!id=' + data.id, code: 'notFound'}
                }
                if (obj._v != data._v) {
                    throw {code: 'concurrentupdate', msg: '对象已经修改过，本次修改被拒绝。old._v=' + obj._v + ',new._v=' + data._v+ ", id=" + obj.id}
                }
                var oldObj = JSON.parse(JSON.stringify(obj))
                //深度合并
                //obj = deepMerge(obj,data);//深度合并会引起 编辑出现bug
                obj = data
                obj['_v'] = Number(obj['_v']) + 1
                f.validate(obj, env)
                data._v = obj._v
                if(obj.__force!=='T'){
                    //如果__force为T，则不更新时间，这代表了强制更新，可以用于数据迁移的场景，希望更新时间保留为旧系统时间
                    obj.owl_modifyTime=new Date().getTime();
                }

                EventBusService.fire(spec['_t'] + '_update_before', {old: oldObj, data: obj, env: env})
                f.addToList(obj)
                f.removeUniqueFields(oldObj, env)
                pigeon.saveObject(id, obj)
                f.saveUniqueFields(data, env)
                f.index(obj)

                if(formSpecs && formSpecs.meta && formSpecs.meta.changeLogEnabled){
                   var id = oldObj.id;
                   oldObj.id = id + "vvv" + oldObj._v;
                   pigeon.saveObject(oldObj.id,oldObj);
                   oldObj.id = id;
                }
                
                var meta = spec['#meta'];
                if(meta && meta.enableJDBC){
                    var sqls = updateObject(obj);
                    // $.log(sqls.join(";\n"));
                }
                if(meta){
                    // $.log("updating meta.push=" + meta.push + ", id=" + obj.id);
                }

                if(meta && meta.push){
                    // $.log("send owlchange," + obj.id)
                    KafkaUtil.send('owlchange',"update",obj.id);
                }
                EventBusService.fire(spec['_t'] + '_update_after', {old: oldObj, data: obj, env: env})
                return data
            }
            finally {
                pigeon.unlock(f.getLock(data))
            }
        },

        del: function (id,userId,ip,ua) {
            //只做软删除
            var data = f.get(id,true)
            if (!data) {
                throw '对象不存在!id=' + id
            }
            data.id = id;
            data.owl_lastModifiedUserId = userId;
            data.owl_lastModifiedIp = ip;
            data.owl_lastModifiedUserAgent = ua;
            data.owl_modifyTime = new Date().getTime();

            var key = pigeon.getRKey(data['owl_createTime'], 13)
            pigeon.deleteFromList(f.getAllListName(), key, id)
            if (data['subplatformId']) {
                var listName = listPrefix + '_' + data['subplatformId']
                pigeon.deleteFromList(listName, key, id)
            }

            //如果这里有店铺id,则加入店铺list
            if (data['shopId']) {
                var listName = listPrefix + '_' + data['shopId']
                pigeon.deleteFromList(listName, key, id)
            }

            var t = data['owl_createTime']
            var d = new Date(t)
            var year = d.getFullYear()
            var month = d.getMonth() + 1
            var day = d.getDate()

            var listName = listPrefix + '_' + year + '_' + month + '_' + day

            EventBusService.fire(spec['_t'] + '_delete_before', {data: data})
            pigeon.deleteFromList(listName, key, id)

            var deletedList = listPrefix + '_deleted'
            pigeon.addToList(deletedList, key, id)
            data.del = 'T'

            f.index(data)
            pigeon.saveObject(data.id, data)
            f.removeUniqueFields(data, {})
            var meta = spec['#meta'];

            if(meta && meta.enableJDBC){
                var sqls = deleteObject(data);
                // $.log(sqls.join(";\n"));
            }

            if(meta){
                // $.log("del meta.push=" + meta.push + ", id=" + data.id);
            }

            if(meta && meta.push){
                KafkaUtil.send('owlchange',"del",data.id);
            }
            EventBusService.fire(spec['_t'] + '_delete_after', {data: data})
        },
        getList: function (listName, start, limit) {
            if (!listName) {
                return null
            }
            if (!start) {
                start = 0
            }
            if (!limit) {
                limit = 10
            }
            return pigeon.getListObjects(listName, start, limit)
        },

        getExportRunningList: function () {
            return listPrefix + '_exportRunning'
        },

        getExportFinishedList: function () {
            return listPrefix + '_exportFinished'
        },

        addExportTask: function (query, env) {
            var now = new Date().getTime()
            var taskInfo = {
                submitUserId:env.loginId,
                submitTime: now,
                beginTime: null,
                processState: 'notstart',
                appId:"owl_" + spec['_t'],
                params:JSON.stringify(env.params),
                taskId:null
            }
            taskInfo.taskId = '';

            owl_task_infoService.add(taskInfo);
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            // var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/export.jsx', {
                query: query,
                env: env,
                taskInfoId:taskInfo.id
            }, now)


            taskInfo = owl_task_infoService.get(taskInfo.id);
            taskInfo.taskId = "" + taskId;
            owl_task_infoService.update(taskInfo);
            return taskInfo.id
        },

        addDocExportTask: function (docId, env) {
            var now = new Date().getTime()
            var taskInfo = {
                // loginUser: f.getEnvValue('$loginUser', env),
                loginUserId:env.loginUserId,
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 1,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/exportDoc.jsx', {
                id: docId,
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)

            return taskInfoId
        },

        addZipExportTask : function(env){
            var now = new Date().getTime()
            var taskInfo = {
                // loginUser: f.getEnvValue('$loginUser', env),
                loginUserId:env.loginUserId,
                submitTime: now,
                startTime: 0,
                processState: 'processing',
                percent: 0,
                total: 0,
                _v: 0,
            }
            //这个key如果不多加一个export，会把原有的单据数据覆盖
            var taskInfoId = objPrefix + '_export_' + pigeon.getId()
            var taskId = JobsService.submitExportTask('@projectCode', 'tasks/exportZip.jsx', {
                env: env,
                taskInfoId: taskInfoId,
            }, now)
            taskInfo.taskId = '' + taskId
            taskInfo.id = taskInfoId
            var key = pigeon.getRKey(taskInfo.submitTime, 13)
            pigeon.addToList(f.getExportRunningList(), key, taskInfoId)
            pigeon.saveObject(taskInfoId, taskInfo)
            return taskInfoId
        },

        getExportTaskInfo: function (taskInfoId) {
           return owl_task_infoService.get(taskInfoId);
        },

        updateExportTaskInfo: function (taskInfoId, taskInfo) {
            return owl_task_infoService.update(taskInfo);
        },


        reindexAll: function () {
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            $.log("reindexAll " + listName);
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                objs.forEach(function (data) {
                    try {
                        f.index(data)
                    } catch (e) {
                        $.log('重建索引异常:' + data.id)
                    }
                })
                pos += indexCount
                count -= indexCount
            }
        },

        getAllSize:function(){
            var listName = f.getAllListName();
            var count = pigeon.getListSize(listName);
            return count;
        },

        getAllObjects:function(){
            var result = [];
            var listName = f.getAllListName()
            var count = pigeon.getListSize(listName)
            var pos = 0
            while (count > 0) {
                var indexCount = 2000
                if (indexCount > count) {
                    indexCount = count
                }
                var objs = pigeon.getListObjects(listName, pos, indexCount)
                result = result.concat(objs);
                pos += indexCount
                count -= indexCount
            }
            return result;
        },

        getAllObjectsEx:function(from,count){
            var listName = f.getAllListName()
            var allsize = pigeon.getListSize(listName)
            var pos = from
            if(count+pos > allsize){
                count = allsize - pos;
            }
            var objs = pigeon.getListObjects(listName, pos, count)
            return objs;
        },

        createQuery:function(searchArgs){
            var filters = [];
            for(var k in searchArgs){
                var v = searchArgs[k];
               if(typeof v === 'object' && v.type === 'or'){
                    if(Array.isArray(v.args)){
                        var shouldClauses = [];
                        v.args.forEach(function(q){
                            var qFilters = f.createQuery(q);
                            if(qFilters.length > 0){
                                shouldClauses.push({
                                    "bool":{
                                        filters:qFilters
                                    }
                                })
                            }
                        });

                        filters.push({bool:{should:shouldClauses}});
                    }
                    else if(v.args && typeof(v.args)=='object'){
                        var shouldClauses = [];
                        var qFilters = f.createQuery(v.args);
                        shouldClauses.push({bool:{filters:qFilters}});
                        filters.push({bool:{should:shouldClauses}});
                    }

                }
                else if(typeof(v)=='object' && Array.isArray(v)){
                    var range={}
                    range[k] = {
                        'gte':v[0],
                        'lte':v[1]
                    }
                    filters.push({range:range});
                }
                else if(typeof(v)=='object' && v.type==='terms'){
                    var terms = {};
                    if(v.values){
                        terms[k + ".keyword"] = v.values;
                        filters.push({terms:terms});
                    }
                }
                else{
                    var term = {};
                    if(typeof v == 'string' ){
                        if(v){
                            term[k+".keyword"] = trim('' + v)
                            filters.push({term:term})
                        }
                    }
                    else if (typeof v == 'number'){
                        term[k] = trim('' + v)
                        filters.push({term:term})
                    }
                }
            }
            return filters;
        },

        // 构建通配查询（将 SQL 风格的 % 转为 ES 的 *）
        buildWildcardQuery:function(k, v){
            // v 预期包含 % 作为通配符标记，转换为 ES 的 *
            var value = ('' + v).replace(/%/g, '*');
            var field = k + ".keyword";
            var wildcard = {};
            wildcard[field] = {
                value: value,
                // 在 ES 7.10+ 支持不区分大小写
                case_insensitive: true
            };
            return { wildcard: wildcard };
        },

        buildQuery:function(mfilters,searchArgs,keyword,isRecycleBin,meta_fields){
            if(!meta_fields){
                var meta = spec["#meta"];
                if(meta){
                    meta_fields = spec["#meta"]["keyword_fields"];
                }
            }
            delete searchArgs.keyword;
            var filters = [];
            var must_not = [{
                term:{
                    "del.keyword":'T'
                }
            }];

            if(isRecycleBin){
                must_not = [];
                filters.push({
                    term:{
                        "del.keyword":'T'
                    }
                });
            }
            for(var k in searchArgs){

                var v = searchArgs[k];

                var isNotClause = false;
                if(k.indexOf("!")===0){
                    isNotClause = true;
                    k = k.substring(1);
                }
                if(typeof v === 'object' && v.type === 'or'){
                    var shouldClauses = [];
                    if(Array.isArray(v.args)){
                        v.args.forEach(function(q){
                            var shouldQueries = f.createQuery(q);
                            if(shouldQueries.length>0){
                                shouldClauses.push({bool:{filter:shouldQueries}});
                            }

                        });
                        if(isNotClause){
                            must_not.push({bool:{should:shouldClauses}});
                        }else{
                            filters.push({bool:{should:shouldClauses}});
                        }

                    }
                    else{
                      //do noting,所以 or 语句不支持 单个对象，必须带一个 args 数组
                    }
                }
                else if(typeof(v)=='object' && Array.isArray(v)){
                    var range={}
                    if(v.length==2){
                        range[k] = {
                            'gte':v[0],
                            'lte':v[1]
                        }
                    }
                    else if(v.length==1){
                        range[k] = {
                            'gte':v[0]
                        }
                    }
                    else if(v.length==3){
                        range[k] = {
                            'lte':v[0]
                        }
                    }
                    else if(v.length==4){
                        range[k] = {
                            'gt':v[0],
                            'lt':v[1]
                        }
                    }
                    else if(v.length==5){
                        range[k] = {
                            'gt': v[0]
                        }
                    }
                    else if(v.length==6) {
                        range[k] = {
                            'lt': v[0]
                        }
                    }

                    if(isNotClause){
                        must_not.push({range:range});
                    }else{
                        filters.push({range:range});
                    }
                }
                else if(typeof(v)=='object' && v.type==='terms'){
                    var terms = {};
                    if(v.values){
                        terms[k + ".keyword"] = v.values;
                        if(isNotClause){
                            must_not.push({terms:terms});
                        }else{
                            filters.push({terms:terms});
                        }
                    }
                }
                else if (typeof(v)==='object' && v.type==='exists'){
                    if(isNotClause){
                        must_not.push({exists:{field:k}});
                    }else{
                        filters.push({exists:{field:k}});
                    }
                }
                else{
                    var term = {};
                    if(typeof v == 'string' ){
                        if(v){
                            term[k+".keyword"] = trim('' + v)
                            if(isNotClause){
                                if(v.indexOf('%')>-1){
                                    must_not.push(f.buildWildcardQuery(k, v));
                                }
                                else{
                                    must_not.push({term:term});
                                }
                            }else{
                                if(v.indexOf('%')>-1){
                                    filters.push(f.buildWildcardQuery(k, v));
                                }
                                else{
                                    filters.push({term:term});
                                }
                            }

                        }
                    }
                    else if (typeof v == 'number'){
                        term[k] = trim('' + v)
                        if(isNotClause){
                            must_not.push({term:term});
                        }else{
                            filters.push({term:term});
                        }
                    }
                }
            }
            if(mfilters && Array.isArray(mfilters) && mfilters.length>0){
                filters = filters.concat(mfilters);
            }

            //getKeyword query
            var keywordQuery = "";
            if(keyword && trim(keyword).length>0){
                keywordQuery = "\"" + trim(keyword) + "\""
            }
            else{
                keywordQuery = "*"
            }
            var keyword_fields = null;
            if(meta_fields && Array.isArray(meta_fields) && meta_fields.length>0){
                keyword_fields = meta_fields;
            }
            var queryStringQuery = {
                "query_string": {
                    "query":keywordQuery
                }
            }

            if(keyword_fields && keyword_fields.length>0){
                queryStringQuery = {
                    "query_string": {
                        "query":keywordQuery,
                        "fields":keyword_fields,
                    }
                }
            }
            var query = {
                "query": {
                    "bool": {
                        "must": queryStringQuery,
                        "must_not": must_not,
                        "filter": filters
                    }
                }
            }
            return query;
        },
        count:function(mfilters,searchArgs,keyword,dataSource,isRecycleBin,meta_fields){
            var query = f.buildQuery(mfilters,searchArgs,keyword,isRecycleBin,meta_fields);
            var elasticSearchUrl = $.getEnv( "elasticSearchUrl" );

            var headers = { "Content-Type": "application/json;charset=utf-8" };
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if(elasticSearchUser && elasticSearchPass){
                var auth =Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl+"/@projectCode/_count";
            if(dataSource){
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_count";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw( searchUrl, sndTxt, headers);
            var result = JSON.parse(s);
            if(result.count){
                return result.count;
            }
            return 0;
        },
        executeSearchQuery:function(query,dataSource){
            var elasticSearchUrl = $.getEnv( "elasticSearchUrl" );

            var headers = { "Content-Type": "application/json;charset=utf-8" };
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if(elasticSearchUser && elasticSearchPass){
                var auth =Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl+"/@projectCode/_search";
            if(dataSource){
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_search";
            }

            var sndTxt = JSON.stringify(query);
            return HttpUtils.postRaw( searchUrl, sndTxt, headers);
        },

        search: function(mfilters, searchArgs, keyword,from, pageSize, sort, dataSource,isRecycleBin,meta_fields){
            var query = f.buildQuery(mfilters,searchArgs,keyword,isRecycleBin,meta_fields);
            var effectiveSort = [{owl_createTime:{order:"desc"}}];
            if(sort){
                effectiveSort = sort;
            }
            query.from = from;
            query.size = pageSize;
            query.sort = effectiveSort;

            var elasticSearchUrl = $.getEnv( "elasticSearchUrl" );

            var headers = { "Content-Type": "application/json;charset=utf-8" };
            var elasticSearchUser = $.getEnv("elasticSearchUser");
            var elasticSearchPass = $.getEnv("elasticSearchPass");
            if(elasticSearchUser && elasticSearchPass){
                var auth =Base64.encode(elasticSearchUser + ":" + elasticSearchPass);
                var basicAuth = "Basic " + auth;
                headers["Authorization"] = basicAuth;
            }
            var searchUrl = elasticSearchUrl+"/@projectCode/_search";
            if(dataSource){
                searchUrl = elasticSearchUrl + "/" + dataSource + "/_search";
            }

            var sndTxt = JSON.stringify(query);
            var s = HttpUtils.postRaw( searchUrl, sndTxt, headers);
            var result = JSON.parse(s);

            if(!result.hits || !result.hits.hits){
                $.log("error search:sndQuery=" + sndTxt + "------\nresponse:\n" + s );
                var ret = {
                    state:'err',
                    list:[],
                    total:{value:0}
                }
                return ret;
            }

            var hits = result.hits.hits;
            var total = result.hits.total;

            var objs = hits.map(function(hit){return hit._source});


            var ret = {
                state:'ok',
                list:objs,
                total:total
            }

            return ret;

        },
        getHistory:function(objId, from, size){
            var objs = [];
            var obj = f.get(objId,true);
            var currentVersion = obj._v;



            for(var i=0; i<size; i++){
                var ver = currentVersion - i - from;
                if(ver<0){
                    break;
                }
                var id = objId + "vvv" + ver;
                var vobj = f.get(id,true);
                if(vobj){
                    objs.push(vobj);
                }
            }
            return objs;

        }
    }
    return f;
})($S);