| | | 1 | | using Hangfire; |
| | | 2 | | |
| | | 3 | | namespace Services.ArmtekEtpServices; |
| | | 4 | | |
| | | 5 | | /// <summary>Плановая выгрузка взаиморасчётов и фактур из кабинета Armtek (etp.armtek.ru).</summary> |
| | | 6 | | public sealed class ArmtekEtpSyncJob |
| | | 7 | | { |
| | | 8 | | public const string JobId = "armtek-etp-settlements-sync"; |
| | | 9 | | /// <summary>Каждое утро в 07:00 по Москве (часовой пояс задаёт регистрация джоба).</summary> |
| | | 10 | | public const string DefaultCron = "0 7 * * *"; |
| | | 11 | | |
| | | 12 | | /// <summary>Один замок на все методы: плановый запуск и загрузка с кнопки не идут одновременно (одна сессия кабинет |
| | | 13 | | private const string LockResource = "armtek-etp-sync"; |
| | | 14 | | |
| | | 15 | | /// <summary>Через сколько продолжать догрузку фактур к Excel, если за раз все не вошли (лимит или суточная квота AP |
| | 1 | 16 | | public static readonly TimeSpan ContinueInvoicesAfter = TimeSpan.FromHours(1); |
| | | 17 | | |
| | | 18 | | private readonly ArmtekEtpSyncService _sync; |
| | | 19 | | private readonly IBackgroundJobClient? _jobs; |
| | | 20 | | |
| | 1 | 21 | | public ArmtekEtpSyncJob(ArmtekEtpSyncService sync, IBackgroundJobClient? jobs = null) |
| | | 22 | | { |
| | 1 | 23 | | _sync = sync; |
| | 1 | 24 | | _jobs = jobs; |
| | 1 | 25 | | } |
| | | 26 | | |
| | | 27 | | [DisableConcurrentExecution(LockResource, 0)] |
| | | 28 | | [AutomaticRetry(Attempts = 0)] |
| | 0 | 29 | | public async Task<string> ExecuteAsync() => (await _sync.SyncAsync()).ToString(); |
| | | 30 | | |
| | | 31 | | /// <summary>Догрузка за произвольный период, например истории: BackgroundJob.Enqueue<ArmtekEtpSyncJob>(j => j |
| | | 32 | | [DisableConcurrentExecution(LockResource, 0)] |
| | | 33 | | [AutomaticRetry(Attempts = 0)] |
| | 0 | 34 | | public Task<string> ExecuteRangeAsync(DateTime from, DateTime to) => ExecuteRangeAsync(from, to, null); |
| | | 35 | | |
| | | 36 | | /// <summary>Загрузка с кнопки на странице: в журнал запусков пишется пользователь.</summary> |
| | | 37 | | [DisableConcurrentExecution(LockResource, 0)] |
| | | 38 | | [AutomaticRetry(Attempts = 0)] |
| | | 39 | | public async Task<string> ExecuteRangeAsync(DateTime from, DateTime to, string? user) => |
| | 0 | 40 | | (await _sync.SyncAsync(DateOnly.FromDateTime(from), DateOnly.FromDateTime(to), |
| | 0 | 41 | | trigger: string.IsNullOrWhiteSpace(user) ? "Вручную" : user)).ToString(); |
| | | 42 | | |
| | | 43 | | /// <summary> |
| | | 44 | | /// Фактуры по API для документов взаиморасчётов за период — после импорта Excel. В кабинет не входит (взаиморасчёты |
| | | 45 | | /// только что пришли из файла), поэтому капча кабинета ему не мешает. |
| | | 46 | | /// </summary> |
| | | 47 | | [DisableConcurrentExecution(LockResource, 0)] |
| | | 48 | | [AutomaticRetry(Attempts = 0)] |
| | | 49 | | public async Task<string> ExecuteInvoicesAsync(DateTime from, DateTime to, string? user) |
| | | 50 | | { |
| | 1 | 51 | | var result = await _sync.SyncAsync(DateOnly.FromDateTime(from), DateOnly.FromDateTime(to), |
| | 1 | 52 | | trigger: "Фактуры к Excel" + (string.IsNullOrWhiteSpace(user) ? "" : " — " + user), includeSettlements: fals |
| | | 53 | | |
| | | 54 | | // Утренняя загрузка смотрит только последние дни, поэтому история из Excel (год и больше) дочитывается |
| | | 55 | | // этой же задачей порциями, пока не кончатся фактуры. Остановились на квоте (ни загрузок, ни ошибок) — тоже |
| | | 56 | | // продолжаем; если же всё упало ошибками без единой загрузки — нет, иначе задача крутилась бы вечно. |
| | 1 | 57 | | if (result.InvoicesRemaining > 0 && (result.Invoices > 0 || result.InvoiceErrors == 0) && _jobs != null) |
| | | 58 | | { |
| | 1 | 59 | | var next = _jobs.Schedule<ArmtekEtpSyncJob>(j => j.ExecuteInvoicesAsync(from, to, user), ContinueInvoicesAft |
| | 1 | 60 | | return result + $"; продолжение — задача {next} через {ContinueInvoicesAfter.TotalMinutes:0} мин"; |
| | | 61 | | } |
| | 0 | 62 | | return result.ToString(); |
| | 1 | 63 | | } |
| | | 64 | | } |