예제 #1
0
        // API: 借书
        // text-level: 用户提示 OPAC续借功能要调用此函数
        // parameters:
        //      strReaderBarcode    读者证条码号。续借的时候可以为空
        //      strItemBarcode  册条码号
        //      strConfirmItemRecPath  册记录路径。在册条码号重复的情况下,才需要使用这个参数,平时为null即可
        //      saBorrowedItemBarcode   同一读者先前已经借阅成功的册条码号集合。用于在返回的读者html中显示出特定的颜色而已。
        //      strStyle    操作风格。"item"表示将返回册记录;"reader"表示将返回读者记录
        //      strItemFormat   规定strItemRecord参数所返回的数据格式
        //      strItemRecord   返回册记录
        //      strReaderFormat 规定strReaderRecord参数所返回的数据格式
        //      strReaderRecord 返回读者记录
        //      aDupPath    如果发生条码号重复,这里返回了相关册记录的路径
        //      return_time 本次借阅要求的最后还书时间。GMT时间。
        // 权限:无论工作人员还是读者,首先应具备borrow或renew权限。
        //      对于读者,还需要他进行的借阅(续借)操作是针对自己的,即strReaderBarcode必须和账户信息中的证条码号一致。
        //      也就是说,读者不允许替他人借阅(续借)图书,这样规定是为了防止读者捣乱。
        public LibraryServerResult Borrow(
            SessionInfo sessioninfo,
            bool bRenew,
            string strReaderBarcode,
            string strItemBarcode,
            string strConfirmItemRecPath,
            bool bForce,
            string[] saBorrowedItemBarcode,
            string strStyle,
            string strItemFormatList,
            out string[] item_records,
            string strReaderFormatList,
            out string[] reader_records,

            string strBiblioFormatList, // 2008/5/9
            out string[] biblio_records, // 2008/5/9

            out string[] aDupPath,
            out string strOutputReaderBarcodeParam, // 2011/9/25
            out BorrowInfo borrow_info   // 2007/12/6
            )
        {
            item_records = null;
            reader_records = null;
            biblio_records = null;
            aDupPath = null;
            strOutputReaderBarcodeParam = "";
            borrow_info = new BorrowInfo();

            List<string> time_lines = new List<string>();
            DateTime start_time = DateTime.Now;
            string strOperLogUID = "";

            LibraryServerResult result = new LibraryServerResult();

            string strAction = "borrow";
            if (bRenew == true)
                strAction = "renew";

            string strActionName = GetBorrowActionName(strAction);

            // 个人书斋名
            string strPersonalLibrary = "";
            if (sessioninfo.UserType == "reader"
                && sessioninfo.Account != null)
                strPersonalLibrary = sessioninfo.Account.PersonalLibrary;

            // 权限判断
            if (bRenew == false)
            {
                // 权限字符串
                if (StringUtil.IsInList("borrow", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    result.ErrorInfo = "借阅操作被拒绝。不具备 borrow 权限。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }

                // 对读者身份的附加判断
                // 注:具有个人书斋的,还可以继续向后执行
                if (sessioninfo.UserType == "reader"
                    && sessioninfo.Account != null && strReaderBarcode != sessioninfo.Account.Barcode
                    && string.IsNullOrEmpty(strPersonalLibrary) == true)
                {
                    result.Value = -1;
                    result.ErrorInfo = "借阅操作被拒绝。作为读者不能代他人进行借阅操作。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    return result;
                }
            }
            else
            {
                // 权限字符串
                if (StringUtil.IsInList("renew", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    // text-level: 用户提示
                    result.ErrorInfo = this.GetString("续借操作被拒绝。不具备 renew 权限。"); // "续借操作被拒绝。不具备renew权限。"
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }

#if NO
                // 延迟到后面判断。因为 strReaderBarcode 可能是 PQR:xxxx 形态

                // 对读者身份的附加判断
                // 注:具有个人书斋的,还可以继续向后执行
                if (sessioninfo.UserType == "reader"
                    && sessioninfo.Account != null && strReaderBarcode != sessioninfo.Account.Barcode
                    && string.IsNullOrEmpty(strPersonalLibrary) == true)
                {
                    result.Value = -1;
                    // text-level: 用户提示
                    // result.ErrorInfo = this.GetString("续借操作被拒绝。作为读者不能代他人进行续借操作。");  // "续借操作被拒绝。作为读者不能代他人进行续借操作。"
                    result.ErrorInfo = string.Format("续借操作被拒绝。作为读者 '{0}' 不能代他人 '{1}' 进行续借操作。", sessioninfo.Account.Barcode, strReaderBarcode);
                    result.ErrorCode = ErrorCode.AccessDenied;
                    return result;
                }
#endif
            }

            // 如果没有普通的权限,需要预检查存取权限
            LibraryServerResult result_save = null;
            if (result.Value == -1 && String.IsNullOrEmpty(sessioninfo.Access) == false)
            {
                string strAccessActionList = GetDbOperRights(sessioninfo.Access,
                        "", // 此时还不知道实体库名,先取得当前帐户关于任意一个实体库的存取定义
                        "circulation");
                if (string.IsNullOrEmpty(strAccessActionList) == true)
                    return result;

                // 通过了这样一番检查后,后面依然要检查存取权限。
                // 如果后面检查中,精确针对某个实体库的存取权限存在,则依存取权限;如果不存在,则依普通权限
                result_save = result.Clone();
            }
            else if (result.Value == -1)
                return result;  // 延迟报错 2014/9/16

            result = new LibraryServerResult();

            string strError = "";

            if (bForce == true)
            {
                strError = "bForce参数不能为true";
                goto ERROR1;
            }

            int nRet = 0;
            long lRet = 0;
            string strIdcardNumber = "";    // 身份证号
            string strQrCode = "";  //

            if (string.IsNullOrEmpty(strReaderBarcode) == false)
            {
                string strOutputCode = "";
                // 把二维码字符串转换为读者证条码号
                // parameters:
                //      strReaderBcode  [out]读者证条码号
                // return:
                //      -1      出错
                //      0       所给出的字符串不是读者证号二维码
                //      1       成功      
                nRet = DecodeQrCode(strReaderBarcode,
                    out strOutputCode,
                    out strError);
                if (nRet == -1)
                    goto ERROR1;
                if (nRet == 1)
                {
                    strQrCode = strReaderBarcode;
                    strReaderBarcode = strOutputCode;
                }
            }

            // 因为 strReaderBarcode 可能是 PQR:xxxx 形态,需要替换为真正的证条码号以后才能参与判断,所以放到这里判断
            if (bRenew == true)
            {
                // 对读者身份的附加判断
                // 注:具有个人书斋的,还可以继续向后执行
                if (sessioninfo.UserType == "reader"
                    && sessioninfo.Account != null && strReaderBarcode != sessioninfo.Account.Barcode
                    && string.IsNullOrEmpty(strPersonalLibrary) == true)
                {
                    result.Value = -1;
                    // text-level: 用户提示
                    // result.ErrorInfo = this.GetString("续借操作被拒绝。作为读者不能代他人进行续借操作。");  // "续借操作被拒绝。作为读者不能代他人进行续借操作。"
                    result.ErrorInfo = string.Format("续借操作被拒绝。作为读者 '{0}' 不能代他人 '{1}' 进行续借操作。", sessioninfo.Account.Barcode, strReaderBarcode);  // TODO: 测试验证完成后需要改回用上一句
                    result.ErrorCode = ErrorCode.AccessDenied;
                    return result;
                }
            }

            int nRedoCount = 0; // 因为时间戳冲突, 重试的次数
            string strLockReaderBarcode = strReaderBarcode; // 加锁专用字符串,不怕后面被修改了

            REDO_BORROW:

            bool bReaderLocked = false;

            string strOutputReaderXml = "";
            string strOutputItemXml = "";
            string strBiblioRecID = "";
            string strOutputItemRecPath = "";
            string strOutputReaderRecPath = "";
            string strLibraryCode = "";

            // 加读者记录锁
            // this.ReaderLocks.LockForWrite(strLockReaderBarcode);
            if (String.IsNullOrEmpty(strReaderBarcode) == false)
            {
                // 加读者记录锁
                strLockReaderBarcode = strReaderBarcode;
#if DEBUG_LOCK_READER
                this.WriteErrorLog("Borrow 开始为读者加写锁 '" + strReaderBarcode + "'");
#endif
                this.ReaderLocks.LockForWrite(strReaderBarcode);
                bReaderLocked = true;
                strOutputReaderBarcodeParam = strReaderBarcode;
            }

            try // 读者记录锁定范围开始
            {
                // 读取读者记录
                XmlDocument readerdom = null;
                byte[] reader_timestamp = null;
                string strOldReaderXml = "";

                if (string.IsNullOrEmpty(strReaderBarcode) == false)
                {
                    bool bReaderDbInCirculation = true;
                    LibraryServerResult result1 = GetReaderRecord(
                sessioninfo,
                strActionName,
                time_lines,
                true,
                ref strReaderBarcode,
                ref strIdcardNumber,
                ref strLibraryCode,
                out bReaderDbInCirculation,
                out readerdom,
                out strOutputReaderRecPath,
                out reader_timestamp);
                    if (result1.Value == 0)
                    {
                    }
                    else
                    {
                        return result1;
                    }

                    if (bReaderDbInCirculation == false)
                    {
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("借书操作被拒绝。读者证条码号s所在的读者记录s因其数据库s属于未参与流通的读者库"),  // "借书操作被拒绝。读者证条码号 '{0}' 所在的读者记录 '{1}' 因其数据库 '{2}' 属于未参与流通的读者库"
                            strReaderBarcode,
                            strOutputReaderRecPath,
                            StringUtil.GetDbName(strOutputReaderRecPath));

                        // "借书操作被拒绝。读者证条码号 '" + strReaderBarcode + "' 所在的读者记录 '" +strOutputReaderRecPath + "' 因其数据库 '" +strReaderDbName+ "' 属于未参与流通的读者库";
                        goto ERROR1;
                    }

                    // 记忆修改前的读者记录
                    strOldReaderXml = readerdom.OuterXml;

                    if (String.IsNullOrEmpty(strIdcardNumber) == false
                        || string.IsNullOrEmpty(strReaderBarcode) == true /* 2013/5/23 */)
                    {
                        // 获得读者证条码号
                        strReaderBarcode = DomUtil.GetElementText(readerdom.DocumentElement,
                            "barcode");
                    }
                    strOutputReaderBarcodeParam = DomUtil.GetElementText(readerdom.DocumentElement,
                            "barcode");

                    string strReaderDbName = ResPath.GetDbName(strOutputReaderRecPath);

                    // 检查当前用户管辖的读者范围
                    // return:
                    //      -1  出错
                    //      0   允许继续访问
                    //      1   权限限制,不允许继续访问。strError 中有说明原因的文字
                    nRet = CheckReaderRange(sessioninfo,
                        readerdom,
                        strReaderDbName,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;
                    if (nRet == 1)
                    {
                        // strError = "当前用户 '" + sessioninfo.UserID + "' 的存取权限或好友关系禁止操作读者(证条码号为 " + strReaderBarcode + ")。具体原因:" + strError;
                        result.Value = -1;
                        result.ErrorInfo = strError;
                        result.ErrorCode = ErrorCode.AccessDenied;
                        return result;
                    }
                }

                byte[] item_timestamp = null;
                List<string> aPath = null;
                string strItemXml = "";
                // string strOutputItemRecPath = "";

                RmsChannel channel = sessioninfo.Channels.GetChannel(this.WsUrl);
                if (channel == null)
                {
                    // text-level: 内部错误
                    strError = "get channel error";
                    goto ERROR1;
                }

                // 加册记录锁
                this.EntityLocks.LockForWrite(strItemBarcode);

                try // 册记录锁定范围开始
                {
                    // 读入册记录
                    DateTime start_time_read_item = DateTime.Now;

                    // 如果已经有确定的册记录路径
                    if (String.IsNullOrEmpty(strConfirmItemRecPath) == false)
                    {
                        // 检查路径中的库名,是不是实体库名
                        // return:
                        //      -1  error
                        //      0   不是实体库名
                        //      1   是实体库名
                        nRet = this.CheckItemRecPath(strConfirmItemRecPath,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                        if (nRet == 0)
                        {
                            strError = strConfirmItemRecPath + strError;
                            goto ERROR1;
                        }

                        string strMetaData = "";

                        lRet = channel.GetRes(strConfirmItemRecPath,
                            out strItemXml,
                            out strMetaData,
                            out item_timestamp,
                            out strOutputItemRecPath,
                            out strError);
                        if (lRet == -1)
                        {
                            // text-level: 内部错误
                            strError = "根据strConfirmItemRecPath '" + strConfirmItemRecPath + "' 获得册记录失败: " + strError;
                            goto ERROR1;
                        }
                    }
                    else
                    {
                        // 从册条码号获得册记录

                        // 获得册记录
                        // return:
                        //      -1  error
                        //      0   not found
                        //      1   命中1条
                        //      >1  命中多于1条
                        nRet = this.GetItemRecXml(
                            // sessioninfo.Channels,
                            channel,
                            strItemBarcode,
                            "first",    // 在若干实体库中顺次检索,命中一个以上则返回,不再继续检索更多
                            out strItemXml,
                            100,
                            out aPath,
                            out item_timestamp,
                            out strError);
                        if (nRet == 0)
                        {
                            result.Value = -1;
                            // text-level: 用户提示
                            result.ErrorInfo = string.Format(this.GetString("册条码号s不存在"),   // "册条码号 '{0}' 不存在"
                                strItemBarcode);

                            // "册条码号 '" + strItemBarcode + "' 不存在";
                            result.ErrorCode = ErrorCode.ItemBarcodeNotFound;
                            return result;
                        }
                        if (nRet == -1)
                        {
                            // text-level: 内部错误
                            strError = "读入册记录时发生错误: " + strError;
                            goto ERROR1;
                        }

                        if (aPath.Count > 1)
                        {
                            if (this.Statis != null)
                                this.Statis.IncreaseEntryValue(
                                    strLibraryCode,
                                    "出纳",
                                    "借书遇册条码号重复次数",
                                    1);

                            result.Value = -1;
                            // text-level: 用户提示
                            result.ErrorInfo = string.Format(this.GetString("册条码号为s的册记录有s条,无法进行借阅操作"),  // "册条码号为 '{0}' 的册记录有 "{1}" 条,无法进行借阅操作。请在附加册记录路径后重新提交借阅操作。"
                                strItemBarcode,
                                aPath.Count.ToString());
                            this.WriteErrorLog(result.ErrorInfo);   // 2012/12/30

                            // "册条码号为 '" + strItemBarcode + "' 的册记录有 " + aPath.Count.ToString() + " 条,无法进行借阅操作。请在附加册记录路径后重新提交借阅操作。";
                            result.ErrorCode = ErrorCode.ItemBarcodeDup;

                            aDupPath = new string[aPath.Count];
                            aPath.CopyTo(aDupPath);
                            return result;
                        }
                        else
                        {
                            Debug.Assert(nRet == 1, "");
                            Debug.Assert(aPath.Count == 1, "");

                            if (nRet == 1)
                            {
                                strOutputItemRecPath = aPath[0];
                            }
                        }
                    }

                    string strItemDbName = "";

                    // 看看册记录所从属的数据库,是否在参与流通的实体库之列
                    // 2008/6/4
                    if (String.IsNullOrEmpty(strOutputItemRecPath) == false)
                    {
                        strItemDbName = ResPath.GetDbName(strOutputItemRecPath);
                        bool bItemDbInCirculation = true;
                        if (this.IsItemDbName(strItemDbName, out bItemDbInCirculation) == false)
                        {
                            // text-level: 内部错误
                            strError = "册记录路径 '" + strOutputItemRecPath + "' 中的数据库名 '" + strItemDbName + "' 居然不在定义的实体库之列。";
                            goto ERROR1;
                        }

                        if (bItemDbInCirculation == false)
                        {
                            // text-level: 内部错误
                            strError = "借书操作被拒绝。册条码号 '" + strItemBarcode + "' 所在的册记录 '" + strOutputItemRecPath + "' 因其数据库 '" + strItemDbName + "' 属于未参与流通的实体库";
                            goto ERROR1;
                        }
                    }

                    // 检查存取权限
                    string strAccessParameters = "";

                    {

                        // 检查存取权限 circulation
                        if (String.IsNullOrEmpty(sessioninfo.Access) == false)
                        {
                            string strAccessActionList = "";
                            strAccessActionList = GetDbOperRights(sessioninfo.Access,
                                strItemDbName,
                                "circulation");
#if NO
                            if (String.IsNullOrEmpty(strAccessActionList) == true && result_save != null)
                            {
                                // TODO: 也可以直接返回 result_save
                                strError = "当前用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳 操作的存取权限";
                                result.Value = -1;
                                result.ErrorInfo = strError;
                                result.ErrorCode = ErrorCode.AccessDenied;
                                return result;
                            }
#endif
                            if (strAccessActionList == null)
                            {
                                strAccessActionList = GetDbOperRights(sessioninfo.Access,
            "", // 此时还不知道实体库名,先取得当前帐户关于任意一个实体库的存取定义
            "circulation");
                                if (strAccessActionList == null)
                                {
                                    // 对所有实体库都没有定义任何存取权限,这时候要退而使用普通权限
                                    strAccessActionList = sessioninfo.Rights;

                                    // 注:其实此时 result_save == null 即表明普通权限检查已经通过了的
                                }
                                else
                                {
                                    // 对其他实体库定义了存取权限,但对 strItemDbName 没有定义
                                    strError = "用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳 操作的存取权限";
                                    result.Value = -1;
                                    result.ErrorInfo = strError;
                                    result.ErrorCode = ErrorCode.AccessDenied;
                                    return result;
                                }
                            }

                            if (strAccessActionList == "*")
                            {
                                // 通配
                            }
                            else
                            {
                                if (IsInAccessList(strAction, strAccessActionList, out strAccessParameters) == false)
                                {
                                    strError = "用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳  " + strActionName + " 操作的存取权限";
                                    result.Value = -1;
                                    result.ErrorInfo = strError;
                                    result.ErrorCode = ErrorCode.AccessDenied;
                                    return result;
                                }
                            }
                        }
                    }

                    XmlDocument itemdom = null;
                    nRet = LibraryApplication.LoadToDom(strItemXml,
                        out itemdom,
                        out strError);
                    if (nRet == -1)
                    {
                        // text-level: 内部错误
                        strError = "装载册记录进入XML DOM时发生错误: " + strError;
                        goto ERROR1;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_read_item,
                        "Borrow() 中读取册记录 耗时 ");

                    DateTime start_time_process = DateTime.Now;

                    // 检查评估模式下书目记录路径
                    if (this.TestMode == true || sessioninfo.TestMode == true)
                    {
                        string strBiblioDbName = "";
                        // 根据实体库名, 找到对应的书目库名
                        // return:
                        //      -1  出错
                        //      0   没有找到
                        //      1   找到
                        nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                            out strBiblioDbName,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "根据实体库名 '" + strItemDbName + "' 获得书目库名时出错: " + strError;
                            goto ERROR1;
                        }

                        string strParentID = DomUtil.GetElementText(itemdom.DocumentElement,
    "parent");
                        // 检查评估模式
                        // return:
                        //      -1  检查过程出错
                        //      0   可以通过
                        //      1   不允许通过
                        nRet = CheckTestModePath(strBiblioDbName + "/" + strParentID,
                            out strError);
                        if (nRet != 0)
                        {
                            strError = strActionName + "操作被拒绝: " + strError;
                            goto ERROR1;
                        }
                    }

                    // ***
                    // 延迟获得读者证条码号
                    if (string.IsNullOrEmpty(strReaderBarcode) == true)
                    {
                        if (bRenew == false)
                        {
                            strError = "必须提供 strReaderBarcode 参数值才能进行 " + strActionName + " 操作";
                            goto ERROR1;
                        }

                        string strOutputReaderBarcode = ""; // 返回的借阅者证条码号
                        // 在册记录中获得借阅者证条码号
                        // return:
                        //      -1  出错
                        //      0   该册为未借出状态
                        //      1   成功
                        nRet = GetBorrowerBarcode(itemdom,
                            out strOutputReaderBarcode,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = strError + " (册记录路径为 '" + strOutputItemRecPath + "')";
                            goto ERROR1;
                        }

                        if (nRet == 0 || string.IsNullOrEmpty(strOutputReaderBarcode) == true)
                        {
                            strError = "册 '" + strItemBarcode + "' 当前未曾被任何读者借阅,所以无法进行" + strActionName + "操作";
                            goto ERROR1;
                        }
#if NO
                    // 如果提供了读者证条码号,则需要核实
                    if (String.IsNullOrEmpty(strReaderBarcode) == false)
                    {
                        if (strOutputReaderBarcode != strReaderBarcode)
                        {
                            // 暂时不报错,滞后验证
                            bDelayVerifyReaderBarcode = true;
                            strIdcardNumber = strReaderBarcode;
                        }
                    }
#endif

                        if (String.IsNullOrEmpty(strReaderBarcode) == true)
                            strReaderBarcode = strOutputReaderBarcode;

                        // *** 如果读者记录在前面没有锁定, 在这里锁定
                        if (bReaderLocked == false)
                        {
                            Debug.Assert(string.IsNullOrEmpty(strReaderBarcode) == false, "");
                            // 2015/9/2
                            nRedoCount++;
                            if (nRedoCount > 10)
                            {
                                strError = "Borrow() 为锁定读者,试图重试,但发现已经超过 10 次, 只好放弃...";
                                this.WriteErrorLog(strError);
                                goto ERROR1;
                            }
                            goto REDO_BORROW;
                        }
                    }

                    // ***

                    // 校验读者证条码号参数是否和XML记录中完全一致
#if NO
                    string strTempReaderBarcode = DomUtil.GetElementText(readerdom.DocumentElement,
                        "barcode");

                    if (string.IsNullOrEmpty(strReaderBarcode) == false
                        && strReaderBarcode != strTempReaderBarcode)
                    {
                        // text-level: 内部错误
                        strError = "借阅操作被拒绝。因读者证条码号参数 '" + strReaderBarcode + "' 和读者记录中<barcode>元素内的读者证条码号值 '" + strTempReaderBarcode + "' 不一致。";
                        goto ERROR1;
                    }
#endif
                    {
                        // return:
                        //      false   不匹配
                        //      true    匹配
                        bool bRet = CheckBarcode(readerdom,
                strReaderBarcode,
                "读者",
                out strError);
                        if (bRet == false)
                        {
                            strError = "借阅操作被拒绝。因" + strError + "。";
                            goto ERROR1;
                        }
                    }

                    // 2007/1/2
                    // 校验册条码号参数是否和XML记录中完全一致

#if NO
                    string strRefID = "";
                    string strHead = "@refID:";
                    // string strFrom = "册条码";
                    if (StringUtil.HasHead(strItemBarcode, strHead, true) == true)
                    {
                        // strFrom = "参考ID";
                        strRefID = strItemBarcode.Substring(strHead.Length);

                        string strTempRefID = DomUtil.GetElementText(itemdom.DocumentElement,
    "refID");
                        if (strRefID != strTempRefID)
                        {
                            // text-level: 内部错误
                            strError = "借阅操作被拒绝。因册参考ID参数 '" + strRefID + "' 和册记录中<refID>元素内的册条码号值 '" + strTempRefID + "' 不一致。";
                            goto ERROR1;
                        }
                    }
                    else
                    {
                        string strTempItemBarcode = DomUtil.GetElementText(itemdom.DocumentElement,
    "barcode");
                        if (strItemBarcode != strTempItemBarcode)
                        {
                            // text-level: 内部错误
                            strError = "借阅操作被拒绝。因册条码号参数 '" + strItemBarcode + "' 和册记录中<barcode>元素内的册条码号值 '" + strTempItemBarcode + "' 不一致。";
                            goto ERROR1;
                        }
                    }
#endif
                    {
                        // return:
                        //      false   不匹配
                        //      true    匹配
                        bool bRet = CheckBarcode(itemdom,
                strItemBarcode,
                "册",
                out strError);
                        if (bRet == false)
                        {
                            // text-level: 内部错误
                            strError = "借阅操作被拒绝。因" + strError + "。";
                            goto ERROR1;
                        }
                    }

                    string strReaderType = DomUtil.GetElementText(readerdom.DocumentElement,
                        "readerType");

                    Calendar calendar = null;
                    // return:
                    //      -1  出错
                    //      0   没有找到日历
                    //      1   找到日历
                    nRet = GetReaderCalendar(strReaderType,
                        strLibraryCode,
                        out calendar,
                        out strError);
                    if (nRet == -1 || nRet == 0)
                        goto ERROR1;

                    /*
                    string strBookType = DomUtil.GetElementText(itemdom.DocumentElement,
                        "bookType");
                     */

                    bool bReaderDomChanged = false;

                    // 刷新以停代金事项
                    if (StringUtil.IsInList("pauseBorrowing", this.OverdueStyle) == true)
                    {
                        //
                        // 处理以停代金功能
                        // return:
                        //      -1  error
                        //      0   readerdom没有修改
                        //      1   readerdom发生了修改
                        nRet = ProcessPauseBorrowing(
                            strLibraryCode,
                            ref readerdom,
                            strOutputReaderRecPath,
                            sessioninfo.UserID,
                            "refresh",
                            sessioninfo.ClientAddress,  // 前端触发
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 内部错误
                            strError = "在刷新以停代金的过程中发生错误: " + strError;
                            goto ERROR1;
                        }

                        if (nRet == 1)
                            bReaderDomChanged = true;
                    }

                    byte[] output_timestamp = null;
                    string strOutputPath = "";

                    StringBuilder debugInfo = null;
                    // 检查借阅权限
                    // return:
                    //      -1  配置参数错误
                    //      0   权限不够,借阅操作应当被拒绝
                    //      1   权限够
                    nRet = CheckBorrowRights(
                        sessioninfo.Account,
                        calendar,
                        bRenew,
                        strLibraryCode, // 读者记录所在读者库的馆代码
                        strAccessParameters,
                        ref readerdom,
                        ref itemdom,
                        ref debugInfo,
                        out strError);
                    if (nRet == -1 || nRet == 0)
                    {
                        // 如果有必要保存回读者记录(因前面刷新了以停代金数据)
                        if (bReaderDomChanged == true)
                        {
                            string strError_1 = "";
                            /*
                            byte[] output_timestamp = null;
                            string strOutputPath = "";
                             * */

                            // 写回读者记录
                            lRet = channel.DoSaveTextRes(strOutputReaderRecPath,
                                readerdom.OuterXml,
                                false,
                                "content",  // ,ignorechecktimestamp
                                reader_timestamp,
                                out output_timestamp,
                                out strOutputPath,
                                out strError_1);
                            if (lRet == -1)
                            {
                                // text-level: 内部错误
                                strError = strError + "。然而在写入读者记录过程中,发生错误: " + strError_1;
                                goto ERROR1;
                            }
                        }

                        if (nRet == 0)
                        {
                            result.Value = -1;
                            result.ErrorInfo = strError;
                            result.ErrorCode = ErrorCode.AccessDenied;  // 权限不够
                            return result;
                        }

                        goto ERROR1;
                    }

                    XmlDocument domOperLog = new XmlDocument();
                    domOperLog.LoadXml("<root />");
                    DomUtil.SetElementText(domOperLog.DocumentElement,
                        "libraryCode",
                        strLibraryCode);    // 读者所在的馆代码
                    DomUtil.SetElementText(domOperLog.DocumentElement,
                        "operation",
                        "borrow");
                    DomUtil.SetElementText(domOperLog.DocumentElement,
                        "action",
                        bRenew == true ? "renew" : "borrow");
                    // 原来在这里

                    // 借阅API的从属函数
                    // 检查预约相关信息
                    // return:
                    //      -1  error
                    //      0   正常
                    //      1   发现该册被保留), 不能借阅
                    //      2   发现该册预约, 不能续借
                    //      3   发现该册被保留, 不能借阅。而且本函数修改了册记录(<location>元素发生了变化),需要本函数返回后,把册记录保存。
                    nRet = DoBorrowReservationCheck(
                        sessioninfo,
                        bRenew,
                        ref readerdom,
                        ref itemdom,
                        bForce,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;
                    if (nRet == 1 || nRet == 2)
                    {
                        // 被预约保留, 不能借阅
                        result.Value = -1;
                        result.ErrorInfo = strError;
                        if (nRet == 1)
                            result.ErrorCode = ErrorCode.BorrowReservationDenied;
                        if (nRet == 2)
                            result.ErrorCode = ErrorCode.RenewReservationDenied;
                        return result;
                    }

                    if (nRet == 3)
                    {
                        result.Value = -1;
                        result.ErrorInfo = strError;
                        result.ErrorCode = ErrorCode.BorrowReservationDenied;

                        /*
                        byte[] output_timestamp = null;
                        string strOutputPath = "";
                         * */

                        // 写回册记录
                        lRet = channel.DoSaveTextRes(strOutputItemRecPath,
                            itemdom.OuterXml,
                            false,
                            "content",  // ,ignorechecktimestamp
                            item_timestamp,
                            out output_timestamp,
                            out strOutputPath,
                            out strError);
                        if (lRet == -1)
                        {
                            // text-level: 内部错误
                            strError = "借阅操作中遇在架预约图书需要写回册记录 " + strOutputItemRecPath + " 时出错: " + strError;
                            this.WriteErrorLog(strError);
                            strError += "。借阅操作被拒绝。";
                            goto ERROR1;
                        }

                        return result;
                    }

                    // 移动到这里

                    // 在读者记录和册记录中添加借阅信息
                    // string strNewReaderXml = "";
                    nRet = DoBorrowReaderAndItemXml(
                        bRenew,
                        strLibraryCode,
                        ref readerdom,
                        ref itemdom,
                        bForce,
                        sessioninfo.UserID,
                        strOutputItemRecPath,
                        strOutputReaderRecPath,
                        ref domOperLog,
                        out borrow_info,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;

                    WriteTimeUsed(
                        time_lines,
                        start_time_process,
                        "Borrow() 中进行各种数据处理 耗时 ");

                    DateTime start_time_write_reader = DateTime.Now;
                    // 原来输出xml或xml的语句在此

                    // 写回读者、册记录

                    // 写回读者记录
                    lRet = channel.DoSaveTextRes(strOutputReaderRecPath,
                        readerdom.OuterXml,
                        false,
                        "content",  // ,ignorechecktimestamp
                        reader_timestamp,
                        out output_timestamp,
                        out strOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        // 2015/9/2
                        this.WriteErrorLog("Borrow() 写入读者记录 '" + strOutputReaderRecPath + "' 时出错: " + strError);

                        if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch)
                        {
                            nRedoCount++;
                            if (nRedoCount > 10)
                            {
                                // text-level: 内部错误
                                strError = "Borrow() 写回读者记录的时候,遇到时间戳冲突,并因此重试10次未能成功,只好放弃...";
                                this.WriteErrorLog(strError);
                                goto ERROR1;
                            }
                            goto REDO_BORROW;
                        }
                        goto ERROR1;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_reader,
                        "Borrow() 中写回读者记录 耗时 ");

                    DateTime start_time_write_item = DateTime.Now;

                    // 及时更新时间戳
                    reader_timestamp = output_timestamp;

                    // 写回册记录
                    lRet = channel.DoSaveTextRes(strOutputItemRecPath,
                        itemdom.OuterXml,
                        false,
                        "content",  // ,ignorechecktimestamp
                        item_timestamp,
                        out output_timestamp,
                        out strOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        // 2015/9/2
                        this.WriteErrorLog("Borrow() 写入册记录 '" + strOutputItemRecPath + "' 时出错: " + strError);

                        // 要Undo刚才对读者记录的写入
                        string strError1 = "";
                        lRet = channel.DoSaveTextRes(strOutputReaderRecPath,
                            strOldReaderXml,
                            false,
                            "content",  // ,ignorechecktimestamp
                            reader_timestamp,
                            out output_timestamp,
                            out strOutputPath,
                            out strError1);
                        if (lRet == -1) // 初次Undo失败
                        {
                            if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch)
                            {
                                // 读者记录Undo的时候, 发现时间戳冲突了
                                // 这时需要读出现存记录, 试图删除新增加的<borrows><borrow>元素
                                // return:
                                //      -1  error
                                //      0   没有必要Undo
                                //      1   Undo成功
                                nRet = UndoBorrowReaderRecord(
                                    channel,
                                    strOutputReaderRecPath,
                                    strReaderBarcode,
                                    strItemBarcode,
                                    out strError);
                                if (nRet == -1)
                                {
                                    // text-level: 内部错误
                                    strError = "Borrow() Undo读者记录 '" + strOutputReaderRecPath + "' (读者证条码号为'" + strReaderBarcode + "') 借阅册条码号 '" + strItemBarcode + "' 的修改时,发生错误,无法Undo: " + strError;
                                    this.WriteErrorLog(strError);
                                    goto ERROR1;
                                }

                                // 成功
                                // 2015/9/2 增加下列防止死循环的语句
                                nRedoCount++;
                                if (nRedoCount > 10)
                                {
                                    strError = "Borrow() Undo 读者记录(1)成功,试图重试 Borrow 时,发现先前重试已经超过 10 次,只好不重试了,做出错返回...";
                                    this.WriteErrorLog(strError);
                                    goto ERROR1;
                                }
                                goto REDO_BORROW;
                            }

                            // 以下为 不是时间戳冲突的其他错误情形
                            // text-level: 内部错误
                            strError = "Borrow() Undo读者记录 '" + strOutputReaderRecPath + "' (读者证条码号为'" + strReaderBarcode + "') 借阅册条码号 '" + strItemBarcode + "' 的修改时,发生错误,无法Undo: " + strError;
                            // strError = strError + ", 并且Undo写回旧读者记录也失败: " + strError1;
                            this.WriteErrorLog(strError);
                            goto ERROR1;
                        } // end of 初次Undo失败

                        // 以下为Undo成功的情形
                        // 2015/9/2 增加下列防止死循环的语句
                        nRedoCount++;
                        if (nRedoCount > 10)
                        {
                            strError = "Borrow() Undo 读者记录(2)成功,试图重试 Borrow 时,发现先前重试已经超过 10 次,只好不重试了,做出错返回...";
                            this.WriteErrorLog(strError);
                            goto ERROR1;
                        }
                        goto REDO_BORROW;

                    } // end of 写回册记录失败

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_item,
                        "Borrow() 中写回册记录 耗时 ");

                    DateTime start_time_write_operlog = DateTime.Now;

                    // 写入日志
                    if (string.IsNullOrEmpty(strConfirmItemRecPath) == false)
                    {
                        DomUtil.SetElementText(domOperLog.DocumentElement,
                            "confirmItemRecPath", strConfirmItemRecPath);
                    }

                    if (string.IsNullOrEmpty(strIdcardNumber) == false)
                    {
                        // 表明是使用身份证号来完成借阅操作的
                        DomUtil.SetElementText(domOperLog.DocumentElement,
        "idcardNumber", strIdcardNumber);
                    }

                    // 记载读者记录
                    XmlNode node = DomUtil.SetElementText(domOperLog.DocumentElement,
                        "readerRecord", readerdom.OuterXml);
                    DomUtil.SetAttr(node, "recPath", strOutputReaderRecPath);

                    // 记载册记录
                    node = DomUtil.SetElementText(domOperLog.DocumentElement,
                        "itemRecord", itemdom.OuterXml);
                    DomUtil.SetAttr(node, "recPath", strOutputItemRecPath);

                    nRet = this.OperLog.WriteOperLog(domOperLog,
                        sessioninfo.ClientAddress,
                        start_time,
                        out strOperLogUID,
                        out strError);
                    if (nRet == -1)
                    {
                        strError = "Borrow() API 写入日志时发生错误: " + strError;
                        goto ERROR1;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_operlog,
                        "Borrow() 中写操作日志 耗时 ");

                    DateTime start_time_write_statis = DateTime.Now;

                    // 写入统计指标
#if NO
                    if (this.m_strLastReaderBarcode != strReaderBarcode)
                    {
                        if (this.Statis != null)
                            this.Statis.IncreaseEntryValue(
                            strLibraryCode,
                            "出纳",
                            "读者数",
                            1);
                        this.m_strLastReaderBarcode = strReaderBarcode;

                    }
#endif
                    if (this.Garden != null)
                        this.Garden.Activate(strReaderBarcode,
                            strLibraryCode);

                    if (this.Statis != null)
                        this.Statis.IncreaseEntryValue(
                        strLibraryCode,
                        "出纳",
                        "借册",
                        1);

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_statis,
                        "Borrow() 中写统计指标 耗时 ");

                    strOutputItemXml = itemdom.OuterXml;

                    // strOutputReaderXml 将用于构造读者记录返回格式
                    DomUtil.DeleteElement(readerdom.DocumentElement, "password");
                    strOutputReaderXml = readerdom.OuterXml;

                    strBiblioRecID = DomUtil.GetElementText(itemdom.DocumentElement, "parent"); //
                } // 册记录锁定范围结束
                finally
                {
                    // 解册记录锁
                    this.EntityLocks.UnlockForWrite(strItemBarcode);    // strItemBarcode 在整个函数中不允许被修改
                }

            } // 读者记录锁定范围结束
            finally
            {
                // this.ReaderLocks.UnlockForWrite(strLockReaderBarcode);
                if (bReaderLocked == true)
                {
                    this.ReaderLocks.UnlockForWrite(strLockReaderBarcode);
#if DEBUG_LOCK_READER
                    this.WriteErrorLog("Borrow 结束为读者加写锁 '" + strLockReaderBarcode + "'");
#endif

                }
            }

            // 输出数据
            // 把输出数据部分放在读者锁以外范围,是为了尽量减少锁定的时间,提高并发运行效率

            DateTime output_start_time = DateTime.Now;

            if (String.IsNullOrEmpty(strOutputReaderXml) == false
    && StringUtil.IsInList("reader", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;
                nRet = BuildReaderResults(
            sessioninfo,
            null,
            strOutputReaderXml,
            strReaderFormatList,
            strLibraryCode,  // calendar/advancexml/html 时需要
            null,    // recpaths 时需要
            strOutputReaderRecPath,   // recpaths 时需要
            null,    // timestamp 时需要
            OperType.Borrow,
            saBorrowedItemBarcode,
            strItemBarcode,
            ref reader_records,
            out strError);
                if (nRet == -1)
                {
                    // text-level: 用户提示
                    strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                        strError);
                    // "虽然出现了下列错误,但是借阅操作已经成功: " + strError;
                    goto ERROR1;
                }

                WriteTimeUsed(
    time_lines,
    start_time_1,
    "Borrow() 中返回读者记录(" + strReaderFormatList + ") 耗时 ");
            }

#if NO
            if (String.IsNullOrEmpty(strOutputReaderXml) == false
                && StringUtil.IsInList("reader", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                string[] reader_formats = strReaderFormatList.Split(new char[] { ',' });
                reader_records = new string[reader_formats.Length];

                for (int i = 0; i < reader_formats.Length; i++)
                {
                    string strReaderFormat = reader_formats[i];
                    // 将读者记录数据从XML格式转换为HTML格式
                    // if (String.Compare(strReaderFormat, "html", true) == 0)
                    if (IsResultType(strReaderFormat, "html") == true)
                    {
                        string strReaderRecord = "";
                        nRet = this.ConvertReaderXmlToHtml(
                            sessioninfo,
                            this.CfgDir + "\\readerxml2html.cs",
                            this.CfgDir + "\\readerxml2html.cs.ref",
                            strLibraryCode,
                            strOutputReaderXml,
                            strOutputReaderRecPath, // 2009/10/18
                            OperType.Borrow,
                            saBorrowedItemBarcode,
                            strItemBarcode,
                            strReaderFormat,
                            out strReaderRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            // "虽然出现了下列错误,但是借阅操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        reader_records[i] = strReaderRecord;

                    }
                    // 将读者记录数据从XML格式转换为text格式
                    // else if (String.Compare(strReaderFormat, "text", true) == 0)
                    else if (IsResultType(strReaderFormat, "text") == true)
                    {
                        string strReaderRecord = "";
                        nRet = this.ConvertReaderXmlToHtml(
                            sessioninfo,
                            this.CfgDir + "\\readerxml2text.cs",
                            this.CfgDir + "\\readerxml2text.cs.ref",
                            strLibraryCode,
                            strOutputReaderXml,
                            strOutputReaderRecPath, // 2009/10/18
                            OperType.Borrow,
                            saBorrowedItemBarcode,
                            strItemBarcode,
                            strReaderFormat,
                            out strReaderRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        reader_records[i] = strReaderRecord;
                    }
                    // else if (String.Compare(strReaderFormat, "xml", true) == 0)
                    else if (IsResultType(strReaderFormat, "xml") == true)
                    {
                        // reader_records[i] = strOutputReaderXml;
                        string strResultXml = "";
                        nRet = GetItemXml(strOutputReaderXml,
            strReaderFormat,
            out strResultXml,
            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        reader_records[i] = strResultXml;
                    }
                    else if (IsResultType(strReaderFormat, "summary") == true)
                    {
                        // 2013/12/15
                        XmlDocument dom = new XmlDocument();
                        try
                        {
                            dom.LoadXml(strOutputReaderXml);
                        }
                        catch (Exception ex)
                        {
                            strError = "读者 XML 装入 DOM 出错: " + ex.Message;
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        reader_records[i] = DomUtil.GetElementText(dom.DocumentElement, "name");
                    }
                    else
                    {
                        strError = "strReaderFormatList参数出现了不支持的数据格式类型 '" + strReaderFormat + "'";
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                            strError);
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
                    time_lines,
                    start_time_1,
                    "Borrow() 中返回读者记录(" + strReaderFormatList + ") 耗时 ");
            }

#endif

            if (String.IsNullOrEmpty(strOutputItemXml) == false
                && StringUtil.IsInList("item", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                string[] item_formats = strItemFormatList.Split(new char[] { ',' });
                item_records = new string[item_formats.Length];

                for (int i = 0; i < item_formats.Length; i++)
                {
                    string strItemFormat = item_formats[i];

                    // 将册记录数据从XML格式转换为HTML格式
                    //if (String.Compare(strItemFormat, "html", true) == 0)
                    if (IsResultType(strItemFormat, "html") == true)
                    {
                        string strItemRecord = "";
                        nRet = this.ConvertItemXmlToHtml(
                            this.CfgDir + "\\itemxml2html.cs",
                            this.CfgDir + "\\itemxml2html.cs.ref",
                            strOutputItemXml,
                            strOutputItemRecPath,   // 2009/10/18
                            out strItemRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        item_records[i] = strItemRecord;
                    }
                    // 将册记录数据从XML格式转换为text格式
                    // else if (String.Compare(strItemFormat, "text", true) == 0)
                    else if (IsResultType(strItemFormat, "text") == true)
                    {
                        string strItemRecord = "";
                        nRet = this.ConvertItemXmlToHtml(
                            this.CfgDir + "\\itemxml2text.cs",
                            this.CfgDir + "\\itemxml2text.cs.ref",
                            strOutputItemXml,
                            strOutputItemRecPath,   // 2009/10/18
                            out strItemRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        item_records[i] = strItemRecord;
                    }
                    // else if (String.Compare(strItemFormat, "xml", true) == 0)
                    else if (IsResultType(strItemFormat, "xml") == true)
                    {
                        string strResultXml = "";
                        nRet = GetItemXml(strOutputItemXml,
            strItemFormat,
            out strResultXml,
            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        item_records[i] = strResultXml;
                    }
                    else
                    {
                        strError = "strItemFormatList参数出现了不支持的数据格式类型 '" + strItemFormat + "'";
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                            strError);
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
    time_lines,
    start_time_1,
    "Borrow() 中返回册记录(" + strItemFormatList + ") 耗时 ");
            }

            // 2008/5/9
            if (StringUtil.IsInList("biblio", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                if (String.IsNullOrEmpty(strBiblioRecID) == true)
                {
                    strError = "册记录XML中<parent>元素缺乏或者值为空, 因此无法定位种记录ID";
                    // text-level: 用户提示
                    strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                        strError);
                    goto ERROR1;
                }

                string strItemDbName = ResPath.GetDbName(strOutputItemRecPath);

                string strBiblioDbName = "";
                // 根据实体库名, 找到对应的书目库名
                // return:
                //      -1  出错
                //      0   没有找到
                //      1   找到
                nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                    out strBiblioDbName,
                    out strError);
                if (nRet == -1)
                    goto ERROR1;
                if (nRet == 0)
                {
                    strError = "实体库名 '" + strItemDbName + "' 没有找到对应的书目库名";
                    // text-level: 用户提示
                    strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                        strError);
                    goto ERROR1;
                }

                string strBiblioRecPath = strBiblioDbName + "/" + strBiblioRecID;

                string[] biblio_formats = strBiblioFormatList.Split(new char[] { ',' });
                biblio_records = new string[biblio_formats.Length];

                string strBiblioXml = "";
                // 至少有html xml text之一,才获取strBiblioXml
                if (StringUtil.IsInList("html", strBiblioFormatList) == true
                    || StringUtil.IsInList("xml", strBiblioFormatList) == true
                    || StringUtil.IsInList("text", strBiblioFormatList) == true)
                {
                    RmsChannel channel = sessioninfo.Channels.GetChannel(this.WsUrl);
                    if (channel == null)
                    {
                        strError = "get channel error";
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                            strError);
                        goto ERROR1;
                    }

                    string strMetaData = "";
                    byte[] timestamp = null;
                    string strTempOutputPath = "";
                    lRet = channel.GetRes(strBiblioRecPath,
                        out strBiblioXml,
                        out strMetaData,
                        out timestamp,
                        out strTempOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        strError = "获得种记录 '" + strBiblioRecPath + "' 时出错: " + strError;
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                            strError);
                        goto ERROR1;
                    }
                }

                for (int i = 0; i < biblio_formats.Length; i++)
                {
                    string strBiblioFormat = biblio_formats[i];

                    // 需要从内核映射过来文件
                    string strLocalPath = "";
                    string strBiblio = "";

                    // 将书目记录数据从XML格式转换为HTML格式
                    if (String.Compare(strBiblioFormat, "html", true) == 0)
                    {
                        // TODO: 可以cache
                        nRet = this.MapKernelScriptFile(
                            sessioninfo,
                            strBiblioDbName,
                            "./cfgs/loan_biblio.fltx",
                            out strLocalPath,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }

                        // 将种记录数据从XML格式转换为HTML格式
                        string strFilterFileName = strLocalPath;    // app.CfgDir + "\\biblio.fltx";
                        if (string.IsNullOrEmpty(strBiblioXml) == false)
                        {
                            nRet = this.ConvertBiblioXmlToHtml(
                                strFilterFileName,
                                strBiblioXml,
                                    null,
                                strBiblioRecPath,
                                out strBiblio,
                                out strError);
                            if (nRet == -1)
                            {
                                // text-level: 用户提示
                                strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                    strError);
                                goto ERROR1;
                            }
                        }
                        else
                            strBiblio = "";

                        biblio_records[i] = strBiblio;
                    }
                    // 将册记录数据从XML格式转换为text格式
                    else if (String.Compare(strBiblioFormat, "text", true) == 0)
                    {
                        // TODO: 可以cache
                        nRet = this.MapKernelScriptFile(
                            sessioninfo,
                            strBiblioDbName,
                            "./cfgs/loan_biblio_text.fltx",
                            out strLocalPath,
                            out strError);
                        if (nRet == -1)
                        {
                            // text-level: 用户提示
                            strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                strError);
                            goto ERROR1;
                        }
                        // 将种记录数据从XML格式转换为TEXT格式
                        string strFilterFileName = strLocalPath;    // app.CfgDir + "\\biblio.fltx";
                        if (string.IsNullOrEmpty(strBiblioXml) == false)
                        {
                            nRet = this.ConvertBiblioXmlToHtml(
                                strFilterFileName,
                                strBiblioXml,
                                    null,
                                strBiblioRecPath,
                                out strBiblio,
                                out strError);
                            if (nRet == -1)
                            {
                                // text-level: 用户提示
                                strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                                    strError);
                                goto ERROR1;
                            }

                        }
                        else
                            strBiblio = "";

                        biblio_records[i] = strBiblio;
                    }
                    else if (String.Compare(strBiblioFormat, "xml", true) == 0)
                    {
                        biblio_records[i] = strBiblioXml;
                    }
                    else if (String.Compare(strBiblioFormat, "recpath", true) == 0)
                    {
                        biblio_records[i] = strBiblioRecPath;
                    }
                    else if (string.IsNullOrEmpty(strBiblioFormat) == true)
                    {
                        biblio_records[i] = "";
                    }
                    else
                    {
                        strError = "strBiblioFormatList参数出现了不支持的数据格式类型 '" + strBiblioFormat + "'";
                        // text-level: 用户提示
                        strError = string.Format(this.GetString("虽然出现了下列错误,但是借阅操作已经成功s"),   // "虽然出现了下列错误,但是借阅操作已经成功: {0}";
                            strError);
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
time_lines,
start_time_1,
"Borrow() 中返回书目记录(" + strBiblioFormatList + ") 耗时 ");

            }

            WriteTimeUsed(
    time_lines,
    start_time,
    "Borrow() 耗时 ");
            // 如果整个时间超过一秒,则需要计入操作日志
            if (DateTime.Now - start_time > new TimeSpan(0, 0, 1))
            {
                WriteLongTimeOperLog(
                    sessioninfo,
                    strAction,
                    start_time,
                    "整个操作耗时超过 1 秒。详情:" + StringUtil.MakePathList(time_lines, ";"),
                    strOperLogUID,
                    out strError);
            }

            // 如果创建输出数据的时间超过一秒,则需要计入操作日志
            if (DateTime.Now - output_start_time > new TimeSpan(0, 0, 1))
            {
                WriteLongTimeOperLog(
                    sessioninfo,
                    strAction,
                    output_start_time,
                    "output 阶段耗时超过 1 秒",
                    strOperLogUID,
                    out strError);
            }

            return result;
        ERROR1:
            result.Value = -1;
            result.ErrorInfo = strError;
            result.ErrorCode = ErrorCode.SystemError;
            return result;
        }
예제 #2
0
        // API: 还书
        // 权限:  工作人员需要return权限,如果是丢失处理需要lost权限;所有读者均不具备还书操作权限。盘点需要 inventory 权限
        // parameters:
        //      strAction   return/lost/inventory/read
        //      strReaderBarcodeParam   读者证条码号。当 strAction 为 "inventory" 时,这里是批次号
        // return:
        //      Result.Value    -1  出错 0 操作成功 1 操作成功,但有值得操作人员留意的情况:如有超期情况;发现条码号重复;需要放入预约架
        public LibraryServerResult Return(
            SessionInfo sessioninfo,
            string strAction,
            string strReaderBarcodeParam,
            string strItemBarcodeParam,
            string strConfirmItemRecPath,
            bool bForce,
            string strStyle,

            string strItemFormatList,   // 2008/5/9
            out string[] item_records,  // 2008/5/9

            string strReaderFormatList,
            out string[] reader_records,

            string strBiblioFormatList, // 2008/5/9
            out string[] biblio_records,    // 2008/5/9

            out string[] aDupPath,
            out string strOutputReaderBarcodeParam,
            out ReturnInfo return_info)
        {
            item_records = null;
            reader_records = null;
            biblio_records = null;
            aDupPath = null;
            strOutputReaderBarcodeParam = "";
            return_info = new ReturnInfo();

            string strError = "";

            List<string> time_lines = new List<string>();
            DateTime start_time = DateTime.Now;
            string strOperLogUID = "";

            LibraryServerResult result = new LibraryServerResult();

            string strActionName = GetReturnActionName(strAction);

            // 个人书斋名
            string strPersonalLibrary = "";
            if (sessioninfo.UserType == "reader"
                && sessioninfo.Account != null)
                strPersonalLibrary = sessioninfo.Account.PersonalLibrary;

            // 权限判断
            if (strAction == "return")
            {
                // 权限字符串
                if (StringUtil.IsInList("return", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    result.ErrorInfo = strActionName + "操作被拒绝。不具备 return 权限。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }
            }
            else if (strAction == "lost")
            {
                // 权限字符串
                if (StringUtil.IsInList("lost", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    result.ErrorInfo = strActionName + " 操作被拒绝。不具备 lost 权限。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }
            }
            else if (strAction == "inventory")
            {
                // 权限字符串
                if (StringUtil.IsInList("inventory", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    result.ErrorInfo = strActionName + " 操作被拒绝。不具备 inventory 权限。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }
            }
            else if (strAction == "read")
            {
                // 权限字符串
                if (StringUtil.IsInList("read", sessioninfo.RightsOrigin) == false)
                {
                    result.Value = -1;
                    result.ErrorInfo = strActionName + " 操作被拒绝。不具备 read 权限。";
                    result.ErrorCode = ErrorCode.AccessDenied;
                    // return result;
                }
            }
            else
            {
                strError = "无法识别的 strAction 参数值 '" + strAction + "'。";
                goto ERROR1;
            }

            // 对读者身份的附加判断
            // 注:具有个人书斋的,还可以继续向后执行
            if (sessioninfo.UserType == "reader"
                && string.IsNullOrEmpty(strPersonalLibrary) == true)
            {
                result.Value = -1;
                result.ErrorInfo = strActionName + "操作被拒绝。作为读者不能进行此类操作。";
                result.ErrorCode = ErrorCode.AccessDenied;
                return result;
            }

            // 如果没有普通的权限,需要预检查存取权限
            LibraryServerResult result_save = null;
            if (result.Value == -1 && String.IsNullOrEmpty(sessioninfo.Access) == false)
            {
                string strAccessActionList = GetDbOperRights(sessioninfo.Access,
                        "", // 此时还不知道实体库名,先取得当前帐户关于任意一个实体库的存取定义
                        "circulation");
                if (string.IsNullOrEmpty(strAccessActionList) == true)
                    return result;

                // 通过了这样一番检查后,后面依然要检查存取权限。
                // 如果后面检查中,精确针对某个实体库的存取权限存在,则依存取权限;如果不存在,则依普通权限
                result_save = result.Clone();
            }
            else if (result.Value == -1)
                return result;  // 延迟报错 2014/9/16

            result = new LibraryServerResult();

            string strReservationReaderBarcode = "";

            string strReaderBarcode = strReaderBarcodeParam;

            if (strAction == "read" && string.IsNullOrEmpty(strReaderBarcode))
            {
                strError = "读过功能 strReaderBarcode 参数值不应为空";
                goto ERROR1;
            }

            string strBatchNo = "";
            if (strAction == "inventory")
            {
                strBatchNo = strReaderBarcodeParam; // 为避免判断发生混乱,后面统一用 strBatchNo 存储批次号
                strReaderBarcodeParam = "";
                strReaderBarcode = "";
            }

            long lRet = 0;
            int nRet = 0;
            string strIdcardNumber = "";
            string strQrCode = "";  //
            bool bDelayVerifyReaderBarcode = false; // 是否延迟验证
            string strLockReaderBarcode = "";

            if (bForce == true)
            {
                strError = "bForce 参数不能为 true";
                goto ERROR1;
            }

            if (string.IsNullOrEmpty(strReaderBarcode) == false)
            {
                string strOutputCode = "";
                // 把二维码字符串转换为读者证条码号
                // parameters:
                //      strReaderBcode  [out]读者证条码号
                // return:
                //      -1      出错
                //      0       所给出的字符串不是读者证号二维码
                //      1       成功      
                nRet = this.DecodeQrCode(strReaderBarcode,
                    out strOutputCode,
                    out strError);
                if (nRet == -1)
                    goto ERROR1;
                if (nRet == 1)
                {
                    strQrCode = strReaderBarcode;
                    strReaderBarcode = strOutputCode;
                }
            }

            RmsChannel channel = sessioninfo.Channels.GetChannel(this.WsUrl);
            if (channel == null)
            {
                strError = "get channel error";
                goto ERROR1;
            }

            int nRedoCount = 0;

        REDO_RETURN:

            bool bReaderLocked = false;
            bool bEntityLocked = false;

            if (String.IsNullOrEmpty(strReaderBarcodeParam) == false)
            {
                // 加读者记录锁
                strLockReaderBarcode = strReaderBarcodeParam;
#if DEBUG_LOCK_READER
                this.WriteErrorLog("Return 开始为读者加写锁 1 '" + strReaderBarcodeParam + "'");
#endif
                this.ReaderLocks.LockForWrite(strReaderBarcodeParam);
                bReaderLocked = true;
                strOutputReaderBarcodeParam = strReaderBarcode;
            }

            string strOutputReaderXml = "";
            string strOutputItemXml = "";
            string strBiblioRecID = "";
            string strOutputItemRecPath = "";
            string strOutputReaderRecPath = "";
            string strLibraryCode = "";
            string strInventoryWarning = "";    // 盘点时的警告信息。先存储在其中,等读者记录完全获得后再报错

            try // 读者记录锁定范围(可能)开始
            {
                // 2016/1/27
                // 读取读者记录
                XmlDocument readerdom = null;
                byte[] reader_timestamp = null;
                string strOldReaderXml = "";
                bool bReaderDbInCirculation = true;

                if (string.IsNullOrEmpty(strReaderBarcode) == false)
                {
                    LibraryServerResult result1 = GetReaderRecord(
                sessioninfo,
                strActionName,
                time_lines,
                strAction != "inventory",
                ref strReaderBarcode,
                ref strIdcardNumber,
                ref strLibraryCode,
                out bReaderDbInCirculation,
                out readerdom,
                out strOutputReaderRecPath,
                out reader_timestamp);
                    if (result1.Value == 0)
                    {
                    }
                    else
                    {
                        return result1;
                    }

                    // 记忆修改前的读者记录
                    strOldReaderXml = readerdom.OuterXml;

                    if (String.IsNullOrEmpty(strIdcardNumber) == false
                        || string.IsNullOrEmpty(strReaderBarcode) == true /* 2013/5/23 */)
                    {
                        // 获得读者证条码号
                        strReaderBarcode = DomUtil.GetElementText(readerdom.DocumentElement,
                            "barcode");
                    }
                    strOutputReaderBarcodeParam = DomUtil.GetElementText(readerdom.DocumentElement,
                            "barcode");

                    string strReaderDbName = ResPath.GetDbName(strOutputReaderRecPath);

                    // 检查当前用户管辖的读者范围
                    // return:
                    //      -1  出错
                    //      0   允许继续访问
                    //      1   权限限制,不允许继续访问。strError 中有说明原因的文字
                    nRet = CheckReaderRange(sessioninfo,
                        readerdom,
                        strReaderDbName,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;
                    if (nRet == 1)
                    {
                        // strError = "当前用户 '" + sessioninfo.UserID + "' 的存取权限或好友关系禁止操作读者(证条码号为 " + strReaderBarcode + ")。具体原因:" + strError;
                        result.Value = -1;
                        result.ErrorInfo = strError;
                        result.ErrorCode = ErrorCode.AccessDenied;
                        return result;
                    }
                }

                //

                List<string> aPath = null;

                string strItemXml = "";
                byte[] item_timestamp = null;

                // *** 获得册记录 ***
                bool bItemBarcodeDup = false;   // 是否发生册条码号重复情况
                string strDupBarcodeList = "";  // 用于最后返回ErrorInfo的重复册条码号列表

                // 册记录可能加锁
                // 如果读者记录此时已经加锁, 就为册记录加锁
                if (bReaderLocked == true)
                {
                    this.EntityLocks.LockForWrite(strItemBarcodeParam);
                    bEntityLocked = true;
                }

                try // 册记录锁定范围开始
                {
                    WriteTimeUsed(
                        time_lines,
                        start_time,
                        "Return() 中前期检查和锁定 耗时 ");

                    DateTime start_time_read_item = DateTime.Now;

                    // 如果已经有确定的册记录路径
                    if (String.IsNullOrEmpty(strConfirmItemRecPath) == false)
                    {
                        // 检查路径中的库名,是不是实体库名
                        // return:
                        //      -1  error
                        //      0   不是实体库名
                        //      1   是实体库名
                        nRet = this.CheckItemRecPath(strConfirmItemRecPath,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                        if (nRet == 0)
                        {
                            strError = strConfirmItemRecPath + strError;
                            goto ERROR1;
                        }

                        string strMetaData = "";

                        lRet = channel.GetRes(strConfirmItemRecPath,
                            out strItemXml,
                            out strMetaData,
                            out item_timestamp,
                            out strOutputItemRecPath,
                            out strError);
                        if (lRet == -1)
                        {
                            strError = "根据strConfirmItemRecPath '" + strConfirmItemRecPath + "' 获得册记录失败: " + strError;
                            goto ERROR1;
                        }
                    }
                    else if (IsBiblioRecPath(strItemBarcodeParam) == false)
                    {
                        // 从册条码号获得册记录

                        // 获得册记录
                        // return:
                        //      -1  error
                        //      0   not found
                        //      1   命中1条
                        //      >1  命中多于1条
                        nRet = this.GetItemRecXml(
                            // sessioninfo.Channels,
                            channel,
                            strItemBarcodeParam,
                            "first",    // 在若干实体库中顺次检索,命中一个以上则返回,不再继续检索更多
                            out strItemXml,
                            100,
                            out aPath,
                            out item_timestamp,
                            out strError);
                        if (nRet == 0)
                        {
                            result.Value = -1;
                            result.ErrorInfo = "册条码号 '" + strItemBarcodeParam + "' 不存在";
                            result.ErrorCode = ErrorCode.ItemBarcodeNotFound;
                            return result;
                        }
                        if (nRet == -1)
                        {
                            strError = "读入册记录时发生错误: " + strError;
                            goto ERROR1;
                        }

                        if (aPath.Count > 1)
                        {
                            if (this.Statis != null)
                                this.Statis.IncreaseEntryValue(
                                strLibraryCode,
                                "出纳",
                                "还书遇册条码号重复次数",
                                1);

                            bItemBarcodeDup = true; // 此时已经需要设置状态。虽然后面可以进一步识别出真正的册记录

                            // 构造strDupBarcodeList
                            /*
                            string[] pathlist = new string[aPath.Count];
                            aPath.CopyTo(pathlist);
                            strDupBarcodeList = String.Join(",", pathlist);
                             * */
                            strDupBarcodeList = StringUtil.MakePathList(aPath);

                            List<string> aFoundPath = null;
                            List<byte[]> aTimestamp = null;
                            List<string> aItemXml = null;

                            if (String.IsNullOrEmpty(strReaderBarcodeParam) == true)
                            {
                                if (this.Statis != null)
                                    this.Statis.IncreaseEntryValue(
                                    strLibraryCode,
                                    "出纳",
                                    "还书遇册条码号重复并无读者证条码号辅助判断次数",
                                    1);

                                // 如果没有给出读者证条码号参数
                                result.Value = -1;
                                result.ErrorInfo = "册条码号为 '" + strItemBarcodeParam + "' 册记录有 " + aPath.Count.ToString() + " 条,无法进行还书操作。请在附加册记录路径后重新提交还书操作。";
                                result.ErrorCode = ErrorCode.ItemBarcodeDup;

                                aDupPath = new string[aPath.Count];
                                aPath.CopyTo(aDupPath);
                                return result;
                            }

                            // 从若干重复条码号的册记录中,选出其中符合当前读者证条码号的
                            // return:
                            //      -1  出错
                            //      其他    选出的数量
                            nRet = FindItem(
                                channel,
                                strReaderBarcode,
                                aPath,
                                true,   // 优化
                                out aFoundPath,
                                out aItemXml,
                                out aTimestamp,
                                out strError);
                            if (nRet == -1)
                            {
                                strError = "选择重复条码号的册记录时发生错误: " + strError;
                                goto ERROR1;
                            }

                            if (nRet == 0)
                            {
                                result.Value = -1;
                                result.ErrorInfo = "册条码号 '" + strItemBarcodeParam + "' 检索出的 " + aPath.Count + " 条记录中,没有任何一条其<borrower>元素表明了被读者 '" + strReaderBarcode + "' 借阅。";
                                result.ErrorCode = ErrorCode.ItemBarcodeNotFound;
                                return result;
                            }

                            if (nRet > 1)
                            {
                                if (this.Statis != null)
                                    this.Statis.IncreaseEntryValue(
                                    strLibraryCode,
                                    "出纳",
                                    "借书遇册条码号重复并读者证条码号也无法去重次数",
                                    1);

                                result.Value = -1;
                                result.ErrorInfo = "册条码号为 '" + strItemBarcodeParam + "' 并且<borrower>元素表明为读者 '" + strReaderBarcode + "' 借阅的册记录有 " + aFoundPath.Count.ToString() + " 条,无法进行还书操作。请在附加册记录路径后重新提交还书操作。";
                                result.ErrorCode = ErrorCode.ItemBarcodeDup;
                                this.WriteErrorLog(result.ErrorInfo);   // 2012/12/30

                                aDupPath = new string[aFoundPath.Count];
                                aFoundPath.CopyTo(aDupPath);
                                return result;
                            }

                            Debug.Assert(nRet == 1, "");

                            if (this.Statis != null)
                                this.Statis.IncreaseEntryValue(strLibraryCode,
                                "出纳",
                                "借书遇册条码号重复但根据读者证条码号成功去重次数",
                                1);

                            this.WriteErrorLog("借书遇册条码号 '" + strItemBarcodeParam + "' 重复但根据读者证条码号 '" + strReaderBarcode + "' 成功去重");   // 2012/12/30

                            strOutputItemRecPath = aFoundPath[0];
                            item_timestamp = aTimestamp[0];
                            strItemXml = aItemXml[0];
                        }
                        else
                        {
                            Debug.Assert(nRet == 1, "");
                            Debug.Assert(aPath.Count == 1, "");
                            if (nRet == 1)
                            {
                                strOutputItemRecPath = aPath[0];
                                // strItemXml已经有册记录了
                            }
                        }

                        // 函数返回后有用
                        aDupPath = new string[1];
                        aDupPath[0] = strOutputItemRecPath;
                    }

                    // 看看册记录所从属的数据库,是否在参与流通的实体库之列
                    // 2008/6/4
                    string strItemDbName = "";
                    bool bItemDbInCirculation = true;
                    if (strAction != "inventory")
                    {
                        if (String.IsNullOrEmpty(strOutputItemRecPath) == false)
                        {
                            strItemDbName = ResPath.GetDbName(strOutputItemRecPath);
                            if (this.IsItemDbName(strItemDbName, out bItemDbInCirculation) == false)
                            {
                                strError = "册记录路径 '" + strOutputItemRecPath + "' 中的数据库名 '" + strItemDbName + "' 居然不在定义的实体库之列。";
                                goto ERROR1;
                            }
                        }
                    }

                    // 检查存取权限
                    string strAccessParameters = "";

                    {

                        // 检查存取权限
                        if (String.IsNullOrEmpty(sessioninfo.Access) == false)
                        {
                            string strAccessActionList = "";
                            strAccessActionList = GetDbOperRights(sessioninfo.Access,
                                strItemDbName,
                                "circulation");
#if NO
                            if (String.IsNullOrEmpty(strAccessActionList) == true && result_save != null)
                            {
                                // TODO: 也可以直接返回 result_save
                                strError = "当前用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳 操作的存取权限";
                                result.Value = -1;
                                result.ErrorInfo = strError;
                                result.ErrorCode = ErrorCode.AccessDenied;
                                return result;
                            }
#endif
                            if (strAccessActionList == null)
                            {
                                strAccessActionList = GetDbOperRights(sessioninfo.Access,
            "", // 此时还不知道实体库名,先取得当前帐户关于任意一个实体库的存取定义
            "circulation");
                                if (strAccessActionList == null)
                                {
                                    // 对所有实体库都没有定义任何存取权限,这时候要退而使用普通权限
                                    strAccessActionList = sessioninfo.Rights;

                                    // 注:其实此时 result_save == null 即表明普通权限检查已经通过了的
                                }
                                else
                                {
                                    // 对其他实体库定义了存取权限,但对 strItemDbName 没有定义
                                    strError = "用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳 操作的存取权限";
                                    result.Value = -1;
                                    result.ErrorInfo = strError;
                                    result.ErrorCode = ErrorCode.AccessDenied;
                                    return result;
                                }
                            }

                            if (strAccessActionList == "*")
                            {
                                // 通配
                            }
                            else
                            {
                                if (IsInAccessList(strAction, strAccessActionList, out strAccessParameters) == false)
                                {
                                    strError = "用户 '" + sessioninfo.UserID + "' 不具备 针对数据库 '" + strItemDbName + "' 执行 出纳  " + strActionName + " 操作的存取权限";
                                    result.Value = -1;
                                    result.ErrorInfo = strError;
                                    result.ErrorCode = ErrorCode.AccessDenied;
                                    return result;
                                }
                            }
                        }
                    }

                    XmlDocument itemdom = null;
                    if (string.IsNullOrEmpty(strItemXml) == false)
                    {
                        nRet = LibraryApplication.LoadToDom(strItemXml,
                            out itemdom,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "装载册记录进入 XML DOM 时发生错误: " + strError;
                            goto ERROR1;
                        }
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_read_item,
                        "Return() 中读取册记录 耗时 ");

                    DateTime start_time_lock = DateTime.Now;

                    // 检查评估模式下书目记录路径
                    if (this.TestMode == true || sessioninfo.TestMode == true)
                    {
                        string strBiblioDbName = "";
                        // 根据实体库名, 找到对应的书目库名
                        // return:
                        //      -1  出错
                        //      0   没有找到
                        //      1   找到
                        nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                            out strBiblioDbName,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "根据实体库名 '" + strItemDbName + "' 获得书目库名时出错: " + strError;
                            goto ERROR1;
                        }

                        string strParentID = DomUtil.GetElementText(itemdom.DocumentElement,
    "parent");
                        // 检查评估模式
                        // return:
                        //      -1  检查过程出错
                        //      0   可以通过
                        //      1   不允许通过
                        nRet = CheckTestModePath(strBiblioDbName + "/" + strParentID,
                            out strError);
                        if (nRet != 0)
                        {
                            strError = strActionName + "操作被拒绝: " + strError;
                            goto ERROR1;
                        }
                    }

                    string strOutputReaderBarcode = ""; // 返回的借阅者证条码号
                    if (strAction != "read")
                    {
                        // 在册记录中获得借阅者证条码号
                        // return:
                        //      -1  出错
                        //      0   该册为未借出状态
                        //      1   成功
                        nRet = GetBorrowerBarcode(itemdom,
                            out strOutputReaderBarcode,
                            out strError);
                        if (strAction == "inventory")
                        {
                            if (nRet == -1)
                            {
                                strError = strError + " (册记录路径为 '" + strOutputItemRecPath + "')";
                                goto ERROR1;
                            }
                            if (string.IsNullOrEmpty(strOutputReaderBarcode) == false)
                            {
                                // 该册处于被借阅状态,需要警告前端,建议立即进行还书操作
                                strInventoryWarning = "册 " + strItemBarcodeParam + " 当前处于被借阅状态。如确属在架已还图书,建议立即为之补办还书手续。" + " (册记录路径为 '" + strOutputItemRecPath + "')";
                            }
                        }
                        else
                        {
                            if (nRet == -1 || nRet == 0)
                            {
                                strError = strError + " (册记录路径为 '" + strOutputItemRecPath + "')";
                                goto ERROR1;
                            }
                        }

                        // 如果提供了读者证条码号,则需要核实
                        if (String.IsNullOrEmpty(strReaderBarcodeParam) == false)
                        {
                            if (strOutputReaderBarcode != strReaderBarcodeParam)
                            {
#if NO
                            if (StringUtil.IsIdcardNumber(strReaderBarcodeParam) == true)
                            {
                                // 暂时不报错,滞后验证
                                bDelayVerifyReaderBarcode = true;
                                strIdcardNumber = strReaderBarcodeParam;
                            }
                            else
                            {
                                strError = "册记录表明,册 " + strItemBarcode + " 实际被读者 " + strOutputReaderBarcode + " 所借阅,而不是您当前输入的读者(证条码号) " + strReaderBarcodeParam + "。还书操作被放弃。";
                                goto ERROR1;
                            }
#endif
                                // 暂时不报错,滞后验证
                                bDelayVerifyReaderBarcode = true;
                                strIdcardNumber = strReaderBarcodeParam;
                            }
                        }

                        if (String.IsNullOrEmpty(strReaderBarcode) == true)
                            strReaderBarcode = strOutputReaderBarcode;
                    }

                    // *** 如果读者记录在前面没有锁定, 在这里锁定
                    if (bReaderLocked == false && string.IsNullOrEmpty(strReaderBarcode) == false)
                    {
                        // 加读者记录锁
                        strLockReaderBarcode = strReaderBarcode;
#if DEBUG_LOCK_READER
                        this.WriteErrorLog("Return 开始为读者加写锁 2 '" + strLockReaderBarcode + "'");
#endif
                        this.ReaderLocks.LockForWrite(strLockReaderBarcode);
                        bReaderLocked = true;
                        strOutputReaderBarcodeParam = strReaderBarcode;
                    }

                    // *** 如果册记录在前面没有锁定,则在这里锁定
                    if (bEntityLocked == false)
                    {
                        this.EntityLocks.LockForWrite(strItemBarcodeParam);
                        bEntityLocked = true;

                        // 因为前面对于册记录一直没有加锁,所以这里锁定后要
                        // 检查时间戳,确保记录内容没有(实质性)改变
                        byte[] temp_timestamp = null;
                        string strTempOutputPath = "";
                        string strTempItemXml = "";
                        string strMetaData = "";

                        lRet = channel.GetRes(
                            strOutputItemRecPath,
                            out strTempItemXml,
                            out strMetaData,
                            out temp_timestamp,
                            out strTempOutputPath,
                            out strError);
                        if (lRet == -1)
                        {
                            strError = "册条码号(滞后)加锁后重新提取册记录 '" + strOutputItemRecPath + "' 时发生错误: " + strError;
                            goto ERROR1;
                        }

                        // 如果时间戳发生过改变
                        if (ByteArray.Compare(item_timestamp, temp_timestamp) != 0)
                        {
                            // 装载新记录进入DOM
                            XmlDocument temp_itemdom = null;
                            nRet = LibraryApplication.LoadToDom(strTempItemXml,
                                out temp_itemdom,
                                out strError);
                            if (nRet == -1)
                            {
                                strError = "装载册记录strTempItemXml 路径'" + strOutputItemRecPath + "' 进入XML DOM时发生错误: " + strError;
                                goto ERROR1;
                            }

                            // 检查新旧册记录有无要害性改变?
                            if (IsItemRecordSignificantChanged(itemdom,
                                temp_itemdom) == true)
                            {
                                // 则只好重做
                                nRedoCount++;
                                if (nRedoCount > 10)
                                {
                                    strError = "Return() 册条码号(滞后)加锁后重新提取册记录的时候,遇到时间戳冲突,并因此重试超过 10 次未能成功, 只好放弃...";
                                    this.WriteErrorLog(strError);
                                    goto ERROR1;
                                }
                                /*
                                // 如果重做超过5次,则索性修改读者证条码号参数,让它具有(经检索提取的)确定的值,这样就不会滞后加锁了
                                if (nRedoCount > 5)
                                    strReaderBarcodeParam = strReaderBarcode;
                                 * */
#if DEBUG_LOCK_READER
                                this.WriteErrorLog("Return goto REDO_RETURN 1 nRedoCount=" + nRedoCount + "");
#endif
                                goto REDO_RETURN;
                            }

                            // 如果没有要害性改变,就刷新相关参数,然后继续向后进行
                            itemdom = temp_itemdom;
                            item_timestamp = temp_timestamp;
                            strItemXml = strTempItemXml;
                        }

                        // 如果时间戳没有发生过改变,则不必刷新任何参数
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_lock,
                        "Return() 中补充锁定 耗时 ");

                    // 读入读者记录
                    DateTime start_time_read_reader = DateTime.Now;

                    if (readerdom == null
                        && string.IsNullOrEmpty(strReaderBarcode) == false)
                    {
                        LibraryServerResult result1 = GetReaderRecord(
                    sessioninfo,
                    strActionName,
                    time_lines,
                    strAction != "inventory",
                    ref strReaderBarcode,
                    ref strIdcardNumber,
                    ref strLibraryCode,
                    out bReaderDbInCirculation,
                    out readerdom,
                    out strOutputReaderRecPath,
                    out reader_timestamp);
                        if (result1.Value == 0)
                        {
                        }
                        else
                        {
                            return result1;
                        }

                        // 记忆修改前的读者记录
                        strOldReaderXml = readerdom.OuterXml;

                        if (String.IsNullOrEmpty(strIdcardNumber) == false
                            || string.IsNullOrEmpty(strReaderBarcode) == true /* 2013/5/23 */)
                        {
                            // 获得读者证条码号
                            strReaderBarcode = DomUtil.GetElementText(readerdom.DocumentElement,
                                "barcode");
                        }
                        strOutputReaderBarcodeParam = DomUtil.GetElementText(readerdom.DocumentElement,
                                "barcode");

                        string strReaderDbName = ResPath.GetDbName(strOutputReaderRecPath);

                        // 检查当前用户管辖的读者范围
                        // return:
                        //      -1  出错
                        //      0   允许继续访问
                        //      1   权限限制,不允许继续访问。strError 中有说明原因的文字
                        nRet = CheckReaderRange(sessioninfo,
                            readerdom,
                            strReaderDbName,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                        if (nRet == 1)
                        {
                            // strError = "当前用户 '" + sessioninfo.UserID + "' 的存取权限或好友关系禁止操作读者(证条码号为 " + strReaderBarcode + ")。具体原因:" + strError;
                            result.Value = -1;
                            result.ErrorInfo = strError;
                            result.ErrorCode = ErrorCode.AccessDenied;
                            return result;
                        }
                    }

#if NO
                    string strReaderXml = "";
                    byte[] reader_timestamp = null;
                    if (string.IsNullOrEmpty(strReaderBarcode) == false)
                    {
                        nRet = this.TryGetReaderRecXml(
                            // sessioninfo.Channels,
                            channel,
                            strReaderBarcode,
                            sessioninfo.LibraryCodeList,    // TODO: 测试个人书斋情况
                            out strReaderXml,
                            out strOutputReaderRecPath,
                            out reader_timestamp,
                            out strError);
                        if (nRet == 0)
                        {
                            // 如果是身份证号,则试探检索“身份证号”途径
                            if (StringUtil.IsIdcardNumber(strReaderBarcode) == true)
                            {
                                strIdcardNumber = strReaderBarcode;
                                strReaderBarcode = "";

                                // 通过特定检索途径获得读者记录
                                // return:
                                //      -1  error
                                //      0   not found
                                //      1   命中1条
                                //      >1  命中多于1条
                                nRet = this.GetReaderRecXmlByFrom(
                                    // sessioninfo.Channels,
                                    channel,
                                    strIdcardNumber,
                                    "身份证号",
                                    out strReaderXml,
                                    out strOutputReaderRecPath,
                                    out reader_timestamp,
                                    out strError);
                                if (nRet == -1)
                                {
                                    // text-level: 内部错误
                                    strError = "用身份证号 '" + strIdcardNumber + "' 读入读者记录时发生错误: " + strError;
                                    goto ERROR1;
                                }
                                if (nRet == 0)
                                {
                                    result.Value = -1;
                                    // text-level: 用户提示
                                    result.ErrorInfo = string.Format(this.GetString("身份证号s不存在"),   // "身份证号 '{0}' 不存在"
                                        strIdcardNumber);
                                    result.ErrorCode = ErrorCode.IdcardNumberNotFound;
                                    return result;
                                }
                                if (nRet > 1)
                                {
                                    // text-level: 用户提示
                                    result.Value = -1;
                                    result.ErrorInfo = "用身份证号 '" + strIdcardNumber + "' 检索读者记录命中 " + nRet.ToString() + " 条,因此无法用身份证号来进行借还操作。请改用证条码号来进行借还操作。";
                                    result.ErrorCode = ErrorCode.IdcardNumberDup;
                                    return result;
                                }
                                Debug.Assert(nRet == 1, "");
                                goto SKIP0;
                            }
                            else
                            {
                                // 2013/5/24
                                // 如果需要,从读者证号等辅助途径进行检索
                                foreach (string strFrom in this.PatronAdditionalFroms)
                                {
                                    nRet = this.GetReaderRecXmlByFrom(
                                        // sessioninfo.Channels,
                                        channel,
                                        null,
                                        strReaderBarcode,
                                        strFrom,
                                    out strReaderXml,
                                    out strOutputReaderRecPath,
                                    out reader_timestamp,
                                        out strError);
                                    if (nRet == -1)
                                    {
                                        // text-level: 内部错误
                                        strError = "用" + strFrom + " '" + strReaderBarcode + "' 读入读者记录时发生错误: " + strError;
                                        goto ERROR1;
                                    }
                                    if (nRet == 0)
                                        continue;
                                    if (nRet > 1)
                                    {
                                        // text-level: 用户提示
                                        result.Value = -1;
                                        result.ErrorInfo = "用" + strFrom + " '" + strReaderBarcode + "' 检索读者记录命中 " + nRet.ToString() + " 条,因此无法用" + strFrom + "来进行借还操作。请改用证条码号来进行借还操作。";
                                        result.ErrorCode = ErrorCode.IdcardNumberDup;
                                        return result;
                                    }

                                    Debug.Assert(nRet == 1, "");

                                    strIdcardNumber = "";
                                    strReaderBarcode = "";

                                    goto SKIP0;
                                }
                            }

                            result.Value = -1;
                            result.ErrorInfo = "读者证条码号 '" + strReaderBarcode + "' 不存在";
                            result.ErrorCode = ErrorCode.ReaderBarcodeNotFound;
                            return result;
                        }
                        if (nRet == -1)
                        {
                            strError = "读入读者记录时发生错误: " + strError;
                            goto ERROR1;
                        }

                        // 2008/6/17
                        if (nRet > 1)
                        {
                            strError = "读入读者记录时,发现读者证条码号 '" + strReaderBarcode + "' 命中 " + nRet.ToString() + " 条,这是一个严重错误,请系统管理员尽快处理。";
                            goto ERROR1;
                        }
                    }
#endif
                SKIP0:

                    if (strAction == "inventory")
                    {
                        nRet = DoInventory(
                            sessioninfo,
                            strAccessParameters,
                            itemdom,
                            strOutputItemRecPath,
                            strBatchNo,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;

                        strOutputItemXml = itemdom.OuterXml;

                        strOutputReaderXml = strOldReaderXml;   // strReaderXml;
                        nRet = RemovePassword(ref strOutputReaderXml, out strError);
                        if (nRet == -1)
                        {
                            strError = "从读者记录中去除 password 阶段出错: " + strError;
                            goto ERROR1;
                        }

                        strBiblioRecID = DomUtil.GetElementText(itemdom.DocumentElement, "parent"); //

                        SetReturnInfo(ref return_info, itemdom);
                        goto END3;
                    }

#if NO

                    // 看看读者记录所从属的数据库,是否在参与流通的读者库之列
                    // 2008/6/4
                    bool bReaderDbInCirculation = true;
                    string strReaderDbName = "";
                    if (strAction != "inventory"
                        && String.IsNullOrEmpty(strOutputReaderRecPath) == false)
                    {
                        if (this.TestMode == true || sessioninfo.TestMode == true)
                        {
                            // 检查评估模式
                            // return:
                            //      -1  检查过程出错
                            //      0   可以通过
                            //      1   不允许通过
                            nRet = CheckTestModePath(strOutputReaderRecPath,
                                out strError);
                            if (nRet != 0)
                            {
                                strError = strActionName + "操作被拒绝: " + strError;
                                goto ERROR1;
                            }
                        }

                        strReaderDbName = ResPath.GetDbName(strOutputReaderRecPath);
                        if (this.IsReaderDbName(strReaderDbName,
                            out bReaderDbInCirculation,
                            out strLibraryCode) == false)
                        {
                            strError = "读者记录路径 '" + strOutputReaderRecPath + "' 中的数据库名 '" + strReaderDbName + "' 居然不在定义的读者库之列。";
                            goto ERROR1;
                        }
                    }

                    // TODO: 即便不是参与流通的数据库,也让还书?

                    // 检查当前操作者是否管辖这个读者库
                    // 观察一个读者记录路径,看看是不是在当前用户管辖的读者库范围内?
                    if (strAction != "inventory"
                        && this.IsCurrentChangeableReaderPath(strOutputReaderRecPath,
            sessioninfo.LibraryCodeList) == false)
                    {
                        strError = "读者记录路径 '" + strOutputReaderRecPath + "' 的读者库不在当前用户管辖范围内";
                        goto ERROR1;
                    }

                    XmlDocument readerdom = null;
                    nRet = LibraryApplication.LoadToDom(strReaderXml,
                        out readerdom,
                        out strError);
                    if (nRet == -1)
                    {
                        strError = "装载读者记录进入XML DOM时发生错误: " + strError;
                        goto ERROR1;
                    }
                    WriteTimeUsed(
                        time_lines,
                        start_time_read_reader,
                        "Return() 中读取读者记录 耗时 ");


                    // string strReaderDbName = ResPath.GetDbName(strOutputReaderRecPath);

                    // 观察读者记录是否在操作范围内
                    // return:
                    //      -1  出错
                    //      0   允许继续访问
                    //      1   权限限制,不允许继续访问。strError 中有说明原因的文字
                    nRet = CheckReaderRange(sessioninfo,
                        readerdom,
                        strReaderDbName,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;
                    if (nRet == 1)
                    {
                        // strError = "当前用户 '" + sessioninfo.UserID + "' 的存取权限禁止操作读者(证条码号为 " + strReaderBarcode + ")。具体原因:" + strError;
                        result.Value = -1;
                        result.ErrorInfo = strError;
                        result.ErrorCode = ErrorCode.AccessDenied;
                        return result;
                    }
#endif

                    DateTime start_time_process = DateTime.Now;

                    string strReaderName = readerdom == null ? "" :
                        DomUtil.GetElementText(readerdom.DocumentElement, "name");

                    if (bDelayVerifyReaderBarcode == true)
                    {
                        // 顺便验证一下身份证号
                        if (string.IsNullOrEmpty(strIdcardNumber) == false)
                        {
                            Debug.Assert(string.IsNullOrEmpty(strIdcardNumber) == false, "");

                            string strTempIdcardNumber = DomUtil.GetElementText(readerdom.DocumentElement, "idCardNumber");
                            if (strIdcardNumber != strTempIdcardNumber)
                            {
                                strError = "册记录表明,册 " + strItemBarcodeParam + " 实际被读者(证条码号) " + strOutputReaderBarcode + " 所借阅,此读者的身份证号为 " + strTempIdcardNumber + ",不是您当前输入的(验证用)身份证号 " + strIdcardNumber + "。还书操作被放弃。";
                                goto ERROR1;
                            }
                        }
                        // 重新获取读者证条码号
                        strReaderBarcode = DomUtil.GetElementText(readerdom.DocumentElement, "barcode");
                        strOutputReaderBarcodeParam = strReaderBarcode; // 为了返回值

                        {
                            if (strOutputReaderBarcode != strReaderBarcode)
                            {
                                strError = "册记录表明,册 " + strItemBarcodeParam + " 实际被读者 " + strOutputReaderBarcode + " 所借阅,而不是您当前指定的读者(证条码号) " + strReaderBarcodeParam + "。还书操作被放弃。";
                                goto ERROR1;
                            }
                        }
                    }

                    XmlDocument domOperLog = new XmlDocument();
                    domOperLog.LoadXml("<root />");
                    DomUtil.SetElementText(domOperLog.DocumentElement,
                        "libraryCode",
                        strLibraryCode);    // 读者所在的馆代码
                    DomUtil.SetElementText(domOperLog.DocumentElement, "operation", "return");
                    DomUtil.SetElementText(domOperLog.DocumentElement, "action", strAction);

                    // 从读者信息中, 找到读者类型
                    string strReaderType = DomUtil.GetElementText(readerdom.DocumentElement,
                        "readerType");

                    // 证状态 2009/1/29
                    string strReaderState = DomUtil.GetElementText(readerdom.DocumentElement,
                        "state");

                    string strOperTime = this.Clock.GetClock();
                    string strWarning = "";

                    // 处理册记录
                    string strOverdueString = "";
                    string strLostComment = "";

                    if (strAction != "read")
                    {
                        // 获得相关日历
                        Calendar calendar = null;
                        // return:
                        //      -1  出错
                        //      0   没有找到日历
                        //      1   找到日历
                        nRet = GetReaderCalendar(strReaderType,
                            strLibraryCode,
                            out calendar,
                            out strError);
                        if (nRet == -1 || nRet == 0)
                            goto ERROR1;

                        // return:
                        //      -1  出错
                        //      0   正常
                        //      1   超期还书或者丢失处理的情况
                        nRet = DoReturnItemXml(
                            strAction,
                            sessioninfo,    // sessioninfo.Account,
                            calendar,
                            strReaderType,
                            strLibraryCode,
                            strAccessParameters,
                            readerdom,  // 为了调用GetLost()脚本函数
                            ref itemdom,
                            bForce,
                            bItemBarcodeDup,  // 若条码号足以定位,则不记载实体记录路径
                            strOutputItemRecPath,
                            sessioninfo.UserID, // 还书操作者
                            strOperTime,
                            domOperLog,
                            out strOverdueString,
                            out strLostComment,
                            out return_info,
                            out strWarning,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                        if (string.IsNullOrEmpty(strWarning) == false)
                        {
                            if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                                result.ErrorInfo += "\r\n";
                            result.ErrorInfo += strWarning;
                            result.Value = 1;
                        }
                    }
                    else
                        nRet = 0;

                    string strItemBarcode = "";
                    if (itemdom != null)
                    {
                        strItemBarcode = DomUtil.GetElementText(itemdom.DocumentElement, "barcode");

                        // 创建日志记录
                        DomUtil.SetElementText(domOperLog.DocumentElement, "itemBarcode",
                            string.IsNullOrEmpty(strItemBarcode) == false ? strItemBarcode : strItemBarcodeParam);
                        /* 后面会写入<overdues>
                        if (nRet == 1)
                        {
                            // 如果有超期和或丢失处理信息
                            DomUtil.SetElementText(domOperLog.DocumentElement, "overdueString",
                            strOverdueString);
                        }
                         * */
                    }

                    bool bOverdue = false;
                    string strOverdueInfo = "";

                    if (nRet == 1)
                    {
                        bOverdue = true;
                        strOverdueInfo = strError;
                    }

                    // 处理读者记录
                    // string strNewReaderXml = "";
                    string strDeletedBorrowFrag = "";
                    if (strAction != "read")
                    {
                        nRet = DoReturnReaderXml(
                            strLibraryCode,
                            ref readerdom,
                            strItemBarcodeParam,
                            strItemBarcode,
                            strOverdueString.StartsWith("!") ? "" : strOverdueString,
                            sessioninfo.UserID, // 还书操作者
                            strOperTime,
                            sessioninfo.ClientAddress,  // 前端触发
                            out strDeletedBorrowFrag,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                    }

                    // 创建日志记录
                    Debug.Assert(string.IsNullOrEmpty(strReaderBarcode) == false, "");
                    DomUtil.SetElementText(domOperLog.DocumentElement, "readerBarcode",
                        strReaderBarcode);
                    DomUtil.SetElementText(domOperLog.DocumentElement, "operator",
                        sessioninfo.UserID);
                    DomUtil.SetElementText(domOperLog.DocumentElement, "operTime",
                        strOperTime);

                    if (strAction == "read")
                    {
                        string strBiblioRecPath = "";
                        string strVolume = "";
                        if (IsBiblioRecPath(strItemBarcodeParam) == false)
                        {
                            strBiblioRecID = DomUtil.GetElementText(itemdom.DocumentElement, "parent"); //

                            string strBiblioDbName = "";
                            // 根据实体库名, 找到对应的书目库名
                            // return:
                            //      -1  出错
                            //      0   没有找到
                            //      1   找到
                            nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                                out strBiblioDbName,
                                out strError);
                            if (nRet == -1)
                                goto ERROR1;

                            if (string.IsNullOrEmpty(strBiblioDbName) == false)
                            {
                                strBiblioRecPath = strBiblioDbName + "/" + strBiblioRecID;
                                DomUtil.SetElementText(domOperLog.DocumentElement, "biblioRecPath",
                                    strBiblioRecPath);
                            }

                            strVolume = DomUtil.GetElementText(itemdom.DocumentElement, "volume");
                            if (string.IsNullOrEmpty(strVolume) == false)
                                DomUtil.SetElementText(domOperLog.DocumentElement, "no", strVolume);
                        }
                        else
                            strBiblioRecPath = strItemBarcodeParam.Substring("@biblioRecPath:".Length);

                        // 探测 mongodb 库中是否已经存在这样的事项
                        IEnumerable<ChargingOperItem> collection = this.ChargingOperDatabase.Exists(
                            strReaderBarcode,
                            "", // string.IsNullOrEmpty(strItemBarcode) == false ? strItemBarcode : strItemBarcodeParam,
                            strBiblioRecPath,
                            strVolume,
                            new DateTime(0),    // DateTime.Now - new TimeSpan(0, 5, 0),
                            new DateTime(0),
                            "read");
                        if (collection != null)
                        {
                            DateTime existingOperTime = new DateTime(0);

                            foreach (ChargingOperItem item in collection)
                            {
                                existingOperTime = item.OperTime;
                                break;
                            }

                            if (existingOperTime != new DateTime(0))
                            {
                                strError = "读者 '" + strReaderBarcode + "' 早先 (" + existingOperTime.ToString("G") + ") 已经读过 [" + GetReadCaption(strBiblioRecPath, strVolume) + "] 了,本次操作被拒绝";
                                goto ERROR1;
                            }
                        }

                        DomUtil.SetElementText(domOperLog.DocumentElement,
                            "biblioRecPath", strBiblioRecPath);
                        goto WRITE_OPERLOG;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_process,
                        "Return() 中进行各种数据处理 耗时 ");

                    // 原来创建输出xml或html格式的代码在此

                    DateTime start_time_reservation_check = DateTime.Now;

                    if (StringUtil.IsInList("simulate_reservation_arrive", strStyle))
                    {
                        // 模拟预约情况
                        nRet = SimulateReservation(
                            ref readerdom,
                            ref itemdom,
                            out strError);
                        if (nRet == -1)
                            goto ERROR1;
                    }

                    // 察看本册预约情况, 并进行初步处理
                    // 如果为丢失处理,需要通知等待者,书已经丢失了,不用再等待
                    // return:
                    //      -1  error
                    //      0   没有修改
                    //      1   进行过修改
                    nRet = DoItemReturnReservationCheck(
                        (strAction == "lost") ? true : false,
                        ref itemdom,
                        out strReservationReaderBarcode,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;

                    if (nRet == 1 && return_info != null)
                    {
                        // <location>元素中可能增加了 #reservation 部分
                        return_info.Location = DomUtil.GetElementText(itemdom.DocumentElement,
                            "location");
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_reservation_check,
                        "Return() 中进行预约检查 耗时 ");

                    // 写回读者、册记录
                    // byte[] timestamp = null;
                    byte[] output_timestamp = null;
                    string strOutputPath = "";

                    /*
                    Channel channel = sessioninfo.Channels.GetChannel(this.WsUrl);
                    if (channel == null)
                    {
                        strError = "get channel error";
                        goto ERROR1;
                    }
                     * */
                    DateTime start_time_write_reader = DateTime.Now;

                    lRet = channel.DoSaveTextRes(strOutputReaderRecPath,
                        readerdom.OuterXml,
                        false,
                        "content",  // ,ignorechecktimestamp
                        reader_timestamp,
                        out output_timestamp,
                        out strOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        // 2015/9/2
                        this.WriteErrorLog("Return() 写入读者记录 '" + strOutputReaderRecPath + "' 时出错: " + strError);

                        if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch)
                        {
                            nRedoCount++;
                            if (nRedoCount > 10)
                            {
                                strError = "Return() 写回读者记录的时候,遇到时间戳冲突,并因此重试超过 10 次未能成功, 只好放弃重试...";
                                this.WriteErrorLog(strError);
                                goto ERROR1;
                            }
#if DEBUG_LOCK_READER
                            this.WriteErrorLog("Return goto REDO_RETURN 2 nRedoCount=" + nRedoCount + "");
#endif
                            goto REDO_RETURN;
                        }

                        goto ERROR1;
                    }

                    reader_timestamp = output_timestamp;

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_reader,
                        "Return() 中写回读者记录 耗时 ");

                    DateTime start_time_write_item = DateTime.Now;

                    lRet = channel.DoSaveTextRes(strOutputItemRecPath,
                        itemdom.OuterXml,
                        false,
                        "content,ignorechecktimestamp",
                        item_timestamp,
                        out output_timestamp,
                        out strOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        // 2015/9/2
                        this.WriteErrorLog("Return() 写入册记录 '" + strOutputItemRecPath + "' 时出错: " + strError);

                        // 要Undo刚才对读者记录的写入
                        string strError1 = "";
                        lRet = channel.DoSaveTextRes(strOutputReaderRecPath,
                            strOldReaderXml,    // strReaderXml,
                            false,
                            "content,ignorechecktimestamp",
                            reader_timestamp,
                            out output_timestamp,
                            out strOutputPath,
                            out strError1);
                        if (lRet == -1)
                        {
                            // 2015/9/2
                            this.WriteErrorLog("Return() 写入读者记录 '" + strOutputReaderRecPath + "' 时出错: " + strError);

                            if (channel.ErrorCode == ChannelErrorCode.TimestampMismatch)
                            {
                                // 读者记录Undo的时候, 发现时间戳冲突了
                                // 这时需要读出现存记录, 试图增加回刚删除的<borrows><borrow>元素
                                // return:
                                //      -1  error
                                //      0   没有必要Undo
                                //      1   Undo成功
                                nRet = UndoReturnReaderRecord(
                                    channel,
                                    strOutputReaderRecPath,
                                    strReaderBarcode,
                                    strItemBarcodeParam,
                                    strDeletedBorrowFrag,
                                    strOverdueString.StartsWith("!") ? "" : strOverdueString,
                                    out strError);
                                if (nRet == -1)
                                {
                                    strError = "Return() Undo读者记录 '" + strOutputReaderRecPath + "' (读者证条码号为 '" + strReaderBarcode + "' 读者姓名为 '" + strReaderName + "') 还书册条码号 '" + strItemBarcodeParam + "' 的修改时,发生错误,无法Undo: " + strError;
                                    this.WriteErrorLog(strError);
                                    goto ERROR1;
                                }

                                // 成功
                                // 2015/9/2 增加下列防止死循环的语句
                                nRedoCount++;
                                if (nRedoCount > 10)
                                {
                                    strError = "Return() Undo 读者记录(1)成功,试图重试 Return 时,发现先前重试已经超过 10 次,只好不重试了,做出错返回...";
                                    this.WriteErrorLog(strError);
                                    goto ERROR1;
                                }
#if DEBUG_LOCK_READER
                                this.WriteErrorLog("Return goto REDO_RETURN 3 nRedoCount=" + nRedoCount + "");
#endif
                                goto REDO_RETURN;
                            }


                            // 以下为 不是时间戳冲突的其他错误情形
                            strError = "Return() Undo读者记录 '" + strOutputReaderRecPath + "' (读者证条码号为 '" + strReaderBarcode + "' 读者姓名为 '" + strReaderName + "') 还书册条码号 '" + strItemBarcodeParam + "' 的修改时,发生错误,无法Undo: " + strError;
                            // strError = strError + ", 并且Undo写回旧读者记录也失败: " + strError1;
                            this.WriteErrorLog(strError);
                            goto ERROR1;
                        }

                        // 以下为Undo成功的情形
                        // 2015/9/2 增加下列防止死循环的语句
                        nRedoCount++;
                        if (nRedoCount > 10)
                        {
                            strError = "Return() Undo 读者记录(2)成功,试图重试 Return 时,发现先前重试已经超过 10 次,只好不重试了,做出错返回...";
                            this.WriteErrorLog(strError);
                            goto ERROR1;
                        }
#if DEBUG_LOCK_READER
                        this.WriteErrorLog("Return goto REDO_RETURN 4 nRedoCount=" + nRedoCount + "");
#endif
                        goto REDO_RETURN;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_item,
                        "Return() 中写回册记录 耗时 ");

                WRITE_OPERLOG:
                    DateTime start_time_write_operlog = DateTime.Now;

                    // 写入日志

                    // overdue信息
                    if (String.IsNullOrEmpty(strOverdueString) == false)
                    {
                        DomUtil.SetElementText(domOperLog.DocumentElement,
                            "overdues",
                            strOverdueString.StartsWith("!") ? strOverdueString.Substring(1) : strOverdueString);
                    }

                    // 确认册路径
                    if (string.IsNullOrEmpty(strConfirmItemRecPath) == false)
                    {
                        DomUtil.SetElementText(domOperLog.DocumentElement,
                            "confirmItemRecPath", strConfirmItemRecPath);
                    }

                    if (string.IsNullOrEmpty(strIdcardNumber) == false)
                    {
                        // 表明是使用身份证号来完成还书操作的
                        DomUtil.SetElementText(domOperLog.DocumentElement,
        "idcardNumber", strIdcardNumber);
                    }

                    // 写入读者记录
                    XmlNode node = DomUtil.SetElementText(domOperLog.DocumentElement,
                        "readerRecord", readerdom.OuterXml);
                    DomUtil.SetAttr(node, "recPath", strOutputReaderRecPath);

                    // 写入册记录
                    if (itemdom != null)
                    {
                        node = DomUtil.SetElementText(domOperLog.DocumentElement,
                            "itemRecord", itemdom.OuterXml);
                        DomUtil.SetAttr(node, "recPath", strOutputItemRecPath);
                    }

                    if (strLostComment != "")
                    {
                        DomUtil.SetElementText(domOperLog.DocumentElement,
                            "lostComment",
                            strLostComment);
                    }

                    nRet = this.OperLog.WriteOperLog(domOperLog,
                        sessioninfo.ClientAddress,
                        start_time,
                        out strOperLogUID,
                        out strError);
                    if (nRet == -1)
                    {
                        strError = "Return() API 写入日志时发生错误: " + strError;
                        goto ERROR1;
                    }

                    WriteTimeUsed(
                        time_lines,
                        start_time_write_operlog,
                        "Return() 中写操作日志 耗时 ");

                    DateTime start_time_write_statis = DateTime.Now;

                    // 写入统计指标
#if NO
                    if (this.m_strLastReaderBarcode != strReaderBarcode)
                    {
                        if (this.Statis != null)
                            this.Statis.IncreaseEntryValue(strLibraryCode,
                            "出纳",
                            "读者数",
                            1);
                        this.m_strLastReaderBarcode = strReaderBarcode;
                    }
#endif
                    if (this.Garden != null)
                        this.Garden.Activate(strReaderBarcode,
                            strLibraryCode);

                    if (this.Statis != null)
                        this.Statis.IncreaseEntryValue(strLibraryCode,
                        "出纳",
                        strAction == "read" ? "读过册" : "还册",
                        1);

                    if (strAction == "lost")
                    {
                        if (this.Statis != null)
                            this.Statis.IncreaseEntryValue(strLibraryCode,
                            "出纳",
                            "声明丢失",
                            1);
                    }
                    WriteTimeUsed(
                        time_lines,
                        start_time_write_statis,
                        "Return() 中写统计指标 耗时 ");

                    result.ErrorInfo = strActionName + "操作成功。" + result.ErrorInfo;  // 2013/11/13

                    if (bReaderDbInCirculation == false)
                    {
                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";
                        result.ErrorInfo += "读者证条码号 '" + strReaderBarcode + "' 所在的读者记录 '" + strOutputReaderRecPath + "' 其数据库 '" + StringUtil.GetDbName(strOutputReaderRecPath) + "' 属于未参与流通的读者库。";
                        result.Value = 1;
                    }

                    if (bItemDbInCirculation == false)
                    {
                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";
                        result.ErrorInfo += "册条码号 '" + strItemBarcodeParam + "' 所在的册记录 '" + strOutputItemRecPath + "' 其数据库 '" + StringUtil.GetDbName(strOutputReaderRecPath) + "' 属于未参与流通的实体库。";
                        result.Value = 1;
                    }

                    if (bOverdue == true)
                    {
                        if (this.Statis != null)
                            this.Statis.IncreaseEntryValue(strLibraryCode,
                            "出纳",
                            "还超期册",
                            1);

                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";

                        result.ErrorInfo += strOverdueInfo;
                        result.ErrorCode = ErrorCode.Overdue;
                        result.Value = 1;
                    }

                    if (bItemBarcodeDup == true)
                    {
                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";
                        result.ErrorInfo += "***警告***: " + strActionName + "操作过程中发现下列册记录它们的册条码号发生了重复: " + strDupBarcodeList + "。请通知系统管理员纠正此数据错误。";
                        result.Value = 1;
                    }

                    if (String.IsNullOrEmpty(strReservationReaderBarcode) == false // 2009/10/19 changed  //bFoundReservation == true
                        && strAction != "lost")
                    {
                        // 为了提示信息中出现读者姓名,这里特以获取读者姓名
                        string strReservationReaderName = "";

                        if (strReaderBarcode == strReservationReaderBarcode)
                            strReservationReaderName = strReaderName;
                        else
                        {
                            DateTime start_time_getname = DateTime.Now;

                            // 获得读者姓名
                            // return:
                            //      -1  error
                            //      0   not found
                            //      1   found
                            nRet = GetReaderName(
                                sessioninfo,
                                strReservationReaderBarcode,
                                out strReservationReaderName,
                                out strError);

                            WriteTimeUsed(
                                time_lines,
                                start_time_getname,
                                "Return() 中获得预约者的姓名 耗时 ");
                        }

                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";
                        result.ErrorInfo += "因本册图书已被读者 " + strReservationReaderBarcode + " "
                            + strReservationReaderName + " 预约,请放入预约保留架。";    // 2009/10/10 changed
                        result.Value = 1;
                    }

                    // 读者证状态不为空情况下的提示
                    // 2008/1/29
                    if (String.IsNullOrEmpty(strReaderState) == false)
                    {
                        if (String.IsNullOrEmpty(result.ErrorInfo) == false)
                            result.ErrorInfo += "\r\n";
                        result.ErrorInfo += "***警告***: 当前读者证状态为: " + strReaderState + "。请注意进行后续处理。";
                        result.Value = 1;
                    }

                    if (itemdom != null)
                        strOutputItemXml = itemdom.OuterXml;

                    // strOutputReaderXml 将用于构造读者记录返回格式
                    DomUtil.DeleteElement(readerdom.DocumentElement, "password");
                    strOutputReaderXml = readerdom.OuterXml;

                    if (itemdom != null)
                    {
                        strBiblioRecID = DomUtil.GetElementText(itemdom.DocumentElement, "parent"); //
                    }
                } // 册记录锁定范围结束
                finally
                {
                    // 册记录解锁
                    if (bEntityLocked == true)
                        this.EntityLocks.UnlockForWrite(strItemBarcodeParam);
                }

            } // 读者记录锁定范围结束
            finally
            {
                if (bReaderLocked == true)
                {
                    this.ReaderLocks.UnlockForWrite(strLockReaderBarcode);
#if DEBUG_LOCK_READER
                    this.WriteErrorLog("Return 结束为读者加写锁 '" + strLockReaderBarcode + "'");
#endif
                }
            }

            // TODO: 将来可以改进为,丢失时发现有人预约,也通知,不过通知的内容是要读者不再等待了。
            if (String.IsNullOrEmpty(strReservationReaderBarcode) == false
                && strAction != "lost")
            {
                DateTime start_time_1 = DateTime.Now;

                List<string> DeletedNotifyRecPaths = null;  // 被删除的通知记录。不用。
                // 通知预约到书的操作
                // 出于对读者库加锁方面的便利考虑, 单独做了此函数
                // return:
                //      -1  error
                //      0   没有找到<request>元素
                nRet = DoReservationNotify(
                    null,
                    channel,
                    strReservationReaderBarcode,
                    true,   // 需要函数内加锁
                    strItemBarcodeParam,
                    false,  // 不在大架
                    false,  // 不需要再修改当前册记录,因为前面已经修改过了
                    out DeletedNotifyRecPaths,
                    out strError);
                if (nRet == -1)
                {
                    strError = "还书操作已经成功, 但是预约到书通知功能失败, 原因: " + strError;
                    goto ERROR1;
                }

                WriteTimeUsed(
time_lines,
start_time_1,
"Return() 中预约到书通知 耗时 ");

                /* 前面已经通知过了
                result.Value = 1;
                result.ErrorCode = ErrorCode.ReturnReservation;
                if (result.ErrorInfo != "")
                    result.ErrorInfo += "\r\n";

                result.ErrorInfo += "还书操作成功。因此册图书被读者 " + strReservationReaderBarcode + " 预约,请放入预约保留架。";
                 * */

                // 最好超期和保留两种状态码可以并存?
            }

        END3:
            // 输出数据
            // 把输出数据部分放在读者锁以外范围,是为了尽量减少锁定的时间,提高并发运行效率
            DateTime output_start_time = DateTime.Now;

            if (String.IsNullOrEmpty(strOutputReaderXml) == false
    && StringUtil.IsInList("reader", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                nRet = BuildReaderResults(
sessioninfo,
null,
strOutputReaderXml,
strReaderFormatList,
strLibraryCode,  // calendar/advancexml/html 时需要
null,    // recpaths 时需要
strOutputReaderRecPath,   // recpaths 时需要
null,    // timestamp 时需要
OperType.Return,
                            null,
                            strItemBarcodeParam,
ref reader_records,
out strError);
                if (nRet == -1)
                {
                    strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                    goto ERROR1;
                }

                WriteTimeUsed(
time_lines,
start_time_1,
"Return() 中返回读者记录(" + strReaderFormatList + ") 耗时 ");
            }

#if NO
            if (String.IsNullOrEmpty(strOutputReaderXml) == false
                && StringUtil.IsInList("reader", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                string[] reader_formats = strReaderFormatList.Split(new char[] { ',' });
                reader_records = new string[reader_formats.Length];

                for (int i = 0; i < reader_formats.Length; i++)
                {
                    string strReaderFormat = reader_formats[i];

                    // 将读者记录数据从XML格式转换为HTML格式
                    // if (String.Compare(strReaderFormat, "html", true) == 0)
                    if (IsResultType(strReaderFormat, "html") == true)
                    {
                        string strReaderRecord = "";
                        nRet = this.ConvertReaderXmlToHtml(
                            sessioninfo,
                            this.CfgDir + "\\readerxml2html.cs",
                            this.CfgDir + "\\readerxml2html.cs.ref",
                            strLibraryCode,
                            strOutputReaderXml,
                            strOutputReaderRecPath, // 2009/10/18
                            OperType.Return,
                            null,
                            strItemBarcodeParam,
                            strReaderFormat,
                            out strReaderRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        reader_records[i] = strReaderRecord;
                    }
                    // 将读者记录数据从XML格式转换为text格式
                    // else if (String.Compare(strReaderFormat, "text", true) == 0)
                    else if (IsResultType(strReaderFormat, "text") == true)
                    {
                        string strReaderRecord = "";
                        nRet = this.ConvertReaderXmlToHtml(
                            sessioninfo,
                            this.CfgDir + "\\readerxml2text.cs",
                            this.CfgDir + "\\readerxml2text.cs.ref",
                            strLibraryCode,
                            strOutputReaderXml,
                            strOutputReaderRecPath, // 2009/10/18
                            OperType.Return,
                            null,
                            strItemBarcodeParam,
                            strReaderFormat,
                            out strReaderRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        reader_records[i] = strReaderRecord;
                    }
                    // else if (String.Compare(strReaderFormat, "xml", true) == 0)
                    else if (IsResultType(strReaderFormat, "xml") == true)
                    {
                        // reader_records[i] = strOutputReaderXml;
                        string strResultXml = "";
                        nRet = GetItemXml(strOutputReaderXml,
            strReaderFormat,
            out strResultXml,
            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        reader_records[i] = strResultXml;
                    }
                    else if (IsResultType(strReaderFormat, "summary") == true)
                    {
                        // 2013/12/15
                        XmlDocument dom = new XmlDocument();
                        try
                        {
                            dom.LoadXml(strOutputReaderXml);
                        }
                        catch (Exception ex)
                        {
                            strError = "读者 XML 装入 DOM 出错: " + ex.Message;
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        reader_records[i] = DomUtil.GetElementText(dom.DocumentElement, "name");
                    }
                    else
                    {
                        strError = "strReaderFormatList参数出现了不支持的数据格式类型 '" + strReaderFormat + "'";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
    time_lines,
    start_time_1,
    "Return() 中返回读者记录(" + strReaderFormatList + ") 耗时 ");

            } // end if
#endif

            // 2008/5/9
            if (String.IsNullOrEmpty(strOutputItemXml) == false
                && StringUtil.IsInList("item", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;

                string[] item_formats = strItemFormatList.Split(new char[] { ',' });
                item_records = new string[item_formats.Length];

                for (int i = 0; i < item_formats.Length; i++)
                {
                    string strItemFormat = item_formats[i];

                    // 将册记录数据从XML格式转换为HTML格式
                    // if (String.Compare(strItemFormat, "html", true) == 0)
                    if (IsResultType(strItemFormat, "html") == true)
                    {
                        string strItemRecord = "";
                        nRet = this.ConvertItemXmlToHtml(
                            this.CfgDir + "\\itemxml2html.cs",
                            this.CfgDir + "\\itemxml2html.cs.ref",
                            strOutputItemXml,
                            strOutputItemRecPath,   // 2009/10/18
                            out strItemRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        item_records[i] = strItemRecord;
                    }
                    // 将册记录数据从XML格式转换为text格式
                    // else if (String.Compare(strItemFormat, "text", true) == 0)
                    else if (IsResultType(strItemFormat, "text") == true)
                    {
                        string strItemRecord = "";
                        nRet = this.ConvertItemXmlToHtml(
                            this.CfgDir + "\\itemxml2text.cs",
                            this.CfgDir + "\\itemxml2text.cs.ref",
                            strOutputItemXml,
                            strOutputItemRecPath,   // 2009/10/18
                            out strItemRecord,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        item_records[i] = strItemRecord;
                    }
                    // else if (String.Compare(strItemFormat, "xml", true) == 0)
                    else if (IsResultType(strItemFormat, "xml") == true)
                    {
                        // item_records[i] = strOutputItemXml;
                        string strResultXml = "";
                        nRet = GetItemXml(strOutputItemXml,
            strItemFormat,
            out strResultXml,
            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        item_records[i] = strResultXml;
                    }
                    else
                    {
                        strError = "strItemFormatList参数出现了不支持的数据格式类型 '" + strItemFormat + "'";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
time_lines,
start_time_1,
"Return() 中返回册记录(" + strItemFormatList + ") 耗时 ");

            }

            // 2008/5/9
            if (StringUtil.IsInList("biblio", strStyle) == true)
            {
                DateTime start_time_1 = DateTime.Now;
                string strBiblioRecPath = "";
                if (IsBiblioRecPath(strItemBarcodeParam) == true)
                    strBiblioRecPath = strItemBarcodeParam.Substring("@biblioRecPath:".Length);

                if (string.IsNullOrEmpty(strBiblioRecPath) == true)
                {
                    if (String.IsNullOrEmpty(strBiblioRecID) == true)
                    {
                        strError = "册记录XML中<parent>元素缺乏或者值为空, 因此无法定位种记录ID";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }

                    string strItemDbName = ResPath.GetDbName(strOutputItemRecPath);

                    string strBiblioDbName = "";
                    // 根据实体库名, 找到对应的书目库名
                    // return:
                    //      -1  出错
                    //      0   没有找到
                    //      1   找到
                    nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                        out strBiblioDbName,
                        out strError);
                    if (nRet == -1)
                        goto ERROR1;
                    if (nRet == 0)
                    {
                        strError = "实体库名 '" + strItemDbName + "' 没有找到对应的书目库名";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }

                    strBiblioRecPath = strBiblioDbName + "/" + strBiblioRecID;
                }

                string[] biblio_formats = strBiblioFormatList.Split(new char[] { ',' });
                biblio_records = new string[biblio_formats.Length];

                string strBiblioXml = "";
                // 至少有html xml text之一,才获取strBiblioXml
                if (StringUtil.IsInList("html", strBiblioFormatList) == true
                    || StringUtil.IsInList("xml", strBiblioFormatList) == true
                    || StringUtil.IsInList("text", strBiblioFormatList) == true)
                {
#if NO
                    RmsChannel channel = sessioninfo.Channels.GetChannel(this.WsUrl);
                    if (channel == null)
                    {
                        strError = "get channel error";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }
#endif

                    string strMetaData = "";
                    byte[] timestamp = null;
                    string strTempOutputPath = "";
                    lRet = channel.GetRes(strBiblioRecPath,
                        out strBiblioXml,
                        out strMetaData,
                        out timestamp,
                        out strTempOutputPath,
                        out strError);
                    if (lRet == -1)
                    {
                        strError = "获得种记录 '" + strBiblioRecPath + "' 时出错: " + strError;
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }
                }

                for (int i = 0; i < biblio_formats.Length; i++)
                {
                    string strBiblioFormat = biblio_formats[i];

                    // 需要从内核映射过来文件
                    string strLocalPath = "";
                    string strBiblio = "";

                    // 将书目记录数据从XML格式转换为HTML格式
                    if (String.Compare(strBiblioFormat, "html", true) == 0)
                    {
                        // TODO: 可以cache
                        nRet = this.MapKernelScriptFile(
                            sessioninfo,
                            StringUtil.GetDbName(strBiblioRecPath),
                            "./cfgs/loan_biblio.fltx",
                            out strLocalPath,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }

                        // 将种记录数据从XML格式转换为HTML格式
                        string strFilterFileName = strLocalPath;    // app.CfgDir + "\\biblio.fltx";

                        if (string.IsNullOrEmpty(strBiblioXml) == false)
                        {
                            nRet = this.ConvertBiblioXmlToHtml(
                                strFilterFileName,
                                strBiblioXml,
                                    null,
                                strBiblioRecPath,
                                out strBiblio,
                                out strError);
                            if (nRet == -1)
                            {
                                strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                                goto ERROR1;
                            }

                        }
                        else
                            strBiblio = "";

                        biblio_records[i] = strBiblio;
                    }
                    // 将册记录数据从XML格式转换为text格式
                    else if (String.Compare(strBiblioFormat, "text", true) == 0)
                    {
                        // TODO: 可以cache
                        nRet = this.MapKernelScriptFile(
                            sessioninfo,
                            StringUtil.GetDbName(strBiblioRecPath),
                            "./cfgs/loan_biblio_text.fltx",
                            out strLocalPath,
                            out strError);
                        if (nRet == -1)
                        {
                            strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                            goto ERROR1;
                        }
                        // 将种记录数据从XML格式转换为TEXT格式
                        string strFilterFileName = strLocalPath;    // app.CfgDir + "\\biblio.fltx";
                        if (string.IsNullOrEmpty(strBiblioXml) == false)
                        {
                            nRet = this.ConvertBiblioXmlToHtml(
                                strFilterFileName,
                                strBiblioXml,
                                    null,
                                strBiblioRecPath,
                                out strBiblio,
                                out strError);
                            if (nRet == -1)
                            {
                                strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                                goto ERROR1;
                            }

                        }
                        else
                            strBiblio = "";

                        biblio_records[i] = strBiblio;
                    }
                    else if (String.Compare(strBiblioFormat, "xml", true) == 0)
                    {
                        biblio_records[i] = strBiblioXml;
                    }
                    else if (String.Compare(strBiblioFormat, "recpath", true) == 0)
                    {
                        biblio_records[i] = strBiblioRecPath;
                    }
                    else if (string.IsNullOrEmpty(strBiblioFormat) == true)
                    {
                        biblio_records[i] = "";
                    }
                    else
                    {
                        strError = "strBiblioFormatList参数出现了不支持的数据格式类型 '" + strBiblioFormat + "'";
                        strError = "虽然出现了下列错误,但是还书操作已经成功: " + strError;
                        goto ERROR1;
                    }
                } // end of for

                WriteTimeUsed(
time_lines,
start_time_1,
"Return() 中返回书目记录(" + strBiblioFormatList + ") 耗时 ");
            }

            this.WriteTimeUsed(
                time_lines,
                start_time,
                "Return() 耗时 ");
            // 如果整个时间超过一秒,则需要计入操作日志
            if (DateTime.Now - start_time > new TimeSpan(0, 0, 1))
            {
                WriteLongTimeOperLog(
                    sessioninfo,
                    strAction,
                    start_time,
                    "整个操作耗时超过 1 秒。详情:" + StringUtil.MakePathList(time_lines, ";"),
                    strOperLogUID,
                    out strError);
            }

            if (string.IsNullOrEmpty(strInventoryWarning) == false)
            {
                result.ErrorInfo = strInventoryWarning;
                result.ErrorCode = ErrorCode.Borrowing;
                result.Value = 1;
            }

            // 如果创建输出数据的时间超过一秒,则需要计入操作日志
            if (DateTime.Now - output_start_time > new TimeSpan(0, 0, 1))
            {
                WriteLongTimeOperLog(
                    sessioninfo,
                    strAction,
                    output_start_time,
                    "output 阶段耗时超过 1 秒",
                    strOperLogUID,
                    out strError);
            }

            // result.Value值在前面可能被设置成1
            return result;
        ERROR1:
            result.Value = -1;
            result.ErrorInfo = strError;
            result.ErrorCode = ErrorCode.SystemError;
            return result;
        }