mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-24 07:15:43 +00:00
fix: adjusment module depletion, chickin, recording refactor
This commit is contained in:
@@ -46,6 +46,7 @@ type adjustmentService struct {
|
||||
const (
|
||||
adjustmentLaneStockable = "STOCKABLE"
|
||||
adjustmentLaneUsable = "USABLE"
|
||||
flagGroupAyam = "AYAM"
|
||||
)
|
||||
|
||||
func NewAdjustmentService(
|
||||
@@ -129,8 +130,11 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
if functionCode == "" {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Transaction subtype is required")
|
||||
}
|
||||
if functionCode == string(utils.AdjustmentTransactionSubtypeRecordingDepletionIn) {
|
||||
functionCode = string(utils.AdjustmentTransactionSubtypeRecordingDepletionOut)
|
||||
if functionCode == string(utils.AdjustmentTransactionSubtypeRecordingDepletionOut) {
|
||||
return nil, fiber.NewError(
|
||||
fiber.StatusBadRequest,
|
||||
"RECORDING_DEPLETION_OUT tidak boleh diinput manual. Gunakan RECORDING_DEPLETION_IN, sistem akan otomatis membuat depletion-out AYAM",
|
||||
)
|
||||
}
|
||||
|
||||
warehouseID, err := s.resolveWarehouseID(c.Context(), req)
|
||||
@@ -211,6 +215,133 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get product warehouse")
|
||||
}
|
||||
|
||||
if functionCode == string(utils.AdjustmentTransactionSubtypeRecordingDepletionIn) {
|
||||
if routeMeta.Lane != adjustmentLaneStockable {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Transaction subtype depletion in harus lane STOCKABLE")
|
||||
}
|
||||
if projectFlockKandangID == nil || *projectFlockKandangID == 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id aktif wajib tersedia untuk depletion conversion")
|
||||
}
|
||||
if s.FifoStockV2Svc == nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "FIFO v2 service is not available")
|
||||
}
|
||||
|
||||
sourcePW, err := s.resolveAyamSourceProductWarehouse(ctx, tx, warehouseID, *projectFlockKandangID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := common.EnsureProjectFlockNotClosedForProductWarehouses(
|
||||
ctx,
|
||||
tx,
|
||||
[]uint{productWarehouse.Id, sourcePW.Id},
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sourceRoute, err := s.resolveRouteByFunctionCode(
|
||||
ctx,
|
||||
sourcePW.ProductId,
|
||||
string(utils.AdjustmentTransactionSubtypeRecordingDepletionOut),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if sourceRoute.Lane != adjustmentLaneUsable {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Route depletion out untuk produk AYAM tidak valid")
|
||||
}
|
||||
|
||||
sourceCode, err := adjustmentStockRepoTX.GenerateSequentialNumber(ctx, utils.AdjustmentStockNumberPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
sourceAdjustment := &entity.AdjustmentStock{
|
||||
ProductWarehouseId: sourcePW.Id,
|
||||
TransactionType: transactionType,
|
||||
FunctionCode: sourceRoute.FunctionCode,
|
||||
UsageQty: qty,
|
||||
Price: req.Price,
|
||||
GrandTotal: grandTotal,
|
||||
AdjNumber: sourceCode,
|
||||
}
|
||||
if err := adjustmentStockRepoTX.CreateOne(ctx, sourceAdjustment, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create depletion source adjustment stock record")
|
||||
}
|
||||
|
||||
destCode, err := adjustmentStockRepoTX.GenerateSequentialNumber(ctx, utils.AdjustmentStockNumberPrefix)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
destinationAdjustment := &entity.AdjustmentStock{
|
||||
ProductWarehouseId: productWarehouse.Id,
|
||||
TransactionType: transactionType,
|
||||
FunctionCode: routeMeta.FunctionCode,
|
||||
TotalQty: qty,
|
||||
Price: req.Price,
|
||||
GrandTotal: grandTotal,
|
||||
AdjNumber: destCode,
|
||||
}
|
||||
if err := adjustmentStockRepoTX.CreateOne(ctx, destinationAdjustment, nil); err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to create depletion destination adjustment stock record")
|
||||
}
|
||||
|
||||
sourceAsOf := sourceAdjustment.CreatedAt
|
||||
if _, err := s.FifoStockV2Svc.Reflow(ctx, common.FifoStockV2ReflowRequest{
|
||||
FlagGroupCode: sourceRoute.FlagGroupCode,
|
||||
ProductWarehouseID: sourcePW.Id,
|
||||
AsOf: &sourceAsOf,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to auto depletion-out AYAM via FIFO v2: %v", err))
|
||||
}
|
||||
|
||||
destinationAsOf := destinationAdjustment.CreatedAt
|
||||
if _, err := s.FifoStockV2Svc.Reflow(ctx, common.FifoStockV2ReflowRequest{
|
||||
FlagGroupCode: routeMeta.FlagGroupCode,
|
||||
ProductWarehouseID: destinationAdjustment.ProductWarehouseId,
|
||||
AsOf: &destinationAsOf,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, fmt.Sprintf("Failed to auto depletion-in destination via FIFO v2: %v", err))
|
||||
}
|
||||
|
||||
refreshedSource, err := adjustmentStockRepoTX.GetByID(ctx, sourceAdjustment.Id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to refresh depletion source adjustment stock")
|
||||
}
|
||||
refreshedDestination, err := adjustmentStockRepoTX.GetByID(ctx, destinationAdjustment.Id, nil)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to refresh depletion destination adjustment stock")
|
||||
}
|
||||
|
||||
if err := s.createAdjustmentStockLog(
|
||||
ctx,
|
||||
stockLogRepoTX,
|
||||
refreshedSource.Id,
|
||||
refreshedSource.ProductWarehouseId,
|
||||
note,
|
||||
actorID,
|
||||
0,
|
||||
refreshedSource.UsageQty+refreshedSource.PendingQty,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.createAdjustmentStockLog(
|
||||
ctx,
|
||||
stockLogRepoTX,
|
||||
refreshedDestination.Id,
|
||||
refreshedDestination.ProductWarehouseId,
|
||||
note,
|
||||
actorID,
|
||||
refreshedDestination.TotalQty,
|
||||
0,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
createdAdjustmentStockId = destinationAdjustment.Id
|
||||
return nil
|
||||
}
|
||||
|
||||
adjustmentStock := &entity.AdjustmentStock{
|
||||
ProductWarehouseId: productWarehouse.Id,
|
||||
TransactionType: transactionType,
|
||||
@@ -264,29 +395,16 @@ func (s *adjustmentService) Adjustment(c *fiber.Ctx, req *validation.Create) (*e
|
||||
decreaseQty = refreshedAdjustment.UsageQty
|
||||
}
|
||||
|
||||
stockLogs, err := stockLogRepoTX.GetByProductWarehouse(ctx, productWarehouse.Id, 1)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||
}
|
||||
|
||||
currentStock := 0.0
|
||||
if len(stockLogs) > 0 {
|
||||
currentStock = stockLogs[0].Stock
|
||||
}
|
||||
|
||||
newLog := &entity.StockLog{
|
||||
LoggableType: string(utils.StockLogTypeAdjustment),
|
||||
LoggableId: adjustmentStock.Id,
|
||||
Notes: note,
|
||||
ProductWarehouseId: productWarehouse.Id,
|
||||
CreatedBy: actorID,
|
||||
Increase: increaseQty,
|
||||
Decrease: decreaseQty,
|
||||
Stock: currentStock + increaseQty - decreaseQty,
|
||||
}
|
||||
|
||||
if err := stockLogRepoTX.CreateOne(ctx, newLog, nil); err != nil {
|
||||
if err := s.createAdjustmentStockLog(
|
||||
ctx,
|
||||
stockLogRepoTX,
|
||||
adjustmentStock.Id,
|
||||
productWarehouse.Id,
|
||||
note,
|
||||
actorID,
|
||||
increaseQty,
|
||||
decreaseQty,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -417,6 +535,88 @@ func (s *adjustmentService) getActiveProjectFlockKandangID(ctx context.Context,
|
||||
return uint(projectFlockKandang.Id), nil
|
||||
}
|
||||
|
||||
func (s *adjustmentService) resolveAyamSourceProductWarehouse(
|
||||
ctx context.Context,
|
||||
tx *gorm.DB,
|
||||
warehouseID uint,
|
||||
projectFlockKandangID uint,
|
||||
) (*entity.ProductWarehouse, error) {
|
||||
if tx == nil {
|
||||
return nil, fmt.Errorf("transaction is required")
|
||||
}
|
||||
if projectFlockKandangID == 0 {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "project_flock_kandang_id tidak valid untuk depletion conversion")
|
||||
}
|
||||
|
||||
var sourcePW entity.ProductWarehouse
|
||||
err := tx.WithContext(ctx).
|
||||
Model(&entity.ProductWarehouse{}).
|
||||
Where("project_flock_kandang_id = ?", projectFlockKandangID).
|
||||
Where(`
|
||||
EXISTS (
|
||||
SELECT 1
|
||||
FROM flags f
|
||||
JOIN fifo_stock_v2_flag_members fm ON fm.flag_name = f.name AND fm.is_active = TRUE
|
||||
WHERE f.flagable_type = ?
|
||||
AND f.flagable_id = product_warehouses.product_id
|
||||
AND fm.flag_group_code = ?
|
||||
)
|
||||
`, entity.FlagableTypeProduct, flagGroupAyam).
|
||||
Order(gorm.Expr("CASE WHEN warehouse_id = ? THEN 0 ELSE 1 END ASC", warehouseID)).
|
||||
Order("id ASC").
|
||||
Take(&sourcePW).Error
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return nil, fiber.NewError(fiber.StatusBadRequest, "Produk sumber AYAM pada project flock kandang yang sama tidak ditemukan")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &sourcePW, nil
|
||||
}
|
||||
|
||||
func (s *adjustmentService) createAdjustmentStockLog(
|
||||
ctx context.Context,
|
||||
stockLogRepo stockLogsRepo.StockLogRepository,
|
||||
adjustmentID uint,
|
||||
productWarehouseID uint,
|
||||
note string,
|
||||
actorID uint,
|
||||
increaseQty float64,
|
||||
decreaseQty float64,
|
||||
) error {
|
||||
if stockLogRepo == nil || adjustmentID == 0 || productWarehouseID == 0 {
|
||||
return nil
|
||||
}
|
||||
if increaseQty == 0 && decreaseQty == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
stockLogs, err := stockLogRepo.GetByProductWarehouse(ctx, productWarehouseID, 1)
|
||||
if err != nil {
|
||||
s.Log.Errorf("Failed to get stock logs: %+v", err)
|
||||
return fiber.NewError(fiber.StatusInternalServerError, "Failed to get stock logs")
|
||||
}
|
||||
|
||||
currentStock := 0.0
|
||||
if len(stockLogs) > 0 {
|
||||
currentStock = stockLogs[0].Stock
|
||||
}
|
||||
|
||||
newLog := &entity.StockLog{
|
||||
LoggableType: string(utils.StockLogTypeAdjustment),
|
||||
LoggableId: adjustmentID,
|
||||
Notes: note,
|
||||
ProductWarehouseId: productWarehouseID,
|
||||
CreatedBy: actorID,
|
||||
Increase: increaseQty,
|
||||
Decrease: decreaseQty,
|
||||
Stock: currentStock + increaseQty - decreaseQty,
|
||||
}
|
||||
|
||||
return stockLogRepo.CreateOne(ctx, newLog, nil)
|
||||
}
|
||||
|
||||
func (s *adjustmentService) AdjustmentHistory(c *fiber.Ctx, query *validation.Query) ([]*entity.AdjustmentStock, int64, error) {
|
||||
if err := s.Validate.Struct(query); err != nil {
|
||||
return nil, 0, err
|
||||
|
||||
Reference in New Issue
Block a user