using foodmarket.Domain.Money;
using Microsoft.EntityFrameworkCore;
namespace foodmarket.Infrastructure.Persistence.Configurations;
/// Схема таблиц модуля «Деньги». Названия колонок/таблиц
/// подчинены общему стилю (snake_case через ToTable + EF-camel для свойств);
/// суммовые — decimal(18,4); концуррентный токен — Postgres xmin.
public static class MoneyConfigurations
{
public static void ConfigureMoney(this ModelBuilder b)
{
b.Entity(e =>
{
e.ToTable("money_accounts");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
e.Property(x => x.BankName).HasMaxLength(200);
e.Property(x => x.AccountNumber).HasMaxLength(100);
e.Property(x => x.OpeningBalance).HasPrecision(18, 4);
e.HasOne(x => x.Store).WithMany().HasForeignKey(x => x.StoreId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.Currency).WithMany().HasForeignKey(x => x.CurrencyId).OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.OrganizationId, x.Name });
e.HasIndex(x => new { x.OrganizationId, x.Kind, x.IsActive });
// IsDefault — на уровне контроллера проверяем «только один per Kind»:
// составной уникальный индекс с фильтром IsDefault=true.
e.HasIndex(x => new { x.OrganizationId, x.Kind })
.IsUnique()
.HasFilter("\"IsDefault\" = true");
});
b.Entity(e =>
{
e.ToTable("expense_items");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Name).HasMaxLength(200).IsRequired();
e.Property(x => x.Code).HasMaxLength(50);
e.Property(x => x.Notes).HasMaxLength(1000);
e.HasIndex(x => new { x.OrganizationId, x.Name }).IsUnique();
e.HasIndex(x => new { x.OrganizationId, x.Code })
.IsUnique()
.HasFilter("\"Code\" IS NOT NULL");
});
b.Entity(e =>
{
e.ToTable("money_transactions");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Number).HasMaxLength(50).IsRequired();
e.Property(x => x.Amount).HasPrecision(18, 4);
e.Property(x => x.Purpose).HasMaxLength(500);
e.Property(x => x.Notes).HasMaxLength(1000);
e.HasOne(x => x.Account).WithMany().HasForeignKey(x => x.AccountId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.Counterparty).WithMany().HasForeignKey(x => x.CounterpartyId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.ExpenseItem).WithMany().HasForeignKey(x => x.ExpenseItemId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.Currency).WithMany().HasForeignKey(x => x.CurrencyId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.LinkedTransaction).WithMany()
.HasForeignKey(x => x.LinkedTransactionId).OnDelete(DeleteBehavior.Restrict);
e.HasMany(x => x.Links).WithOne(l => l.Transaction)
.HasForeignKey(l => l.TransactionId).OnDelete(DeleteBehavior.Cascade);
e.HasIndex(x => new { x.OrganizationId, x.Number }).IsUnique();
e.HasIndex(x => new { x.OrganizationId, x.Date });
e.HasIndex(x => new { x.OrganizationId, x.Kind, x.Status });
e.HasIndex(x => new { x.OrganizationId, x.AccountId, x.Date });
e.HasIndex(x => new { x.OrganizationId, x.CounterpartyId })
.HasFilter("\"CounterpartyId\" IS NOT NULL");
});
b.Entity(e =>
{
e.ToTable("money_transaction_links");
e.Property(x => x.LinkedSum).HasPrecision(18, 4);
e.HasOne(x => x.Supply).WithMany().HasForeignKey(x => x.SupplyId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.SupplierReturn).WithMany().HasForeignKey(x => x.SupplierReturnId).OnDelete(DeleteBehavior.Restrict);
// CHECK: ровно одна целевая ссылка должна быть заполнена.
e.ToTable(t => t.HasCheckConstraint(
"ck_money_link_single_target",
"((\"SupplyId\" IS NOT NULL)::int + (\"SupplierReturnId\" IS NOT NULL)::int) = 1"));
e.HasIndex(x => new { x.OrganizationId, x.SupplyId })
.HasFilter("\"SupplyId\" IS NOT NULL");
e.HasIndex(x => new { x.OrganizationId, x.SupplierReturnId })
.HasFilter("\"SupplierReturnId\" IS NOT NULL");
e.HasIndex(x => x.TransactionId);
});
b.Entity(e =>
{
e.ToTable("cash_shifts");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Number).HasMaxLength(50).IsRequired();
e.Property(x => x.OpeningFloat).HasPrecision(18, 4);
e.Property(x => x.SalesCash).HasPrecision(18, 4);
e.Property(x => x.SalesCard).HasPrecision(18, 4);
e.Property(x => x.ReturnsCash).HasPrecision(18, 4);
e.Property(x => x.ReturnsCard).HasPrecision(18, 4);
e.Property(x => x.DrawerIn).HasPrecision(18, 4);
e.Property(x => x.DrawerOut).HasPrecision(18, 4);
e.Property(x => x.ExpectedCash).HasPrecision(18, 4);
e.Property(x => x.ActualCash).HasPrecision(18, 4);
e.Property(x => x.CashDifference).HasPrecision(18, 4);
e.Property(x => x.Notes).HasMaxLength(1000);
e.HasOne(x => x.RetailPoint).WithMany().HasForeignKey(x => x.RetailPointId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.CashAccount).WithMany().HasForeignKey(x => x.CashAccountId).OnDelete(DeleteBehavior.Restrict);
e.HasOne(x => x.AcquiringAccount).WithMany().HasForeignKey(x => x.AcquiringAccountId).OnDelete(DeleteBehavior.Restrict);
e.HasMany(x => x.DrawerOperations).WithOne(d => d.CashShift)
.HasForeignKey(d => d.CashShiftId).OnDelete(DeleteBehavior.Cascade);
e.HasIndex(x => new { x.OrganizationId, x.Number }).IsUnique();
e.HasIndex(x => new { x.OrganizationId, x.OpenedAt });
e.HasIndex(x => new { x.OrganizationId, x.RetailPointId, x.Status });
// Только одна открытая смена per RetailPoint.
e.HasIndex(x => new { x.OrganizationId, x.RetailPointId })
.IsUnique()
.HasFilter("\"Status\" = 1");
});
b.Entity(e =>
{
e.ToTable("cash_drawer_operations");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Amount).HasPrecision(18, 4);
e.Property(x => x.Reason).HasMaxLength(500).IsRequired();
e.HasIndex(x => new { x.OrganizationId, x.CashShiftId });
e.HasIndex(x => new { x.OrganizationId, x.At });
});
b.Entity(e =>
{
e.ToTable("counterparty_adjustments");
e.UseXminAsConcurrencyToken();
e.Ignore(x => x.Xmin);
e.Property(x => x.Number).HasMaxLength(50).IsRequired();
e.Property(x => x.Amount).HasPrecision(18, 4);
e.Property(x => x.Notes).HasMaxLength(1000);
e.HasOne(x => x.Counterparty).WithMany().HasForeignKey(x => x.CounterpartyId).OnDelete(DeleteBehavior.Restrict);
e.HasIndex(x => new { x.OrganizationId, x.Number }).IsUnique();
e.HasIndex(x => new { x.OrganizationId, x.Date });
e.HasIndex(x => new { x.OrganizationId, x.CounterpartyId, x.Status });
});
// Расширения существующих таблиц:
b.Entity(e =>
{
e.Property(x => x.PayedSum).HasPrecision(18, 4);
e.HasIndex(x => new { x.OrganizationId, x.SupplierId, x.Status, x.PayedSum });
});
b.Entity(e =>
{
e.Property(x => x.PayedSum).HasPrecision(18, 4);
});
b.Entity(e =>
{
e.HasIndex(x => new { x.OrganizationId, x.CashShiftId })
.HasFilter("\"CashShiftId\" IS NOT NULL");
});
}
}