food-market/src/food-market.infrastructure/Persistence/Configurations/CatalogConfigurations.cs
nurdotnet 8fc9ef1a2e
Some checks are pending
CI / POS (WPF, Windows) (push) Waiting to run
CI / Backend (.NET 8) (push) Successful in 27s
CI / Web (React + Vite) (push) Successful in 23s
Docker Images / API image (push) Successful in 34s
Docker Images / Web image (push) Successful in 27s
Docker Images / Deploy stage (push) Successful in 15s
feat: strict MoySklad schema — реплика потерянного f7087e9
Main расходился с БД стейджа (Phase2c3_MsStrict в history, но код ещё ссылался на VatRate etc.) — деплой ломался. Реплицирую удаление сущностей вручную, чтобы код совпадал с таблицами.

Убрано (нет в MoySklad — не выдумываем):
- Domain: VatRate сущность целиком; Counterparty.Kind + enum CounterpartyKind; Store.Kind + enum StoreKind; Product.IsAlcohol; UnitOfMeasure.Symbol/DecimalPlaces/IsBase.
- EF: DbSet<VatRate>, ConfigureVatRate, Product.VatRate navigation, индекс Counterparty.Kind.
- DTO/Input: соответствующие поля и VatRateDto/Input.
- API: VatRatesController удалён; references в Products/Counterparties/Stores/UoM/Supplies/Retail/Stock.

Добавлено как в MoySklad:
- Product.Vat (int) + Product.VatEnabled — MoySklad держит НДС числом на товаре.
- KZ default VAT 16% — applied в сидерах и в MoySkladImportService когда товар не принёс свой vat.

MoySkladImportService:
- ResolveKind убран; CompanyType=entrepreneur→Individual (как и было).
- VatRates lookup → прямой p.Vat ?? 16 + p.Vat > 0 для VatEnabled.
- baseUnit ищется по code="796" вместо IsBase.

Web:
- types.ts: убраны CounterpartyKind/StoreKind/VatRate/Product.vatRateId/vatPercent/isAlcohol/UoM.symbol/decimalPlaces/isBase; добавлено Product.vat/vatEnabled; унифицировано unitSymbol→unitName.
- VatRatesPage удалён, роут из App.tsx тоже.
- CounterpartiesPage/StoresPage/UnitsOfMeasurePage: убраны соответствующие поля в формах.
- ProductEditPage: select "Ставка НДС" теперь с фиксированными 0/10/12/16/20 + чекбокс VatEnabled.
- Stock/RetailSale/Supply pages: unitSymbol → unitName.

deploy-stage unguarded — теперь код соответствует DB, авто-deploy безопасен.
2026-04-23 17:32:02 +05:00

