Refactor EKÁER mapping: unify company info, doc updates

- Introduced ICompanyInfoBase for standardized company/partner data; refactored PartnerBase and interfaces to implement it
- Replaced EkaerMappingOptions with EkaerCompanyInfo; updated all usages, constructors, and tests
- Refactored EKÁER mapping logic to use ICompanyInfoBase; improved normalization and address handling
- Added regex/validation for plate numbers and country codes; new error codes
- Added Currency to PartnerBase; updated grids to display it
- Updated ProductDto doc for GTIN/VTSZ data model issue
- Enabled validation in CargoTruck grid
- Added DMODEL topic docs: TOPIC_CODES.md, DATAMODEL_ISSUES.md, README.md
- Removed obsolete files and updated settings.local.json
- General code and doc improvements for maintainability
This commit is contained in:
Loretta 2026-06-03 16:58:47 +02:00
parent b3c82d6ad2
commit d2bdf7e4eb
7 changed files with 128 additions and 0 deletions

View File

@ -252,6 +252,8 @@ public interface IMgProductDto : IEntityInt
string ShortDescription { get; set; } string ShortDescription { get; set; }
string FullDescription { get; set; } string FullDescription { get; set; }
public string Sku { get; set; }
int WarehouseId { get; set; } int WarehouseId { get; set; }
decimal Price { get; set; } decimal Price { get; set; }
int StockQuantity { get; set; } int StockQuantity { get; set; }
@ -290,6 +292,8 @@ public abstract class MgProductDto : MgEntityBase, /*Product,*/ IMgProductDto//I
public string ShortDescription { get; set; } public string ShortDescription { get; set; }
public string FullDescription { get; set; } public string FullDescription { get; set; }
public string Sku { get; set; }
public int WarehouseId { get; set; } public int WarehouseId { get; set; }
public decimal Price { get; set; } public decimal Price { get; set; }
public int StockQuantity { get; set; } public int StockQuantity { get; set; }

View File

@ -52,6 +52,8 @@
DisableAddUser = 44, DisableAddUser = 44,
PhoneNumberFormatIsNotValid = 45, PhoneNumberFormatIsNotValid = 45,
PlateNumberFormatIsNotValid = 46,
CountryCodeFormatIsNotValid = 47,
RefreshTokenUpdateError = 50, RefreshTokenUpdateError = 50,
EmailIsNullOrEmpty = 55, EmailIsNullOrEmpty = 55,

View File

@ -12,9 +12,29 @@ public static partial class AcRegExpression
//public const string PhoneNumberMask = @"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*(\d{1,2})"; //public const string PhoneNumberMask = @"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*\d\W*(\d{1,2})";
public const string PhoneNumberMask = @"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d(\W*\d){1,14}"; public const string PhoneNumberMask = @"\+(9[976]\d|8[987530]\d|6[987]\d|5[90]\d|42\d|3[875]\d|2[98654321]\d|9[8543210]|8[6421]|6[6543210]|5[87654321]|4[987654310]|3[9643210]|2[70]|7|1)\W*\d(\W*\d){1,14}";
/// <summary>Rendszám (EKÁER-kompatibilis): nagybetű + szám (+ <c>Ö Ő Ü Ű</c>), 415 karakter — kötőjel/szóköz NÉLKÜL.</summary>
public const string PlateNumberMask = "[A-Z0-9ÖŐÜŰ]{4,15}";
/// <summary>Országkód, ISO 3166-1 alpha-2: pontosan 2 nagybetű (pl. <c>HU</c>).</summary>
public const string CountryCodeAlpha2Mask = "[A-Z]{2}";
/// <summary>Országkód: 13 nagybetű (pl. jármű-országkód az EKÁER-ben).</summary>
public const string CountryCodeMax3Mask = "[A-Z]{1,3}";
[GeneratedRegex(AcRegExpression.EmailMask)] [GeneratedRegex(AcRegExpression.EmailMask)]
public static partial Regex EmailRegex(); public static partial Regex EmailRegex();
[GeneratedRegex(AcRegExpression.PhoneNumberMask)] [GeneratedRegex(AcRegExpression.PhoneNumberMask)]
public static partial Regex PhoneNumberRegex(); public static partial Regex PhoneNumberRegex();
// A Regex-eket horgonyozzuk (^…$): a const Mask nyers (a [RegularExpression] attribútum maga
// teljes-string egyezést vár), de az IsMatch különben részstringet is elfogadna.
[GeneratedRegex("^" + AcRegExpression.PlateNumberMask + "$")]
public static partial Regex PlateNumberRegex();
[GeneratedRegex("^" + AcRegExpression.CountryCodeAlpha2Mask + "$")]
public static partial Regex CountryCodeAlpha2Regex();
[GeneratedRegex("^" + AcRegExpression.CountryCodeMax3Mask + "$")]
public static partial Regex CountryCodeMax3Regex();
} }

View File

