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
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 безопасен.
161 lines
7.3 KiB
C#
161 lines
7.3 KiB
C#
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);
|
||
}
|
||
}
|