• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

b1f6c1c4 / ProfessionalAccounting / 320

16 Oct 2024 05:07PM UTC coverage: 55.799% (-1.1%) from 56.944%
320

push

appveyor

b1f6c1c4
final taobao

6264 of 11226 relevant lines covered (55.8%)

126.37 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.4
/AccountingServer.Shell/Serializer/DiscountSerializer.cs
1
/* Copyright (C) 2020-2024 b1f6c1c4
2
 *
3
 * This file is part of ProfessionalAccounting.
4
 *
5
 * ProfessionalAccounting is free software: you can redistribute it and/or
6
 * modify it under the terms of the GNU Affero General Public License as
7
 * published by the Free Software Foundation, version 3.
8
 *
9
 * ProfessionalAccounting is distributed in the hope that it will be useful, but
10
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Affero General Public License
12
 * for more details.
13
 *
14
 * You should have received a copy of the GNU Affero General Public License
15
 * along with ProfessionalAccounting.  If not, see
16
 * <https://www.gnu.org/licenses/>.
17
 */
18

19
using System;
20
using System.Collections.Generic;
21
using System.Linq;
22
using System.Text.RegularExpressions;
23
using AccountingServer.BLL;
24
using AccountingServer.BLL.Util;
25
using AccountingServer.Entities;
26
using AccountingServer.Entities.Util;
27
using AccountingServer.Shell.Util;
28
using static AccountingServer.BLL.Parsing.Facade;
29
using static AccountingServer.BLL.Parsing.FacadeF;
30

31
namespace AccountingServer.Shell.Serializer;
32

