A bejövő számlák szinkronja javítva, és kap egy admin felületet
- A parse a <reszlet> elemeket számlaként olvasta be. A TABLE_ID NEM egyedi a válaszon belül: a számlának, a tételnek és a részletfizetésnek is van sajátja, ugyanabból a kicsi számtartományból — élesben ez ütközött a számla saját azonosítójával, és az egyedi index kapta el. Az elem-felismerés mostantól szerkezeti: számla az, aminek van TABLE_ID gyereke és NINCS olyan őse, aminek szintén van. Névtől független, tehát egy jövőbeli negyedik gyerek-elem sem tud átcsúszni. - A beszúrt sor bekerül a keresőszótárba, így egy batchen belül ismétlődő TABLE_ID frissítés lesz, nem újabb beszúrás. - Soronkénti hibatűrés + "hibás" számláló: egy rossz sor eddig eldobta a batch maradékát, miközben a vízjel már elmozdult — a kimaradt sorok legközelebb sem jöttek volna vissza. - Admin → Szállítmányok → Bejövő számlák: a tükör állapota (darabszám, lefedett időszak, vízjel, teljesítés-dátum nélküliek, félretettek) és a futtató gombok. Eddig a szinkron csak konzolból volt indítható. - Sync?full=true: kényszerített teljes lekérdezés. A vízjel a TÁROLT adatból áll elő, tehát egy hiányosan indult tükör magától soha nem egészülne ki. - Raw endpoint: az InnVoice nyers válasza. A szerkezetet a dokumentáció nem rögzíti, és egyszer már vakon találgattam. Éles betöltés: 6551 számla, 12296 tétel, 0 hibás.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using LinqToDB;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Domains.DataLayer;
|
||||
using Nop.Plugin.Misc.FruitBankPlugin.Services;
|
||||
using Nop.Services.Security;
|
||||
@@ -19,9 +20,66 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Controllers;
|
||||
public class IncomingInvoiceController(
|
||||
IPermissionService permissionService,
|
||||
IncomingInvoiceSyncService syncService,
|
||||
InnVoiceApiService apiService,
|
||||
FruitBankDbContext dbContext)
|
||||
: BaseAdminController
|
||||
{
|
||||
/// <summary>A szinkron felülete: a tükör állapota és a futtató gombok.</summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Index()
|
||||
{
|
||||
if (!await permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
||||
return AccessDeniedView();
|
||||
|
||||
var invoices = dbContext.IncomingInvoices.GetAll();
|
||||
var withDate = dbContext.IncomingInvoices.GetAll().Where(invoice => invoice.PerformanceDate != null);
|
||||
|
||||
var model = new IncomingInvoiceStatusModel
|
||||
{
|
||||
InvoiceCount = await AsyncExtensions.CountAsync(invoices),
|
||||
ItemCount = await AsyncExtensions.CountAsync(dbContext.IncomingInvoiceItems.GetAll()),
|
||||
Watermark = await dbContext.IncomingInvoices.GetLatestSourceUpdatedAtAsync(),
|
||||
WithoutPerformanceDate = await AsyncExtensions.CountAsync(dbContext.IncomingInvoices.GetAllWithoutPerformanceDate()),
|
||||
SetAsideCount = await AsyncExtensions.CountAsync(invoices.Where(invoice => invoice.IsSetAside)),
|
||||
FirstPerformanceDate = await AsyncExtensions.MinAsync(withDate, invoice => invoice.PerformanceDate),
|
||||
LastPerformanceDate = await AsyncExtensions.MaxAsync(withDate, invoice => invoice.PerformanceDate)
|
||||
};
|
||||
|
||||
return View("~/Plugins/Misc.FruitBankPlugin/Areas/Admin/Views/IncomingInvoice/Index.cshtml", model);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Az InnVoice NYERS válasza, feldolgozás nélkül — a válasz szerkezetének megállapításához.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// A szolgáltató dokumentációja a mezőket felsorolja, a befoglaló elemek nevét nem. Az első éles futáson
|
||||
/// emiatt a tétel-elemek is számlaként parse-olódtak. Ez az endpoint azért van, hogy a szerkezetet ne
|
||||
/// találgatni kelljen. Olvas, tehát GET — de a szolgáltató kvótájából fogyaszt (id nélkül óránként 20).
|
||||
/// <para><paramref name="full"/> = true → a TELJES lekérdezés (szűk kvóta); egyébként a tárolt vízjeltől
|
||||
/// kért növekményes válasz, vagyis pontosan az, amit a szinkron is látna.</para>
|
||||
/// </remarks>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Raw(bool full = false, int maxChars = 8000)
|
||||
{
|
||||
if (!await permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
||||
return AccessDeniedView();
|
||||
|
||||
try
|
||||
{
|
||||
var since = full ? null : await dbContext.IncomingInvoices.GetLatestSourceUpdatedAtAsync();
|
||||
var raw = await apiService.GetIncomingInvoicesRawAsync(since);
|
||||
|
||||
return Content(
|
||||
$"--- kérés: {(full ? "TELJES" : $"updatedtime {since:yyyy-MM-dd HH:mm:ss}")} · teljes hossz: {raw.Length} karakter ---\r\n\r\n" +
|
||||
(raw.Length > maxChars ? raw[..maxChars] + "\r\n\r\n--- levágva ---" : raw),
|
||||
"text/plain; charset=utf-8");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
return Content($"HIBA: {ex.Message}", "text/plain; charset=utf-8");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>A tükör állapota — futtatás nélkül megmutatja, hol tart a szinkron.</summary>
|
||||
[HttpGet]
|
||||
public async Task<IActionResult> Status()
|
||||
@@ -54,15 +112,19 @@ public class IncomingInvoiceController(
|
||||
/// és a túllépést NEM hibával jelzi, hanem csendben kevesebb adattal — ezért ez kézi művelet marad, amíg
|
||||
/// az ütemezése nincs végiggondolva.
|
||||
/// </remarks>
|
||||
/// <param name="full">
|
||||
/// Kényszerített teljes lekérdezés a vízjeltől függetlenül — menekülőút hiányos tükörhöz. Fogyaszt a
|
||||
/// 30 naponta 10-es keretből, ezért kézzel, tudatosan.
|
||||
/// </param>
|
||||
[HttpPost]
|
||||
public async Task<IActionResult> Sync()
|
||||
public async Task<IActionResult> Sync(bool full = false)
|
||||
{
|
||||
if (!await permissionService.AuthorizeAsync(StandardPermission.Security.ACCESS_ADMIN_PANEL))
|
||||
return AccessDeniedView();
|
||||
|
||||
try
|
||||
{
|
||||
var result = await syncService.SyncAsync();
|
||||
var result = await syncService.SyncAsync(full);
|
||||
|
||||
return Json(new
|
||||
{
|
||||
@@ -71,7 +133,8 @@ public class IncomingInvoiceController(
|
||||
fetched = result.FetchedCount,
|
||||
inserted = result.InsertedCount,
|
||||
updated = result.UpdatedCount,
|
||||
skipped = result.SkippedCount
|
||||
skipped = result.SkippedCount,
|
||||
failed = result.FailedCount
|
||||
});
|
||||
}
|
||||
catch (Exception ex)
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models;
|
||||
|
||||
/// <summary>A bejövő számla tükör állapota — a szinkron-felület fejléce.</summary>
|
||||
public class IncomingInvoiceStatusModel
|
||||
{
|
||||
/// <summary>Tükrözött számlák száma.</summary>
|
||||
public int InvoiceCount { get; set; }
|
||||
|
||||
/// <summary>Tükrözött számla-tételek száma.</summary>
|
||||
public int ItemCount { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A növekményes szinkron vízjele: a tárolt <c>SourceUpdatedAt</c> maximuma.
|
||||
/// </summary>
|
||||
/// <remarks><c>null</c> = üres tükör, tehát a következő szinkron TELJES lekérdezést indít.</remarks>
|
||||
public DateTime? Watermark { get; set; }
|
||||
|
||||
/// <summary>Teljesítés-dátum nélküli számlák: ezek EGYIK hónapba sem esnek bele.</summary>
|
||||
public int WithoutPerformanceDate { get; set; }
|
||||
|
||||
/// <summary>Félretett (nem lezárt) számlák — megjelennek, de jelölve.</summary>
|
||||
public int SetAsideCount { get; set; }
|
||||
|
||||
/// <summary>A legkorábbi és a legkésőbbi teljesítés-dátum a tükörben — egy pillantásra látszik a lefedettség.</summary>
|
||||
public DateTime? FirstPerformanceDate { get; set; }
|
||||
public DateTime? LastPerformanceDate { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,164 @@
|
||||
@using Nop.Web.Framework.Infrastructure
|
||||
@model Nop.Plugin.Misc.FruitBankPlugin.Areas.Admin.Models.IncomingInvoiceStatusModel
|
||||
|
||||
@{
|
||||
ViewBag.PageTitle = "Bejövő számlák (InnVoice)";
|
||||
NopHtml.SetActiveMenuItemSystemName("FruitBank");
|
||||
}
|
||||
|
||||
@* A nop admin minden POST-ot antiforgery-tokennel véd — enélkül a szinkron 400-at adna. *@
|
||||
@Html.AntiForgeryToken()
|
||||
|
||||
<div class="content-header clearfix">
|
||||
<h1 class="float-left">Bejövő számlák · InnVoice tükör</h1>
|
||||
</div>
|
||||
|
||||
<section class="content">
|
||||
<div class="container-fluid">
|
||||
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-body py-2">
|
||||
<p class="mb-1 text-muted">
|
||||
A szállítói számlákat az admin az <strong>InnVoice-ba</strong> rögzíti; innen tükrözzük őket a havi
|
||||
ÁFA-kimutatáshoz. A hónapba a <strong>teljesítés kelte</strong> sorolja a számlát.
|
||||
</p>
|
||||
<p class="mb-0 text-muted">
|
||||
⚠️ A szolgáltató kvótája szűk: <strong>teljes lekérdezésből 30 naponta 10</strong>, id nélküli hívásból
|
||||
óránként 20. A túllépést <strong>nem hibával</strong> jelzi, hanem csendben kevesebb adattal — ezért a
|
||||
teljes lekérdezés csak üres tükörnél fut, egyébként a vízjeltől kérünk változásokat.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ── A tükör állapota ───────────────────────────────────────── *@
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-header"><strong>A tükör állapota</strong></div>
|
||||
<div class="card-body">
|
||||
<table class="table table-sm mb-0">
|
||||
<tr>
|
||||
<td style="width:280px;">Tükrözött számla</td>
|
||||
<td><strong>@Model.InvoiceCount</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tükrözött tétel</td>
|
||||
<td><strong>@Model.ItemCount</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Lefedett időszak (teljesítés)</td>
|
||||
<td>
|
||||
@if (Model.FirstPerformanceDate.HasValue)
|
||||
{
|
||||
<text>@Model.FirstPerformanceDate.Value.ToString("yyyy.MM.dd.") – @Model.LastPerformanceDate!.Value.ToString("yyyy.MM.dd.")</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">—</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Vízjel (eddig szinkronizálva)</td>
|
||||
<td>
|
||||
@if (Model.Watermark.HasValue)
|
||||
{
|
||||
<text>@Model.Watermark.Value.ToString("yyyy.MM.dd. HH:mm:ss")</text>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-warning"><strong>üres</strong> — a következő szinkron TELJES lekérdezést indít</span>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Teljesítés-dátum nélkül</td>
|
||||
<td>
|
||||
@if (Model.WithoutPerformanceDate > 0)
|
||||
{
|
||||
<span class="text-danger"><strong>@Model.WithoutPerformanceDate</strong> — ezek egyik hónapba sem esnek bele</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<text>0</text>
|
||||
}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Félretett</td>
|
||||
<td>@Model.SetAsideCount</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@* ── Műveletek ──────────────────────────────────────────────── *@
|
||||
<div class="card card-default mb-2">
|
||||
<div class="card-header"><strong>Műveletek</strong></div>
|
||||
<div class="card-body">
|
||||
<button type="button" id="btnSync" class="btn btn-primary">
|
||||
<i class="fas fa-sync"></i> Szinkronizálás
|
||||
</button>
|
||||
<button type="button" id="btnFullSync" class="btn btn-secondary ml-2">
|
||||
<i class="fas fa-history"></i> Teljes újraszinkron
|
||||
</button>
|
||||
<a href="@Url.Action("Raw", "IncomingInvoice", new { area = "Admin" })" target="_blank" class="btn btn-light ml-2">
|
||||
<i class="fas fa-code"></i> Nyers válasz
|
||||
</a>
|
||||
|
||||
<p class="text-muted mt-2 mb-0">
|
||||
A <strong>Teljes újraszinkron</strong> a vízjeltől függetlenül mindent újra lekér — csak akkor, ha a
|
||||
tükörről kiderül, hogy hiányos. Fogyaszt a 30 naponta 10-es keretből.
|
||||
</p>
|
||||
|
||||
<div id="syncResult" class="mt-3"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@* Sima script, ahogy a szomszédos admin nézetek is (ShippingCost/Index.cshtml) — nem az asp-location
|
||||
tag-helperrel, hogy ne függjön a plugin nézet-importjaitól. *@
|
||||
<script>
|
||||
(function () {
|
||||
function token() {
|
||||
var el = document.querySelector('input[name="__RequestVerificationToken"]');
|
||||
return el ? el.value : '';
|
||||
}
|
||||
|
||||
function run(full, button) {
|
||||
var box = document.getElementById('syncResult');
|
||||
var buttons = [document.getElementById('btnSync'), document.getElementById('btnFullSync')];
|
||||
buttons.forEach(function (b) { b.disabled = true; });
|
||||
box.innerHTML = '<div class="alert alert-info mb-0">Szinkron fut… nagy állománynál ez percekig tarthat.</div>';
|
||||
|
||||
fetch('@Url.Action("Sync", "IncomingInvoice", new { area = "Admin" })' + (full ? '?full=true' : ''), {
|
||||
method: 'POST',
|
||||
headers: { 'RequestVerificationToken': token() }
|
||||
})
|
||||
.then(function (r) { return r.json(); })
|
||||
.then(function (d) {
|
||||
if (!d.success) {
|
||||
box.innerHTML = '<div class="alert alert-danger mb-0"><strong>Hiba:</strong> ' + (d.message || 'ismeretlen') + '</div>';
|
||||
return;
|
||||
}
|
||||
box.innerHTML = '<div class="alert alert-success mb-0">'
|
||||
+ '<strong>Kész.</strong> Lekérve: ' + d.fetched
|
||||
+ ' · új: ' + d.inserted
|
||||
+ ' · frissítve: ' + d.updated
|
||||
+ ' · kihagyva: ' + d.skipped
|
||||
+ ' · hibás: ' + d.failed
|
||||
+ (d.wasFullLoad ? ' · <em>teljes lekérdezés volt</em>' : '')
|
||||
+ '<br/><a href="javascript:location.reload()">Állapot frissítése</a></div>';
|
||||
})
|
||||
.catch(function (e) {
|
||||
box.innerHTML = '<div class="alert alert-danger mb-0"><strong>Hálózati hiba:</strong> ' + e + '</div>';
|
||||
})
|
||||
.finally(function () { buttons.forEach(function (b) { b.disabled = false; }); });
|
||||
}
|
||||
|
||||
document.getElementById('btnSync').addEventListener('click', function () { run(false, this); });
|
||||
document.getElementById('btnFullSync').addEventListener('click', function () {
|
||||
if (confirm('Teljes újraszinkron: mindent újra lekér, és fogyaszt a 30 naponta 10-es keretből. Biztosan?')) run(true, this);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -293,6 +293,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
|
||||
shippingConfigurationItem.ChildNodes.Insert(7, marginReportMenuItem);
|
||||
|
||||
var incomingInvoiceMenuItem = new AdminMenuItem
|
||||
{
|
||||
Visible = true,
|
||||
SystemName = "FruitBank",
|
||||
Title = "Bejövő számlák",
|
||||
IconClass = "fas fa-file-invoice",
|
||||
Url = _adminMenu.GetMenuItemUrl("IncomingInvoice", "Index")
|
||||
};
|
||||
|
||||
shippingConfigurationItem.ChildNodes.Insert(8, incomingInvoiceMenuItem);
|
||||
|
||||
var preordersRootMenuItem = new AdminMenuItem
|
||||
{
|
||||
Visible = true,
|
||||
|
||||
@@ -51,9 +51,15 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
/// <summary>
|
||||
/// Lefuttatja a szinkront: üres táblánál teljes feltöltés, egyébként a vízjel óta módosultak.
|
||||
/// </summary>
|
||||
public async Task<IncomingInvoiceSyncResult> SyncAsync()
|
||||
/// <param name="forceFullLoad">
|
||||
/// Kényszerített teljes lekérdezés a vízjeltől függetlenül. Menekülőút, ha a tükörről kiderül, hogy
|
||||
/// hiányos: a vízjel a TÁROLT adatból áll elő, tehát egy hiányosan indult tükör magától soha nem
|
||||
/// egészülne ki. Tudatos, kézi művelet — a teljes lekérdezés kvótája 30 naponta 10.
|
||||
/// </param>
|
||||
public async Task<IncomingInvoiceSyncResult> SyncAsync(bool forceFullLoad = false)
|
||||
{
|
||||
var watermark = await _invoices.GetLatestSourceUpdatedAtAsync();
|
||||
var storedWatermark = await _invoices.GetLatestSourceUpdatedAtAsync();
|
||||
var watermark = forceFullLoad ? null : storedWatermark;
|
||||
var result = new IncomingInvoiceSyncResult { WasFullLoad = watermark == null };
|
||||
|
||||
List<InnVoiceIncomingInvoice> fetched;
|
||||
@@ -95,25 +101,43 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
continue;
|
||||
}
|
||||
|
||||
if (existingByTableId.TryGetValue(source.TableId, out var existing))
|
||||
// Soronkénti hibatűrés: egy rossz számla nem viheti el a maradékot. Hibatűrés nélkül az első
|
||||
// ütközés eldobta a batch hátralévő részét, és a féloldalas állapot bent maradt — a vízjel
|
||||
// viszont már elmozdult, tehát a kimaradt sorok legközelebb sem jöttek volna vissza.
|
||||
try
|
||||
{
|
||||
ApplySource(existing, source);
|
||||
await _invoices.UpdateAsync(existing);
|
||||
await ReplaceItemsAsync(existing.Id, source);
|
||||
result.UpdatedCount++;
|
||||
if (existingByTableId.TryGetValue(source.TableId, out var existing))
|
||||
{
|
||||
ApplySource(existing, source);
|
||||
await _invoices.UpdateAsync(existing);
|
||||
await ReplaceItemsAsync(existing.Id, source);
|
||||
result.UpdatedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
var created = new IncomingInvoice();
|
||||
ApplySource(created, source);
|
||||
await _invoices.InsertAsync(created);
|
||||
await ReplaceItemsAsync(created.Id, source);
|
||||
result.InsertedCount++;
|
||||
|
||||
// A szótár a ciklus ELŐTT készült; a most beszúrt sort fel kell venni bele. Enélkül egy
|
||||
// batchen belül ismétlődő TABLE_ID másodszor is beszúrásnak indulna, és az egyedi index
|
||||
// eldobná — pontosan ez bukott el az első éles futáson.
|
||||
existingByTableId[created.InnVoiceTableId] = created;
|
||||
}
|
||||
}
|
||||
else
|
||||
catch (Exception ex)
|
||||
{
|
||||
var created = new IncomingInvoice();
|
||||
ApplySource(created, source);
|
||||
await _invoices.InsertAsync(created);
|
||||
await ReplaceItemsAsync(created.Id, source);
|
||||
result.InsertedCount++;
|
||||
result.FailedCount++;
|
||||
_logger.Error($"IncomingInvoiceSync: a(z) {source.TableId} TABLE_ID-jű számla feldolgozása nem sikerült " +
|
||||
$"(szállító: {source.SupplierName}, bizonylat: {source.DocumentNumber}).", ex);
|
||||
}
|
||||
}
|
||||
|
||||
_logger.Info($"IncomingInvoiceSync kész; lekérve: {result.FetchedCount}, új: {result.InsertedCount}, " +
|
||||
$"frissítve: {result.UpdatedCount}, kihagyva: {result.SkippedCount}, teljes lekérdezés: {result.WasFullLoad}.");
|
||||
$"frissítve: {result.UpdatedCount}, kihagyva: {result.SkippedCount}, hibás: {result.FailedCount}, " +
|
||||
$"teljes lekérdezés: {result.WasFullLoad}.");
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -194,5 +218,8 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
|
||||
/// <summary>TABLE_ID nélküli, ezért nem tükrözhető sorok.</summary>
|
||||
public int SkippedCount { get; set; }
|
||||
|
||||
/// <summary>Feldolgozás közben elszállt sorok. A futás nem áll meg tőlük, de a szám nem hallgatható el.</summary>
|
||||
public int FailedCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -58,6 +58,25 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
return invoices.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A NYERS válasz, feldolgozás nélkül — diagnosztikához.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Azért van, mert az elemek felismerését egyszer már vakon találgattam, és rosszat találtam el: a
|
||||
/// tétel-elemek is számlaként parse-olódtak. A szerkezetet a dokumentáció nem rögzíti, tehát látni kell.
|
||||
/// <para><paramref name="updatedSince"/> = null → a TELJES lekérdezés (szűkös kvóta!), egyébként a
|
||||
/// növekményes út — ez utóbbi ugyanazt a választ adja, amit a szinkron látna.</para>
|
||||
/// </remarks>
|
||||
public async Task<string> GetIncomingInvoicesRawAsync(DateTime? updatedSince)
|
||||
{
|
||||
var url = updatedSince == null
|
||||
? $"{_baseUrl}/{_companyName}/incominginvoice"
|
||||
: $"{_baseUrl}/{_companyName}/incominginvoice/updatedtime/{updatedSince.Value.ToString("yyyyMMddHHmmss", CultureInfo.InvariantCulture)}";
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
}
|
||||
|
||||
private async Task<List<InnVoiceIncomingInvoice>> GetIncomingInvoicesFromUrlAsync(string url)
|
||||
{
|
||||
try
|
||||
@@ -81,11 +100,17 @@ namespace Nop.Plugin.Misc.FruitBankPlugin.Services
|
||||
{
|
||||
var document = XDocument.Parse(xml);
|
||||
|
||||
// A gyökér elem nevét a dokumentáció nem rögzíti, a mezőnevek viszont igen — ezért a TARTALOMRA
|
||||
// szűrünk: az az elem számla, amelyiknek van TABLE_ID gyereke. Így egy "invoice"/"incominginvoice"
|
||||
// elnevezés-váltás nem üres listát ad, hanem továbbra is működik.
|
||||
// A válasz szerkezete: <response> → <invoice> → benne a fejmezők, majd <tetel>, <reszlet> és
|
||||
// <attachment> gyerekek. A TABLE_ID NEM egyedi a dokumentumon belül: a számlának, a tételnek és a
|
||||
// részlet-fizetésnek is van sajátja, ugyanabból a kicsi számtartományból.
|
||||
//
|
||||
// ⚠️ Ezért a számla az az elem, aminek van TABLE_ID gyereke ÉS NINCS olyan őse, aminek szintén van.
|
||||
// Az első változat név szerint zárta ki a "tetel"-t — a <reszlet> viszont átcsúszott rajta, és
|
||||
// számlaként került be. Élesben ez ütközött a számla saját TABLE_ID-jával (1-es és 2-es érték), és
|
||||
// az egyedi index kapta el. A szerkezeti szabály névtől független: egy befoglalt elem soha nem számla.
|
||||
var invoiceElements = document.Descendants()
|
||||
.Where(element => element.Element("TABLE_ID") != null && element.Name.LocalName != "tetel");
|
||||
.Where(element => element.Element("TABLE_ID") != null
|
||||
&& !element.Ancestors().Any(ancestor => ancestor.Element("TABLE_ID") != null));
|
||||
|
||||
return [.. invoiceElements.Select(ParseIncomingInvoice)];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user