/// <summary>
        ///     解析记账凭证表达式
        /// </summary>
        /// <param name="expr">表达式</param>
        /// <returns>记账凭证</returns>
        private Voucher GetVoucher(ref string expr)
        {
            Parsing.TrimStartComment(ref expr);
            var id = Parsing.Quoted(ref expr, '^');
            Parsing.TrimStartComment(ref expr);
            DateTime? date = ClientDateTime.Today;
            try
            {
                date = ParsingF.UniqueTime(ref expr);
            }
            catch (Exception)
            {
                // ignore
            }

            Parsing.TrimStartComment(ref expr);
            var remark = Parsing.Quoted(ref expr, '%');
            Parsing.TrimStartComment(ref expr);
            var typeT = VoucherType.Ordinary;
            var type = Parsing.Token(ref expr, false, t => TryParse(t, out typeT)) != null ? (VoucherType?)typeT : null;
            Parsing.TrimStartComment(ref expr);

            var lst = new List<VoucherDetail>();
            VoucherDetail d;
            while ((d = ParseVoucherDetail(ref expr)) != null)
                lst.Add(d);

            return new Voucher
                {
                    ID = id,
                    Remark = remark,
                    Type = type,
                    Date = date,
                    Details = lst
                };
        }
        /// <summary>
        ///     解析记账凭证表达式
        /// </summary>
        /// <param name="expr">表达式</param>
        /// <returns>记账凭证</returns>
        private Voucher GetVoucher(ref string expr)
        {
            Parsing.TrimStartComment(ref expr);
            DateTime?date = ClientDateTime.Today;

            try
            {
                date = ParsingF.UniqueTime(ref expr);
            }
            catch (Exception)
            {
                // ignore
            }

            var currency = Parsing.Token(ref expr, false, s => s.StartsWith("@", StringComparison.Ordinal))
                           ?.Substring(1)
                           .ToUpperInvariant()
                           ?? BaseCurrency.Now;

            var         lst = new List <Item>();
            List <Item> ds;

            while ((ds = ParseItem(currency, ref expr))?.Any() == true)
            {
                lst.AddRange(ds);
            }

            var d   = (double?)0D;
            var t   = (double?)0D;
            var reg = new Regex(@"(?<dt>[dt])(?<num>[0-9]+(?:\.[0-9]{1,2})?|null)");

            while (true)
            {
                var res = Parsing.Token(ref expr, false, reg.IsMatch);
                if (res == null)
                {
                    break;
                }

                var m   = reg.Match(res);
                var num = m.Groups["num"].Value == "null" ? (double?)null : Convert.ToDouble(m.Groups["num"].Value);
                if (m.Groups["dt"].Value == "d")
                {
                    d += num;
                }
                else // if (m.Groups["dt"].Value == "t")
                {
                    t += num;
                }
            }

            if (!d.HasValue && !t.HasValue)
            {
                throw new ApplicationException("不定项过多");
            }

            var           resLst = new List <VoucherDetail>();
            VoucherDetail vd;
            var           exprS = new AbbrSerializer();

            while ((vd = exprS.ParseVoucherDetail(ref expr)) != null)
            {
                resLst.Add(vd);
            }

            // Don't use Enumerable.Sum, it ignores null values
            var actualVals = resLst.Aggregate((double?)0D, (s, dd) => s + dd.Fund);

            if (!d.HasValue || !t.HasValue)
            {
                if (!actualVals.HasValue)
                {
                    throw new ApplicationException("不定项过多");
                }

                var sum = lst.Sum(item => item.Fund - item.DiscountFund);
                if (!d.HasValue)
                {
                    d = sum + t + actualVals;
                }
                else // if (!t.HasValue)
                {
                    t = -(sum - d + actualVals);
                }
            }


            // ReSharper disable PossibleInvalidOperationException
            var total = lst.Sum(it => it.Fund.Value);

            foreach (var item in lst)
            {
                item.DiscountFund += d.Value / total * item.Fund.Value;
                item.Fund         += t.Value / total * item.Fund;
            }
            // ReSharper restore PossibleInvalidOperationException

            foreach (var item in lst)
            {
                if (!item.UseActualFund)
                {
                    continue;
                }

                item.Fund        -= item.DiscountFund;
                item.DiscountFund = 0D;
            }

            var totalD = lst.Sum(it => it.DiscountFund);

            foreach (var grp in lst.GroupBy(
                         it => new VoucherDetail
            {
                Currency = it.Currency,
                Title = it.Title,
                SubTitle = it.SubTitle,
                Content = it.Content,
                Remark = it.Remark
            },
                         new DetailEqualityComparer()))
            {
                // ReSharper disable once PossibleInvalidOperationException
                grp.Key.Fund = grp.Sum(it => it.Fund.Value);
                resLst.Add(grp.Key);
            }

            if (!totalD.IsZero())
            {
                resLst.Add(
                    new VoucherDetail
                {
                    Currency = currency,
                    Title    = 6603,
                    Fund     = -totalD
                });
            }

            return(new Voucher
            {
                Type = VoucherType.Ordinary,
                Date = date,
                Details = resLst
            });
        }