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

b1f6c1c4 / ProfessionalAccounting / 307

25 Sep 2023 04:48AM UTC coverage: 56.975% (-1.3%) from 58.254%
307

push

appveyor

b1f6c1c4
raw subtotal more controllable

5947 of 10438 relevant lines covered (56.97%)

125.9 hits per line

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

67.72
/AccountingServer.Shell/AccountingShell.cs
1
/* Copyright (C) 2020-2023 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.Security;
23
using AccountingServer.BLL;
24
using AccountingServer.Entities;
25
using AccountingServer.Entities.Util;
26
using AccountingServer.Shell.Subtotal;
27
using AccountingServer.Shell.Util;
28
using static AccountingServer.BLL.Parsing.FacadeF;
29

30
namespace AccountingServer.Shell;
31

32
/// <summary>
33
///     基本表达式解释器
34
/// </summary>
35
internal class AccountingShell : IShellComponent
36
{
37
    /// <inheritdoc />
38
    public IAsyncEnumerable<string> Execute(string expr, Session session) => Parse(expr, session.Client)(session);
6✔
39

40
    /// <inheritdoc />
41
    public bool IsExecutable(string expr) => true;
6✔
42

43
    /// <summary>
44
    ///     解析表达式
45
    /// </summary>
46
    /// <param name="expr">表达式</param>
47
    /// <param name="client">客户端</param>
48
    /// <returns>解析结果</returns>
49
    private Func<Session, IAsyncEnumerable<string>> Parse(string expr, Client client)
50
    {
6✔
51
        var type = ExprType.None;
6✔
52
        ISubtotalStringify visitor;
53

54
        if (ParsingF.Token(ref expr, false, static t => t == "unsafe") != null)
6✔
55
            type |= ExprType.Unsafe;
2✔
56

57
        if (ParsingF.Token(ref expr, false, static t => t == "json") != null)
6✔
58
        {
1✔
59
            type |= ExprType.GroupedQueries;
1✔
60
            visitor = new JsonSubtotal();
1✔
61
        }
1✔
62
        else if (ParsingF.Token(ref expr, false, static t => t == "Rps") != null)
5✔
63
        {
×
64
            type |= ExprType.GroupedQueries;
×
65
            visitor = new PreciseSubtotalPre();
×
66
        }
×
67
        else if (ParsingF.Token(ref expr, false, static t => t == "rps") != null)
5✔
68
        {
×
69
            type |= ExprType.GroupedQueries;
×
70
            visitor = new PreciseSubtotalPre(false);
×
71
        }
×
72
        else if (ParsingF.Token(ref expr, false, static t => t == "raw") != null)
5✔
73
        {
×
74
            type |= ExprType.GroupedQueries | ExprType.DetailQuery;
×
75
            visitor = new RawSubtotal(0);
×
76
        }
×
77
        else if (ParsingF.Token(ref expr, false, static t => t == "sraw") != null)
5✔
78
        {
2✔
79
            type |= ExprType.GroupedQueries | ExprType.DetailRQuery;
2✔
80
            visitor = new RawSubtotal(0);
2✔
81
        }
2✔
82
        else if (ParsingF.Token(ref expr, false, static t => t == "fancy") != null)
3✔
83
        {
1✔
84
            type |= ExprType.FancyQuery;
1✔
85
            visitor = null;
1✔
86
        }
1✔
87
        else if (ParsingF.Token(ref expr, false, static t => t.StartsWith("raw", StringComparison.OrdinalIgnoreCase)) is var t && t != null)
2✔
88
        {
×
89
            type |= ExprType.GroupedQueries;
×
90
            visitor = new RawSubtotal(int.Parse(t[4..]))
×
91
                {
92
                    Ratio = t[3] switch
×
93
                        {
94
                            '+' => +1.0,
×
95
                            '-' => -1.0,
×
96
                            _ => throw new ApplicationException("Unknown raw ratio specifier."),
×
97
                        },
98
                    Inject = ParsingF.Quoted(ref expr, '@'),
99
                };
100
        }
×
101
        else
102
        {
2✔
103
            type |= ExprType.GroupedQueries | ExprType.VoucherQuery;
2✔
104
            visitor = new RichSubtotalPre();
2✔
105
        }
2✔
106

107
        (visitor as IClientDependable).Assign(client);
6✔
108

109
        if (type.HasFlag(ExprType.GroupedQuery))
6✔
110
            try
111
            {
5✔
112
                return TryGroupedQuery(expr, visitor, client);
5✔
113
            }
114
            catch (Exception)
4✔
115
            {
4✔
116
                // ignored
117
            }
4✔
118

119
        if (type.HasFlag(ExprType.VoucherGroupedQuery))
5✔
120
            try
121
            {
4✔
122
                return TryVoucherGroupedQuery(expr, visitor, client);
4✔
123
            }
124
            catch (Exception)
4✔
125
            {
4✔
126
                // ignored
127
            }
4✔
128

129
        return (type & ExprType.NonGroupedQueries) switch
×
130
            {
131
                ExprType.VoucherQuery => TryVoucherQuery(expr, !type.HasFlag(ExprType.Unsafe), client),
2✔
132
                ExprType.DetailQuery => TryDetailQuery(expr, !type.HasFlag(ExprType.Unsafe), client),
×
133
                ExprType.DetailRQuery => TryDetailRQuery(expr, !type.HasFlag(ExprType.Unsafe), client),
2✔
134
                ExprType.FancyQuery => TryFancyQuery(expr, !type.HasFlag(ExprType.Unsafe), client),
1✔
135
                _ => throw new InvalidOperationException("表达式无效"),
×
136
            };
137
    }
4✔
138

139
    /// <summary>
140
    ///     按记账凭证检索式解析
141
    /// </summary>
142
    /// <param name="expr">表达式</param>
143
    /// <param name="safe">仅限强检索式</param>
144
    /// <param name="client">客户端</param>
145
    /// <returns>执行结果</returns>
146
    private Func<Session, IAsyncEnumerable<string>> TryVoucherQuery(string expr, bool safe, Client client)
147
    {
2✔
148
        var res = ParsingF.VoucherQuery(ref expr, client);
2✔
149
        ParsingF.Eof(expr);
2✔
150
        if (res.IsDangerous() && safe)
1✔
151
            throw new SecurityException("检测到弱检索式");
×
152

153
        return session => PresentVoucherQuery(res, session);
1✔
154
    }
1✔
155

156
    /// <summary>
157
    ///     按分类汇总检索式解析
158
    /// </summary>
159
    /// <param name="expr">表达式</param>
160
    /// <param name="trav">呈现器</param>
161
    /// <param name="client">客户端</param>
162
    /// <returns>执行结果</returns>
163
    private Func<Session, IAsyncEnumerable<string>> TryGroupedQuery(string expr, ISubtotalStringify trav, Client client)
164
    {
5✔
165
        var res = ParsingF.GroupedQuery(ref expr, client);
5✔
166
        ParsingF.Eof(expr);
1✔
167
        return session => PresentSubtotal(res, trav, session);
1✔
168
    }
1✔
169

170
    /// <summary>
171
    ///     按记账凭证分类汇总检索式解析
172
    /// </summary>
173
    /// <param name="expr">表达式</param>
174
    /// <param name="trav">呈现器</param>
175
    /// <param name="client">客户端</param>
176
    /// <returns>执行结果</returns>
177
    private Func<Session, IAsyncEnumerable<string>> TryVoucherGroupedQuery(string expr, ISubtotalStringify trav,
178
        Client client)
179
    {
4✔
180
        var res = ParsingF.VoucherGroupedQuery(ref expr, client);
4✔
181
        ParsingF.Eof(expr);
×
182
        return session => PresentSubtotal(res, trav, session);
×
183
    }
×
184

185
    /// <summary>
186
    ///     执行记账凭证检索式并呈现记账凭证
187
    /// </summary>
188
    /// <param name="query">记账凭证检索式</param>
189
    /// <param name="session">客户端会话</param>
190
    /// <returns>记账凭证表达式</returns>
191
    private IAsyncEnumerable<string> PresentVoucherQuery(IQueryCompounded<IVoucherQueryAtom> query, Session session)
192
        => session.Serializer.PresentVouchers(session.Accountant.SelectVouchersAsync(query));
1✔
193

194
    /// <summary>
195
    ///     按细目检索式解析
196
    /// </summary>
197
    /// <param name="expr">表达式</param>
198
    /// <param name="safe">仅限强检索式</param>
199
    /// <param name="client">客户端</param>
200
    /// <returns>执行结果</returns>
201
    private Func<Session, IAsyncEnumerable<string>> TryDetailQuery(string expr, bool safe, Client client)
202
    {
×
203
        var res = ParsingF.DetailQuery(ref expr, client);
×
204
        ParsingF.Eof(expr);
×
205
        if (res.IsDangerous() && safe)
×
206
            throw new SecurityException("检测到弱检索式");
×
207

208
        return session => PresentDetailQuery(res, session);
×
209
    }
×
210

211
    /// <summary>
212
    ///     执行细目检索式并呈现结果
213
    /// </summary>
214
    /// <param name="query">细目检索式</param>
215
    /// <param name="session">客户端会话</param>
216
    /// <returns>执行结果</returns>
217
    private IAsyncEnumerable<string> PresentDetailQuery(IVoucherDetailQuery query, Session session)
218
        => session.Serializer.PresentVoucherDetails(session.Accountant.SelectVoucherDetailsAsync(query));
×
219

220
    /// <summary>
221
    ///     按带记账凭证的细目检索式解析
222
    /// </summary>
223
    /// <param name="expr">表达式</param>
224
    /// <param name="safe">仅限强检索式</param>
225
    /// <param name="client">客户端</param>
226
    /// <returns>执行结果</returns>
227
    private Func<Session, IAsyncEnumerable<string>> TryDetailRQuery(string expr, bool safe, Client client)
228
    {
2✔
229
        var res = ParsingF.DetailQuery(ref expr, client);
2✔
230
        ParsingF.Eof(expr);
2✔
231
        if (res.IsDangerous() && safe)
2✔
232
            throw new SecurityException("检测到弱检索式");
1✔
233

234
        return session => PresentDetailRQuery(res, session);
1✔
235
    }
1✔
236

237
    /// <summary>
238
    ///     执行带记账凭证的细目检索式并呈现结果
239
    /// </summary>
240
    /// <param name="query">细目检索式</param>
241
    /// <param name="session">客户端会话</param>
242
    /// <returns>执行结果</returns>
243
    private IAsyncEnumerable<string> PresentDetailRQuery(IVoucherDetailQuery query, Session session)
244
        => session.Serializer.PresentVoucherDetails(
1✔
245
            session.Accountant.SelectVouchersAsync(query.VoucherQuery).SelectMany(
246
                v => v.Details.Where(d => d.IsMatch(query.ActualDetailFilter()))
4✔
247
                    .Select(d => new VoucherDetailR(v, d)).ToAsyncEnumerable()));
2✔
248

249
    /// <summary>
250
    ///     按记账凭证检索式解析,但仅保留部分细目
251
    /// </summary>
252
    /// <param name="expr">表达式</param>
253
    /// <param name="safe">仅限强检索式</param>
254
    /// <param name="client">客户端</param>
255
    /// <returns>执行结果</returns>
256
    private Func<Session, IAsyncEnumerable<string>> TryFancyQuery(string expr, bool safe, Client client)
257
    {
1✔
258
        var res = ParsingF.DetailQuery(ref expr, client);
1✔
259
        ParsingF.Eof(expr);
1✔
260
        if (res.IsDangerous() && safe)
1✔
261
            throw new SecurityException("检测到弱检索式");
×
262

263
        return session => PresentFancyQuery(res, session);
1✔
264
    }
1✔
265

266
    /// <summary>
267
    ///     执行记账凭证检索式,保留部分细目并呈现结果
268
    /// </summary>
269
    /// <param name="query">带记账凭证的细目检索式</param>
270
    /// <param name="session">客户端会话</param>
271
    /// <returns>执行结果</returns>
272
    private IAsyncEnumerable<string> PresentFancyQuery(IVoucherDetailQuery query, Session session)
273
        => session.Serializer.PresentVouchers(
1✔
274
            session.Accountant.SelectVouchersAsync(query.VoucherQuery).Select(v =>
275
                {
1✔
276
                    v.Details.RemoveAll(d => !d.IsMatch(query.ActualDetailFilter()));
4✔
277
                    return v;
1✔
278
                }));
1✔
279

280
    /// <summary>
281
    ///     执行记账凭证分类汇总检索式并呈现结果
282
    /// </summary>
283
    /// <param name="query">记账凭证分类汇总检索式</param>
284
    /// <param name="trav">呈现器</param>
285
    /// <param name="session">客户端会话</param>
286
    /// <returns>执行结果</returns>
287
    private async IAsyncEnumerable<string> PresentSubtotal(IVoucherGroupedQuery query, ISubtotalStringify trav,
288
        Session session)
289
    {
×
290
        var result = await session.Accountant.SelectVouchersGroupedAsync(query);
×
291
        await foreach (var s in trav.PresentSubtotal(result, query.Subtotal, session.Serializer))
×
292
            yield return s;
×
293
    }
×
294

295
    /// <summary>
296
    ///     执行分类汇总检索式并呈现结果
297
    /// </summary>
298
    /// <param name="query">分类汇总检索式</param>
299
    /// <param name="trav">呈现器</param>
300
    /// <param name="session">客户端会话</param>
301
    /// <returns>执行结果</returns>
302
    private async IAsyncEnumerable<string> PresentSubtotal(IGroupedQuery query, ISubtotalStringify trav,
303
        Session session)
304
    {
1✔
305
        var result = await session.Accountant.SelectVoucherDetailsGroupedAsync(query);
1✔
306
        await foreach (var s in trav.PresentSubtotal(result, query.Subtotal, session.Serializer))
2✔
307
            yield return s;
1✔
308
    }
1✔
309

310
    [Flags]
311
    private enum ExprType
312
    {
313
        None = 0b00000000,
314
        GroupedQueries = 0b00000011,
315
        GroupedQuery = 0b00000001,
316
        VoucherGroupedQuery = 0b00000010,
317
        NonGroupedQueries = 0b00111100,
318
        DetailQuery = 0b00000100,
319
        DetailRQuery = 0b00001000,
320
        VoucherQuery = 0b00010000,
321
        FancyQuery = 0b00100000,
322
        Unsafe = 0b10000000,
323
    }
324
}
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

© 2026 Coveralls, Inc