fix(catalog): widen Article + Barcode.Code to 500 chars for real-world catalogs

Import against a live MoySklad account crashed with PostgreSQL 22001 after
loading 21500/~N products: Article column was varchar(100), but some MoySklad
items have longer internal codes, and Barcode.Code needed to grow for future
GS1 DataMatrix / Честный ЗНАК tracking codes (up to ~300 chars).

- EF config: Product.Article 100 → 500, ProductBarcode.Code 100 → 500.
- Migration Phase1e_WidenArticleBarcode (applied to dev DB).
- Defensive Trim() in the MoySklad importer for Name/Article/Barcode so even
  future schema drift won't take the whole import down.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
nurdotnet 2026-04-22 00:15:00 +05:00
parent 22502c11fd
commit c47826e015
5 changed files with 1466 additions and 9 deletions

View file

@ -126,8 +126,8 @@ await foreach (var p in _client.StreamProductsAsync(token, ct))
var product = new Product
{
OrganizationId = orgId,
Name = p.Name,
Article = article,
Name = Trim(p.Name, 500),
Article = Trim(article, 500),
Description = p.Description,
UnitOfMeasureId = baseUnit.Id,
VatRateId = vatId,
@ -197,13 +197,16 @@ private static List<ProductBarcode> ExtractBarcodes(MsProduct p)
"upce" => BarcodeType.Upce,
_ => BarcodeType.Other,
};
list.Add(new ProductBarcode { Code = code, Type = type, IsPrimary = !primarySet });
list.Add(new ProductBarcode { Code = code.Length > 500 ? code[..500] : code, Type = type, IsPrimary = !primarySet });
primarySet = true;
}
}
return list;
}
private static string? Trim(string? s, int max)
=> string.IsNullOrEmpty(s) ? s : (s.Length <= max ? s : s[..max]);
private static string? TryExtractId(string href)
{
// href like "https://api.moysklad.ru/api/remap/1.2/entity/productfolder/<guid>"

View file

@ -123,7 +123,7 @@ private static void ConfigureProduct(EntityTypeBuilder<Product> b)
{
b.ToTable("products");
b.Property(x => x.Name).HasMaxLength(500).IsRequired();
b.Property(x => x.Article).HasMaxLength(100);
b.Property(x => x.Article).HasMaxLength(500);
b.Property(x => x.MinStock).HasPrecision(18, 4);
b.Property(x => x.MaxStock).HasPrecision(18, 4);
b.Property(x => x.PurchasePrice).HasPrecision(18, 4);
@ -155,7 +155,8 @@ private static void ConfigureProductPrice(EntityTypeBuilder<ProductPrice> b)
private static void ConfigureBarcode(EntityTypeBuilder<ProductBarcode> b)
{
b.ToTable("product_barcodes");
b.Property(x => x.Code).HasMaxLength(100).IsRequired();
// Up to 500 to accommodate GS1 DataMatrix / crypto-tail tracking codes (Честный ЗНАК etc.)
b.Property(x => x.Code).HasMaxLength(500).IsRequired();
b.HasOne(x => x.Product).WithMany(p => p.Barcodes).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade);
b.HasIndex(x => new { x.OrganizationId, x.Code }).IsUnique();
}

View file

@ -0,0 +1,64 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace foodmarket.Infrastructure.Persistence.Migrations
{
/// <inheritdoc />
public partial class Phase1e_WidenArticleBarcode : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Article",
schema: "public",
table: "products",
type: "character varying(500)",
maxLength: 500,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(100)",
oldMaxLength: 100,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Code",
schema: "public",
table: "product_barcodes",
type: "character varying(500)",
maxLength: 500,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(100)",
oldMaxLength: 100);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Article",
schema: "public",
table: "products",
type: "character varying(100)",
maxLength: 100,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(500)",
oldMaxLength: 500,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Code",
schema: "public",
table: "product_barcodes",
type: "character varying(100)",
maxLength: 100,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(500)",
oldMaxLength: 500);
}
}
}

View file

@ -546,8 +546,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
.HasColumnType("uuid");
b.Property<string>("Article")
.HasMaxLength(100)
.HasColumnType("character varying(100)");
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<Guid?>("CountryOfOriginId")
.HasColumnType("uuid");
@ -648,8 +648,8 @@ protected override void BuildModel(ModelBuilder modelBuilder)
b.Property<string>("Code")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("character varying(100)");
.HasMaxLength(500)
.HasColumnType("character varying(500)");
b.Property<DateTime>("CreatedAt")
.HasColumnType("timestamp with time zone");