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

b1f6c1c4 / ProfessionalAccounting / 327

28 Apr 2025 05:37PM UTC coverage: 51.346% (-3.0%) from 54.325%
327

push

appveyor

b1f6c1c4
ci buildx

6620 of 12893 relevant lines covered (51.35%)

126.37 hits per line

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

9.18
/AccountingServer.Shell/CheckShell.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 AccountingServer.BLL.Util;
23
using AccountingServer.Entities;
24
using AccountingServer.Entities.Util;
25
using AccountingServer.Shell.Serializer;
26
using AccountingServer.Shell.Util;
27
using static AccountingServer.BLL.Parsing.Facade;
28

29
namespace AccountingServer.Shell;
30

31
/// <summary>
32
///     检验表达式解释器
33
/// </summary>
34
internal class CheckShell : IShellComponent
35
{
36
    /// <inheritdoc />
37
    public IAsyncEnumerable<string> Execute(string expr, Context ctx, string term)
38
        => expr.Rest() switch
×
39
            {
40
                "1" => BasicCheck(ctx),
1✔
41
                "2" => AdvancedCheck(ctx),
×
42
                "3" => UpsertCheck(ctx),
×
43
                var x when x.StartsWith("4", StringComparison.Ordinal) => DuplicationCheck(ctx, x.Rest()),
×
44
                _ => throw new InvalidOperationException("表达式无效"),
×
45
            };
46

47
    /// <inheritdoc />
48
    public bool IsExecutable(string expr) => expr.Initial() == "chk";
7✔
49

50
    /// <summary>
51
    ///     检查每张会计记账凭证借贷方是否相等
52
    /// </summary>
53
    /// <param name="ctx">客户端上下文</param>
54
    /// <returns>有误的会计记账凭证表达式</returns>
55
    private async IAsyncEnumerable<string> BasicCheck(Context ctx)
56
    {
1✔
57
        ctx.Identity.WillInvoke("chk-1");
1✔
58
        Voucher old = null;
1✔
59
        await foreach (var (voucher, user, curr, v) in
1✔
60
                       ctx.Accountant.SelectUnbalancedVouchersAsync(VoucherQueryUnconstrained.Instance))
1✔
61
        {
×
62
            if (old != null && voucher.ID != old.ID)
×
63
                yield return ctx.Serializer.PresentVoucher(old).Wrap();
×
64
            old = voucher;
×
65

66
            yield return $"/* {user.AsUser()} {curr.AsCurrency()}: Debit - Credit = {v:R} */\n";
×
67
        }
×
68

69
        if (old != null)
1✔
70
            yield return ctx.Serializer.PresentVoucher(old).Wrap();
×
71
    }
1✔
72

73
    /// <summary>
74
    ///     检查每科目每内容借贷方向
75
    /// </summary>
76
    /// <param name="ctx">客户端上下文</param>
77
    /// <returns>发生错误的信息</returns>
78
    private async IAsyncEnumerable<string> AdvancedCheck(Context ctx)
79
    {
×
80
        ctx.Identity.WillInvoke("chk-2");
×
81
        foreach (var title in TitleManager.Titles)
×
82
        {
×
83
            if (!title.IsVirtual)
×
84
                if (Math.Abs(title.Direction) == 1)
×
85
                    await foreach (var s in DoCheck(
×
86
                                       ctx.Accountant
87
                                           .RunVoucherQueryAsync(
88
                                               $"{title.Id.AsTitle()}00 {(title.Direction < 0 ? ">" : "<")} G")
89
                                           .SelectMany(v
90
                                               => v.Details.Where(d => d.Title == title.Id)
×
91
                                                   .Select(d => (Voucher: v, Detail: d)).ToAsyncEnumerable()),
×
92
                                       $"{title.Id.AsTitle()}00"))
93
                        yield return s;
×
94
                else if (Math.Abs(title.Direction) == 2)
×
95
                    foreach (var s in DoCheck(
×
96
                                 title.Direction,
97
                                 await ctx.Accountant.RunGroupedQueryAsync($"{title.Id.AsTitle()}00 G`CcD"),
98
                                 $"{title.Id.AsTitle()}00"))
99
                        yield return s;
×
100

101
            foreach (var subTitle in title.SubTitles)
×
102
                if (Math.Abs(subTitle.Direction) == 1)
×
103
                    await foreach (var s in DoCheck(
×
104
                                       ctx.Accountant.RunVoucherQueryAsync(
105
                                               $"{title.Id.AsTitle()}{subTitle.Id.AsSubTitle()} {(subTitle.Direction < 0 ? ">" : "<")} G")
106
                                           .SelectMany(v
107
                                               => v.Details.Where(d => d.Title == title.Id && d.SubTitle == subTitle.Id)
×
108
                                                   .Select(d => (Voucher: v, Detail: d)).ToAsyncEnumerable()),
×
109
                                       $"{title.Id.AsTitle()}{subTitle.Id.AsSubTitle()}"))
110
                        yield return s;
×
111
                else if (Math.Abs(subTitle.Direction) == 2)
×
112
                    foreach (var s in DoCheck(
×
113
                                 subTitle.Direction,
114
                                 await ctx.Accountant.RunGroupedQueryAsync(
115
                                     $"{title.Id.AsTitle()}{subTitle.Id.AsSubTitle()} G`CcD"),
116
                                 $"{title.Id.AsTitle()}{subTitle.Id.AsSubTitle()}"))
117
                        yield return s;
×
118
        }
×
119
    }
×
120

121
    private static IEnumerable<string> DoCheck(int dir, ISubtotalResult res, string info)
122
    {
×
123
        foreach (var grpC in res.Items.Cast<ISubtotalCurrency>())
×
124
        foreach (var grpc in grpC.Items.Cast<ISubtotalContent>())
×
125
        foreach (var grpd in grpc.Items.Cast<ISubtotalDate>())
×
126
            switch (dir)
×
127
            {
128
                case > 0 when grpd.Fund.IsNonNegative():
×
129
                case < 0 when grpd.Fund.IsNonPositive():
×
130
                    continue;
×
131
                default:
132
                    yield return $"{grpd.Date:yyyyMMdd} {info} {grpc.Content}:{grpC.Currency.AsCurrency()} {grpd.Fund:R}\n";
×
133
                    break;
×
134
            }
135
    }
×
136

137
    private static async IAsyncEnumerable<string> DoCheck(IAsyncEnumerable<(Voucher Voucher, VoucherDetail Detail)> res,
138
        string info)
139
    {
×
140
        await foreach (var (v, d) in res)
×
141
        {
×
142
            if (d.Remark == "reconciliation")
×
143
                continue;
×
144

145
            yield return $"{v.ID} {v.Date:yyyyMMdd} {info} {d.Content}:{d.Fund!.Value:R}\n";
×
146
        }
×
147
    }
×
148

149
    private async IAsyncEnumerable<string> UpsertCheck(Context ctx)
150
    {
×
151
        ctx.Identity.WillInvoke("chk-3");
×
152
        {
×
153
            yield return "Reading vouchers...\n";
×
154
            var lst = await ctx.Accountant.SelectVouchersAsync(VoucherQueryUnconstrained.Instance).ToListAsync();
×
155
            yield return $"Read {lst.Count} vouchers, writing...\n";
×
156
            await ctx.Accountant.UpsertAsync(lst);
×
157
        }
×
158
        {
×
159
            yield return "Reading assets...\n";
×
160
            var lst = await ctx.Accountant.SelectAssetsAsync(DistributedQueryUnconstrained.Instance).ToListAsync();
×
161
            yield return $"Read {lst.Count} assets, writing...\n";
×
162
            await ctx.Accountant.UpsertAsync(lst);
×
163
        }
×
164
        {
×
165
            yield return "Reading amorts...\n";
×
166
            var lst = await ctx.Accountant.SelectAmortizationsAsync(DistributedQueryUnconstrained.Instance).ToListAsync();
×
167
            yield return $"Read {lst.Count} amorts, writing...\n";
×
168
            await ctx.Accountant.UpsertAsync(lst);
×
169
        }
×
170
        yield return "Written\n";
×
171
    }
×
172

173
    private async IAsyncEnumerable<string> DuplicationCheck(Context ctx, string expr)
174
    {
×
175
        ctx.Identity.WillInvoke("chk-4");
×
176
        var query = Parsing.VoucherQuery(ref expr, ctx.Client);
×
177
        Parsing.Eof(expr);
×
178
        await foreach (var (v, ids) in ctx.Accountant.SelectDuplicatedVouchersAsync(query))
×
179
        {
×
180
            yield return $"// Date = {v.Date.AsDate()} Duplication = {ids.Count}\n";
×
181
            foreach (var id in ids)
×
182
                yield return $"//   ^{id}^\n";
×
183
            yield return ctx.Serializer.PresentVoucher(v).Wrap();
×
184
        }
×
185
    }
×
186
}
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