using System; using System.Threading; using System.Threading.Tasks; using FAMDAL.CustomCode.TFam; using FAMDAL.DTO.TFam; using GB5Shared.DaprCache; using GB5Shared.DTO.Framework.Criteria; using GB5Shared.DTO.Framework.Login; using GB5Shared.GenerateAutoNumber; using GB5Shared.QueryExecutor; using GB5Shared.Resource.Response; using GB5Shared.Telemetry; using Microsoft.Extensions.Logging; using static GB5Shared.GB5Constant.Constant; namespace FAMBLL.TFam { public class TFamBLL : ITFamBLL { private readonly ITFamDAL _dal; private readonly IQueryExecutor _queryExecutor; private readonly AutoNumber _autoNumber; private readonly KeyInvalidate _keyInvalidate; private readonly ILogger _logger; public TFamBLL( ITFamDAL dal, IQueryExecutor queryExecutor, AutoNumber autoNumber, KeyInvalidate keyInvalidate, ILogger logger) { _dal = dal; _queryExecutor = queryExecutor; _autoNumber = autoNumber; _keyInvalidate = keyInvalidate; _logger = logger; } public async Task GetTFam(int tFamLedgerId, LoginDTO login, CancellationToken ct) => await _dal.GetTFam(tFamLedgerId, login, ct).ConfigureAwait(false); public async Task SaveTFam(TFamDTO dto, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("validate-tfam", new { dto.TFamLedgerId, dto.AssetId }); if (dto.PeriodId <= 0) throw new ArgumentException("PeriodId is required"); if (dto.AssetId <= 0) throw new ArgumentException("AssetId is required"); if (dto.BizTransactionTypeId <= 0) throw new ArgumentException("BizTransactionTypeId is required"); if (dto.OUId <= 0) throw new ArgumentException("OUId is required"); // Period-validation business rule: the transaction date must fall inside an open // (Active, STATUS=1) accounting period — confirmed against MPERIOD (AdminDAL/Query/Period/PeriodQB.cs). var period = await _dal.GetPeriodWindow(dto.PeriodId, login, ct).ConfigureAwait(false) ?? throw new ArgumentException($"Period {dto.PeriodId} not found"); if (period.Status != 1) throw new ArgumentException("Selected period is not open for posting."); if (dto.TFamFamDate < period.FromDate || dto.TFamFamDate > period.ToDate) throw new ArgumentException("Transaction date falls outside the selected period."); // Duplicate-capitalization guard: reject re-capitalizing the same AssetId+AssetBatch. // ISCAPITALIZED = 0 means "capitalized = YES" per the TFAM DDL's own comment. if (dto.TFamIsCapitalized == 0 && await _dal.IsAssetBatchAlreadyCapitalized(dto.AssetId, dto.TFamAssetBatch, login, ct).ConfigureAwait(false)) throw new ArgumentException(SuccessResponse.DuplicateAssetBatch); bool isNewTransaction = dto.TFamFamId == 0; if (isNewTransaction) { var auto = await _autoNumber.GetNumberAsync(1, AUTONUMBERCONSTANT.TFAM, login).ConfigureAwait(false); dto.TFamFamId = auto.StartNumber; } GB5Trace.Step("save-tfam", new { dto.TFamFamId, isNewTransaction }); int newLedgerId; await using var tx = await _queryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { newLedgerId = await _dal.SaveTFam(dto, login, tx, ct).ConfigureAwait(false); await tx.CommitAsync(ct).ConfigureAwait(false); } catch { await tx.RollbackAsync(ct).ConfigureAwait(false); throw; } dto.TFamLedgerId = newLedgerId; var cacheKey = new CacheKeyGeneration().KeyGeneration(dto.TFamLedgerId, EntityConstant.OBJECTTFAM, CacheKeyLevel.NOT_REQUIRED, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return $"{SuccessResponse.SaveSuccessMessage} {dto.TFamLedgerId}"; } catch (Exception ex) { GB5Trace.MarkFailed("save-tfam-failed", ex); _logger.LogError(ex, "SaveTFam failed for AssetId {AssetId}", dto.AssetId); throw; } } public async Task DeleteTFam(int tFamLedgerId, LoginDTO login, CancellationToken ct) { try { GB5Trace.Step("delete-tfam", new { tFamLedgerId }); await using var tx = await _queryExecutor.BeginTransactionAsync(login).ConfigureAwait(false); try { await _dal.DeleteTFam(tFamLedgerId, login.UserId, login, tx, ct).ConfigureAwait(false); await tx.CommitAsync(ct).ConfigureAwait(false); } catch { await tx.RollbackAsync(ct).ConfigureAwait(false); throw; } var cacheKey = new CacheKeyGeneration().KeyGeneration(tFamLedgerId, EntityConstant.OBJECTTFAM, CacheKeyLevel.NOT_REQUIRED, login); await _keyInvalidate.AllInvalidateCache(cacheKey).ConfigureAwait(false); return SuccessResponse.DeleteSuccessMessage; } catch (Exception ex) { GB5Trace.MarkFailed("delete-tfam-failed", ex); _logger.LogError(ex, "DeleteTFam failed for TFamLedgerId {Id}", tFamLedgerId); throw; } } public async Task GetSelectListTFam( int FirstNumber, int MaxResult, CriteriaDTO CriteriaDTO, LoginDTO LoginDTO) { try { return await _dal.GetSelectListTFam( FirstNumber, MaxResult, CriteriaDTO, LoginDTO); } catch (Exception) { throw; } } } }