@ -68,6 +68,36 @@ namespace AyCode.Core.Consts
return isValid; return isValid;
} }
public static bool IsValidPlateNumberFormat(string? plateNumber, out AcErrorCode acErrorCode)
{
acErrorCode = AcErrorCode.Unset;
var isValid = !string.IsNullOrWhiteSpace(plateNumber) && AcRegExpression.PlateNumberRegex().IsMatch(plateNumber);
if (!isValid) acErrorCode = AcErrorCode.PlateNumberFormatIsNotValid;
return isValid;
}
public static bool IsValidCountryCodeAlpha2Format(string? countryCode, out AcErrorCode acErrorCode)
{
acErrorCode = AcErrorCode.Unset;
var isValid = !string.IsNullOrWhiteSpace(countryCode) && AcRegExpression.CountryCodeAlpha2Regex().IsMatch(countryCode);
if (!isValid) acErrorCode = AcErrorCode.CountryCodeFormatIsNotValid;
return isValid;
}
public static bool IsValidCountryCodeMax3Format(string? countryCode, out AcErrorCode acErrorCode)
{
acErrorCode = AcErrorCode.Unset;
var isValid = !string.IsNullOrWhiteSpace(countryCode) && AcRegExpression.CountryCodeMax3Regex().IsMatch(countryCode);
if (!isValid) acErrorCode = AcErrorCode.CountryCodeFormatIsNotValid;
return isValid;
}
public static bool IsValidUserTokenFormat(string verificationToken, out AcErrorCode acErrorCode) public static bool IsValidUserTokenFormat(string verificationToken, out AcErrorCode acErrorCode)
{ {
acErrorCode = AcErrorCode.Unset; acErrorCode = AcErrorCode.Unset;

View File

@ -0,0 +1,31 @@
namespace AyCode.Core.Interfaces;
/// <summary>
/// Postai címmel és adóazonosítóval azonosított fél (cég vagy partner) — <b>általános</b>, alsó-rétegű szerződés.
/// Bárhol használható, ahol egy felet név + adószám + postai cím alapján kell egységesen kezelni (számlázás,
/// szállítás, hatósági bejelentés stb.). Több, eltérő rétegű típus is implementálhatja (egy domain-entitás és
/// egy konfig-objektum egyaránt), így a feldolgozó kód egyetlen interfészen dolgozhat, a konkrét típus ismerete nélkül.
/// </summary>
public interface ICompanyInfoBase
{
string Name { get; }
string TaxId { get; }
string CountryCode { get; }
string PostalCode { get; }
string City { get; }
string Street { get; }
/// <summary>A <see cref="PostalCode"/> + <see cref="City"/> + <see cref="Street"/> egy sorba fűzve (a nem üres részekből).</summary>
string FullAddress { get; }
}
/// <summary>Közös <see cref="ICompanyInfoBase"/> segédműveletek — hogy a <c>FullAddress</c> logikája EGY helyen éljen.</summary>
public static class CompanyInfoBaseExtensions
{
/// <summary>A <c>PostalCode</c> + <c>City</c> + <c>Street</c> összefűzése egy sorba (a nem üres részekből); üres → <c>null</c>.</summary>
public static string? ComposeFullAddress(this ICompanyInfoBase company)
{
var joined = string.Join(" ", new[] { company.PostalCode, company.City, company.Street }.Where(p => !string.IsNullOrWhiteSpace(p))).Trim();
return string.IsNullOrWhiteSpace(joined) ? null : joined;
}
}

View File

@ -0,0 +1,27 @@
using AyCode.Core.Interfaces;
using AyCode.Entities;
using AyCode.Services.Nav.Ekaer.Models;
namespace AyCode.Services.Nav.Ekaer;
/// <summary>
/// A NAV-bejelentő saját cégadatai — általános (bármely bejelentő), a DI konfigurációból tölti.
/// A leképezés <see cref="ICompanyInfoBase"/>-ként kezeli, ugyanúgy, mint a beszállító <c>Partner</c>-t.
/// </summary>
public sealed class EkaerCompanyInfo : ICompanyInfoBase
{
public string? Name { get; set; }
public string? TaxId { get; set; }
/// <summary>A bejelentő országkódja. A NAV EKÁER magyar rendszer → gyakorlatilag mindig <c>"HU"</c>.</summary>
public string? CountryCode { get; set; } = "HU";
public string? PostalCode { get; set; }
public string? City { get; set; }
public string? Street { get; set; }
public string? FullAddress => this.ComposeFullAddress();
/// <summary>A saját telephely / lerakodási hely. Magyar címnél a NAV a Name/VatNumber/Phone/Email-t is megköveteli.</summary>
public LocationType? UnloadLocation { get; set; }
}

View File

@ -0,0 +1,14 @@
namespace AyCode.Services.Nav;
/// <summary>
/// Az <see cref="INavCredentials"/> egyszerű, beállítható megvalósítása — a hívó (DI) konfigurációból
/// (appsettings / secret store) tölti. ⚠️ Soha NE hardcode-old a kulcsokat a forráskódba.
/// </summary>
public sealed class NavCredentials : INavCredentials
{
public string User { get; set; } = "";
public string Password { get; set; } = "";
public string SigningKey { get; set; } = "";
public string TaxNumber { get; set; } = "";
public string BaseUrl { get; set; } = "";
}