diff --git a/internal/common/service/common.fifo.service.go b/internal/common/service/common.fifo.service.go index e3b80268..bf97f831 100644 --- a/internal/common/service/common.fifo.service.go +++ b/internal/common/service/common.fifo.service.go @@ -505,12 +505,25 @@ func (s *fifoService) fetchStockLots(ctx context.Context, tx *gorm.DB, productWa var lots []stockLot for key, cfg := range configs { - selectStmt := fmt.Sprintf( - "%s AS id, %s AS available_qty, %s AS created_at", - cfg.Columns.ID, - fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity), - cfg.Columns.CreatedAt, - ) + + usesNumericTime := cfg.Columns.CreatedAt == cfg.Columns.ID + + var selectStmt string + if usesNumericTime { + + selectStmt = fmt.Sprintf( + "%s AS id, %s AS available_qty, '1970-01-01 00:00:00 UTC'::timestamp AS created_at", + cfg.Columns.ID, + fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity), + ) + } else { + selectStmt = fmt.Sprintf( + "%s AS id, %s AS available_qty, %s AS created_at", + cfg.Columns.ID, + fmt.Sprintf("%s - COALESCE(%s,0)", cfg.Columns.TotalQuantity, cfg.Columns.TotalUsedQuantity), + cfg.Columns.CreatedAt, + ) + } var rows []struct { ID uint diff --git a/internal/entities/stock_log.go b/internal/entities/stock_log.go index 310d8cf8..d6acafb8 100644 --- a/internal/entities/stock_log.go +++ b/internal/entities/stock_log.go @@ -2,16 +2,6 @@ package entities import "time" -const ( - LogTypeAdjustment = "ADJUSTMENT" - LogTypeTransfer = "TRANSFER" -) - -const ( - TransactionTypeIncrease = "INCREASE" - TransactionTypeDecrease = "DECREASE" -) - type StockLog struct { Id uint `gorm:"primaryKey;column:id"` ProductWarehouseId uint `gorm:"column:product_warehouse_id;not null;index"` diff --git a/internal/modules/closings/repositories/closing.repository.go b/internal/modules/closings/repositories/closing.repository.go index 6a59c5f9..cf49826a 100644 --- a/internal/modules/closings/repositories/closing.repository.go +++ b/internal/modules/closings/repositories/closing.repository.go @@ -783,7 +783,7 @@ func splitStockLogs(rows []stockLogSapronakRow, refFn func(stockLogSapronakRow) } func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { - rows, err := r.fetchStockLogs(ctx, kandangID, entity.LogTypeAdjustment, false) + rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeAdjustment), false) if err != nil { return nil, nil, err } @@ -792,7 +792,7 @@ func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, ka } func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { - rows, err := r.fetchStockLogs(ctx, kandangID, entity.LogTypeTransfer, true) + rows, err := r.fetchStockLogs(ctx, kandangID, string(utils.StockLogTypeTransfer), true) if err != nil { return nil, nil, err } diff --git a/internal/modules/inventory/adjustments/services/adjustment.service.go b/internal/modules/inventory/adjustments/services/adjustment.service.go index 7bcbca7e..5a634382 100644 --- a/internal/modules/inventory/adjustments/services/adjustment.service.go +++ b/internal/modules/inventory/adjustments/services/adjustment.service.go @@ -70,7 +70,7 @@ func (s *adjustmentService) GetOne(c *fiber.Ctx, id uint) (*entity.StockLog, err return nil, err } - if stockLog.LoggableType != entity.LogTypeAdjustment { + if stockLog.LoggableType != string(utils.StockLogTypeAdjustment) { return nil, fiber.NewError(fiber.StatusNotFound, "Adjustment not found") } @@ -97,7 +97,7 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e return nil, fiber.NewError(fiber.StatusBadRequest, "Quantity must be greater than zero") } transactionType := strings.ToUpper(req.TransactionType) - if transactionType != entity.TransactionTypeIncrease && transactionType != entity.TransactionTypeDecrease { + if transactionType != string(utils.StockLogTransactionTypeIncrease) && transactionType != string(utils.StockLogTransactionTypeDecrease) { return nil, fiber.NewError(fiber.StatusBadRequest, "Invalid transaction type") } @@ -154,14 +154,14 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e afterQuantity := productWarehouse.Quantity newLog := &entity.StockLog{ - // TransactionType: transactionType, - LoggableType: entity.LogTypeAdjustment, + + LoggableType: string(utils.StockLogTypeAdjustment), LoggableId: 0, Notes: req.Note, ProductWarehouseId: productWarehouse.Id, CreatedBy: actorID, // TODO: should Get from auth middleware } - if transactionType == entity.TransactionTypeIncrease { + if transactionType == string(utils.StockLogTransactionTypeIncrease) { afterQuantity += req.Quantity newLog.Increase = afterQuantity } else { @@ -248,7 +248,7 @@ func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Qu db = s.withRelations(db) - db = db.Where("loggable_type = ?", entity.LogTypeAdjustment) + db = db.Where("loggable_type = ?", string(utils.StockLogTypeAdjustment)) if query.TransactionType != "" { db = db.Where("transaction_type = ?", strings.ToUpper(query.TransactionType)) diff --git a/internal/modules/inventory/transfers/services/transfer.service.go b/internal/modules/inventory/transfers/services/transfer.service.go index 89e7b271..a8a8996e 100644 --- a/internal/modules/inventory/transfers/services/transfer.service.go +++ b/internal/modules/inventory/transfers/services/transfer.service.go @@ -259,7 +259,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques } if s.DocumentSvc != nil && len(files) > 0 { - // Upload documents for each delivery + for idx, file := range files { documentFiles := []commonSvc.DocumentFile{ { @@ -296,7 +296,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques decreaseLog := &entity.StockLog{ Decrease: product.ProductQty, Notes: "", - LoggableType: entity.LogTypeTransfer, + LoggableType: string(utils.StockLogTypeTransfer), LoggableId: uint(entityTransfer.Id), ProductWarehouseId: sourcePW.Id, CreatedBy: actorID, @@ -335,7 +335,7 @@ func (s *transferService) CreateOne(c *fiber.Ctx, req *validation.TransferReques increaseLog := &entity.StockLog{ Increase: product.ProductQty, - LoggableType: entity.LogTypeTransfer, + LoggableType: string(utils.StockLogTypeTransfer), LoggableId: uint(entityTransfer.Id), Notes: "", ProductWarehouseId: destPW.Id, diff --git a/internal/modules/production/chickins/module.go b/internal/modules/production/chickins/module.go index 2cd0ad7e..6c9b8984 100644 --- a/internal/modules/production/chickins/module.go +++ b/internal/modules/production/chickins/module.go @@ -42,7 +42,6 @@ func (ChickinModule) RegisterRoutes(router fiber.Router, db *gorm.DB, validate * fifoService := commonSvc.NewFifoService(db, stockAllocationRepo, productWarehouseRepo, utils.Log) userRepo := rUser.NewUserRepository(db) - // Register PROJECT_CHICKIN as usable if err := fifoService.RegisterUsable(fifo.UsableConfig{ Key: fifo.UsableKeyProjectChickin, Table: "project_chickins", diff --git a/internal/modules/production/chickins/services/chickin.service.go b/internal/modules/production/chickins/services/chickin.service.go index 965e39ba..871c8fce 100644 --- a/internal/modules/production/chickins/services/chickin.service.go +++ b/internal/modules/production/chickins/services/chickin.service.go @@ -16,6 +16,7 @@ import ( repository "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/repositories" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/production/chickins/validations" rProjectFlock "gitlab.com/mbugroup/lti-api.git/internal/modules/production/project_flocks/repositories" + rStockLogs "gitlab.com/mbugroup/lti-api.git/internal/modules/shared/repositories" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gitlab.com/mbugroup/lti-api.git/internal/utils/fifo" @@ -48,6 +49,7 @@ type chickinService struct { ProjectflockPopulationRepo rProjectFlock.ProjectFlockPopulationRepository ProjectChickinDetailRepo repository.ProjectChickinDetailRepository FifoSvc commonSvc.FifoService + StockLogRepo rStockLogs.StockLogRepository } func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo KandangRepo.KandangRepository, warehouseRepo rWarehouse.WarehouseRepository, productWarehouseRepo rProductWarehouse.ProductWarehouseRepository, projectFlockRepo rProjectFlock.ProjectflockRepository, projectflockkandangRepo rProjectFlock.ProjectFlockKandangRepository, projectflockpopulationRepo rProjectFlock.ProjectFlockPopulationRepository, projectChickinDetailRepo repository.ProjectChickinDetailRepository, validate *validator.Validate, fifoSvc commonSvc.FifoService) ChickinService { @@ -63,6 +65,7 @@ func NewChickinService(repo repository.ProjectChickinRepository, kandangRepo Kan ProjectflockPopulationRepo: projectflockpopulationRepo, ProjectChickinDetailRepo: projectChickinDetailRepo, FifoSvc: fifoSvc, + StockLogRepo: rStockLogs.NewStockLogRepository(repo.DB()), } } @@ -135,7 +138,7 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti return nil, err } newChikins := make([]*entity.ProjectChickin, 0) - chickinQtyMap := make(map[uint]float64) // Store desired qty for each chickin index + chickinQtyMap := make(map[uint]float64) for idx, chickinReq := range req.ChickinRequests { @@ -197,13 +200,11 @@ func (s *chickinService) CreateOne(c *fiber.Ctx, req *validation.Create) ([]enti for idx, chickin := range newChikins { desiredQty := chickinQtyMap[uint(idx)] - if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty); err != nil { + if err := s.ConsumeChickinStocks(c.Context(), dbTransaction, chickin, desiredQty, actorID); err != nil { return err } } - // Note: FIFO Consume already adjusts product warehouse quantities, no need to adjust again - latest, err := approvalSvcTx.LatestByTarget(c.Context(), utils.ApprovalWorkflowChickin, projectFlockKandang.Id, nil) if err != nil { return fiber.NewError(fiber.StatusInternalServerError, "Failed to get latest approval") @@ -306,8 +307,13 @@ func (s chickinService) DeleteOne(c *fiber.Ctx, id uint) error { return err } + actorID, err := m.ActorIDFromContext(c) + if err != nil { + return err + } + if chickin.UsageQty > 0 { - if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin); err != nil { + if err := s.ReleaseChickinStocks(c.Context(), s.Repository.DB(), chickin, actorID); err != nil { return err } @@ -461,7 +467,7 @@ func (s chickinService) Approval(c *fiber.Ctx, req *validation.Approve) ([]entit for _, chickin := range chickins { - if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin); err != nil { + if err := s.ReleaseChickinStocks(c.Context(), dbTransaction, &chickin, actorID); err != nil { return fiber.NewError(fiber.StatusInternalServerError, fmt.Sprintf("Failed to release stock for rejected chickin %d: %v", chickin.Id, err)) } @@ -591,7 +597,7 @@ func (s *chickinService) convertChickinsToTarget(ctx *fiber.Ctx, chickins []enti return nil } -func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64) error { +func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, desiredQty float64, actorID uint) error { if chickin == nil || s.FifoSvc == nil { return nil } @@ -622,14 +628,35 @@ func (s *chickinService) ConsumeChickinStocks(ctx context.Context, tx *gorm.DB, return err } + if result.UsageQuantity > 0 { + decreaseLog := &entity.StockLog{ + Decrease: result.UsageQuantity, + LoggableType: string(utils.StockLogTypeChikin), + LoggableId: chickin.Id, + ProductWarehouseId: chickin.ProductWarehouseId, + CreatedBy: actorID, + Notes: fmt.Sprintf("Chickin #%d", chickin.Id), + } + if err := s.StockLogRepo.CreateOne(ctx, decreaseLog, nil); err != nil { + s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err) + + } + } + return nil } -func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin) error { +func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, chickin *entity.ProjectChickin, actorID uint) error { if chickin == nil || s.FifoSvc == nil { return nil } + var currentUsage float64 + if err := tx.Model(&entity.ProjectChickin{}).Where("id = ?", chickin.Id).Select("usage_qty").Scan(¤tUsage).Error; err != nil { + s.Log.Warnf("Failed to get current usage for chickin %d: %+v", chickin.Id, err) + currentUsage = 0 + } + if err := s.FifoSvc.ReleaseUsage(ctx, commonSvc.StockReleaseRequest{ UsableKey: chickinUsableKey, UsableID: chickin.Id, @@ -646,6 +673,22 @@ func (s *chickinService) ReleaseChickinStocks(ctx context.Context, tx *gorm.DB, return err } + // Create stock log for the restoration + if currentUsage > 0 { + increaseLog := &entity.StockLog{ + Increase: currentUsage, + LoggableType: string(utils.StockLogTypeChikin), + LoggableId: chickin.Id, + ProductWarehouseId: chickin.ProductWarehouseId, + CreatedBy: actorID, + Notes: fmt.Sprintf("Chickin #%d - Stock released", chickin.Id), + } + if err := s.StockLogRepo.CreateOne(ctx, increaseLog, nil); err != nil { + s.Log.Errorf("Failed to create stock log for chickin %d: %+v", chickin.Id, err) + // Don't return error here, stock already released + } + } + return nil } diff --git a/internal/utils/constant.go b/internal/utils/constant.go index 02b15102..19711c47 100644 --- a/internal/utils/constant.go +++ b/internal/utils/constant.go @@ -111,6 +111,8 @@ type StockLogType string const ( StockLogTypeAdjustment StockLogType = "ADJUSTMENT" StockLogTypeTransfer StockLogType = "TRANSFER" + StockLogTypeMarketing StockLogType = "MARKETING" + StockLogTypeChikin StockLogType = "CHICKIN" ) // -------------------------------------------------------------------