diff --git a/src/food-market.api/Controllers/Admin/MoySkladImportController.cs b/src/food-market.api/Controllers/Admin/MoySkladImportController.cs index 8ee19da..d6de7d2 100644 --- a/src/food-market.api/Controllers/Admin/MoySkladImportController.cs +++ b/src/food-market.api/Controllers/Admin/MoySkladImportController.cs @@ -151,6 +151,7 @@ public async Task> ImportSupplies([FromBody] ImportRequest return BadRequest(new { error = "Токен не настроен." }); var orgId = _tenant.OrganizationId ?? throw new InvalidOperationException("No tenant."); var type = HttpContext.Request.Query["type"].ToString(); // захватываем ДО Task.Run (HttpContext потом недоступен) + int startOffset = int.TryParse(HttpContext.Request.Query["offset"].ToString(), out var off) ? off : 0; var job = _jobs.Create(string.IsNullOrEmpty(type) ? "supplies" : type); job.Stage = $"Перенос документов ({(string.IsNullOrEmpty(type) ? "приёмки" : type)}) с историей…"; _ = Task.Run(async () => @@ -165,11 +166,11 @@ public async Task> ImportSupplies([FromBody] ImportRequest var pr = new foodmarket.Infrastructure.Integrations.MoySklad.MoySkladDocumentImportService.DocImportProgress(); switch (type) { - case "enter": await docs.ImportEntersAsync(token, pr, CancellationToken.None); break; - case "loss": await docs.ImportLossesAsync(token, pr, CancellationToken.None); break; - case "purchasereturn": await docs.ImportSupplierReturnsAsync(token, pr, CancellationToken.None); break; - case "retaildemand": await docs.ImportRetailSalesAsync(token, pr, CancellationToken.None); break; - default: await docs.ImportSuppliesAsync(token, 0, pr, CancellationToken.None); break; + case "enter": await docs.ImportEntersAsync(token, startOffset, pr, CancellationToken.None); break; + case "loss": await docs.ImportLossesAsync(token, startOffset, pr, CancellationToken.None); break; + case "purchasereturn": await docs.ImportSupplierReturnsAsync(token, startOffset, pr, CancellationToken.None); break; + case "retaildemand": await docs.ImportRetailSalesAsync(token, startOffset, pr, CancellationToken.None); break; + default: await docs.ImportSuppliesAsync(token, startOffset, pr, CancellationToken.None); break; } job.Total = pr.Seen; job.Created = pr.Created; job.Skipped = pr.Skipped; job.Message = $"Приёмки: перенесено {pr.Created}, пропущено {pr.Skipped}, ошибок {pr.Errors}. " + diff --git a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs index 4321a0c..66edf64 100644 --- a/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs +++ b/src/food-market.infrastructure/Integrations/MoySklad/MoySkladDocumentImportService.cs @@ -195,14 +195,14 @@ private async Task LoadMapsAsync(Guid orgId, CancellationToken ct) } // ── Оприходования (enter) ──────────────────────────────────────────────── - public async Task ImportEntersAsync(string token, DocImportProgress progress, CancellationToken ct) + public async Task ImportEntersAsync(string token, int startOffset, DocImportProgress progress, CancellationToken ct) { var orgId = _tenant.OrganizationId!.Value; progress.Entity = "enter"; var m = await LoadMapsAsync(orgId, ct); var done = (await _db.Enters.Where(x => x.MoySkladExternalId != null).Select(x => x.MoySkladExternalId!).ToListAsync(ct)).ToHashSet(); var used = (await _db.Enters.Select(x => x.Number).ToListAsync(ct)).ToHashSet(StringComparer.OrdinalIgnoreCase); string Uniq(string b, int y) { if (used.Add(b)) return b; var w = $"{b}-{y}"; if (used.Add(w)) return w; for (var n = 2; ; n++) { var c = $"{b}-{y}-{n}"; if (used.Add(c)) return c; } } - await foreach (var d in _client.StreamDocumentsAsync(token, "entity/enter", "positions,store", 0, null, ct)) + await foreach (var d in _client.StreamDocumentsAsync(token, "entity/enter", "positions,store", startOffset, null, ct)) { progress.Seen++; if (d.Id is null || done.Contains(d.Id)) { progress.Skipped++; continue; } try @@ -224,14 +224,14 @@ await foreach (var d in _client.StreamDocumentsAsync(token, "entity/ } // ── Списания (loss) ────────────────────────────────────────────────────── - public async Task ImportLossesAsync(string token, DocImportProgress progress, CancellationToken ct) + public async Task ImportLossesAsync(string token, int startOffset, DocImportProgress progress, CancellationToken ct) { var orgId = _tenant.OrganizationId!.Value; progress.Entity = "loss"; var m = await LoadMapsAsync(orgId, ct); var done = (await _db.Losses.Where(x => x.MoySkladExternalId != null).Select(x => x.MoySkladExternalId!).ToListAsync(ct)).ToHashSet(); var used = (await _db.Losses.Select(x => x.Number).ToListAsync(ct)).ToHashSet(StringComparer.OrdinalIgnoreCase); string Uniq(string b, int y) { if (used.Add(b)) return b; var w = $"{b}-{y}"; if (used.Add(w)) return w; for (var n = 2; ; n++) { var c = $"{b}-{y}-{n}"; if (used.Add(c)) return c; } } - await foreach (var d in _client.StreamDocumentsAsync(token, "entity/loss", "positions,store", 0, null, ct)) + await foreach (var d in _client.StreamDocumentsAsync(token, "entity/loss", "positions,store", startOffset, null, ct)) { progress.Seen++; if (d.Id is null || done.Contains(d.Id)) { progress.Skipped++; continue; } try @@ -253,14 +253,14 @@ await foreach (var d in _client.StreamDocumentsAsync(token, "entity/ } // ── Возвраты поставщику (purchasereturn) ───────────────────────────────── - public async Task ImportSupplierReturnsAsync(string token, DocImportProgress progress, CancellationToken ct) + public async Task ImportSupplierReturnsAsync(string token, int startOffset, DocImportProgress progress, CancellationToken ct) { var orgId = _tenant.OrganizationId!.Value; progress.Entity = "purchasereturn"; var m = await LoadMapsAsync(orgId, ct); var done = (await _db.SupplierReturns.Where(x => x.MoySkladExternalId != null).Select(x => x.MoySkladExternalId!).ToListAsync(ct)).ToHashSet(); var used = (await _db.SupplierReturns.Select(x => x.Number).ToListAsync(ct)).ToHashSet(StringComparer.OrdinalIgnoreCase); string Uniq(string b, int y) { if (used.Add(b)) return b; var w = $"{b}-{y}"; if (used.Add(w)) return w; for (var n = 2; ; n++) { var c = $"{b}-{y}-{n}"; if (used.Add(c)) return c; } } - await foreach (var d in _client.StreamDocumentsAsync(token, "entity/purchasereturn", "positions,agent,store", 0, null, ct)) + await foreach (var d in _client.StreamDocumentsAsync(token, "entity/purchasereturn", "positions,agent,store", startOffset, null, ct)) { progress.Seen++; if (d.Id is null || done.Contains(d.Id)) { progress.Skipped++; continue; } try @@ -284,14 +284,14 @@ await foreach (var d in _client.StreamDocumentsAsync(token, "entity/ } // ── Розничные продажи (retaildemand) ───────────────────────────────────── - public async Task ImportRetailSalesAsync(string token, DocImportProgress progress, CancellationToken ct) + public async Task ImportRetailSalesAsync(string token, int startOffset, DocImportProgress progress, CancellationToken ct) { var orgId = _tenant.OrganizationId!.Value; progress.Entity = "retaildemand"; var m = await LoadMapsAsync(orgId, ct); var done = (await _db.RetailSales.Where(x => x.MoySkladExternalId != null).Select(x => x.MoySkladExternalId!).ToListAsync(ct)).ToHashSet(); var used = (await _db.RetailSales.Select(x => x.Number).ToListAsync(ct)).ToHashSet(StringComparer.OrdinalIgnoreCase); string Uniq(string b, int y) { if (used.Add(b)) return b; var w = $"{b}-{y}"; if (used.Add(w)) return w; for (var n = 2; ; n++) { var c = $"{b}-{y}-{n}"; if (used.Add(c)) return c; } } - await foreach (var d in _client.StreamDocumentsAsync(token, "entity/retaildemand", "positions,store", 0, null, ct)) + await foreach (var d in _client.StreamDocumentsAsync(token, "entity/retaildemand", "positions,store", startOffset, null, ct)) { progress.Seen++; if (d.Id is null || done.Contains(d.Id)) { progress.Skipped++; continue; } try