161 lines
7.3 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using foodmarket.Domain.Catalog;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace foodmarket.Infrastructure.Persistence.Configurations;
internal static class CatalogConfigurations
{
public static void ConfigureCatalog(this ModelBuilder b)
{
b.Entity<Country>(ConfigureCountry);
b.Entity<Currency>(ConfigureCurrency);
b.Entity<UnitOfMeasure>(ConfigureUnit);
b.Entity<Counterparty>(ConfigureCounterparty);
b.Entity<Store>(ConfigureStore);
b.Entity<RetailPoint>(ConfigureRetailPoint);
b.Entity<ProductGroup>(ConfigureProductGroup);
b.Entity<PriceType>(ConfigurePriceType);
b.Entity<Product>(ConfigureProduct);
b.Entity<ProductPrice>(ConfigureProductPrice);
b.Entity<ProductBarcode>(ConfigureBarcode);
b.Entity<ProductImage>(ConfigureImage);
}
private static void ConfigureCountry(EntityTypeBuilder<Country> b)
{
b.ToTable("countries");
b.Property(x => x.Code).HasMaxLength(2).IsRequired();
b.Property(x => x.Name).HasMaxLength(100).IsRequired();
b.HasIndex(x => x.Code).IsUnique();
}
private static void ConfigureCurrency(EntityTypeBuilder<Currency> b)
{
b.ToTable("currencies");
b.Property(x => x.Code).HasMaxLength(3).IsRequired();
b.Property(x => x.Name).HasMaxLength(100).IsRequired();
b.Property(x => x.Symbol).HasMaxLength(5).IsRequired();
b.HasIndex(x => x.Code).IsUnique();
}
private static void ConfigureUnit(EntityTypeBuilder<UnitOfMeasure> b)
{
b.ToTable("units_of_measure");
b.Property(x => x.Code).HasMaxLength(10).IsRequired();
b.Property(x => x.Name).HasMaxLength(100).IsRequired();
b.Property(x => x.Description).HasMaxLength(500);
b.HasIndex(x => new { x.OrganizationId, x.Code }).IsUnique();
}
private static void ConfigureCounterparty(EntityTypeBuilder<Counterparty> b)
{
b.ToTable("counterparties");
b.Property(x => x.Name).HasMaxLength(255).IsRequired();
b.Property(x => x.LegalName).HasMaxLength(500);
b.Property(x => x.Bin).HasMaxLength(20);
b.Property(x => x.Iin).HasMaxLength(20);
b.Property(x => x.TaxNumber).HasMaxLength(20);
b.Property(x => x.Phone).HasMaxLength(50);
b.Property(x => x.Email).HasMaxLength(255);
b.Property(x => x.BankName).HasMaxLength(255);
b.Property(x => x.BankAccount).HasMaxLength(50);
b.Property(x => x.Bik).HasMaxLength(20);
b.Property(x => x.ContactPerson).HasMaxLength(255);
b.HasOne(x => x.Country).WithMany().HasForeignKey(x => x.CountryId).OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.OrganizationId, x.Name });
b.HasIndex(x => new { x.OrganizationId, x.Bin });
}
private static void ConfigureStore(EntityTypeBuilder<Store> b)
{
b.ToTable("stores");
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
b.Property(x => x.Code).HasMaxLength(50);
b.Property(x => x.Phone).HasMaxLength(50);
b.Property(x => x.ManagerName).HasMaxLength(200);
b.HasIndex(x => new { x.OrganizationId, x.Name });
}
private static void ConfigureRetailPoint(EntityTypeBuilder<RetailPoint> b)
{
b.ToTable("retail_points");
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
b.Property(x => x.Code).HasMaxLength(50);
b.Property(x => x.Phone).HasMaxLength(50);
b.Property(x => x.FiscalSerial).HasMaxLength(50);
b.Property(x => x.FiscalRegNumber).HasMaxLength(50);
b.HasOne(x => x.Store).WithMany().HasForeignKey(x => x.StoreId).OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.OrganizationId, x.Name });
}
private static void ConfigureProductGroup(EntityTypeBuilder<ProductGroup> b)
{
b.ToTable("product_groups");
b.Property(x => x.Name).HasMaxLength(200).IsRequired();
b.Property(x => x.Path).HasMaxLength(1000);
b.HasOne(x => x.Parent)
.WithMany(x => x.Children)
.HasForeignKey(x => x.ParentId)
.OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.OrganizationId, x.ParentId });
b.HasIndex(x => new { x.OrganizationId, x.Path });
}
private static void ConfigurePriceType(EntityTypeBuilder<PriceType> b)
{
b.ToTable("price_types");
b.Property(x => x.Name).HasMaxLength(100).IsRequired();
b.HasIndex(x => new { x.OrganizationId, x.Name }).IsUnique();
}
private static void ConfigureProduct(EntityTypeBuilder<Product> b)
{
b.ToTable("products");
b.Property(x => x.Name).HasMaxLength(500).IsRequired();
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);
b.Property(x => x.ImageUrl).HasMaxLength(1000);
b.HasOne(x => x.UnitOfMeasure).WithMany().HasForeignKey(x => x.UnitOfMeasureId).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.ProductGroup).WithMany().HasForeignKey(x => x.ProductGroupId).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.DefaultSupplier).WithMany().HasForeignKey(x => x.DefaultSupplierId).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.CountryOfOrigin).WithMany().HasForeignKey(x => x.CountryOfOriginId).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.PurchaseCurrency).WithMany().HasForeignKey(x => x.PurchaseCurrencyId).OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.OrganizationId, x.Name });
b.HasIndex(x => new { x.OrganizationId, x.Article });
b.HasIndex(x => new { x.OrganizationId, x.ProductGroupId });
b.HasIndex(x => new { x.OrganizationId, x.IsActive });
}
private static void ConfigureProductPrice(EntityTypeBuilder<ProductPrice> b)
{
b.ToTable("product_prices");
b.Property(x => x.Amount).HasPrecision(18, 4);
b.HasOne(x => x.Product).WithMany(p => p.Prices).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade);
b.HasOne(x => x.PriceType).WithMany().HasForeignKey(x => x.PriceTypeId).OnDelete(DeleteBehavior.Restrict);
b.HasOne(x => x.Currency).WithMany().HasForeignKey(x => x.CurrencyId).OnDelete(DeleteBehavior.Restrict);
b.HasIndex(x => new { x.ProductId, x.PriceTypeId }).IsUnique();
}
private static void ConfigureBarcode(EntityTypeBuilder<ProductBarcode> b)
{
b.ToTable("product_barcodes");
// 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();
}
private static void ConfigureImage(EntityTypeBuilder<ProductImage> b)
{
b.ToTable("product_images");
b.Property(x => x.Url).HasMaxLength(1000).IsRequired();
b.HasOne(x => x.Product).WithMany(p => p.Images).HasForeignKey(x => x.ProductId).OnDelete(DeleteBehavior.Cascade);
b.HasIndex(x => x.ProductId);
}
}