feat: Add new services and interfaces for GRN, Purchase Return, Reason Code, Reorder, Stock, and Transfer functionalities
- Implemented IGrnService for managing goods receipts including retrieval, creation, and confirmation. - Created IPurchaseReturnService for handling purchase return operations. - Added IReasonCodeService for managing reason codes with listing and creation capabilities. - Developed IReorderService for fetching reorder alerts and creating suggested requisitions. - Introduced IStockMutator for applying stock changes and posting ledger entries. - Established IStockService for stock inquiries, ledger retrieval, and valuation. - Created ITransferService for managing inter-warehouse transfers including dispatch and receiving operations. - Implemented PurchaseReturnService to handle purchase return logic and stock adjustments. - Developed ReasonCodeService for listing and creating reason codes. - Created ReorderService for fetching reorder alerts and generating requisitions. - Implemented FifoCostingService for FIFO cost-layer management and ledger writing. - Developed StockMutator for applying stock deltas and posting ledger entries. - Created StockService for stock inquiries and ledger management. - Implemented TransferService for managing inter-warehouse transfers with dispatch and receive functionalities.
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.ChangeTracking;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Auditing;
|
||||
|
||||
/// <summary>A mutation captured before save, awaiting its (possibly generated) key.</summary>
|
||||
public sealed record PendingAudit(EntityEntry Entry, string EntityType, AuditAction Action, string ChangeSet, long CapturedId, bool IsAdded);
|
||||
|
||||
/// <summary>
|
||||
/// Builds audit-trail rows from the EF change tracker (FR-X-02). High-volume /
|
||||
/// derived / self-referential tables are excluded (the stock ledger is itself the
|
||||
/// stock movement audit). Change sets are captured <b>before</b> save so old→new is
|
||||
/// accurate; generated keys for inserts are read <b>after</b> save.
|
||||
/// </summary>
|
||||
public static class AuditScribe
|
||||
{
|
||||
private static readonly HashSet<Type> Excluded =
|
||||
[
|
||||
typeof(AuditLog), typeof(JournalEntryStub), typeof(NumberSequence),
|
||||
typeof(StockLedger), typeof(StockLayer),
|
||||
];
|
||||
|
||||
private static readonly JsonSerializerOptions Json = new()
|
||||
{
|
||||
Converters = { new JsonStringEnumConverter() },
|
||||
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
|
||||
};
|
||||
|
||||
public static List<PendingAudit> Capture(ChangeTracker tracker)
|
||||
{
|
||||
var pending = new List<PendingAudit>();
|
||||
foreach (var entry in tracker.Entries())
|
||||
{
|
||||
if (entry.State is not (EntityState.Added or EntityState.Modified or EntityState.Deleted)) continue;
|
||||
if (Excluded.Contains(entry.Entity.GetType())) continue;
|
||||
|
||||
var action = entry.State switch
|
||||
{
|
||||
EntityState.Added => AuditAction.Create,
|
||||
EntityState.Deleted => AuditAction.Delete,
|
||||
_ => AuditAction.Update,
|
||||
};
|
||||
|
||||
var changeSet = BuildChangeSet(entry, action);
|
||||
if (action == AuditAction.Update && changeSet == "{}") continue; // only concurrency token touched, etc.
|
||||
|
||||
var isAdded = entry.State == EntityState.Added;
|
||||
pending.Add(new PendingAudit(entry, entry.Entity.GetType().Name, action, changeSet, isAdded ? 0 : ReadKey(entry), isAdded));
|
||||
}
|
||||
return pending;
|
||||
}
|
||||
|
||||
public static AuditLog ToLog(PendingAudit p, long userId, DateTime now) => new()
|
||||
{
|
||||
UserId = userId,
|
||||
EntityType = p.EntityType,
|
||||
EntityId = p.IsAdded ? ReadKey(p.Entry) : p.CapturedId,
|
||||
Action = p.Action,
|
||||
ChangeSet = p.ChangeSet,
|
||||
CreatedAt = now,
|
||||
};
|
||||
|
||||
private static long ReadKey(EntityEntry entry)
|
||||
{
|
||||
var pk = entry.Metadata.FindPrimaryKey();
|
||||
if (pk is null || pk.Properties.Count != 1) return 0;
|
||||
var value = entry.Property(pk.Properties[0].Name).CurrentValue;
|
||||
return value is null ? 0 : Convert.ToInt64(value);
|
||||
}
|
||||
|
||||
private static string BuildChangeSet(EntityEntry entry, AuditAction action)
|
||||
{
|
||||
var set = new Dictionary<string, object?>();
|
||||
foreach (var p in entry.Properties)
|
||||
{
|
||||
if (p.Metadata.IsPrimaryKey()) continue;
|
||||
if (p.Metadata.Name == nameof(Item.RowVersion)) continue;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case AuditAction.Create when p.CurrentValue is not null:
|
||||
set[p.Metadata.Name] = p.CurrentValue;
|
||||
break;
|
||||
case AuditAction.Delete:
|
||||
set[p.Metadata.Name] = p.OriginalValue;
|
||||
break;
|
||||
case AuditAction.Update when p.IsModified && !Equals(p.OriginalValue, p.CurrentValue):
|
||||
set[p.Metadata.Name] = new Dictionary<string, object?> { ["old"] = p.OriginalValue, ["new"] = p.CurrentValue };
|
||||
break;
|
||||
}
|
||||
}
|
||||
return JsonSerializer.Serialize(set, Json);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class AuditLogConfiguration : IEntityTypeConfiguration<AuditLog>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<AuditLog> builder)
|
||||
{
|
||||
builder.ToTable("audit_logs");
|
||||
builder.HasKey(a => a.AuditId);
|
||||
|
||||
builder.Property(a => a.EntityType).IsRequired().HasMaxLength(80);
|
||||
builder.Property(a => a.Action).HasConversion<string>().HasMaxLength(10).IsRequired();
|
||||
builder.Property(a => a.ChangeSet).IsRequired().HasColumnType("jsonb");
|
||||
builder.Property(a => a.CreatedAt).IsRequired();
|
||||
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(a => a.UserId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(a => new { a.EntityType, a.EntityId });
|
||||
builder.HasIndex(a => a.CreatedAt);
|
||||
builder.HasIndex(a => a.UserId);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class JournalEntryStubConfiguration : IEntityTypeConfiguration<JournalEntryStub>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<JournalEntryStub> builder)
|
||||
{
|
||||
builder.ToTable("journal_entry_stubs");
|
||||
builder.HasKey(j => j.JournalId);
|
||||
|
||||
builder.Property(j => j.SourceDocType).IsRequired().HasMaxLength(10);
|
||||
builder.Property(j => j.DebitAccount).IsRequired().HasMaxLength(20);
|
||||
builder.Property(j => j.CreditAccount).IsRequired().HasMaxLength(20);
|
||||
builder.Property(j => j.Amount).HasPrecision(18, 4);
|
||||
|
||||
builder.HasIndex(j => new { j.SourceDocType, j.SourceDocId });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class BatchConfiguration : IEntityTypeConfiguration<Batch>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Batch> builder)
|
||||
{
|
||||
builder.ToTable("batches");
|
||||
builder.HasKey(b => b.BatchId);
|
||||
|
||||
builder.Property(b => b.BatchNo).IsRequired().HasMaxLength(50);
|
||||
|
||||
builder.HasOne(b => b.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(b => b.ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Batch number unique within an item.
|
||||
builder.HasIndex(b => new { b.ItemId, b.BatchNo }).IsUnique();
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class SerialConfiguration : IEntityTypeConfiguration<Serial>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Serial> builder)
|
||||
{
|
||||
builder.ToTable("serials");
|
||||
builder.HasKey(s => s.SerialId);
|
||||
|
||||
builder.Property(s => s.SerialNo).IsRequired().HasMaxLength(100);
|
||||
builder.Property(s => s.Status).IsRequired().HasMaxLength(20);
|
||||
|
||||
builder.HasOne(s => s.Item)
|
||||
.WithMany()
|
||||
.HasForeignKey(s => s.ItemId)
|
||||
.OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(s => new { s.ItemId, s.SerialNo }).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class GrnConfiguration : IEntityTypeConfiguration<Grn>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<Grn> builder)
|
||||
{
|
||||
builder.ToTable("grns");
|
||||
builder.HasKey(g => g.GrnId);
|
||||
|
||||
builder.Property(g => g.DocNo).IsRequired().HasMaxLength(30);
|
||||
builder.HasIndex(g => g.DocNo).IsUnique();
|
||||
|
||||
builder.Property(g => g.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(g => g.CreatedAt).IsRequired();
|
||||
builder.Property(g => g.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(g => g.PurchaseOrder).WithMany().HasForeignKey(g => g.PoId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(g => g.Vendor).WithMany().HasForeignKey(g => g.VendorId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(g => g.Warehouse).WithMany().HasForeignKey(g => g.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(g => g.Creator).WithMany().HasForeignKey(g => g.CreatedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(g => g.Status);
|
||||
builder.HasIndex(g => g.PoId);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class GrnLineConfiguration : IEntityTypeConfiguration<GrnLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<GrnLine> builder)
|
||||
{
|
||||
builder.ToTable("grn_lines");
|
||||
builder.HasKey(l => l.GrnLineId);
|
||||
|
||||
builder.Property(l => l.Qty).HasPrecision(18, 4);
|
||||
builder.Property(l => l.UnitCost).HasPrecision(18, 6);
|
||||
builder.Property(l => l.ReceivedValue).HasPrecision(18, 4);
|
||||
builder.Property(l => l.HoldStatus).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
|
||||
builder.HasOne(l => l.Grn).WithMany(g => g.Lines).HasForeignKey(l => l.GrnId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne(l => l.PoLine).WithMany().HasForeignKey(l => l.PoLineId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Uom).WithMany().HasForeignKey(l => l.UomId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Bin).WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Batch).WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class PurchaseReturnConfiguration : IEntityTypeConfiguration<PurchaseReturn>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PurchaseReturn> builder)
|
||||
{
|
||||
builder.ToTable("purchase_returns");
|
||||
builder.HasKey(r => r.ReturnId);
|
||||
|
||||
builder.Property(r => r.DocNo).IsRequired().HasMaxLength(30);
|
||||
builder.HasIndex(r => r.DocNo).IsUnique();
|
||||
|
||||
builder.Property(r => r.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(r => r.CreatedAt).IsRequired();
|
||||
|
||||
builder.HasOne(r => r.Vendor).WithMany().HasForeignKey(r => r.VendorId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(r => r.Warehouse).WithMany().HasForeignKey(r => r.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(r => r.ReasonCode).WithMany().HasForeignKey(r => r.ReasonCodeId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(r => r.Creator).WithMany().HasForeignKey(r => r.CreatedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class PurchaseReturnLineConfiguration : IEntityTypeConfiguration<PurchaseReturnLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<PurchaseReturnLine> builder)
|
||||
{
|
||||
builder.ToTable("purchase_return_lines");
|
||||
builder.HasKey(l => l.ReturnLineId);
|
||||
|
||||
builder.Property(l => l.Qty).HasPrecision(18, 4);
|
||||
|
||||
builder.HasOne(l => l.Return).WithMany(r => r.Lines).HasForeignKey(l => l.ReturnId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne(l => l.GrnLine).WithMany().HasForeignKey(l => l.GrnLineId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class ReasonCodeConfiguration : IEntityTypeConfiguration<ReasonCode>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<ReasonCode> builder)
|
||||
{
|
||||
builder.ToTable("reason_codes");
|
||||
builder.HasKey(r => r.ReasonCodeId);
|
||||
|
||||
builder.Property(r => r.Code).IsRequired().HasMaxLength(20);
|
||||
builder.Property(r => r.Description).IsRequired().HasMaxLength(200);
|
||||
builder.Property(r => r.Context).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
|
||||
builder.HasIndex(r => new { r.Context, r.Code }).IsUnique();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class StockAdjustmentConfiguration : IEntityTypeConfiguration<StockAdjustment>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockAdjustment> builder)
|
||||
{
|
||||
builder.ToTable("stock_adjustments");
|
||||
builder.HasKey(a => a.AdjustmentId);
|
||||
|
||||
builder.Property(a => a.DocNo).IsRequired().HasMaxLength(30);
|
||||
builder.HasIndex(a => a.DocNo).IsUnique();
|
||||
|
||||
builder.Property(a => a.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(a => a.CreatedAt).IsRequired();
|
||||
builder.Property(a => a.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(a => a.Warehouse).WithMany().HasForeignKey(a => a.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(a => a.ReasonCode).WithMany().HasForeignKey(a => a.ReasonCodeId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(a => a.Creator).WithMany().HasForeignKey(a => a.CreatedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StockAdjustmentLineConfiguration : IEntityTypeConfiguration<StockAdjustmentLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockAdjustmentLine> builder)
|
||||
{
|
||||
builder.ToTable("stock_adjustment_lines");
|
||||
builder.HasKey(l => l.AdjLineId);
|
||||
|
||||
builder.Property(l => l.QtyDelta).HasPrecision(18, 4);
|
||||
|
||||
builder.HasOne(l => l.Adjustment).WithMany(a => a.Lines).HasForeignKey(l => l.AdjustmentId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Batch>().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Serial>().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class StockLayerConfiguration : IEntityTypeConfiguration<StockLayer>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockLayer> builder)
|
||||
{
|
||||
builder.ToTable("stock_layers");
|
||||
builder.HasKey(l => l.LayerId);
|
||||
|
||||
builder.Property(l => l.QtyReceived).HasPrecision(18, 4);
|
||||
builder.Property(l => l.QtyRemaining).HasPrecision(18, 4);
|
||||
builder.Property(l => l.UnitCost).HasPrecision(18, 6);
|
||||
builder.Property(l => l.ReceiptDate).IsRequired();
|
||||
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Warehouse).WithMany().HasForeignKey(l => l.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Batch).WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.Serial).WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(l => l.GrnLine).WithMany().HasForeignKey(l => l.GrnLineId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// FIFO consumption orders by receipt date then layer id, scoped per item+warehouse.
|
||||
builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.ReceiptDate, l.LayerId });
|
||||
builder.HasIndex(l => l.GrnLineId);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StockLedgerConfiguration : IEntityTypeConfiguration<StockLedger>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockLedger> builder)
|
||||
{
|
||||
// Append-only (FR-STK-01/FR-X-05): the app never updates/deletes ledger rows.
|
||||
// DB-level revocation of UPDATE/DELETE is a deferred hardening step (02-SECURITY B.3).
|
||||
builder.ToTable("stock_ledger");
|
||||
builder.HasKey(l => l.LedgerId);
|
||||
|
||||
builder.Property(l => l.Direction).HasConversion<string>().HasMaxLength(5).IsRequired();
|
||||
builder.Property(l => l.QtyBase).HasPrecision(18, 4);
|
||||
builder.Property(l => l.UnitCost).HasPrecision(18, 6);
|
||||
builder.Property(l => l.Value).HasPrecision(18, 4);
|
||||
builder.Property(l => l.RunningBalance).HasPrecision(18, 4);
|
||||
builder.Property(l => l.SourceDocType).IsRequired().HasMaxLength(10);
|
||||
builder.Property(l => l.CreatedAt).IsRequired();
|
||||
|
||||
builder.HasOne<Item>().WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Warehouse>().WithMany().HasForeignKey(l => l.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<User>().WithMany().HasForeignKey(l => l.UserId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Batch>().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Serial>().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
// Time-series query paths (NFR-06) and polymorphic source tracing.
|
||||
builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.LedgerId });
|
||||
builder.HasIndex(l => new { l.ItemId, l.WarehouseId, l.CreatedAt });
|
||||
builder.HasIndex(l => new { l.SourceDocType, l.SourceDocId });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class StockCountConfiguration : IEntityTypeConfiguration<StockCount>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockCount> builder)
|
||||
{
|
||||
builder.ToTable("stock_counts");
|
||||
builder.HasKey(c => c.CountId);
|
||||
|
||||
builder.Property(c => c.DocNo).IsRequired().HasMaxLength(30);
|
||||
builder.HasIndex(c => c.DocNo).IsUnique();
|
||||
|
||||
builder.Property(c => c.CountType).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(c => c.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(c => c.CreatedAt).IsRequired();
|
||||
builder.Property(c => c.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(c => c.Warehouse).WithMany().HasForeignKey(c => c.WarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(c => c.Creator).WithMany().HasForeignKey(c => c.CreatedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(c => c.Status);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StockCountLineConfiguration : IEntityTypeConfiguration<StockCountLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockCountLine> builder)
|
||||
{
|
||||
builder.ToTable("stock_count_lines");
|
||||
builder.HasKey(l => l.CountLineId);
|
||||
|
||||
builder.Property(l => l.SystemQty).HasPrecision(18, 4);
|
||||
builder.Property(l => l.CountedQty).HasPrecision(18, 4);
|
||||
builder.Property(l => l.Variance).HasPrecision(18, 4);
|
||||
|
||||
builder.HasOne(l => l.Count).WithMany(c => c.Lines).HasForeignKey(l => l.CountId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.BinId).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Configurations;
|
||||
|
||||
public sealed class StockTransferConfiguration : IEntityTypeConfiguration<StockTransfer>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockTransfer> builder)
|
||||
{
|
||||
builder.ToTable("stock_transfers");
|
||||
builder.HasKey(t => t.TransferId);
|
||||
|
||||
builder.Property(t => t.DocNo).IsRequired().HasMaxLength(30);
|
||||
builder.HasIndex(t => t.DocNo).IsUnique();
|
||||
|
||||
builder.Property(t => t.Status).HasConversion<string>().HasMaxLength(20).IsRequired();
|
||||
builder.Property(t => t.CreatedAt).IsRequired();
|
||||
builder.Property(t => t.RowVersion).IsRowVersion();
|
||||
|
||||
builder.HasOne(t => t.SrcWarehouse).WithMany().HasForeignKey(t => t.SrcWarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(t => t.DestWarehouse).WithMany().HasForeignKey(t => t.DestWarehouseId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne(t => t.Creator).WithMany().HasForeignKey(t => t.CreatedBy).OnDelete(DeleteBehavior.Restrict);
|
||||
|
||||
builder.HasIndex(t => t.Status);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StockTransferLineConfiguration : IEntityTypeConfiguration<StockTransferLine>
|
||||
{
|
||||
public void Configure(EntityTypeBuilder<StockTransferLine> builder)
|
||||
{
|
||||
builder.ToTable("stock_transfer_lines");
|
||||
builder.HasKey(l => l.TransferLineId);
|
||||
|
||||
builder.Property(l => l.Qty).HasPrecision(18, 4);
|
||||
builder.Property(l => l.UnitCost).HasPrecision(18, 6);
|
||||
builder.Property(l => l.QtyReceived).HasPrecision(18, 4);
|
||||
|
||||
builder.HasOne(l => l.Transfer).WithMany(t => t.Lines).HasForeignKey(l => l.TransferId).OnDelete(DeleteBehavior.Cascade);
|
||||
builder.HasOne(l => l.Item).WithMany().HasForeignKey(l => l.ItemId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.SrcBinId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Bin>().WithMany().HasForeignKey(l => l.DestBinId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Batch>().WithMany().HasForeignKey(l => l.BatchId).OnDelete(DeleteBehavior.Restrict);
|
||||
builder.HasOne<Serial>().WithMany().HasForeignKey(l => l.SerialId).OnDelete(DeleteBehavior.Restrict);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Domain.Enums;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ERPCore.Infra.Persistence;
|
||||
|
||||
/// <summary>
|
||||
/// Idempotent startup seeding of configurable reference data (docs/10 §B.8.3).
|
||||
/// Reason codes are seeded at runtime (not via <c>HasData</c>) so the identity
|
||||
/// sequence advances normally and later admin <c>POST /reason-codes</c> calls
|
||||
/// cannot collide with seeded ids.
|
||||
/// </summary>
|
||||
public static class DataSeeder
|
||||
{
|
||||
private static readonly (string Code, string Description, ReasonContext Context)[] StandardReasonCodes =
|
||||
[
|
||||
("DMG", "Damage", ReasonContext.Adjustment),
|
||||
("THEFT", "Theft/Loss", ReasonContext.Adjustment),
|
||||
("VAR", "Count Variance", ReasonContext.Adjustment),
|
||||
("EXP", "Expiry Write-off", ReasonContext.Adjustment),
|
||||
("SYS", "System Correction", ReasonContext.Adjustment),
|
||||
("DEF", "Defective", ReasonContext.Return),
|
||||
("WRONG", "Wrong Item", ReasonContext.Return),
|
||||
("OVER", "Over-supply", ReasonContext.Return),
|
||||
("QREJ", "Quality Reject", ReasonContext.Return),
|
||||
];
|
||||
|
||||
public static async Task SeedAsync(ErpDbContext db, CancellationToken ct = default)
|
||||
{
|
||||
var existing = await db.ReasonCodes
|
||||
.Select(r => new { r.Context, r.Code })
|
||||
.ToListAsync(ct);
|
||||
var have = existing.Select(x => (x.Context, x.Code)).ToHashSet();
|
||||
|
||||
var toAdd = StandardReasonCodes
|
||||
.Where(r => !have.Contains((r.Context, r.Code)))
|
||||
.Select(r => new ReasonCode { Code = r.Code, Description = r.Description, Context = r.Context })
|
||||
.ToList();
|
||||
|
||||
if (toAdd.Count == 0) return;
|
||||
|
||||
db.ReasonCodes.AddRange(toAdd);
|
||||
await db.SaveChangesAsync(ct);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
using ERPCore.Domain.Entities;
|
||||
using ERPCore.Infra.Auth;
|
||||
using ERPCore.Infra.Persistence.Auditing;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace ERPCore.Infra.Persistence;
|
||||
@@ -8,11 +10,15 @@ namespace ERPCore.Infra.Persistence;
|
||||
/// <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.
|
||||
/// Every save writes an immutable audit trail (FR-X-02) via <see cref="AuditScribe"/>.
|
||||
/// </summary>
|
||||
public class ErpDbContext : DbContext
|
||||
{
|
||||
public ErpDbContext(DbContextOptions<ErpDbContext> options) : base(options)
|
||||
private readonly ICurrentUser _currentUser;
|
||||
|
||||
public ErpDbContext(DbContextOptions<ErpDbContext> options, ICurrentUser currentUser) : base(options)
|
||||
{
|
||||
_currentUser = currentUser;
|
||||
}
|
||||
|
||||
// --- Master Data (docs/10 Part C.1) ---
|
||||
@@ -39,6 +45,37 @@ public class ErpDbContext : DbContext
|
||||
public DbSet<PurchaseOrder> PurchaseOrders => Set<PurchaseOrder>();
|
||||
public DbSet<PoLine> PoLines => Set<PoLine>();
|
||||
|
||||
// --- Goods Receipt (docs/10 Part C.3) ---
|
||||
public DbSet<Grn> Grns => Set<Grn>();
|
||||
public DbSet<GrnLine> GrnLines => Set<GrnLine>();
|
||||
|
||||
// --- Batch / Serial (docs/10 Part C.4) ---
|
||||
public DbSet<Batch> Batches => Set<Batch>();
|
||||
public DbSet<Serial> Serials => Set<Serial>();
|
||||
|
||||
// --- Stock core: FIFO layers + immutable ledger (docs/10 Part C.5) ---
|
||||
public DbSet<StockLayer> StockLayers => Set<StockLayer>();
|
||||
public DbSet<StockLedger> StockLedger => Set<StockLedger>();
|
||||
|
||||
// --- Stock transactions (docs/10 Part C.6) ---
|
||||
public DbSet<StockTransfer> StockTransfers => Set<StockTransfer>();
|
||||
public DbSet<StockTransferLine> StockTransferLines => Set<StockTransferLine>();
|
||||
public DbSet<StockAdjustment> StockAdjustments => Set<StockAdjustment>();
|
||||
public DbSet<StockAdjustmentLine> StockAdjustmentLines => Set<StockAdjustmentLine>();
|
||||
public DbSet<StockCount> StockCounts => Set<StockCount>();
|
||||
public DbSet<StockCountLine> StockCountLines => Set<StockCountLine>();
|
||||
|
||||
// --- Purchase returns (docs/10 Part C.2) ---
|
||||
public DbSet<PurchaseReturn> PurchaseReturns => Set<PurchaseReturn>();
|
||||
public DbSet<PurchaseReturnLine> PurchaseReturnLines => Set<PurchaseReturnLine>();
|
||||
|
||||
// --- Reference data (docs/10 Part C.7) ---
|
||||
public DbSet<ReasonCode> ReasonCodes => Set<ReasonCode>();
|
||||
|
||||
// --- Cross-cutting: audit trail + GL-ready journal (docs/10 Part C.7) ---
|
||||
public DbSet<AuditLog> AuditLogs => Set<AuditLog>();
|
||||
public DbSet<JournalEntryStub> JournalEntryStubs => Set<JournalEntryStub>();
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
@@ -47,4 +84,39 @@ public class ErpDbContext : DbContext
|
||||
// (Infra/Persistence/Configurations/*).
|
||||
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ErpDbContext).Assembly);
|
||||
}
|
||||
|
||||
// Audit trail (FR-X-02): capture mutations before save (accurate old→new), then
|
||||
// write the log rows once inserts have their generated keys. A second base save
|
||||
// persists the logs without re-auditing them.
|
||||
public override async Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken ct = default)
|
||||
{
|
||||
var pending = AuditScribe.Capture(ChangeTracker);
|
||||
var result = await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct);
|
||||
if (pending.Count > 0)
|
||||
{
|
||||
WriteAuditLogs(pending);
|
||||
await base.SaveChangesAsync(acceptAllChangesOnSuccess, ct);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public override int SaveChanges(bool acceptAllChangesOnSuccess)
|
||||
{
|
||||
var pending = AuditScribe.Capture(ChangeTracker);
|
||||
var result = base.SaveChanges(acceptAllChangesOnSuccess);
|
||||
if (pending.Count > 0)
|
||||
{
|
||||
WriteAuditLogs(pending);
|
||||
base.SaveChanges(acceptAllChangesOnSuccess);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void WriteAuditLogs(IReadOnlyList<PendingAudit> pending)
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
var userId = _currentUser.AuditUserId;
|
||||
foreach (var p in pending)
|
||||
AuditLogs.Add(AuditScribe.ToLog(p, userId, now));
|
||||
}
|
||||
}
|
||||
|
||||
+1480
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,434 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStockAndGrn : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "batches",
|
||||
columns: table => new
|
||||
{
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BatchNo = table.Column<string>(type: "character varying(50)", maxLength: 50, nullable: false),
|
||||
ExpiryDate = table.Column<DateOnly>(type: "date", nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_batches", x => x.BatchId);
|
||||
table.ForeignKey(
|
||||
name: "FK_batches_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "grns",
|
||||
columns: table => new
|
||||
{
|
||||
GrnId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DocNo = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
PoId = table.Column<long>(type: "bigint", nullable: true),
|
||||
VendorId = table.Column<long>(type: "bigint", nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
PostedAt = 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_grns", x => x.GrnId);
|
||||
table.ForeignKey(
|
||||
name: "FK_grns_purchase_orders_PoId",
|
||||
column: x => x.PoId,
|
||||
principalTable: "purchase_orders",
|
||||
principalColumn: "PoId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grns_users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grns_vendors_VendorId",
|
||||
column: x => x.VendorId,
|
||||
principalTable: "vendors",
|
||||
principalColumn: "VendorId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grns_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "serials",
|
||||
columns: table => new
|
||||
{
|
||||
SerialId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SerialNo = table.Column<string>(type: "character varying(100)", maxLength: 100, nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_serials", x => x.SerialId);
|
||||
table.ForeignKey(
|
||||
name: "FK_serials_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "grn_lines",
|
||||
columns: table => new
|
||||
{
|
||||
GrnLineId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
GrnId = table.Column<long>(type: "bigint", nullable: false),
|
||||
PoLineId = table.Column<long>(type: "bigint", nullable: true),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
UomId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: true),
|
||||
Qty = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
UnitCost = table.Column<decimal>(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
ReceivedValue = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
HoldStatus = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_grn_lines", x => x.GrnLineId);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_batches_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "batches",
|
||||
principalColumn: "BatchId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_bins_BinId",
|
||||
column: x => x.BinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_grns_GrnId",
|
||||
column: x => x.GrnId,
|
||||
principalTable: "grns",
|
||||
principalColumn: "GrnId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_po_lines_PoLineId",
|
||||
column: x => x.PoLineId,
|
||||
principalTable: "po_lines",
|
||||
principalColumn: "PoLineId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_grn_lines_uoms_UomId",
|
||||
column: x => x.UomId,
|
||||
principalTable: "uoms",
|
||||
principalColumn: "UomId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_ledger",
|
||||
columns: table => new
|
||||
{
|
||||
LedgerId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: true),
|
||||
SerialId = table.Column<long>(type: "bigint", nullable: true),
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Direction = table.Column<string>(type: "character varying(5)", maxLength: 5, nullable: false),
|
||||
QtyBase = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
UnitCost = table.Column<decimal>(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
Value = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
RunningBalance = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
SourceDocType = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||
SourceDocId = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_ledger", x => x.LedgerId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_batches_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "batches",
|
||||
principalColumn: "BatchId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_bins_BinId",
|
||||
column: x => x.BinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_serials_SerialId",
|
||||
column: x => x.SerialId,
|
||||
principalTable: "serials",
|
||||
principalColumn: "SerialId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_ledger_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_layers",
|
||||
columns: table => new
|
||||
{
|
||||
LayerId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: true),
|
||||
SerialId = table.Column<long>(type: "bigint", nullable: true),
|
||||
GrnLineId = table.Column<long>(type: "bigint", nullable: true),
|
||||
QtyReceived = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
QtyRemaining = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
UnitCost = table.Column<decimal>(type: "numeric(18,6)", precision: 18, scale: 6, nullable: false),
|
||||
ReceiptDate = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_layers", x => x.LayerId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_layers_batches_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "batches",
|
||||
principalColumn: "BatchId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_layers_grn_lines_GrnLineId",
|
||||
column: x => x.GrnLineId,
|
||||
principalTable: "grn_lines",
|
||||
principalColumn: "GrnLineId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_layers_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_layers_serials_SerialId",
|
||||
column: x => x.SerialId,
|
||||
principalTable: "serials",
|
||||
principalColumn: "SerialId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_layers_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_batches_ItemId_BatchNo",
|
||||
table: "batches",
|
||||
columns: new[] { "ItemId", "BatchNo" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_BatchId",
|
||||
table: "grn_lines",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_BinId",
|
||||
table: "grn_lines",
|
||||
column: "BinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_GrnId",
|
||||
table: "grn_lines",
|
||||
column: "GrnId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_ItemId",
|
||||
table: "grn_lines",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_PoLineId",
|
||||
table: "grn_lines",
|
||||
column: "PoLineId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grn_lines_UomId",
|
||||
table: "grn_lines",
|
||||
column: "UomId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_CreatedBy",
|
||||
table: "grns",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_DocNo",
|
||||
table: "grns",
|
||||
column: "DocNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_PoId",
|
||||
table: "grns",
|
||||
column: "PoId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_Status",
|
||||
table: "grns",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_VendorId",
|
||||
table: "grns",
|
||||
column: "VendorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_grns_WarehouseId",
|
||||
table: "grns",
|
||||
column: "WarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_serials_ItemId_SerialNo",
|
||||
table: "serials",
|
||||
columns: new[] { "ItemId", "SerialNo" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_layers_BatchId",
|
||||
table: "stock_layers",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_layers_GrnLineId",
|
||||
table: "stock_layers",
|
||||
column: "GrnLineId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_layers_ItemId_WarehouseId_ReceiptDate_LayerId",
|
||||
table: "stock_layers",
|
||||
columns: new[] { "ItemId", "WarehouseId", "ReceiptDate", "LayerId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_layers_SerialId",
|
||||
table: "stock_layers",
|
||||
column: "SerialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_layers_WarehouseId",
|
||||
table: "stock_layers",
|
||||
column: "WarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_BatchId",
|
||||
table: "stock_ledger",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_BinId",
|
||||
table: "stock_ledger",
|
||||
column: "BinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_ItemId_WarehouseId_CreatedAt",
|
||||
table: "stock_ledger",
|
||||
columns: new[] { "ItemId", "WarehouseId", "CreatedAt" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_ItemId_WarehouseId_LedgerId",
|
||||
table: "stock_ledger",
|
||||
columns: new[] { "ItemId", "WarehouseId", "LedgerId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_SerialId",
|
||||
table: "stock_ledger",
|
||||
column: "SerialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_SourceDocType_SourceDocId",
|
||||
table: "stock_ledger",
|
||||
columns: new[] { "SourceDocType", "SourceDocId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_UserId",
|
||||
table: "stock_ledger",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_ledger_WarehouseId",
|
||||
table: "stock_ledger",
|
||||
column: "WarehouseId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_layers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_ledger");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "grn_lines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "serials");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "batches");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "grns");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+1847
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,337 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStockTransactions : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "reason_codes",
|
||||
columns: table => new
|
||||
{
|
||||
ReasonCodeId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Code = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Description = table.Column<string>(type: "character varying(200)", maxLength: 200, nullable: false),
|
||||
Context = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_reason_codes", x => x.ReasonCodeId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_transfers",
|
||||
columns: table => new
|
||||
{
|
||||
TransferId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DocNo = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
SrcWarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
DestWarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_transfers", x => x.TransferId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfers_users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfers_warehouses_DestWarehouseId",
|
||||
column: x => x.DestWarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfers_warehouses_SrcWarehouseId",
|
||||
column: x => x.SrcWarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_adjustments",
|
||||
columns: table => new
|
||||
{
|
||||
AdjustmentId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DocNo = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ReasonCodeId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_adjustments", x => x.AdjustmentId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustments_reason_codes_ReasonCodeId",
|
||||
column: x => x.ReasonCodeId,
|
||||
principalTable: "reason_codes",
|
||||
principalColumn: "ReasonCodeId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustments_users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustments_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_transfer_lines",
|
||||
columns: table => new
|
||||
{
|
||||
TransferLineId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
TransferId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
SrcBinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
DestBinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: true),
|
||||
SerialId = table.Column<long>(type: "bigint", nullable: true),
|
||||
Qty = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
UnitCost = table.Column<decimal>(type: "numeric(18,6)", precision: 18, scale: 6, nullable: true),
|
||||
QtyReceived = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_transfer_lines", x => x.TransferLineId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_batches_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "batches",
|
||||
principalColumn: "BatchId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_bins_DestBinId",
|
||||
column: x => x.DestBinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_bins_SrcBinId",
|
||||
column: x => x.SrcBinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_serials_SerialId",
|
||||
column: x => x.SerialId,
|
||||
principalTable: "serials",
|
||||
principalColumn: "SerialId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_transfer_lines_stock_transfers_TransferId",
|
||||
column: x => x.TransferId,
|
||||
principalTable: "stock_transfers",
|
||||
principalColumn: "TransferId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_adjustment_lines",
|
||||
columns: table => new
|
||||
{
|
||||
AdjLineId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
AdjustmentId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
BatchId = table.Column<long>(type: "bigint", nullable: true),
|
||||
SerialId = table.Column<long>(type: "bigint", nullable: true),
|
||||
QtyDelta = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_adjustment_lines", x => x.AdjLineId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustment_lines_batches_BatchId",
|
||||
column: x => x.BatchId,
|
||||
principalTable: "batches",
|
||||
principalColumn: "BatchId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustment_lines_bins_BinId",
|
||||
column: x => x.BinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustment_lines_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustment_lines_serials_SerialId",
|
||||
column: x => x.SerialId,
|
||||
principalTable: "serials",
|
||||
principalColumn: "SerialId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_adjustment_lines_stock_adjustments_AdjustmentId",
|
||||
column: x => x.AdjustmentId,
|
||||
principalTable: "stock_adjustments",
|
||||
principalColumn: "AdjustmentId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_reason_codes_Context_Code",
|
||||
table: "reason_codes",
|
||||
columns: new[] { "Context", "Code" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustment_lines_AdjustmentId",
|
||||
table: "stock_adjustment_lines",
|
||||
column: "AdjustmentId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustment_lines_BatchId",
|
||||
table: "stock_adjustment_lines",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustment_lines_BinId",
|
||||
table: "stock_adjustment_lines",
|
||||
column: "BinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustment_lines_ItemId",
|
||||
table: "stock_adjustment_lines",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustment_lines_SerialId",
|
||||
table: "stock_adjustment_lines",
|
||||
column: "SerialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustments_CreatedBy",
|
||||
table: "stock_adjustments",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustments_DocNo",
|
||||
table: "stock_adjustments",
|
||||
column: "DocNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustments_ReasonCodeId",
|
||||
table: "stock_adjustments",
|
||||
column: "ReasonCodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_adjustments_WarehouseId",
|
||||
table: "stock_adjustments",
|
||||
column: "WarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_BatchId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "BatchId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_DestBinId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "DestBinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_ItemId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_SerialId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "SerialId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_SrcBinId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "SrcBinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfer_lines_TransferId",
|
||||
table: "stock_transfer_lines",
|
||||
column: "TransferId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfers_CreatedBy",
|
||||
table: "stock_transfers",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfers_DestWarehouseId",
|
||||
table: "stock_transfers",
|
||||
column: "DestWarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfers_DocNo",
|
||||
table: "stock_transfers",
|
||||
column: "DocNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfers_SrcWarehouseId",
|
||||
table: "stock_transfers",
|
||||
column: "SrcWarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_transfers_Status",
|
||||
table: "stock_transfers",
|
||||
column: "Status");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_adjustment_lines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_transfer_lines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_adjustments");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_transfers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "reason_codes");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2134
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,253 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddCountsAndReturns : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "purchase_returns",
|
||||
columns: table => new
|
||||
{
|
||||
ReturnId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DocNo = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
VendorId = table.Column<long>(type: "bigint", nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ReasonCodeId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_purchase_returns", x => x.ReturnId);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_returns_reason_codes_ReasonCodeId",
|
||||
column: x => x.ReasonCodeId,
|
||||
principalTable: "reason_codes",
|
||||
principalColumn: "ReasonCodeId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_returns_users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_returns_vendors_VendorId",
|
||||
column: x => x.VendorId,
|
||||
principalTable: "vendors",
|
||||
principalColumn: "VendorId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_returns_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_counts",
|
||||
columns: table => new
|
||||
{
|
||||
CountId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DocNo = table.Column<string>(type: "character varying(30)", maxLength: 30, nullable: false),
|
||||
WarehouseId = table.Column<long>(type: "bigint", nullable: false),
|
||||
CountType = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Status = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreatedBy = table.Column<long>(type: "bigint", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false),
|
||||
xmin = table.Column<uint>(type: "xid", rowVersion: true, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_counts", x => x.CountId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_counts_users_CreatedBy",
|
||||
column: x => x.CreatedBy,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_counts_warehouses_WarehouseId",
|
||||
column: x => x.WarehouseId,
|
||||
principalTable: "warehouses",
|
||||
principalColumn: "WarehouseId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "purchase_return_lines",
|
||||
columns: table => new
|
||||
{
|
||||
ReturnLineId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ReturnId = table.Column<long>(type: "bigint", nullable: false),
|
||||
GrnLineId = table.Column<long>(type: "bigint", nullable: true),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Qty = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_purchase_return_lines", x => x.ReturnLineId);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_return_lines_grn_lines_GrnLineId",
|
||||
column: x => x.GrnLineId,
|
||||
principalTable: "grn_lines",
|
||||
principalColumn: "GrnLineId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_return_lines_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_purchase_return_lines_purchase_returns_ReturnId",
|
||||
column: x => x.ReturnId,
|
||||
principalTable: "purchase_returns",
|
||||
principalColumn: "ReturnId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "stock_count_lines",
|
||||
columns: table => new
|
||||
{
|
||||
CountLineId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
CountId = table.Column<long>(type: "bigint", nullable: false),
|
||||
ItemId = table.Column<long>(type: "bigint", nullable: false),
|
||||
BinId = table.Column<long>(type: "bigint", nullable: true),
|
||||
SystemQty = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false),
|
||||
CountedQty = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true),
|
||||
Variance = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_stock_count_lines", x => x.CountLineId);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_count_lines_bins_BinId",
|
||||
column: x => x.BinId,
|
||||
principalTable: "bins",
|
||||
principalColumn: "BinId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_count_lines_items_ItemId",
|
||||
column: x => x.ItemId,
|
||||
principalTable: "items",
|
||||
principalColumn: "ItemId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_stock_count_lines_stock_counts_CountId",
|
||||
column: x => x.CountId,
|
||||
principalTable: "stock_counts",
|
||||
principalColumn: "CountId",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_return_lines_GrnLineId",
|
||||
table: "purchase_return_lines",
|
||||
column: "GrnLineId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_return_lines_ItemId",
|
||||
table: "purchase_return_lines",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_return_lines_ReturnId",
|
||||
table: "purchase_return_lines",
|
||||
column: "ReturnId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_returns_CreatedBy",
|
||||
table: "purchase_returns",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_returns_DocNo",
|
||||
table: "purchase_returns",
|
||||
column: "DocNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_returns_ReasonCodeId",
|
||||
table: "purchase_returns",
|
||||
column: "ReasonCodeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_returns_VendorId",
|
||||
table: "purchase_returns",
|
||||
column: "VendorId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_purchase_returns_WarehouseId",
|
||||
table: "purchase_returns",
|
||||
column: "WarehouseId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_count_lines_BinId",
|
||||
table: "stock_count_lines",
|
||||
column: "BinId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_count_lines_CountId",
|
||||
table: "stock_count_lines",
|
||||
column: "CountId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_count_lines_ItemId",
|
||||
table: "stock_count_lines",
|
||||
column: "ItemId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_counts_CreatedBy",
|
||||
table: "stock_counts",
|
||||
column: "CreatedBy");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_counts_DocNo",
|
||||
table: "stock_counts",
|
||||
column: "DocNo",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_counts_Status",
|
||||
table: "stock_counts",
|
||||
column: "Status");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_stock_counts_WarehouseId",
|
||||
table: "stock_counts",
|
||||
column: "WarehouseId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "purchase_return_lines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_count_lines");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "purchase_returns");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "stock_counts");
|
||||
}
|
||||
}
|
||||
}
|
||||
Generated
+2222
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,87 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace ERPCore.Infra.Persistence.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuditAndJournal : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "audit_logs",
|
||||
columns: table => new
|
||||
{
|
||||
AuditId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<long>(type: "bigint", nullable: false),
|
||||
EntityType = table.Column<string>(type: "character varying(80)", maxLength: 80, nullable: false),
|
||||
EntityId = table.Column<long>(type: "bigint", nullable: false),
|
||||
Action = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||
ChangeSet = table.Column<string>(type: "jsonb", nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "timestamp with time zone", nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_audit_logs", x => x.AuditId);
|
||||
table.ForeignKey(
|
||||
name: "FK_audit_logs_users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "users",
|
||||
principalColumn: "UserId",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "journal_entry_stubs",
|
||||
columns: table => new
|
||||
{
|
||||
JournalId = table.Column<long>(type: "bigint", nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
SourceDocType = table.Column<string>(type: "character varying(10)", maxLength: 10, nullable: false),
|
||||
SourceDocId = table.Column<long>(type: "bigint", nullable: false),
|
||||
DebitAccount = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
CreditAccount = table.Column<string>(type: "character varying(20)", maxLength: 20, nullable: false),
|
||||
Amount = table.Column<decimal>(type: "numeric(18,4)", precision: 18, scale: 4, nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_journal_entry_stubs", x => x.JournalId);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_audit_logs_CreatedAt",
|
||||
table: "audit_logs",
|
||||
column: "CreatedAt");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_audit_logs_EntityType_EntityId",
|
||||
table: "audit_logs",
|
||||
columns: new[] { "EntityType", "EntityId" });
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_audit_logs_UserId",
|
||||
table: "audit_logs",
|
||||
column: "UserId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_journal_entry_stubs_SourceDocType_SourceDocId",
|
||||
table: "journal_entry_stubs",
|
||||
columns: new[] { "SourceDocType", "SourceDocId" });
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "audit_logs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "journal_entry_stubs");
|
||||
}
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user