completed invetory updates
This commit is contained in:
@@ -0,0 +1,29 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class BrandConfiguration : IEntityTypeConfiguration<Brand>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Brand> builder)
|
||||
{
|
||||
builder.ToTable("brands");
|
||||
builder.HasKey(b => b.BrandId);
|
||||
|
||||
builder.Property(b => b.Name).IsRequired().HasMaxLength(200);
|
||||
builder.HasIndex(b => b.Name).IsUnique();
|
||||
|
||||
builder.Property(b => b.Status)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired()
|
||||
.HasDefaultValue(EntityStatus.Active);
|
||||
|
||||
builder.Property(b => b.CreatedAt).IsRequired();
|
||||
|
||||
// PostgreSQL xmin system column as the optimistic concurrency token (ETag).
|
||||
builder.Property(b => b.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasIndex(b => b.Status);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
@@ -12,12 +13,17 @@ public sealed class CategoryConfiguration : IEntityTypeConfiguration<Category>
|
||||
builder.HasKey(c => c.CategoryId);
|
||||
|
||||
builder.Property(c => c.Name).IsRequired().HasMaxLength(200);
|
||||
builder.HasIndex(c => c.Name).IsUnique();
|
||||
|
||||
builder.HasOne(c => c.Parent)
|
||||
.WithMany(c => c.Children)
|
||||
.HasForeignKey(c => c.ParentId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
builder.Property(c => c.Status)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired()
|
||||
.HasDefaultValue(EntityStatus.Active);
|
||||
|
||||
builder.HasIndex(c => c.ParentId);
|
||||
builder.Property(c => c.CreatedAt).IsRequired();
|
||||
|
||||
// PostgreSQL xmin system column as the optimistic concurrency token (ETag).
|
||||
builder.Property(c => c.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasIndex(c => c.Status);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ public sealed class ItemConfiguration : IEntityTypeConfiguration<Item>
|
||||
builder.Property(i => i.Description).HasMaxLength(1000);
|
||||
builder.Property(i => i.TaxClass).HasMaxLength(20);
|
||||
|
||||
builder.Property(i => i.ItemType)
|
||||
builder.Property(i => i.StockNature)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(i => i.TrackingMode)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
@@ -37,6 +37,16 @@ public sealed class ItemConfiguration : IEntityTypeConfiguration<Item>
|
||||
.HasForeignKey(i => i.CategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(i => i.SubCategory)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.SubCategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(i => i.Brand)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.BrandId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasOne(i => i.BaseUom)
|
||||
.WithMany()
|
||||
.HasForeignKey(i => i.BaseUomId)
|
||||
@@ -49,5 +59,6 @@ public sealed class ItemConfiguration : IEntityTypeConfiguration<Item>
|
||||
|
||||
builder.HasIndex(i => i.Status);
|
||||
builder.HasIndex(i => i.CategoryId);
|
||||
builder.HasIndex(i => i.BrandId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the ItemType master (Color/Size/Material). Note there are deliberately no
|
||||
/// relationships here — nothing references this table (docs/10 Part C.9).
|
||||
/// </summary>
|
||||
public sealed class ItemTypeConfiguration : IEntityTypeConfiguration<ItemType>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ItemType> builder)
|
||||
{
|
||||
builder.ToTable("item_types");
|
||||
builder.HasKey(t => t.ItemTypeId);
|
||||
|
||||
builder.Property(t => t.Name).IsRequired().HasMaxLength(200);
|
||||
builder.HasIndex(t => t.Name).IsUnique();
|
||||
|
||||
builder.Property(t => t.Status)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired()
|
||||
.HasDefaultValue(EntityStatus.Active);
|
||||
|
||||
builder.Property(t => t.CreatedAt).IsRequired();
|
||||
|
||||
// PostgreSQL xmin system column as the optimistic concurrency token (ETag).
|
||||
builder.Property(t => t.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasIndex(t => t.Status);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
/// <summary>
|
||||
/// Configures the singleton product-configuration row (FR-MD-11). The check constraint
|
||||
/// is what makes "singleton" a database guarantee rather than a convention.
|
||||
/// </summary>
|
||||
public sealed class ProductConfigConfiguration : IEntityTypeConfiguration<ProductConfig>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ProductConfig> builder)
|
||||
{
|
||||
// The column is created as quoted PascalCase ("ConfigId"), so the constraint must
|
||||
// quote it too — an unquoted config_id would fold to a column that does not exist.
|
||||
builder.ToTable("product_config", t =>
|
||||
t.HasCheckConstraint("ck_product_config_singleton", $"\"ConfigId\" = {ProductConfig.SingletonId}"));
|
||||
|
||||
builder.HasKey(c => c.ConfigId);
|
||||
|
||||
// The id is fixed, never generated — there is exactly one row, seeded by DataSeeder.
|
||||
builder.Property(c => c.ConfigId).ValueGeneratedNever();
|
||||
|
||||
builder.Property(c => c.SubcategoriesEnabled).IsRequired().HasDefaultValue(true);
|
||||
builder.Property(c => c.BrandsEnabled).IsRequired().HasDefaultValue(true);
|
||||
builder.Property(c => c.ItemTypesEnabled).IsRequired().HasDefaultValue(true);
|
||||
|
||||
// PostgreSQL xmin system column as the optimistic concurrency token (ETag).
|
||||
builder.Property(c => c.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(c => c.UpdatedByUser)
|
||||
.WithMany()
|
||||
.HasForeignKey(c => c.UpdatedBy)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class SubCategoryConfiguration : IEntityTypeConfiguration<SubCategory>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<SubCategory> builder)
|
||||
{
|
||||
builder.ToTable("subcategories");
|
||||
builder.HasKey(s => s.SubCategoryId);
|
||||
|
||||
builder.Property(s => s.Name).IsRequired().HasMaxLength(200);
|
||||
|
||||
builder.Property(s => s.Status)
|
||||
.HasConversion<string>().HasMaxLength(20).IsRequired()
|
||||
.HasDefaultValue(EntityStatus.Active);
|
||||
|
||||
builder.Property(s => s.CreatedAt).IsRequired();
|
||||
|
||||
// PostgreSQL xmin system column as the optimistic concurrency token (ETag).
|
||||
builder.Property(s => s.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(s => s.Category)
|
||||
.WithMany(c => c.SubCategories)
|
||||
.HasForeignKey(s => s.CategoryId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Names need only be unique within their parent category.
|
||||
builder.HasIndex(s => new { s.CategoryId, s.Name }).IsUnique();
|
||||
builder.HasIndex(s => s.Status);
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,13 @@ namespace ERPCore.Infra.Persistence;
|
||||
/// </summary>
|
||||
public static class DataSeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// Item type names the frontend builder has always assumed exist (they were hardcoded
|
||||
/// while it ran on mock data). Seeded so the dropdown is not empty on a fresh database;
|
||||
/// users add their own (e.g. Material) from the admin screen.
|
||||
/// </summary>
|
||||
private static readonly string[] StandardItemTypes = ["Color", "Size"];
|
||||
|
||||
private static readonly (string Code, string Description, ReasonContext Context)[] StandardReasonCodes =
|
||||
[
|
||||
("DMG", "Damage", ReasonContext.Adjustment),
|
||||
@@ -26,6 +33,15 @@ public static class DataSeeder
|
||||
];
|
||||
|
||||
public static async Task SeedAsync(ErpDbContext db, CancellationToken ct = default)
|
||||
{
|
||||
var dirty = await SeedReasonCodesAsync(db, ct);
|
||||
dirty |= await SeedItemTypesAsync(db, ct);
|
||||
dirty |= await SeedProductConfigAsync(db, ct);
|
||||
|
||||
if (dirty) await db.SaveChangesAsync(ct);
|
||||
}
|
||||
|
||||
private static async Task<bool> SeedReasonCodesAsync(ErpDbContext db, CancellationToken ct)
|
||||
{
|
||||
var existing = await db.ReasonCodes
|
||||
.Select(r => new { r.Context, r.Code })
|
||||
@@ -37,9 +53,44 @@ public static class DataSeeder
|
||||
.Select(r => new ReasonCode { Code = r.Code, Description = r.Description, Context = r.Context })
|
||||
.ToList();
|
||||
|
||||
if (toAdd.Count == 0) return;
|
||||
if (toAdd.Count == 0) return false;
|
||||
|
||||
db.ReasonCodes.AddRange(toAdd);
|
||||
await db.SaveChangesAsync(ct);
|
||||
return true;
|
||||
}
|
||||
|
||||
private static async Task<bool> SeedItemTypesAsync(ErpDbContext db, CancellationToken ct)
|
||||
{
|
||||
var have = await db.ItemTypes.Select(t => t.Name).ToListAsync(ct);
|
||||
|
||||
var toAdd = StandardItemTypes
|
||||
.Where(name => !have.Contains(name, StringComparer.OrdinalIgnoreCase))
|
||||
.Select(name => new ItemType { Name = name, Status = EntityStatus.Active, CreatedAt = DateTime.UtcNow })
|
||||
.ToList();
|
||||
|
||||
if (toAdd.Count == 0) return false;
|
||||
|
||||
db.ItemTypes.AddRange(toAdd);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensures the singleton product-config row exists (FR-MD-11). Migration #2 inserts it,
|
||||
/// so this only fires for a database built some other way — but without it every Item
|
||||
/// write would 404 on the missing config, so it is worth the one query at startup.
|
||||
/// New deployments start with all features on.
|
||||
/// </summary>
|
||||
private static async Task<bool> SeedProductConfigAsync(ErpDbContext db, CancellationToken ct)
|
||||
{
|
||||
if (await db.ProductConfig.AnyAsync(c => c.ConfigId == ProductConfig.SingletonId, ct)) return false;
|
||||
|
||||
db.ProductConfig.Add(new ProductConfig
|
||||
{
|
||||
ConfigId = ProductConfig.SingletonId,
|
||||
SubcategoriesEnabled = true,
|
||||
BrandsEnabled = true,
|
||||
ItemTypesEnabled = true
|
||||
});
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore;
|
||||
namespace ERPCore.Infra.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// EF Core context for the ERP database. The 38 Phase 1 entities and their
|
||||
/// EF Core context for the ERP database. The 42 Phase 1 entities and their
|
||||
/// <see cref="IEntityTypeConfiguration{TEntity}"/> configurations are added under
|
||||
/// Domain/Entities and Infra/Persistence/Configurations as they are implemented.
|
||||
/// The authoritative schema lives in docs/10-BACKEND-PHASE1.md — do not invent it here.
|
||||
@@ -23,6 +23,10 @@ public class ErpDbContext : DbContext
|
||||
|
||||
// --- Master Data (docs/10 Part C.1) ---
|
||||
public DbSet<Category> Categories => Set<Category>();
|
||||
public DbSet<SubCategory> SubCategories => Set<SubCategory>();
|
||||
public DbSet<Brand> Brands => Set<Brand>();
|
||||
/// <summary>Color/Size/Material dimension names. Unlinked to Item by design (docs/10 C.9).</summary>
|
||||
public DbSet<ItemType> ItemTypes => Set<ItemType>();
|
||||
public DbSet<Uom> Uoms => Set<Uom>();
|
||||
public DbSet<UomConversion> UomConversions => Set<UomConversion>();
|
||||
public DbSet<Item> Items => Set<Item>();
|
||||
@@ -30,6 +34,8 @@ public class ErpDbContext : DbContext
|
||||
public DbSet<Vendor> Vendors => Set<Vendor>();
|
||||
public DbSet<Warehouse> Warehouses => Set<Warehouse>();
|
||||
public DbSet<Bin> Bins => Set<Bin>();
|
||||
/// <summary>Singleton row (FR-MD-11).</summary>
|
||||
public DbSet<ProductConfig> ProductConfig => Set<ProductConfig>();
|
||||
|
||||
// --- Cross-cutting (docs/10 Part C.7) ---
|
||||
public DbSet<User> Users => Set<User>();
|
||||
|
||||
+2454
File diff suppressed because it is too large
Load Diff
+442
@@ -0,0 +1,442 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the Brand / SubCategory / ItemType masters and the singleton product config,
|
||||
/// and converts CATEGORY from a self-nesting tree into a fixed two-level
|
||||
/// Category → SubCategory hierarchy (docs/10 Part C.1).
|
||||
/// <para>
|
||||
/// <b>This migration carries data, not just DDL.</b> The scaffolded version dropped
|
||||
/// <c>categories.ParentId</c> outright, which would have silently flattened every
|
||||
/// child category into a root and left items pointing at what is now a top-level
|
||||
/// category — losing the parent entirely. The hand-written steps below (marked
|
||||
/// "data migration") move child categories into <c>subcategories</c> and repoint items
|
||||
/// onto the correct (category, subcategory) pair before the column goes away.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public partial class AddBrandsSubcategoriesItemTypesAndProductConfig : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
// NOTE: the ParentId drop is deliberately deferred to the bottom of this method —
|
||||
// the data migration reads it. Order here is load-bearing.
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "ItemType",
|
||||
table: "items",
|
||||
newName: "StockNature");
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "BrandId",
|
||||
table: "items",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "SubCategoryId",
|
||||
table: "items",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CreatedAt",
|
||||
table: "categories",
|
||||
type: "timestamp with time zone",
|
||||
nullable: false,
|
||||
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "Status",
|
||||
table: "categories",
|
||||
type: "character varying(20)",
|
||||
maxLength: 20,
|
||||
nullable: false,
|
||||
defaultValue: "Active");
|
||||
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "UpdatedAt",
|
||||
table: "categories",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<uint>(
|
||||
name: "xmin",
|
||||
table: "categories",
|
||||
type: "xid",
|
||||
rowVersion: true,
|
||||
nullable: false,
|
||||
defaultValue: 0u);
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "brands",
|
||||
columns: table => new
|
||||
{
|
||||
BrandId = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_brands", x => x.BrandId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "item_types",
|
||||
columns: table => new
|
||||
{
|
||||
ItemTypeId = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_item_types", x => x.ItemTypeId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "product_config",
|
||||
columns: table => new
|
||||
{
|
||||
ConfigId = table.Column<int>(type: "integer", nullable: false),
|
||||
SubcategoriesEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
BrandsEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
ItemTypesEnabled = table.Column<bool>(type: "boolean", nullable: false, defaultValue: true),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
UpdatedBy = table.Column<int>(type: "integer", nullable: true),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_product_config", x => x.ConfigId);
|
||||
table.CheckConstraint("ck_product_config_singleton", "\"ConfigId\" = 1");
|
||||
table.ForeignKey(
|
||||
name: "FK_product_config_users_UpdatedBy",
|
||||
column: x => x.UpdatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "subcategories",
|
||||
columns: table => new
|
||||
{
|
||||
SubCategoryId = table.Column<int>(type: "integer", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
CategoryId = table.Column<int>(type: "integer", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false, defaultValue: "Active"),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
UpdatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: true),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_subcategories", x => x.SubCategoryId);
|
||||
table.ForeignKey(
|
||||
name: "FK_subcategories_categories_CategoryId",
|
||||
column: x => x.CategoryId,
|
||||
principalTable: "categories",
|
||||
principalColumn: "CategoryId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DATA MIGRATION — must run before ParentId is dropped.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Existing categories predate CreatedAt; the added column defaulted them to
|
||||
// 0001-01-01. Stamp them with the migration time instead of a sentinel date.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE categories SET ""CreatedAt"" = NOW() AT TIME ZONE 'utc';
|
||||
");
|
||||
|
||||
// Carry the old category id alongside each new subcategory so items can be
|
||||
// repointed by join below. Dropped again once the repoint is done.
|
||||
migrationBuilder.Sql(@"
|
||||
ALTER TABLE subcategories ADD COLUMN legacy_category_id integer;
|
||||
");
|
||||
|
||||
// Walk the old tree to its roots. The previous model allowed unlimited nesting,
|
||||
// but the new one is exactly two levels — so a category at any depth below the
|
||||
// root collapses into a subcategory of its ROOT ancestor (a grandchild cannot
|
||||
// become a subcategory of its immediate parent, since that parent is itself
|
||||
// ceasing to be a category).
|
||||
migrationBuilder.Sql(@"
|
||||
WITH RECURSIVE tree AS (
|
||||
SELECT ""CategoryId"", ""ParentId"", ""Name"", ""CategoryId"" AS root_id
|
||||
FROM categories
|
||||
WHERE ""ParentId"" IS NULL
|
||||
UNION ALL
|
||||
SELECT c.""CategoryId"", c.""ParentId"", c.""Name"", t.root_id
|
||||
FROM categories c
|
||||
JOIN tree t ON c.""ParentId"" = t.""CategoryId""
|
||||
)
|
||||
INSERT INTO subcategories (""Name"", ""CategoryId"", ""Status"", ""CreatedAt"", legacy_category_id)
|
||||
SELECT t.""Name"", t.root_id, 'Active', NOW() AT TIME ZONE 'utc', t.""CategoryId""
|
||||
FROM tree t
|
||||
WHERE t.""ParentId"" IS NOT NULL;
|
||||
");
|
||||
|
||||
// Repoint items: an item that pointed at a child category now carries the root
|
||||
// category plus the subcategory it actually meant.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE items i
|
||||
SET ""SubCategoryId"" = s.""SubCategoryId"",
|
||||
""CategoryId"" = s.""CategoryId""
|
||||
FROM subcategories s
|
||||
WHERE s.legacy_category_id = i.""CategoryId"";
|
||||
");
|
||||
|
||||
// The self-FK must go before the delete, or RESTRICT rejects removing a parent
|
||||
// whose own child row is still present.
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_categories_categories_ParentId",
|
||||
table: "categories");
|
||||
|
||||
// Every non-root category now lives in `subcategories`, and no item references
|
||||
// one any more (repointed above), so the rows can go.
|
||||
migrationBuilder.Sql(@"
|
||||
DELETE FROM categories WHERE ""ParentId"" IS NOT NULL;
|
||||
ALTER TABLE subcategories DROP COLUMN legacy_category_id;
|
||||
");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_categories_ParentId",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "ParentId",
|
||||
table: "categories");
|
||||
|
||||
// Seed the singleton config (FR-MD-11) — all features on. Item writes read this
|
||||
// row, so it must exist before the app serves a single request.
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO product_config (""ConfigId"", ""SubcategoriesEnabled"", ""BrandsEnabled"", ""ItemTypesEnabled"")
|
||||
VALUES (1, TRUE, TRUE, TRUE)
|
||||
ON CONFLICT (""ConfigId"") DO NOTHING;
|
||||
");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_items_BrandId",
|
||||
table: "items",
|
||||
column: "BrandId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_items_SubCategoryId",
|
||||
table: "items",
|
||||
column: "SubCategoryId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_categories_Name",
|
||||
table: "categories",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_categories_Status",
|
||||
table: "categories",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_brands_Name",
|
||||
table: "brands",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_brands_Status",
|
||||
table: "brands",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_item_types_Name",
|
||||
table: "item_types",
|
||||
column: "Name",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_item_types_Status",
|
||||
table: "item_types",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_product_config_UpdatedBy",
|
||||
table: "product_config",
|
||||
column: "UpdatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_subcategories_CategoryId_Name",
|
||||
table: "subcategories",
|
||||
columns: new[] { "CategoryId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_subcategories_Status",
|
||||
table: "subcategories",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_items_brands_BrandId",
|
||||
table: "items",
|
||||
column: "BrandId",
|
||||
principalTable: "brands",
|
||||
principalColumn: "BrandId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_items_subcategories_SubCategoryId",
|
||||
table: "items",
|
||||
column: "SubCategoryId",
|
||||
principalTable: "subcategories",
|
||||
principalColumn: "SubCategoryId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reverses the schema change and puts the subcategory data back where it came from.
|
||||
/// <para>
|
||||
/// The scaffolded version simply dropped <c>subcategories</c>, which would have
|
||||
/// discarded exactly what <see cref="Up"/> preserved. Instead each subcategory is
|
||||
/// restored as a child category and its items are repointed back onto it. This is
|
||||
/// not perfectly lossless: the old tree's depth is gone (a former grandchild comes
|
||||
/// back as a direct child of its root), and Brand data cannot survive a schema that
|
||||
/// has nowhere to put it.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_items_brands_BrandId",
|
||||
table: "items");
|
||||
|
||||
migrationBuilder.DropForeignKey(
|
||||
name: "FK_items_subcategories_SubCategoryId",
|
||||
table: "items");
|
||||
|
||||
// Restore the parent column + self-FK first so subcategories have somewhere to
|
||||
// land, then move them back before the table is dropped.
|
||||
migrationBuilder.AddColumn<int>(
|
||||
name: "ParentId",
|
||||
table: "categories",
|
||||
type: "integer",
|
||||
nullable: true);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// DATA MIGRATION (reverse) — must run before `subcategories` is dropped.
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
ALTER TABLE categories ADD COLUMN legacy_subcategory_id integer;
|
||||
");
|
||||
|
||||
// Each subcategory becomes a child category again under the same parent.
|
||||
migrationBuilder.Sql(@"
|
||||
INSERT INTO categories (""Name"", ""ParentId"", ""CreatedAt"", ""Status"", legacy_subcategory_id)
|
||||
SELECT s.""Name"", s.""CategoryId"", s.""CreatedAt"", s.""Status"", s.""SubCategoryId""
|
||||
FROM subcategories s;
|
||||
");
|
||||
|
||||
// Items that carried a subcategory point back at the restored child category.
|
||||
migrationBuilder.Sql(@"
|
||||
UPDATE items i
|
||||
SET ""CategoryId"" = c.""CategoryId""
|
||||
FROM categories c
|
||||
WHERE c.legacy_subcategory_id = i.""SubCategoryId"";
|
||||
");
|
||||
|
||||
migrationBuilder.Sql(@"
|
||||
ALTER TABLE categories DROP COLUMN legacy_subcategory_id;
|
||||
");
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "brands");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "item_types");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "product_config");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "subcategories");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_items_BrandId",
|
||||
table: "items");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_items_SubCategoryId",
|
||||
table: "items");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_categories_Name",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_categories_Status",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "BrandId",
|
||||
table: "items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubCategoryId",
|
||||
table: "items");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CreatedAt",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "Status",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "UpdatedAt",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "xmin",
|
||||
table: "categories");
|
||||
|
||||
migrationBuilder.RenameColumn(
|
||||
name: "StockNature",
|
||||
table: "items",
|
||||
newName: "ItemType");
|
||||
|
||||
// ParentId itself was re-added at the top of this method, ahead of the reverse
|
||||
// data migration that populates it.
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_categories_ParentId",
|
||||
table: "categories",
|
||||
column: "ParentId");
|
||||
|
||||
migrationBuilder.AddForeignKey(
|
||||
name: "FK_categories_categories_ParentId",
|
||||
table: "categories",
|
||||
column: "ParentId",
|
||||
principalTable: "categories",
|
||||
principalColumn: "CategoryId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,6 +119,48 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.ToTable("bins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Brand", b =>
|
||||
{
|
||||
b.Property<int>("BrandId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("BrandId"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("BrandId");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("brands", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Category", b =>
|
||||
{
|
||||
b.Property<int>("CategoryId")
|
||||
@@ -127,17 +169,36 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("CategoryId"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<int?>("ParentId")
|
||||
.HasColumnType("integer");
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("CategoryId");
|
||||
|
||||
b.HasIndex("ParentId");
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("categories", (string)null);
|
||||
});
|
||||
@@ -273,6 +334,9 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.Property<int>("BaseUomId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("BrandId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
@@ -286,11 +350,6 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
.HasMaxLength(1000)
|
||||
.HasColumnType("character varying(1000)");
|
||||
|
||||
b.Property<string>("ItemType")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
@@ -314,6 +373,14 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<string>("StockNature")
|
||||
.IsRequired()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
|
||||
b.Property<int?>("SubCategoryId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("TaxClass")
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)");
|
||||
@@ -330,6 +397,8 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
|
||||
b.HasIndex("BaseUomId");
|
||||
|
||||
b.HasIndex("BrandId");
|
||||
|
||||
b.HasIndex("CategoryId");
|
||||
|
||||
b.HasIndex("DefaultVendorId");
|
||||
@@ -339,6 +408,8 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("SubCategoryId");
|
||||
|
||||
b.ToTable("items", (string)null);
|
||||
});
|
||||
|
||||
@@ -374,6 +445,48 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.ToTable("item_reorders", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ItemType", b =>
|
||||
{
|
||||
b.Property<int>("ItemTypeId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("ItemTypeId"));
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("ItemTypeId");
|
||||
|
||||
b.HasIndex("Name")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.ToTable("item_types", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.JournalEntryStub", b =>
|
||||
{
|
||||
b.Property<int>("JournalId")
|
||||
@@ -490,6 +603,48 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.ToTable("po_lines", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b =>
|
||||
{
|
||||
b.Property<int>("ConfigId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<bool>("BrandsEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<bool>("ItemTypesEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.Property<bool>("SubcategoriesEnabled")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("boolean")
|
||||
.HasDefaultValue(true);
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<int?>("UpdatedBy")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("ConfigId");
|
||||
|
||||
b.HasIndex("UpdatedBy");
|
||||
|
||||
b.ToTable("product_config", null, t =>
|
||||
{
|
||||
t.HasCheckConstraint("ck_product_config_singleton", "\"ConfigId\" = 1");
|
||||
});
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b =>
|
||||
{
|
||||
b.Property<int>("PoId")
|
||||
@@ -1239,6 +1394,51 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.ToTable("stock_transfer_lines", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b =>
|
||||
{
|
||||
b.Property<int>("SubCategoryId")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("integer");
|
||||
|
||||
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("SubCategoryId"));
|
||||
|
||||
b.Property<int>("CategoryId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasMaxLength(200)
|
||||
.HasColumnType("character varying(200)");
|
||||
|
||||
b.Property<uint>("RowVersion")
|
||||
.IsConcurrencyToken()
|
||||
.ValueGeneratedOnAddOrUpdate()
|
||||
.HasColumnType("xid")
|
||||
.HasColumnName("xmin");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasMaxLength(20)
|
||||
.HasColumnType("character varying(20)")
|
||||
.HasDefaultValue("Active");
|
||||
|
||||
b.Property<DateTime?>("UpdatedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("SubCategoryId");
|
||||
|
||||
b.HasIndex("Status");
|
||||
|
||||
b.HasIndex("CategoryId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("subcategories", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Uom", b =>
|
||||
{
|
||||
b.Property<int>("UomId")
|
||||
@@ -1516,16 +1716,6 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.Navigation("Warehouse");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Category", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.Category", "Parent")
|
||||
.WithMany("Children")
|
||||
.HasForeignKey("ParentId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("Parent");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "Creator")
|
||||
@@ -1616,6 +1806,11 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.Brand", "Brand")
|
||||
.WithMany()
|
||||
.HasForeignKey("BrandId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.Category", "Category")
|
||||
.WithMany()
|
||||
.HasForeignKey("CategoryId")
|
||||
@@ -1627,11 +1822,20 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
.HasForeignKey("DefaultVendorId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.HasOne("ERPCore.Domain.Entities.SubCategory", "SubCategory")
|
||||
.WithMany()
|
||||
.HasForeignKey("SubCategoryId")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("BaseUom");
|
||||
|
||||
b.Navigation("Brand");
|
||||
|
||||
b.Navigation("Category");
|
||||
|
||||
b.Navigation("DefaultVendor");
|
||||
|
||||
b.Navigation("SubCategory");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ItemReorder", b =>
|
||||
@@ -1688,6 +1892,16 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.Navigation("Warehouse");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.ProductConfig", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "UpdatedByUser")
|
||||
.WithMany()
|
||||
.HasForeignKey("UpdatedBy")
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
b.Navigation("UpdatedByUser");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.PurchaseOrder", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.User", "Creator")
|
||||
@@ -2092,6 +2306,17 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
b.Navigation("Transfer");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.SubCategory", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.Category", "Category")
|
||||
.WithMany("SubCategories")
|
||||
.HasForeignKey("CategoryId")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("Category");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.UomConversion", b =>
|
||||
{
|
||||
b.HasOne("ERPCore.Domain.Entities.Uom", "FromUom")
|
||||
@@ -2159,7 +2384,7 @@ namespace ERPCore.Infra.Persistence.Migrations
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Category", b =>
|
||||
{
|
||||
b.Navigation("Children");
|
||||
b.Navigation("SubCategories");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("ERPCore.Domain.Entities.Grn", b =>
|
||||
|
||||
Reference in New Issue
Block a user