Files
baya-monorepo/server/src/Infrastructure/Baya.Infrastructure.Persistence/Configuration/HolidaysConfig/IranianHolidayConfig.cs
T
hamid 2f2aec61a2 backend phase 1: config, reference & platform signals
Lay the cross-cutting platform backbone every later phase reads from. Adds
the first marketplace EF migration baseline (new `ops` schema) and the
mechanisms b2..b15 reuse: typed runtime config, an append-only audit trail,
an analytics event log, the holiday/bank-closure calendar, in-app
notifications, and the internal support-alert worklist.

Schema & migration
- New `ops` schema + migration InitialMarketplaceBaseline with 6 tables:
  PlatformConfigs (IAuditable), AuditLogs (append-only), SystemEvents,
  IranianHolidays, Notifications, SupportAlerts — with indexes/uniques and
  FKs to usr.Users. Seeded 12 config keys + 7 sample holidays via HasData.

Domain / Application
- IAuditable marker + [AuditRedacted] attribute; entities + string-code
  constant holders (config data_type, holiday type, alert type/severity/status).
- Facade contracts: IPlatformConfig, IHolidayCalendar, IAnalyticsSink,
  IAuditLogger, INotificationService, ISupportAlertService; DTOs +
  PagedResult<T>; evolved the INotificationDispatcher.Notification record to
  carry Type + DataJson; Pagination helper.
- 14 CQRS commands/queries (+ validators) wiring the endpoints to the facades.

Infrastructure
- DB-backed facade implementations in Persistence/Services/; real in-app
  INotificationDispatcher (removes the b0 log stub); notification-retention
  hosted service (purge is_read=1 AND age>90d).
- Extended AuditFieldInterceptor to also append an old/new-diff audit_logs row
  for every IAuditable change in the same transaction (PII redacted).
- Registered all facades + hosted service in AddPersistenceServices; removed
  the dispatcher registration from AddCrossCuttingSeams.

API
- 5 controllers: admin PlatformConfig/Holidays/Audit/SupportAlerts
  ([Authorize(DynamicPermission)]) + current-user Notifications ([Authorize]),
  all tenant-scoped and paginated. 16 Swagger paths total.

Money-correctness & safety rules honoured
- Config read at compute time (cached, parsed by data_type), never hardcoded;
  every config change is audited in the same transaction; audit_logs is
  append-only (no update/delete path); support alerts are admin-only;
  notifications are tenant-scoped; analytics is fire-and-forget.

Tests & docs
- 18 new foundation tests over in-memory SQLite (config typing + audit,
  holidays, notifications + tenancy + retention, support alerts, analytics);
  build clean (0 new code warnings), 22 tests green; migration applied to the
  dev DB and swagger.v1.json refreshed.
- Updated server Project map + CONVENTIONS, product data-model doc 12 (seeded
  config defaults), config-reference contract, mock registry, backend handoff/
  STATUS/report.

Follow-ups: add FK constraints for SupportAlerts.BookingId (b9) and ReviewId
(b14) when those tables land.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 01:18:00 +03:30

51 lines
2.0 KiB
C#

using Baya.Domain.Entities.Holidays;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Baya.Infrastructure.Persistence.Configuration.HolidaysConfig;
internal sealed class IranianHolidayConfig : IEntityTypeConfiguration<IranianHoliday>
{
public void Configure(EntityTypeBuilder<IranianHoliday> builder)
{
builder.ToTable("IranianHolidays", "ops");
builder.Property(h => h.NameFa).HasMaxLength(200).IsRequired();
builder.Property(h => h.Type).HasMaxLength(20).IsRequired();
builder.HasIndex(h => h.HolidayDate).IsUnique();
builder.HasData(SeedData());
}
// A representative sample so IsBankClosed/NextBusinessDay are testable. The full, maintained,
// partly-lunar-Hijri feed is deferred behind IHolidayCalendar's "make it real" path.
private static object[] SeedData()
{
var ts = SeedConstants.Timestamp;
(long Id, DateOnly Date, string NameFa, string Type, bool BankClosed)[] rows =
[
(1, new DateOnly(2026, 2, 11), "پیروزی انقلاب اسلامی", HolidayType.National, true),
(2, new DateOnly(2026, 3, 21), "نوروز", HolidayType.National, true),
(3, new DateOnly(2026, 3, 22), "نوروز", HolidayType.National, true),
(4, new DateOnly(2026, 3, 23), "نوروز", HolidayType.National, true),
(5, new DateOnly(2026, 3, 24), "نوروز", HolidayType.National, true),
(6, new DateOnly(2026, 4, 1), "روز طبیعت (سیزده‌به‌در)", HolidayType.Official, true),
(7, new DateOnly(2026, 6, 26), "عید سعید قربان", HolidayType.Religious, true),
];
return rows
.Select(r => (object)new
{
r.Id,
HolidayDate = r.Date,
r.NameFa,
r.Type,
IsBankClosed = r.BankClosed,
CreatedAt = ts
})
.ToArray();
}
}