• 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

5.8
/AccountingServer.Shell/ExchangeShell.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.Threading.Tasks;
23
using System.Timers;
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

30
namespace AccountingServer.Shell;
31

32
/// <summary>
33
///     汇率
34
/// </summary>
35
internal class ExchangeShell : IShellComponent
36
{
37
    /// <summary>
38
    ///     定时登记汇率
39
    /// </summary>
40
    private readonly Timer m_Timer = new(60 * 60 * 1000) { AutoReset = true }; // hourly
10✔
41

42
    private DbSession m_TimerSession;
43

44
    public ExchangeShell()
10✔
45
        => m_Timer.Elapsed += OnTimedEvent;
10✔
46

47
    public async IAsyncEnumerable<string> Execute(string expr, Context ctx, string term)
48
    {
×
49
        ctx.Identity.WillInvoke("?e");
×
50
        expr = expr.Rest();
×
51
        var isAccurate = expr.Initial() == "acc";
×
52
        if (isAccurate)
×
53
        {
×
54
            ctx.Identity.WillInvoke("?e-acc");
×
55
            expr = expr.Rest();
×
56
        }
×
57
        var from = Parsing.Token(ref expr).ToUpperInvariant();
×
58
        var val = Parsing.DoubleF(ref expr);
×
59
        var to = Parsing.Token(ref expr)?.ToUpperInvariant() ?? BaseCurrency.Now;
×
60

61
        if (Parsing.UniqueTime(ref expr, ctx.Client) is var date && date.HasValue)
×
62
            yield return await Inquiry(ctx, date.Value, from, to, val, isAccurate);
×
63
        else if (Parsing.Range(ref expr, ctx.Client) is var rng && rng != null)
×
64
            for (var dt = rng.StartDate!.Value; dt <= rng.EndDate; dt = dt.AddMonths(1))
×
65
                yield return await Inquiry(ctx, DateHelper.LastDayOfMonth(dt.Year, dt.Month), from, to, val,
×
66
                    isAccurate);
67
        else if (isAccurate)
×
68
            yield return await Inquiry(ctx, null, from, to, val, true);
×
69
        else
70
            yield return await Inquiry(ctx, DateTime.UtcNow.AddMinutes(-30), from, to, val, false);
×
71
        Parsing.Eof(expr);
×
72
    }
×
73

74
    public bool IsExecutable(string expr) => expr.Initial() == "?e";
6✔
75

76
    private async ValueTask<string> Inquiry(Context ctx, DateTime? dt, string from, string to, double value,
77
        bool isAccurate)
78
    {
×
79
        var rate = isAccurate
×
80
            ? await ctx.Accountant.SaveHistoricalRate(dt!.Value, from, to)
81
            : await ctx.Accountant.Query(dt, from, to);
82
        var v = value * rate;
×
83
        return $"{dt.AsDate()} {from.AsCurrency()} {value.AsFund(from)} = {to.AsCurrency()} {v.AsFund(to)} ({v:R})\n";
×
84
    }
×
85

86
    /// <summary>
87
    ///     启动定时登记汇率
88
    /// </summary>
89
    public void EnableTimer(DbSession db)
90
    {
×
91
        m_TimerSession = db;
×
92
 #pragma warning disable CS4014
93
        ImmediateExchange();
×
94
 #pragma warning restore CS4014
95
        m_Timer.Enabled = true;
×
96
    }
×
97

98
    /// <summary>
99
    ///     立即登记汇率
100
    /// </summary>
101
    public Task ImmediateExchange(DbSession db)
102
    {
×
103
        m_TimerSession = db;
×
104
        return ImmediateExchange();
×
105
    }
×
106

107
    private static DateTime CriticalTime(DateTime now)
108
    {
×
109
        // every T00:00:00+Z of base currency change day
110
        if (BaseCurrency.History.Any(bci => bci.Date == now.Date))
×
111
            return now.Date.AddMinutes(-5);
×
112

113
        // every T00:00:00+Z of early Sunday
114
        if (now.DayOfWeek == DayOfWeek.Sunday)
×
115
            return now.Date.AddMinutes(-5);
×
116

117
        // every T00:00:00+Z of last day of month (year)
118
        if (now.AddDays(1).Month != now.Month)
×
119
            return now.Date.AddMinutes(-5);
×
120

121
        // every 3.5 days
122
        return now.AddDays(-3.5).AddMinutes(-35);
×
123
    }
×
124

125
    // ReSharper disable once AsyncVoidMethod
126
    private void OnTimedEvent(object source, ElapsedEventArgs e)
127
 #pragma warning disable CS4014
128
        => ImmediateExchange();
×
129
 #pragma warning restore CS4014
130

131
    private async Task ImmediateExchange()
132
    {
×
133
        var date = CriticalTime(DateTime.UtcNow);
×
134
        var log = $"{DateTime.UtcNow:o} Ensuring exchange rates exist since {date:o}";
×
135
        if (m_TimerSession.ExchangeLogger != null)
×
136
            m_TimerSession.ExchangeLogger(log, false);
×
137
        else
138
            Console.WriteLine(log);
×
139
        var ctx = new Context(m_TimerSession);
×
140
        try
141
        {
×
142
            Task.WhenAll((await ctx.Accountant.RunGroupedQueryAsync("U - U Revenue - U Expense !C"))
×
143
                .Items.Cast<ISubtotalCurrency>()
144
                .Where(static grpC => !grpC.Currency.EndsWith('#'))
×
145
                .Concat((await ctx.Accountant.RunGroupedQueryAsync("U Revenue + U Expense 0 !C"))
146
                    .Items.Cast<ISubtotalCurrency>())
147
                .Select(grpC =>
148
                    ctx.Accountant.Query(date, grpC.Currency, BaseCurrency.Now).AsTask())).Wait();
×
149
        }
×
150
        catch (Exception err)
×
151
        {
×
152
            if (m_TimerSession.ExchangeLogger != null)
×
153
                m_TimerSession.ExchangeLogger(err.ToString(), true);
×
154
            else
155
                Console.WriteLine(err);
×
156
        }
×
157
    }
×
158
}
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