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

b1f6c1c4 / ProfessionalAccounting / 329

19 Sep 2025 05:22AM UTC coverage: 47.301% (-0.9%) from 48.177%
329

push

appveyor

b1f6c1c4
support WeeklyItem in $cf$

4951 of 10467 relevant lines covered (47.3%)

106.77 hits per line

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

83.04
/AccountingServer.Shell/Serializer/ExprSerializer.cs
1
/* Copyright (C) 2020-2025 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;
23
using System.Text.RegularExpressions;
24
using AccountingServer.BLL;
25
using AccountingServer.BLL.Util;
26
using AccountingServer.Entities;
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
/// <summary>
34
///     实体表达式
35
/// </summary>
36
public class ExprSerializer : IClientDependable, IIdentityDependable, IEntitySerializer
37
{
38
    private const string TheToken = "new Voucher {";
39

40
    public Client Client { private get; set; }
41

42
    public Identity Identity { private get; set; }
43

44
    /// <inheritdoc />
45
    public string PresentVoucher(Voucher voucher)
46
        => PresentVoucher(voucher, null);
29✔
47

48
    /// <inheritdoc />
49
    public string PresentVoucher(Voucher voucher, string inject)
50
    {
29✔
51
        var sb = new StringBuilder();
29✔
52
        sb.Append(TheToken);
29✔
53
        if (voucher == null)
29✔
54
        {
3✔
55
            sb.Append("\n");
3✔
56
            sb.Append("\n");
3✔
57
        }
3✔
58
        else
59
        {
26✔
60
            sb.Append($"{voucher.ID?.Quotation('^')}");
26✔
61
            if (Identity != null)
26✔
62
            {
12✔
63
                var lst = Identity.Audit(voucher).ToList();
12✔
64
                if (lst.Any())
12✔
65
                {
×
66
                    sb.Append(" //");
×
67
                    foreach (var (id, ed) in lst)
×
68
                        sb.Append(ed ? $" <{id.Name.AsId()}>" : $" {id.Name.AsId()}");
×
69
                }
×
70
            }
12✔
71
            sb.Append("\n");
26✔
72
            if (voucher.Date.HasValue || inject == null)
26✔
73
                sb.Append($"{voucher.Date.AsDate()}\n");
26✔
74
            if (voucher.Remark != null)
26✔
75
                sb.Append($"{voucher.Remark.Quotation('%')}\n");
14✔
76
            if (voucher.Type.HasValue &&
26✔
77
                voucher.Type != VoucherType.Ordinary)
78
                sb.Append($"{voucher.Type}\n");
12✔
79

80
            if (inject != null)
26✔
81
                sb.Append($"{inject}\n");
×
82
            if (voucher.Redacted)
26✔
83
                sb.Append("[[REDACTED]]\n");
×
84

85
            foreach (var d in voucher.Details)
114✔
86
                sb.Append(PresentVoucherDetail(d, voucher));
88✔
87
        }
26✔
88

89
        sb.Append('}');
29✔
90
        return sb.ToString();
29✔
91
    }
29✔
92

93
    /// <inheritdoc />
94
    public string PresentVoucherDetail(VoucherDetail detail)
95
        => PresentVoucherDetail(detail, null);
×
96

97
    private string PresentVoucherDetail(VoucherDetail detail, Voucher v)
98
    {
90✔
99
        var sb = new StringBuilder();
90✔
100
        var t = TitleManager.GetTitleName(detail.Title);
90✔
101
        sb.Append(
90✔
102
            detail.SubTitle.HasValue
103
                ? $"// {t}-{TitleManager.GetTitleName(detail.Title, detail.SubTitle)}"
104
                : $"// {t}");
105
        if (Identity != null)
90✔
106
        {
48✔
107
            var res = Identity.Audit(detail, v).ToList();
48✔
108
            if (res.Any())
48✔
109
            {
×
110
                if (v == null)
×
111
                    sb.Append(" -- Access:");
×
112
                else
113
                    sb.Append(" -- Denied:");
×
114
                foreach (var (id, ed) in res)
×
115
                    sb.Append(ed ? $" <{id.Name.AsId()}>" : $" {id.Name.AsId()}");
×
116
            }
×
117
        }
48✔
118
        sb.Append("\n");
90✔
119
        if (Client == null || detail.User != Client.User)
90✔
120
            sb.Append($"{detail.User.AsUser()} ");
25✔
121
        if (detail.Currency != BaseCurrency.Now)
90✔
122
            sb.Append($"{detail.Currency.AsCurrency()} ");
42✔
123
        sb.Append($"{detail.Title.AsTitle()}{detail.SubTitle.AsSubTitle()} ");
90✔
124
        if (detail.Content == null &&
90✔
125
            detail.Remark != null)
126
            sb.Append("''");
39✔
127
        else
128
            sb.Append(detail.Content?.Quotation('\''));
51✔
129
        sb.Append($" {detail.Remark?.Quotation('\"')} {detail.Fund}\n");
90✔
130
        return sb.ToString();
90✔
131
    }
90✔
132

133
    /// <inheritdoc />
134
    public string PresentVoucherDetail(VoucherDetailR detail)
135
        => $"{detail.Voucher.Date.AsDate()} {PresentVoucherDetail(detail, detail.Voucher)}";
2✔
136

137
    /// <inheritdoc />
138
    public Voucher ParseVoucher(string expr)
139
    {
32✔
140
        if (!expr.StartsWith(TheToken, StringComparison.Ordinal))
32✔
141
            throw new FormatException("格式错误");
2✔
142

143
        expr = expr[TheToken.Length..];
30✔
144
        var v = GetVoucher(ref expr);
30✔
145
        Parsing.TrimStartComment(ref expr);
30✔
146
        if (Parsing.Token(ref expr, false) != "}")
30✔
147
            throw new FormatException("格式错误" + expr);
2✔
148

149
        Parsing.Eof(expr);
28✔
150
        return v;
28✔
151
    }
28✔
152

153
    /// <inheritdoc />
154
    public virtual VoucherDetail ParseVoucherDetail(string expr)
155
    {
×
156
        var res = ParseVoucherDetail(ref expr);
×
157
        Parsing.Eof(expr);
×
158
        return res;
×
159
    }
×
160

161
    public string PresentAsset(Asset asset) => throw new NotImplementedException();
2✔
162
    public Asset ParseAsset(string str) => throw new NotImplementedException();
2✔
163
    public string PresentAmort(Amortization amort) => throw new NotImplementedException();
2✔
164
    public Amortization ParseAmort(string str) => throw new NotImplementedException();
2✔
165

166
    /// <summary>
167
    ///     解析记账凭证表达式
168
    /// </summary>
169
    /// <param name="expr">表达式</param>
170
    /// <returns>记账凭证</returns>
171
    private Voucher GetVoucher(ref string expr)
172
    {
30✔
173
        Parsing.TrimStartComment(ref expr);
30✔
174
        var id = Parsing.Quoted(ref expr, '^');
30✔
175
        Parsing.TrimStartComment(ref expr);
30✔
176
        DateTime? date = Client.Today;
30✔
177
        try
178
        {
30✔
179
            date = ParsingF.UniqueTime(ref expr, Client);
30✔
180
        }
14✔
181
        catch (Exception)
16✔
182
        {
16✔
183
            // ignore
184
        }
16✔
185

186
        Parsing.TrimStartComment(ref expr);
30✔
187
        var remark = Parsing.Quoted(ref expr, '%');
30✔
188
        Parsing.TrimStartComment(ref expr);
30✔
189
        var typeT = VoucherType.Ordinary;
30✔
190
        var type = Parsing.Token(ref expr, false, t => TryParse(t, out typeT)) != null ? (VoucherType?)typeT : null;
28✔
191
        Parsing.TrimStartComment(ref expr);
30✔
192

193
        var lst = new List<VoucherDetail>();
30✔
194
        VoucherDetail d;
195
        while ((d = ParseVoucherDetail(ref expr)) != null)
95✔
196
            lst.Add(d);
65✔
197

198
        return new()
30✔
199
            {
200
                ID = id,
201
                Remark = remark,
202
                Type = type,
203
                Date = date,
204
                Details = lst,
205
            };
206
    }
30✔
207

208
    /// <summary>
209
    ///     解析记账凭证类别表达式
210
    /// </summary>
211
    /// <param name="s">表达式</param>
212
    /// <param name="typeT">记账凭证类别</param>
213
    /// <returns>是否解析成功</returns>
214
    private static bool TryParse(string s, out VoucherType typeT)
215
    {
28✔
216
        // Don't use Enum.TryParse here:
217
        // Enum.TryParse("1001", out _) gives true
218
        switch (s)
28✔
219
        {
220
            case "Ordinary":
221
                typeT = VoucherType.Ordinary;
×
222
                return true;
×
223
            case "Carry":
224
                typeT = VoucherType.Carry;
2✔
225
                return true;
2✔
226
            case "AnnualCarry":
227
                typeT = VoucherType.AnnualCarry;
2✔
228
                return true;
2✔
229
            case "Depreciation":
230
                typeT = VoucherType.Depreciation;
2✔
231
                return true;
2✔
232
            case "Devalue":
233
                typeT = VoucherType.Devalue;
2✔
234
                return true;
2✔
235
            case "Amortization":
236
                typeT = VoucherType.Amortization;
2✔
237
                return true;
2✔
238
            case "Uncertain":
239
                typeT = VoucherType.Uncertain;
2✔
240
                return true;
2✔
241
            default:
242
                typeT = VoucherType.General;
16✔
243
                return false;
16✔
244
        }
245
    }
28✔
246

247
    private Regex m_SimpleCurrency = new(@"^@[a-zA-Z]+$");
38✔
248

249
    public VoucherDetail ParseVoucherDetail(ref string expr)
250
    {
111✔
251
        var lst = new List<string>();
111✔
252

253
        Parsing.TrimStartComment(ref expr);
111✔
254
        var user = Parsing.Token(ref expr, false, static t => t.StartsWith("U", StringComparison.Ordinal))
109✔
255
            .ParseUserSpec(Client);
256
        var currency = BaseCurrency.Now;
111✔
257
        if (Parsing.Token(ref expr, false, m_SimpleCurrency.IsMatch) is var t && t != null)
111✔
258
            currency = t.ParseCurrency();
50✔
259
        else if (expr.FirstOrDefault() == '@')
61✔
260
        {
×
261
            expr = expr[1..];
×
262
            currency = Parsing.Quoted(ref expr, '#') + '#';
×
263
        }
×
264
        Parsing.TrimStartComment(ref expr);
111✔
265
        var title = Parsing.Title(ref expr);
111✔
266
        if (title == null)
111✔
267
            if (!AlternativeTitle(ref expr, lst, ref title))
50✔
268
                return null;
38✔
269

270
        double? fund;
271

272
        while (true)
164✔
273
        {
164✔
274
            Parsing.TrimStartComment(ref expr);
164✔
275
            if ((fund = Parsing.Double(ref expr)) != null)
164✔
276
                break;
60✔
277

278
            Parsing.TrimStartComment(ref expr);
104✔
279
            if (Parsing.Optional(ref expr, "null"))
104✔
280
                break;
3✔
281
            if (Parsing.Optional(ref expr, "/"))
101✔
282
                break;
10✔
283

284
            if (lst.Count > 2)
91✔
285
                throw new ArgumentException("语法错误", nameof(expr));
×
286

287
            Parsing.TrimStartComment(ref expr);
91✔
288
            lst.Add(Parsing.Token(ref expr));
91✔
289
        }
91✔
290

291
        var content = lst.Count >= 1 ? lst[0] : null;
73✔
292
        var remark = lst.Count >= 2 ? lst[1] : null;
73✔
293

294
        if (content == "G()")
73✔
295
            content = Guid.NewGuid().ToString().ToUpperInvariant();
×
296

297
        if (remark == "G()")
73✔
298
            remark = Guid.NewGuid().ToString().ToUpperInvariant();
×
299

300
        return new()
73✔
301
            {
302
                User = user,
303
                Currency = currency,
304
                Title = title.Title,
305
                SubTitle = title.SubTitle,
306
                Content = string.IsNullOrEmpty(content) ? null : content,
307
                Fund = fund,
308
                Remark = string.IsNullOrEmpty(remark) ? null : remark,
309
            };
310
    }
111✔
311

312
    protected virtual bool AlternativeTitle(ref string expr, ICollection<string> lst, ref ITitle title) => false;
9✔
313
}
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