Beispiel #1
0
        // 盘点操作中,设置 ReturnInfo 信息
        static void SetReturnInfo(ref ReturnInfo return_info, XmlDocument item_dom)
        {
#if NO
            return_info.LatestReturnTime = DateTimeUtil.Rfc1123DateTimeStringEx(timeEnd.ToLocalTime());
            return_info.BorrowOperator = strBorrowOperator;
            return_info.ReturnOperator = strReturnOperator;
            return_info.BorrowTime = strBorrowDate;
            return_info.Period = strPeriod;
            return_info.OverdueString = strOverdueString;
            return_info.BorrowCount = 0;
#endif
            return_info.BookType = DomUtil.GetElementText(item_dom.DocumentElement, "bookType");
            // return_info.Location = StringUtil.GetPureLocation(DomUtil.GetElementText(item_dom.DocumentElement, "location"));
            return_info.Location = DomUtil.GetElementText(item_dom.DocumentElement, "location");    // 可能会携带 #reservatoin, 部分
        }
Beispiel #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;
        }
Beispiel #3
0
        // 在册记录中删除借书信息
        // parameters:
        //      strOverdueString    表示超期信息的字符串。borrowOperator属性表示借阅操作者;operator属性表示还书操作者
        // return:
        //      -1  出错
        //      0   正常
        //      1   超期还书或者丢失处理的情况
        int DoReturnItemXml(
            string strAction,
            SessionInfo sessioninfo,
            Calendar calendar,
            string strReaderType,
            string strLibraryCode,
            string strAccessParameters,
            XmlDocument readerdom,
            ref XmlDocument itemdom,
            bool bForce,
            bool bItemBarcodeDup,
            string strItemRecPath,
            string strReturnOperator,
            string strOperTime,
            XmlDocument domOperLog,
            out string strOverdueString,
            out string strLostComment,
            out ReturnInfo return_info,
            out string strWarning,
            out string strError)
        {
            int nRet = 0;
            strError = "";
            strOverdueString = "";
            strLostComment = "";
            strWarning = "";

            Debug.Assert(String.IsNullOrEmpty(strItemRecPath) == false, "");

            string strActionName = GetReturnActionName(strAction);

            return_info = new ReturnInfo();

            LibraryApplication app = this;

            string strItemBarcode = DomUtil.GetElementText(itemdom.DocumentElement, "barcode");
            if (String.IsNullOrEmpty(strItemBarcode) == true)
            {
#if NO
                // text-level: 内部错误
                strError = "册记录中册条码号不能为空";
                return -1;
#endif
                // 如果册条码号为空,则记载 参考ID
                string strRefID = DomUtil.GetElementText(itemdom.DocumentElement,
    "refID");
                if (String.IsNullOrEmpty(strRefID) == true)
                {
                    // text-level: 内部错误
                    strError = "册记录中册条码号和参考ID不应同时为空";
                    return -1;
                }
                strItemBarcode = "@refID:" + strRefID;
            }

            // 馆藏地点
            string strLocation = DomUtil.GetElementText(itemdom.DocumentElement, "location");
            // 去掉#reservation部分
            strLocation = StringUtil.GetPureLocationString(strLocation);

            // 既然一个册记录已经被允许借了,那就无条件要允许还,不管册的馆藏地点是否属于这个读者所在的馆藏地点。如果发现不一致,需要警告
            // 检查册所属的馆藏地点是否合读者所在的馆藏地点吻合
            string strCode = "";
            string strRoom = "";
            {

                // 解析
                ParseCalendarName(strLocation,
            out strCode,
            out strRoom);
                if (strCode != strLibraryCode)
                {
                    strWarning += "册记录的馆藏地 '" + strLocation + "' 不属于读者所在馆代码 '" + strLibraryCode + "',请注意后续处理。";
                }
            }

            // 检查存取定义馆藏地列表
            if (string.IsNullOrEmpty(strAccessParameters) == false && strAccessParameters != "*")
            {
                bool bFound = false;
                List<string> locations = StringUtil.SplitList(strAccessParameters);
                foreach (string s in locations)
                {
                    string c = "";
                    string r = "";
                    ParseCalendarName(s,
                        out c,
                        out r);
                    if (/*string.IsNullOrEmpty(c) == false && */ c != "*")
                    {
                        if (c != strCode)
                            continue;
                    }

                    if (/*string.IsNullOrEmpty(r) == false && */ r != "*")
                    {
                        if (r != strRoom)
                            continue;
                    }

                    bFound = true;
                    break;
                }

                if (bFound == false)
                {
                    strError = strActionName + "操作被拒绝。因册记录的馆藏地 '" + strLocation + "' 不在当前用户存取定义规定的 " + strActionName + " 操作的馆藏地许可范围 '" + strAccessParameters + "' 之内";
                    return -1;
                }
            }
            ///
            // 检查册是否能够被还回
            bool bResultValue = false;
            string strMessageText = "";

            // 执行脚本函数ItemCanReturn
            // parameters:
            // return:
            //      -2  not found script
            //      -1  出错
            //      0   成功
            nRet = app.DoItemCanReturnScriptFunction(
                sessioninfo.Account,
                itemdom,
                out bResultValue,
                out strMessageText,
                out strError);
            if (nRet == -1)
            {
                strError = "执行CanReturn()脚本函数时出错: " + strError;
                return -1;
            }
            if (nRet == -2)
            {
            }
            else
            {
                // 根据脚本返回结果
                if (bResultValue == false)
                {
                    strError = "还书失败。因为册 " + strItemBarcode + " 的状态为 " + strMessageText;
                    return -1;
                }
            }

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

            if (string.IsNullOrEmpty(strPersonalLibrary) == false)
            {
                if (strPersonalLibrary != "*" && StringUtil.IsInList(strRoom, strPersonalLibrary) == false)
                {
                    strError = "还书失败。当前用户 '" + sessioninfo.Account.Barcode + "' 只能操作馆代码 '" + strLibraryCode + "' 中地点为 '" + strPersonalLibrary + "' 的图书,不能操作地点为 '" + strRoom + "' 的图书";
                    return -1;
                }
            }

            bool bOverdue = false;

            string strOverdueMessage = "";

            // 图书类型
            string strBookType = DomUtil.GetElementText(itemdom.DocumentElement, "bookType");

            string strBorrowDate = DomUtil.GetElementText(itemdom.DocumentElement, "borrowDate");
            string strPeriod = DomUtil.GetElementText(itemdom.DocumentElement, "borrowPeriod");
            // 2016/6/7
            string strDenyPeriod = DomUtil.GetElementText(itemdom.DocumentElement, "denyPeriod");

            // 这是借阅时的操作者
            string strBorrowOperator = DomUtil.GetElementText(itemdom.DocumentElement, "operator");

            // 2016/7/22
            if (domOperLog != null)
            {
                DomUtil.SetElementText(domOperLog.DocumentElement, "borrowDate", strBorrowDate);
                DomUtil.SetElementText(domOperLog.DocumentElement, "borrowPeriod", strPeriod);
                if (string.IsNullOrEmpty(strDenyPeriod) == false)
                    DomUtil.SetElementText(domOperLog.DocumentElement, "denyPeriod", strDenyPeriod);

                string strReturningDate = DomUtil.GetElementText(itemdom.DocumentElement, "returningDate");
                if (string.IsNullOrEmpty(strReturningDate) == false)
                    DomUtil.SetElementText(domOperLog.DocumentElement, "returningDate", strReturningDate);

                DomUtil.SetElementText(domOperLog.DocumentElement, "borrowOperator", strBorrowOperator);
            }

            // 册状态
            string strState = DomUtil.GetElementText(itemdom.DocumentElement,
                "state");
            string strComment = DomUtil.GetElementText(itemdom.DocumentElement,
                "comment");
            string strBorrower = DomUtil.GetElementText(itemdom.DocumentElement,
                "borrower");

            // 册价格
            string strItemPrice = DomUtil.GetElementText(itemdom.DocumentElement, "price");

            if (strAction == "lost"
                && String.IsNullOrEmpty(strItemPrice) == true
                && bForce == false)
            {
                strError = "册价格(<price>元素)为空,无法计算丢失图书违约金数。请先为该册登入价格信息,再重新进行丢失声明处理。";
                return -1;
            }

            DateTime borrowdate = new DateTime((long)0);

            try
            {
                borrowdate = DateTimeUtil.FromRfc1123DateTimeString(strBorrowDate);
            }
            catch
            {
                if (bForce == true)
                    goto DOCHANGE;
                strError = "借阅日期字符串 '" + strBorrowDate + "' 格式错误";
                return -1;
            }

            // 看看是否超期
            string strPeriodUnit = "";
            long lPeriodValue = 0;

            nRet = LibraryApplication.ParsePeriodUnit(strPeriod,
                out lPeriodValue,
                out strPeriodUnit,
                out strError);
            if (nRet == -1)
            {
                if (bForce == true)
                    goto DOCHANGE;
                strError = "册记录中借阅期限值 '" + strPeriod + "' 格式错误: " + strError;
                return -1;
            }

            if (string.IsNullOrEmpty(strDenyPeriod) == false)
            {
                // 检查是否在禁止还书时间范围内
                // return:
                //      -1  出错
                //      0   正常,可以还书
                //      1   在禁止还书时间范围内
                nRet = CheckDenyPeriod(
            calendar,
            borrowdate,
            strDenyPeriod,
            strPeriodUnit,
            out strError);
                if (nRet == -1)
                    return -1;
                if (nRet == 1)
                    return -1;
            }

            DateTime timeEnd = DateTime.MinValue;
            DateTime nextWorkingDay = DateTime.MinValue;

            // 测算还书日期
            // parameters:
            //      calendar    工作日历。如果为null,表示函数不进行非工作日判断。
            // return:
            //      -1  出错
            //      0   成功。timeEnd在工作日范围内。
            //      1   成功。timeEnd正好在非工作日。nextWorkingDay已经返回了下一个工作日的时间
            nRet = LibraryApplication.GetReturnDay(
                calendar,
                borrowdate,
                lPeriodValue,
                strPeriodUnit,
                out timeEnd,
                out nextWorkingDay,
                out strError);
            if (nRet == -1)
            {
                if (bForce == true)
                    goto DOCHANGE;
                strError = "测算还书日期过程发生错误: " + strError;
                return -1;
            }

            return_info.LatestReturnTime = DateTimeUtil.Rfc1123DateTimeStringEx(timeEnd.ToLocalTime());

            bool bEndInNonWorkingDay = false;
            if (nRet == 1)
            {
                // 结束在非工作日
                bEndInNonWorkingDay = true;
            }

            DateTime now = app.Clock.UtcNow;  //  今天  当下

            // 正规化时间
            DateTime now_rounded = now;
            nRet = DateTimeUtil.RoundTime(strPeriodUnit,
                ref now_rounded,
                out strError);
            if (nRet == -1)
                return -1;

            TimeSpan delta = now_rounded - timeEnd;

            long lOver = 0;
            long lDelta = 0;
            long lDelta1 = 0;   // 校正(考虑工作日)后的差额

            nRet = ParseTimeSpan(
                delta,
                strPeriodUnit,
                out lDelta,
                out strError);
            if (nRet == -1)
                return -1;

            TimeSpan delta1 = new TimeSpan(0);
            if (bEndInNonWorkingDay == true)
            {
                delta1 = now_rounded - nextWorkingDay;

                nRet = ParseTimeSpan(
    delta1,
    strPeriodUnit,
    out lDelta1,
    out strError);
                if (nRet == -1)
                    return -1;
            }
            else
            {
                delta1 = delta;
                lDelta1 = lDelta;
            }

            if (lDelta1 > 0)
            {
                // 获得 '超期违约金因子' 配置参数
                bool bComputePrice = true;
                string strPriceCfgString = "";
                {
                    MatchResult matchresult;
                    // return:
                    //      reader和book类型均匹配 算4分
                    //      只有reader类型匹配,算3分
                    //      只有book类型匹配,算2分
                    //      reader和book类型都不匹配,算1分
                    nRet = app.GetLoanParam(
                        //null,
                        strLibraryCode,
                        strReaderType,
                        strBookType,
                        "超期违约金因子",
                        out strPriceCfgString,
                        out matchresult,
                        out strError);
                }

                if (nRet == -1)
                {
                    if (bForce == true)
                        bComputePrice = false;  // goto CONTINUE_OVERDUESTRING;
                    else
                    {
                        strError = "还书失败。获得 馆代码 '" + strLibraryCode + "' 中 读者类型 '" + strReaderType + "' 针对图书类型 '" + strBookType + "' 的 超期违约金因子 参数时发生错误: " + strError;
                        return -1;
                    }
                }
                if (nRet < 4) // nRet == 0
                {
                    if (bForce == true)
                        bComputePrice = false;  // goto CONTINUE_OVERDUESTRING;
                    else
                    {
                        strError = "还书失败。馆代码 '" + strLibraryCode + "' 中 读者类型 '" + strReaderType + "' 针对图书类型 '" + strBookType + "' 的 超期违约金因子 参数无法获得: " + strError;
                        return -1;
                    }
                }

                // 注: '超期违约金因子' 参数值如果为空,表明不对超期做违约金处理(方法是在 strOverdueString 第一字符添加一个 '!' )。但需要记入日志
                {
                    // long lOver = 0;
                    // 如果<amerce overdueStyle="...">中包含了includeNoneworkingDay,表示超期天数按照包含了末尾非工作日的算法。否则,就是不包含末尾非工作日,从第一个工作日开始后面的超过天数。
                    if (StringUtil.IsInList("includeNoneworkingDay", this.OverdueStyle) == true)
                        lOver = lDelta;
                    else
                        lOver = lDelta1;

                    string strOverduePrice = "";
                    if (bComputePrice == true && string.IsNullOrEmpty(strPriceCfgString) == false)
                    {
                        nRet = ComputeOverduePrice(
                            strPriceCfgString,
                            lDelta1,    // 按照调整后的差额计算
                            strPeriodUnit,
                            out strOverduePrice,
                            out strError);
                        if (nRet == -1)
                        {
                            if (bForce == true)
                            {
                                // goto CONTINUE_OVERDUESTRING;
                            }
                            else
                            {
                                strError = "还书失败。计算超期违约金价格时出错: " + strError;
                                return -1;
                            }
                        }
                    }

                    // CONTINUE_OVERDUESTRING:
                    strOverdueMessage += "还书时已超过借阅期限 " + Convert.ToString(lOver) + GetDisplayTimeUnitLang(strPeriodUnit) + "。请履行超期手续。";

                    // 最好用XmlTextWriter或者DOM来构造strOverdueString
                    XmlDocument tempdom = new XmlDocument();
                    tempdom.LoadXml("<overdue />");
                    DomUtil.SetAttr(tempdom.DocumentElement, "barcode", strItemBarcode);

                    // 2016/9/5
                    DomUtil.SetAttr(tempdom.DocumentElement, "location", strLocation);

                    if (bItemBarcodeDup == true)
                    {
                        // 若条码号足以定位,则不记载实体记录路径
                        DomUtil.SetAttr(tempdom.DocumentElement, "recPath", strItemRecPath);
                    }

                    string strReason = "超期。超 " + (lOver).ToString() + GetDisplayTimeUnitLang(strPeriodUnit) + "; 违约金因子: " + strPriceCfgString;
                    DomUtil.SetAttr(tempdom.DocumentElement, "reason", strReason);

                    // 超期时间长度 2007/12/17
                    DomUtil.SetAttr(tempdom.DocumentElement, "overduePeriod", (lOver).ToString() + strPeriodUnit);

                    DomUtil.SetAttr(tempdom.DocumentElement, "price", strOverduePrice);
                    DomUtil.SetAttr(tempdom.DocumentElement, "borrowDate", strBorrowDate);
                    DomUtil.SetAttr(tempdom.DocumentElement, "borrowPeriod", strPeriod);
                    DomUtil.SetAttr(tempdom.DocumentElement, "returnDate", DateTimeUtil.Rfc1123DateTimeStringEx(now.ToLocalTime()));
                    DomUtil.SetAttr(tempdom.DocumentElement, "borrowOperator", strBorrowOperator);
                    DomUtil.SetAttr(tempdom.DocumentElement, "operator", strReturnOperator);
                    // id属性是唯一的, 为交违约金C/S界面创造了有利条件
                    string strOverdueID = GetOverdueID();
                    DomUtil.SetAttr(tempdom.DocumentElement, "id", strOverdueID);

                    strOverdueString = tempdom.DocumentElement.OuterXml;

                    /*
                    strOverdueString = "<overdue barcode='" + strItemBarcode
                        + "' over='" + Convert.ToString(lOver) + strPeriodUnit
                        + "' borrowDate='" + strBorrowDate
                        + "' borrowPeriod='" + strPeriod 
                        + "' returnDate='" + DateTimeUtil.Rfc1123DateTimeString(now) 
                        + "' operator='" + strOperator
                        + "' id='" + GetOverdueID() + "'/>";
                     */

                    if (string.IsNullOrEmpty(strPriceCfgString) == false)
                        bOverdue = true;
                    else
                        strOverdueString = "!" + strOverdueString;  // 表示后面不写入读者记录,但需记入日志
                }
            }

            if (strAction == "lost")
            {
                string strLostPrice = "?";
                string strReason = "丢失。";

                string strBiblioRecID = DomUtil.GetElementText(itemdom.DocumentElement, "parent");  //
                string strItemDbName = ResPath.GetDbName(strItemRecPath);
                string strBiblioDbName = "";
                // 根据实体库名, 找到对应的书目库名
                // return:
                //      -1  出错
                //      0   没有找到
                //      1   找到
                nRet = this.GetBiblioDbNameByItemDbName(strItemDbName,
                    out strBiblioDbName,
                    out strError);
                if (nRet == -1)
                    return -1;
                if (nRet == 0)
                {
                    strError = "实体库名 '" + strItemDbName + "' 没有找到对应的书目库名";
                    return -1;
                }
                string strBiblioRecPath = strBiblioDbName + "/" + strBiblioRecID;


                int nResultValue = 0;
                string strTempReason = "";
                // 执行脚本函数GetLost
                // 根据当前读者记录、实体记录、书目记录,计算出丢失后的赔偿金额
                // parameters:
                // return:
                //      -2  not found script
                //      -1  出错
                //      0   成功
                nRet = this.DoGetLostScriptFunction(
                    sessioninfo,
            readerdom,
            itemdom,
            strBiblioRecPath,
            out nResultValue,
            out strLostPrice,
            out strTempReason,
            out strError);
                if (nRet == -1)
                {
                    strError = "调用脚本函数GetLost()时出错: " + strError;
                    return -1;
                }

                if (nRet == 0)
                {
                    if (nResultValue == -1)
                    {
                        strError = "(脚本函数)计算丢失赔偿金额时报错: " + strError;
                        return -1;
                    }

                    strReason += strTempReason;
                }
                // 没有发现脚本函数,则从配置表中找到参数并计算
                else if (nRet == -2)
                {
                    // 获得 '丢失违约金因子' 配置参数
                    string strPriceCfgString = "";
                    MatchResult matchresult;
                    // return:
                    //      reader和book类型均匹配 算4分
                    //      只有reader类型匹配,算3分
                    //      只有book类型匹配,算2分
                    //      reader和book类型都不匹配,算1分
                    nRet = app.GetLoanParam(
                        //null,
                        strLibraryCode,
                        strReaderType,
                        strBookType,
                        "丢失违约金因子",
                        out strPriceCfgString,
                        out matchresult,
                        out strError);
                    if (nRet == -1)
                    {
                        if (bForce == true)
                            goto CONTINUE_LOSTING;
                        strError = "丢失处理失败。获得 馆代码 '" + strLibraryCode + "' 中 读者类型 '" + strReaderType + "' 针对图书类型 '" + strBookType + "' 的 丢失违约金因子 参数时发生错误: " + strError;
                        return -1;
                    }
                    if (nRet < 4)  // nRet == 0
                    {
                        if (bForce == true)
                            goto CONTINUE_LOSTING;

                        strError = "还书失败。馆代码 '" + strLibraryCode + "' 中 读者类型 '" + strReaderType + "' 针对图书类型 '" + strBookType + "' 的 丢失违约金因子 参数无法获得: " + strError;
                        return -1;
                    }

                    nRet = ComputeLostPrice(
                        strPriceCfgString,
                        strItemPrice,
                        out strLostPrice,
                        out strError);
                    if (nRet == -1)
                    {
                        if (bForce == true)
                            goto CONTINUE_LOSTING;

                        strError = "丢失处理失败。计算丢失违约金价格时出错: " + strError;
                        return -1;
                    }

                    strReason = "丢失。原价格: " + strItemPrice + "; 违约金因子:" + strPriceCfgString;
                }
                else
                {
                    Debug.Assert(false, "");
                }

            CONTINUE_LOSTING:

                strOverdueMessage += "有丢失违约金 " + strLostPrice + "。请履行付违约金手续。";

                // 构造strOverdueString
                XmlDocument tempdom = new XmlDocument();
                tempdom.LoadXml("<overdue />");
                DomUtil.SetAttr(tempdom.DocumentElement, "barcode", strItemBarcode);
                DomUtil.SetAttr(tempdom.DocumentElement, "location", strLocation);
                DomUtil.SetAttr(tempdom.DocumentElement, "reason", strReason);
                DomUtil.SetAttr(tempdom.DocumentElement, "price", strLostPrice);
                DomUtil.SetAttr(tempdom.DocumentElement, "borrowDate", strBorrowDate);
                DomUtil.SetAttr(tempdom.DocumentElement, "borrowPeriod", strPeriod);
                DomUtil.SetAttr(tempdom.DocumentElement, "returnDate", DateTimeUtil.Rfc1123DateTimeStringEx(now.ToLocalTime()));
                DomUtil.SetAttr(tempdom.DocumentElement, "borrowOperator", strBorrowOperator);
                DomUtil.SetAttr(tempdom.DocumentElement, "operator", strReturnOperator);
                // id属性是唯一的, 为交违约金C/S界面创造了有利条件
                string strOverdueID = GetOverdueID();
                DomUtil.SetAttr(tempdom.DocumentElement, "id", strOverdueID);

                strOverdueString += tempdom.DocumentElement.OuterXml;

                strLostComment = "本册于 " + DateTimeUtil.Rfc1123DateTimeStringEx(now.ToLocalTime()) + " 由读者 " + strBorrower + " 声明丢失。违约金记录id为 " + strOverdueID + "。最后一次借阅的情况如下: 借阅日期: " + strBorrowDate + "; 借阅期限: " + strPeriod + "。";
            }


        DOCHANGE:

            XmlNode nodeOldBorrower = null;

            // 加入到借阅历史字段中
            {
                // 看看根下面是否有borrowHistory元素
                XmlNode root = itemdom.DocumentElement.SelectSingleNode("borrowHistory");
                if (root == null)
                {
                    root = itemdom.CreateElement("borrowHistory");
                    itemdom.DocumentElement.AppendChild(root);
                }

                if (this.MaxItemHistoryItems > 0)
                {
                    nodeOldBorrower = itemdom.CreateElement("borrower");
                    // 插入到最前面
                    XmlNode temp = DomUtil.InsertFirstChild(root, nodeOldBorrower);
                    if (temp != null)
                    {
                        // 加入还书时间
                        DomUtil.SetAttr(temp, "returnDate", strOperTime);
                    }
                }

                // 如果超过100个,则删除多余的
                while (root.ChildNodes.Count > this.MaxItemHistoryItems)
                    root.RemoveChild(root.ChildNodes[root.ChildNodes.Count - 1]);

                // 增量借阅量属性值
                string strBorrowCount = DomUtil.GetAttr(root, "count");
                if (String.IsNullOrEmpty(strBorrowCount) == true)
                    strBorrowCount = "1";
                else
                {
                    long lCount = 1;
                    try
                    {
                        lCount = Convert.ToInt64(strBorrowCount);
                    }
                    catch { }
                    lCount++;
                    strBorrowCount = lCount.ToString();
                }
                DomUtil.SetAttr(root, "count", strBorrowCount);
            }

            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
                    "barcode",
                    DomUtil.GetElementText(itemdom.DocumentElement, "borrower"));
            // DomUtil.SetElementText(itemdom.DocumentElement, "borrower", "");
            if (EnsureDeleteElement(ref itemdom,
    "borrower") == false)
            {
                strError = "!!! 还书操作过程中 DeleteElement() 出错。详情请见 dp2library 错误日志";
                return -1;
            }

            // 2009/9/18
            //DomUtil.SetElementText(itemdom.DocumentElement,
            //    "borrowerReaderType", "");
            DomUtil.DeleteElements(itemdom.DocumentElement,
    "borrowerReaderType");
            // 2012/9/8
            //DomUtil.SetElementText(itemdom.DocumentElement,
            //    "borrowerRecPath", "");
            DomUtil.DeleteElements(itemdom.DocumentElement,
"borrowerRecPath");

            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
               "borrowDate",
               DomUtil.GetElementText(itemdom.DocumentElement, "borrowDate"));
            //DomUtil.SetElementText(itemdom.DocumentElement,
            //    "borrowDate", "");
            DomUtil.DeleteElements(itemdom.DocumentElement,
"borrowDate");

            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
               "borrowPeriod",
               DomUtil.GetElementText(itemdom.DocumentElement, "borrowPeriod"));
            //DomUtil.SetElementText(itemdom.DocumentElement,
            //    "borrowPeriod", "");
            DomUtil.DeleteElements(itemdom.DocumentElement,
"borrowPeriod");

            // 2016/6/8
            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
               "denyPeriod",
               DomUtil.GetElementText(itemdom.DocumentElement, "denyPeriod"));
            DomUtil.DeleteElements(itemdom.DocumentElement,
"denyPeriod");

            // 2014/11/14
            if (nodeOldBorrower != null)
            {
                string strValue = DomUtil.GetElementText(itemdom.DocumentElement, "returningDate");
                if (string.IsNullOrEmpty(strValue) == false)
                    DomUtil.SetAttr(nodeOldBorrower,
                        "returningDate",
                        strValue);
            }
            DomUtil.DeleteElements(itemdom.DocumentElement,
                "returningDate");

            // 2014/11/14
            DomUtil.DeleteElements(itemdom.DocumentElement,
                "lastReturningDate");

            // string strBorrowOperator = DomUtil.GetElementText(itemdom.DocumentElement, "operator");
            //DomUtil.SetElementText(itemdom.DocumentElement,
            //    "operator", "");    // 清除
            DomUtil.DeleteElements(itemdom.DocumentElement,
"operator");

            // item中原operator元素值表示借阅操作者,此时应转入历史中的borrowOperator元素中
            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
                "borrowOperator",
                strBorrowOperator);

            // 借阅历史中operator属性值表示还书操作者
            if (nodeOldBorrower != null)
                DomUtil.SetAttr(nodeOldBorrower,
                "operator",
                strReturnOperator);

            // 2011/6/28
            return_info.BorrowOperator = strBorrowOperator;
            return_info.ReturnOperator = strReturnOperator;

            string strNo = DomUtil.GetElementText(itemdom.DocumentElement, "no");
            if (nodeOldBorrower != null)
            {
                if (string.IsNullOrEmpty(strNo) == false
                && strNo != "0")    // 2013/12/23
                    DomUtil.SetAttr(nodeOldBorrower,
                        "no",
                        strNo);
            }
            DomUtil.DeleteElements(itemdom.DocumentElement,
                "no");

            string strRenewComment = DomUtil.GetElementText(itemdom.DocumentElement, "renewComment");
            if (nodeOldBorrower != null)
            {
                if (string.IsNullOrEmpty(strRenewComment) == false) // 2013/12/23
                    DomUtil.SetAttr(nodeOldBorrower,
                        "renewComment",
                        strRenewComment);
            }
            DomUtil.DeleteElements(itemdom.DocumentElement,
                "renewComment");

            if (nodeOldBorrower != null)
            {
                if (strAction == "lost"
                && strLostComment != "")
                {
                    DomUtil.SetAttr(nodeOldBorrower,
        "state",
        strState);
                    DomUtil.SetAttr(nodeOldBorrower,
                        "comment",
                        strComment);

                    /*
                    if (String.IsNullOrEmpty(strState) == false)
                        strState += ",";
                    strState += "丢失";
                     * */

                    StringUtil.SetInList(ref strState,
                "丢失",
                true);

                    DomUtil.SetElementText(itemdom.DocumentElement,
                        "state", strState);
                    if (strLostComment != "")
                    {
                        if (String.IsNullOrEmpty(strComment) == false)
                            strComment += "\r\n";
                        strComment += strLostComment;
                        DomUtil.SetElementText(itemdom.DocumentElement,
                            "comment", strComment);
                    }
                }
            }

            //  统计指标
            {
                TimeSpan delta_0 = this.Clock.UtcNow - borrowdate;
                if (delta_0.TotalDays < 1)
                {
                    if (this.Statis != null)
                        this.Statis.IncreaseEntryValue(
                        strLibraryCode,
                        "出纳",
                        "当日内立即还册",
                        1);
                }
            }

            // return_info
            return_info.BorrowTime = strBorrowDate;
            return_info.Period = strPeriod;
            return_info.OverdueString = strOverdueString;
            // string strNo = DomUtil.GetElementText(itemdom.DocumentElement, "no");
            return_info.BorrowCount = 0;

            // 2012/3/28
            if (string.IsNullOrEmpty(strNo) == false)
                Int64.TryParse(strNo, out return_info.BorrowCount);
#if NO
            try
            {
                return_info.BorrowCount = Convert.ToInt32(strNo);
            }
            catch
            {
                return_info.BorrowCount = 0;
            }
#endif

            return_info.BookType = strBookType;
            /*
            string strLocation = DomUtil.GetElementText(itemdom.DocumentElement,
                "location");
             * */
            return_info.Location = strLocation;

            if (bOverdue == true
                || strAction == "lost")
            {
                strError = strOverdueMessage;
                return 1;
            }

            return 0;
        }