33
public class DiscountSerializer : IClientDependable, IEntitySerializer
34
{
35
    private const string TheToken = "new Voucher {";
36

37
    public Client Client { private get; set; }
38

39
    /// <inheritdoc />
40
    public string PresentVoucher(Voucher voucher) => throw new NotImplementedException();
12✔
41

42
    /// <inheritdoc />
43
    public string PresentVoucher(Voucher voucher, string inject) => throw new NotImplementedException();
×
44

45
    /// <inheritdoc />
46
    public string PresentVoucherDetail(VoucherDetail detail) => throw new NotImplementedException();
1✔
47

48
    /// <inheritdoc />
49
    public string PresentVoucherDetail(VoucherDetailR detail) => throw new NotImplementedException();
3✔
50

51
    /// <inheritdoc />
52
    public Voucher ParseVoucher(string expr)
53
    {
24✔
54
        if (!expr.StartsWith(TheToken, StringComparison.Ordinal))
24✔
55
            throw new FormatException("格式错误");
1✔
56

57
        expr = expr[TheToken.Length..];
23✔
58
        if (ParsingF.Token(ref expr, false, static s => s == "!") == null)
22✔
59
            throw new NotImplementedException();
14✔
60

61
        var v = GetVoucher(ref expr);
9✔
62
        Parsing.TrimStartComment(ref expr);
5✔
63
        if (Parsing.Token(ref expr, false) != "}")
5✔
64
            throw new FormatException("格式错误" + expr);
×
65

66
        Parsing.Eof(expr);
5✔
67
        return v;
5✔
68
    }
5✔
69

70
    /// <inheritdoc />
71
    public VoucherDetail ParseVoucherDetail(string expr) => throw new NotImplementedException();
1✔
72

73
    public string PresentAsset(Asset asset) => throw new NotImplementedException();
1✔
74
    public Asset ParseAsset(string str) => throw new NotImplementedException();
1✔
75
    public string PresentAmort(Amortization amort) => throw new NotImplementedException();
1✔
76
    public Amortization ParseAmort(string str) => throw new NotImplementedException();
1✔
77

78
    /// <summary>
79
    ///     解析记账凭证表达式
80
    /// </summary>
81
    /// <param name="expr">表达式</param>
82
    /// <returns>记账凭证</returns>
83
    private Voucher GetVoucher(ref string expr)
84
    {
9✔
85
        Parsing.TrimStartComment(ref expr);
9✔
86
        DateTime? date = Client.Today;
9✔
87
        try
88
        {
9✔
89
            date = ParsingF.UniqueTime(ref expr, Client);
9✔
90
        }
1✔
91
        catch (Exception)
8✔
92
        {
8✔
93
            // ignore
94
        }
8✔
95

96
        var vremark = Parsing.Quoted(ref expr, '%');
9✔
97

98
        var currency = Parsing.Token(ref expr, false, static s => s.StartsWith("@", StringComparison.Ordinal))?[1..]
9✔
99
                .ToUpperInvariant()
100
            ?? BaseCurrency.Now;
101

102
        var lst = new List<Item>();
9✔
103
        List<Item> ds;
104
        while ((ds = ParseItem(currency, ref expr))?.Any() == true)
23✔
105
            lst.AddRange(ds);
14✔
106

107
        var d = (double?)0D;
9✔
108
        var t = (double?)0D;
9✔
109
        var reg = new Regex(@"(?<dt>[dt])(?<num>\.[0-9]+|[0-9]+(?:\.[0-9]+)?|null)");
9✔
110
        while (true)
28✔
111
        {
28✔
112
            var res = Parsing.Token(ref expr, false, reg.IsMatch);
28✔
113
            if (res == null)
28✔
114
                break;
9✔
115

116
            var m = reg.Match(res);
19✔
117
            var num = m.Groups["num"].Value == "null" ? (double?)null : Convert.ToDouble(m.Groups["num"].Value);
19✔
118
            if (m.Groups["dt"].Value == "d")
19✔
119
                d += num;
8✔
120
            else // if (m.Groups["dt"].Value == "t")
121
                t += num;
11✔
122
        }
19✔
123

124
        if (!d.HasValue && !t.HasValue)
9✔
125
            throw new ApplicationException("不定项过多");
2✔
126

127
        var resLst = new List<VoucherDetail>();
7✔
128
        VoucherDetail vd;
129
        var exprS = new AbbrSerializer { Client = Client };
7✔
130
        while ((vd = exprS.ParseVoucherDetail(ref expr)) != null)
14✔
131
            resLst.Add(vd);
7✔
132

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

136
        if (!d.HasValue || !t.HasValue)
7✔
137
        {
4✔
138
            if (!actualVals.HasValue)
4✔
139
                throw new ApplicationException("不定项过多");
2✔
140

141
            var sum = lst.Sum(static item => item.Fund - item.DiscountFund);
12✔
142
            if (!d.HasValue)
2✔
143
                d = sum + t + actualVals;
1✔
144
            else // if (!t.HasValue)
145
                t = -(sum - d + actualVals);
1✔
146
        }
2✔
147

148

149
        var total = lst.Sum(static it => it.Fund!.Value);
30✔
150
        foreach (var item in lst)
35✔
151
        {
30✔
152
            item.DiscountFund += d!.Value / total * item.Fund!.Value;
30✔
153
            item.Fund += t.Value / total * item.Fund;
30✔
154
        }
30✔
155

156
        foreach (var item in lst)
35✔
157
        {
30✔
158
            if (!item.UseActualFund)
30✔
159
                continue;
15✔
160

161
            item.Fund -= item.DiscountFund;
15✔
162
            item.DiscountFund = 0D;
15✔
163
        }
15✔
164

165
        foreach (var grp in lst.GroupBy(static it => new()
30✔
166
                         {
167
                             User = it.User,
168
                             Currency = it.Currency,
169
                             Title = it.Title,
170
                             SubTitle = it.SubTitle,
171
                             Content = it.Content,
172
                             Remark = it.Remark,
173
                         },
174
                     new DetailEqualityComparer()))
175
        {
15✔
176
            grp.Key.Fund = grp.Sum(static it => it.Fund!.Value);
30✔
177
            resLst.Add(grp.Key);
15✔
178
        }
15✔
179

180
        var totalDs = new Dictionary<string, double>();
5✔
181
        foreach (var it in lst)
35✔
182
            if (totalDs.ContainsKey(it.User))
30✔
183
                totalDs[it.User] += it.DiscountFund;
24✔
184
            else
185
                totalDs[it.User] = it.DiscountFund;
6✔
186

187
        foreach (var kvp in totalDs)
11✔
188
            if (!kvp.Value.IsZero())
6✔
189
                resLst.Add(
6✔
190
                    new() { User = kvp.Key, Currency = currency, Title = 6603, Fund = -kvp.Value });
191

192
        return new() { Type = VoucherType.Ordinary, Date = date, Remark = vremark, Details = resLst };
5✔
193
    }
5✔
194

195
    private VoucherDetail ParseVoucherDetail(string currency, ref string expr)
196
    {
47✔
197
        var lst = new List<string>();
47✔
198

199
        Parsing.TrimStartComment(ref expr);
47✔
200
        var user = Parsing.Token(ref expr, false, static t => t.StartsWith("U", StringComparison.Ordinal))
47✔
201
            .ParseUserSpec(Client);
202
        var title = Parsing.Title(ref expr);
47✔
203
        if (title == null)
47✔
204
            if (!AlternativeTitle(ref expr, lst, ref title))
32✔
205
                return null;
23✔
206

207
        while (true)
32✔
208
        {
32✔
209
            Parsing.TrimStartComment(ref expr);
32✔
210
            if (Parsing.Optional(ref expr, "+"))
32✔
211
                break;
10✔
212

213
            if (Parsing.Optional(ref expr, ":"))
22✔
214
            {
14✔
215
                expr = $": {expr}";
14✔
216
                break;
14✔
217
            }
218

219
            if (lst.Count > 2)
8✔
220
                throw new ArgumentException("语法错误", nameof(expr));
×
221

222
            Parsing.TrimStartComment(ref expr);
8✔
223
            lst.Add(Parsing.Token(ref expr));
8✔
224
        }
8✔
225

226
        var content = lst.Count >= 1 ? lst[0] : null;
24✔
227
        var remark = lst.Count >= 2 ? lst[1] : null;
24✔
228

229
        if (content == "G()")
24✔
230
            content = Guid.NewGuid().ToString().ToUpperInvariant();
×
231

232
        if (remark == "G()")
24✔
233
            remark = Guid.NewGuid().ToString().ToUpperInvariant();
×
234

235

236
        return new()
24✔
237
            {
238
                User = user,
239
                Currency = currency,
240
                Title = title.Title,
241
                SubTitle = title.SubTitle,
242
                Content = string.IsNullOrEmpty(content) ? null : content,
243
                Remark = string.IsNullOrEmpty(remark) ? null : remark,
244
            };
245
    }
47✔
246

247
    private List<Item> ParseItem(string currency, ref string expr)
248
    {
23✔
249
        var lst = new List<(VoucherDetail Detail, bool Actual)>();
23✔
250

251
        while (true)
47✔
252
        {
47✔
253
            var actual = Parsing.Optional(ref expr, "!");
47✔
254
            var vd = ParseVoucherDetail(currency, ref expr);
47✔
255
            if (vd == null)
47✔
256
                break;
23✔
257

258
            lst.Add((vd, actual));
24✔
259
        }
24✔
260

261
        if (ParsingF.Token(ref expr, false, static s => s == ":") == null)
23✔
262
            return null;
9✔
263

264
        var resLst = new List<Item>();
14✔
265

266
        var reg = new Regex(
14✔
267
            @"(?<num>\.[0-9]+|[0-9]+(?:\.[0-9]+)?)(?:(?<equals>=\.[0-9]+|=[0-9]+(?:\.[0-9]+)?)|(?<plus>(?:\+\.[0-9]+|\+[0-9]+(?:\.[0-9]+)?)+)|(?<minus>(?:-\.[0-9]+|-[0-9]+(?:\.[0-9]+)?)+))?(?<times>\*\.[0-9]+|\*[0-9]+(?:\.[0-9]+)?)?");
268
        while (true)
33✔
269
        {
33✔
270
            var res = Parsing.Token(ref expr, false, reg.IsMatch);
33✔
271
            if (res == null)
33✔
272
                break;
14✔
273

274
            var m = reg.Match(res);
19✔
275
            var fund0 = Convert.ToDouble(m.Groups["num"].Value);
19✔
276
            var fundd = 0D;
19✔
277
            if (m.Groups["equals"].Success)
19✔
278
                fundd = fund0 - Convert.ToDouble(m.Groups["equals"].Value[1..]);
2✔
279
            else if (m.Groups["plus"].Success)
17✔
280
            {
5✔
281
                var sreg = new Regex(@"\+\.[0-9]+|\+[0-9]+(?:\.[0-9]+)?");
5✔
282
                foreach (Match sm in sreg.Matches(m.Groups["plus"].Value))
11✔
283
                    fundd += Convert.ToDouble(sm.Value);
6✔
284
                fund0 += fundd;
5✔
285
            }
5✔
286
            else if (m.Groups["minus"].Success)
12✔
287
            {
4✔
288
                var sreg = new Regex(@"-\.[0-9]+|-[0-9]+(?:\.[0-9]+)?");
4✔
289
                foreach (Match sm in sreg.Matches(m.Groups["minus"].Value))
9✔
290
                    fundd -= Convert.ToDouble(sm.Value);
5✔
291
            }
4✔
292

293
            if (m.Groups["times"].Success)
19✔
294
            {
2✔
295
                var mult = Convert.ToDouble(m.Groups["times"].Value[1..]);
2✔
296
                fund0 *= mult;
2✔
297
                fundd *= mult;
2✔
298
            }
2✔
299

300
            resLst.AddRange(
19✔
301
                lst.Select(
302
                    d => new Item
34✔
303
                        {
304
                            User = d.Detail.User,
305
                            Currency = d.Detail.Currency,
306
                            Title = d.Detail.Title,
307
                            SubTitle = d.Detail.SubTitle,
308
                            Content = d.Detail.Content,
309
                            Fund = fund0 / lst.Count,
310
                            DiscountFund = fundd / lst.Count,
311
                            Remark = d.Detail.Remark,
312
                            UseActualFund = d.Actual,
313
                        }));
314
        }
19✔
315

316
        ParsingF.Optional(ref expr, ";");
14✔
317

318
        return resLst;
14✔
319
    }
23✔
320

321
    private static bool AlternativeTitle(ref string expr, ICollection<string> lst, ref ITitle title)
322
        => AbbrSerializer.GetAlternativeTitle(ref expr, lst, ref title);
32✔
323

324
    private sealed class Item : VoucherDetail
325
    {
326
        public double DiscountFund { get; set; }
327

328
        public bool UseActualFund { get; init; }
329
    }
330

331
    private sealed class DetailEqualityComparer : IEqualityComparer<VoucherDetail>
332
    {
333
        public bool Equals(VoucherDetail x, VoucherDetail y)
334
        {
40✔
335
            if (x == null &&
40✔
336
                y == null)
337
                return true;
×
338
            if (x == null ||
40✔
339
                y == null)
340
                return false;
×
341
            if (x.User != y.User)
40✔
342
                return false;
4✔
343
            if (x.Currency != y.Currency)
36✔
344
                return false;
×
345
            if (x.Title != y.Title)
36✔
346
                return false;
21✔
347
            if (x.SubTitle != y.SubTitle)
15✔
348
                return false;
×
349
            if (x.Content != y.Content)
15✔
350
                return false;
×
351
            if (x.Fund.HasValue != y.Fund.HasValue)
15✔
352
                return false;
×
353
            if (x.Fund.HasValue &&
15✔
354
                y.Fund.HasValue)
355
                if (!(x.Fund.Value - y.Fund.Value).IsZero())
×
356
                    return false;
×
357

358
            return x.Remark == y.Remark;
15✔
359
        }
40✔
360

361
        public int GetHashCode(VoucherDetail obj) => obj.Currency?.GetHashCode() | obj.Title?.GetHashCode() |
30✔
362
            obj.SubTitle?.GetHashCode() | obj.Content?.GetHashCode() | obj.Fund?.GetHashCode() |
363
            obj.Remark?.GetHashCode() ?? 0;
364
    }
365
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc