init depresiasi

This commit is contained in:
giovanni
2026-04-17 21:26:56 +07:00
parent a54c6184a2
commit fcde3b0a36
34 changed files with 3588 additions and 46 deletions
@@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"time"
commonRepo "gitlab.com/mbugroup/lti-api.git/internal/common/repository"
commonSvc "gitlab.com/mbugroup/lti-api.git/internal/common/service"
@@ -358,6 +359,7 @@ func (s *expenseService) CreateOne(c *fiber.Ctx, req *validation.Create) (*expen
if err != nil {
return nil, err
}
s.invalidateDepreciationSnapshotsByExpense(c.Context(), nil, uint(expense.Id), expenseDate, nil)
return responseDTO, nil
}
@@ -385,6 +387,7 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
}
updateBody := make(map[string]any)
var requestedTransactionDate *time.Time
if req.TransactionDate != nil {
expenseDate, err := utils.ParseDateString(*req.TransactionDate)
@@ -392,6 +395,7 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction_date format")
}
updateBody["transaction_date"] = expenseDate
requestedTransactionDate = &expenseDate
}
if req.Category != nil {
@@ -429,6 +433,8 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
return responseDTO, nil
}
var invalidationFromDate time.Time
var invalidationFarmIDs []uint
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
expenseRepoTx := repository.NewExpenseRepository(tx)
@@ -446,6 +452,16 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), currentExpense); err != nil {
return err
}
oldFarmIDs, resolveOldFarmErr := commonSvc.ResolveProjectFlockIDsByExpenseID(c.Context(), tx, id)
if resolveOldFarmErr != nil {
s.Log.Warnf("Failed to resolve old expense farm ids for invalidation (expense_id=%d): %+v", id, resolveOldFarmErr)
}
invalidationFarmIDs = append(invalidationFarmIDs, oldFarmIDs...)
invalidationFromDate = currentExpense.TransactionDate
if requestedTransactionDate != nil {
invalidationFromDate = commonSvc.MinNonZeroDateOnlyUTC(currentExpense.TransactionDate, *requestedTransactionDate)
}
categoryChanged := false
var newCategory string
if req.Category != nil && *req.Category != currentExpense.Category {
@@ -631,6 +647,12 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
}
}
newFarmIDs, resolveNewFarmErr := commonSvc.ResolveProjectFlockIDsByExpenseID(c.Context(), tx, id)
if resolveNewFarmErr != nil {
s.Log.Warnf("Failed to resolve new expense farm ids for invalidation (expense_id=%d): %+v", id, resolveNewFarmErr)
}
invalidationFarmIDs = append(invalidationFarmIDs, newFarmIDs...)
return nil
})
@@ -645,6 +667,7 @@ func (s expenseService) UpdateOne(c *fiber.Ctx, req *validation.Update, id uint)
if err != nil {
return nil, err
}
s.invalidateDepreciationSnapshots(c.Context(), nil, invalidationFarmIDs, invalidationFromDate)
return responseDTO, nil
}
@@ -671,6 +694,10 @@ func (s expenseService) DeleteOne(c *fiber.Ctx, id uint64) error {
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return err
}
farmIDs, resolveFarmErr := commonSvc.ResolveProjectFlockIDsByExpenseID(c.Context(), s.Repository.DB(), idUint)
if resolveFarmErr != nil {
s.Log.Warnf("Failed to resolve expense farm ids before delete (expense_id=%d): %+v", idUint, resolveFarmErr)
}
if err := s.Repository.DeleteOne(c.Context(), idUint); err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
s.Log.Errorf("Expense not found for ID %d: %+v", id, err)
@@ -680,6 +707,8 @@ func (s expenseService) DeleteOne(c *fiber.Ctx, id uint64) error {
return err
}
s.Log.Infof("Successfully deleted expense with ID %d", id)
invalidationFromDate := commonSvc.MinNonZeroDateOnlyUTC(expense.TransactionDate, expense.RealizationDate)
s.invalidateDepreciationSnapshots(c.Context(), nil, farmIDs, invalidationFromDate)
return nil
}
@@ -800,6 +829,8 @@ func (s *expenseService) CreateRealization(c *fiber.Ctx, expenseID uint, req *va
if err != nil {
return nil, err
}
invalidateFromDate := commonSvc.MinNonZeroDateOnlyUTC(expense.TransactionDate, realizationDate, expense.RealizationDate)
s.invalidateDepreciationSnapshotsByExpense(c.Context(), nil, expenseID, invalidateFromDate, nil)
return responseDTO, nil
}
@@ -857,6 +888,13 @@ func (s *expenseService) CompleteExpense(c *fiber.Ctx, id uint, notes *string) (
if err != nil {
return nil, err
}
expense, expenseErr := s.Repository.GetByID(c.Context(), id, nil)
if expenseErr != nil {
s.Log.Warnf("Failed to load expense for depreciation invalidation after complete (expense_id=%d): %+v", id, expenseErr)
} else {
invalidateFromDate := commonSvc.MinNonZeroDateOnlyUTC(expense.TransactionDate, expense.RealizationDate)
s.invalidateDepreciationSnapshotsByExpense(c.Context(), nil, id, invalidateFromDate, nil)
}
return responseDTO, nil
}
@@ -884,6 +922,12 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
if err := s.ensureProjectFlockNotClosedForExpense(c.Context(), expense); err != nil {
return nil, err
}
invalidateFromDate := commonSvc.MinNonZeroDateOnlyUTC(expense.TransactionDate, expense.RealizationDate)
if req.RealizationDate != nil {
if parsedDate, parseErr := utils.ParseDateString(*req.RealizationDate); parseErr == nil {
invalidateFromDate = commonSvc.MinNonZeroDateOnlyUTC(invalidateFromDate, parsedDate)
}
}
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, expenseID, nil)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed to validate workflow")
@@ -996,6 +1040,7 @@ func (s *expenseService) UpdateRealization(c *fiber.Ctx, expenseID uint, req *va
if err != nil {
return nil, err
}
s.invalidateDepreciationSnapshotsByExpense(c.Context(), nil, expenseID, invalidateFromDate, nil)
return responseDTO, nil
}
@@ -1057,6 +1102,7 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
}
var results []expenseDto.ExpenseDetailDTO
invalidateFromDateByExpenseID := make(map[uint]time.Time)
err = s.Repository.DB().WithContext(c.Context()).Transaction(func(tx *gorm.DB) error {
@@ -1069,6 +1115,17 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
); err != nil {
return err
}
expenseForInvalidation, err := expenseRepoTx.GetByID(c.Context(), id, nil)
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return fiber.NewError(fiber.StatusNotFound, "Expense not found")
}
return fiber.NewError(fiber.StatusInternalServerError, "Failed to load expense")
}
invalidateFromDateByExpenseID[id] = commonSvc.MinNonZeroDateOnlyUTC(
expenseForInvalidation.TransactionDate,
expenseForInvalidation.RealizationDate,
)
latestApproval, err := s.ApprovalSvc.LatestByTarget(c.Context(), utils.ApprovalWorkflowExpense, id, nil)
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
@@ -1170,10 +1227,73 @@ func (s *expenseService) Approval(c *fiber.Ctx, req *validation.ApprovalRequest,
}
return nil, fiber.NewError(fiber.StatusInternalServerError, "Failed approve expenses")
}
for expenseID, invalidateFromDate := range invalidateFromDateByExpenseID {
s.invalidateDepreciationSnapshotsByExpense(c.Context(), nil, expenseID, invalidateFromDate, nil)
}
return results, nil
}
func (s *expenseService) invalidateDepreciationSnapshotsByExpense(
ctx context.Context,
tx *gorm.DB,
expenseID uint,
fromDate time.Time,
fallbackFarmIDs []uint,
) {
targetDB := s.Repository.DB()
if tx != nil {
targetDB = tx
}
farmIDs := append([]uint{}, fallbackFarmIDs...)
if expenseID != 0 {
resolvedFarmIDs, err := commonSvc.ResolveProjectFlockIDsByExpenseID(ctx, targetDB, expenseID)
if err != nil {
s.Log.Warnf("Failed to resolve expense farm ids for invalidation (expense_id=%d): %+v", expenseID, err)
} else {
farmIDs = append(farmIDs, resolvedFarmIDs...)
}
}
s.invalidateDepreciationSnapshots(ctx, tx, farmIDs, fromDate)
}
func (s *expenseService) invalidateDepreciationSnapshots(
ctx context.Context,
tx *gorm.DB,
farmIDs []uint,
fromDate time.Time,
) {
if fromDate.IsZero() {
return
}
targetDB := s.Repository.DB()
if tx != nil {
targetDB = tx
}
farmIDs = utils.UniqueUintSlice(farmIDs)
if len(farmIDs) == 0 {
if err := commonSvc.InvalidateFarmDepreciationSnapshotsFromDate(ctx, targetDB, nil, fromDate); err != nil {
s.Log.Warnf(
"Failed to invalidate depreciation snapshots globally (from=%s): %+v",
fromDate.Format("2006-01-02"),
err,
)
}
return
}
if err := commonSvc.InvalidateFarmDepreciationSnapshotsFromDate(ctx, targetDB, farmIDs, fromDate); err != nil {
s.Log.Warnf(
"Failed to invalidate depreciation snapshots (farm_ids=%v, from=%s): %+v",
farmIDs,
fromDate.Format("2006-01-02"),
err,
)
}
}
func (s *expenseService) generatePoNumber(ctx *gorm.DB, expenseID uint) (string, error) {
expenseRepoTx := repository.NewExpenseRepository(ctx)