mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
Merge branch 'development' of https://gitlab.com/mbugroup/lti-api into HOTFIX/BE/marketing
This commit is contained in:
@@ -25,6 +25,7 @@ type FifoService interface {
|
||||
Replenish(ctx context.Context, req StockReplenishRequest) (*StockReplenishResult, error)
|
||||
Consume(ctx context.Context, req StockConsumeRequest) (*StockConsumeResult, error)
|
||||
ReleaseUsage(ctx context.Context, req StockReleaseRequest) error
|
||||
AdjustStockableQuantity(ctx context.Context, req StockAdjustRequest) error
|
||||
}
|
||||
|
||||
type fifoService struct {
|
||||
@@ -95,6 +96,15 @@ type StockReplenishRequest struct {
|
||||
Tx *gorm.DB
|
||||
}
|
||||
|
||||
type StockAdjustRequest struct {
|
||||
StockableKey fifo.StockableKey
|
||||
StockableID uint
|
||||
ProductWarehouseID uint
|
||||
Quantity float64
|
||||
Note *string
|
||||
Tx *gorm.DB
|
||||
}
|
||||
|
||||
type PendingResolution struct {
|
||||
UsableKey fifo.UsableKey
|
||||
UsableID uint
|
||||
@@ -137,6 +147,37 @@ type StockReleaseRequest struct {
|
||||
Reason *string
|
||||
Tx *gorm.DB
|
||||
}
|
||||
func (s *fifoService) AdjustStockableQuantity(ctx context.Context, req StockAdjustRequest) error {
|
||||
if req.StockableID == 0 || strings.TrimSpace(req.StockableKey.String()) == "" {
|
||||
return errors.New("stockable key and id are required")
|
||||
}
|
||||
if req.ProductWarehouseID == 0 {
|
||||
return errors.New("product warehouse id is required")
|
||||
}
|
||||
if req.Quantity == 0 {
|
||||
return nil
|
||||
}
|
||||
if req.Quantity > 0 {
|
||||
return errors.New("quantity must be negative")
|
||||
}
|
||||
|
||||
cfg, ok := fifo.Stockable(req.StockableKey)
|
||||
if !ok {
|
||||
return fmt.Errorf("stockable %q is not registered", req.StockableKey)
|
||||
}
|
||||
|
||||
return s.withTransaction(ctx, req.Tx, func(tx *gorm.DB) error {
|
||||
if err := s.incrementStockableQty(ctx, tx, cfg, req.StockableID, req.Quantity); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return s.productWarehouseRepo.AdjustQuantities(ctx, map[uint]float64{
|
||||
req.ProductWarehouseID: req.Quantity,
|
||||
}, func(db *gorm.DB) *gorm.DB {
|
||||
return s.txOrDB(tx, db)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func (s *fifoService) Replenish(ctx context.Context, req StockReplenishRequest) (*StockReplenishResult, error) {
|
||||
if req.StockableID == 0 || strings.TrimSpace(req.StockableKey.String()) == "" {
|
||||
|
||||
@@ -28,10 +28,31 @@ func NewClosingController(closingService service.ClosingService, sapronakService
|
||||
}
|
||||
|
||||
func (u *ClosingController) GetAll(c *fiber.Ctx) error {
|
||||
var projectStatus *int
|
||||
if raw := c.Query("project_status"); raw != "" {
|
||||
statusValue, err := strconv.Atoi(raw)
|
||||
if err != nil {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid project_status")
|
||||
}
|
||||
projectStatus = &statusValue
|
||||
}
|
||||
|
||||
var locationID *uint
|
||||
if raw := c.Query("location_id"); raw != "" {
|
||||
locationValue, err := strconv.Atoi(raw)
|
||||
if err != nil || locationValue <= 0 {
|
||||
return fiber.NewError(fiber.StatusBadRequest, "Invalid location_id")
|
||||
}
|
||||
locationUint := uint(locationValue)
|
||||
locationID = &locationUint
|
||||
}
|
||||
|
||||
query := &validation.Query{
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
Page: c.QueryInt("page", 1),
|
||||
Limit: c.QueryInt("limit", 10),
|
||||
Search: c.Query("search", ""),
|
||||
ProjectStatus: projectStatus,
|
||||
LocationID: locationID,
|
||||
}
|
||||
|
||||
if query.Page < 1 || query.Limit < 1 {
|
||||
|
||||
@@ -98,26 +98,26 @@ type ClosingEggSalesDTO struct {
|
||||
}
|
||||
|
||||
type ClosingPerformanceDTO struct {
|
||||
Depletion float64 `json:"depletion"`
|
||||
Age float64 `json:"age_day"`
|
||||
MortalityStd float64 `json:"mor_std"`
|
||||
MortalityAct float64 `json:"mor_act"`
|
||||
DeffMortality float64 `json:"mor_diff"`
|
||||
FcrStd float64 `json:"fcr_std"`
|
||||
FcrAct float64 `json:"fcr_act"`
|
||||
DeffFcr float64 `json:"fcr_diff"`
|
||||
AwgAct float64 `json:"awg_act"`
|
||||
AwgStd float64 `json:"awg_std"`
|
||||
FeedIntake float64 `json:"feed_intake"`
|
||||
FeedIntakeStd float64 `json:"feed_intake_std"`
|
||||
HenDayAct *float64 `json:"hen_day_act,omitempty"`
|
||||
HendayStd float64 `json:"hen_day_std"`
|
||||
EggMass *float64 `json:"egg_mass,omitempty"`
|
||||
EggMassStd float64 `json:"egg_mass_std"`
|
||||
EggWeight *float64 `json:"egg_weight,omitempty"`
|
||||
EggWeightStd float64 `json:"egg_weight_std"`
|
||||
HenHouseAct *float64 `json:"hen_housed_act,omitempty"`
|
||||
HenHouseStd float64 `json:"hen_housed_std"`
|
||||
Depletion float64 `json:"depletion"`
|
||||
Age float64 `json:"age_day"`
|
||||
MortalityStd float64 `json:"mor_std"`
|
||||
MortalityAct float64 `json:"mor_act"`
|
||||
DeffMortality float64 `json:"mor_diff"`
|
||||
FcrStd float64 `json:"fcr_std"`
|
||||
FcrAct float64 `json:"fcr_act"`
|
||||
DeffFcr float64 `json:"fcr_diff"`
|
||||
AwgAct float64 `json:"awg_act"`
|
||||
AwgStd float64 `json:"awg_std"`
|
||||
FeedIntake float64 `json:"feed_intake"`
|
||||
FeedIntakeStd float64 `json:"feed_intake_std"`
|
||||
HenDayAct float64 `json:"hen_day_act,omitempty"`
|
||||
HendayStd float64 `json:"hen_day_std"`
|
||||
EggMass float64 `json:"egg_mass,omitempty"`
|
||||
EggMassStd float64 `json:"egg_mass_std"`
|
||||
EggWeight float64 `json:"egg_weight,omitempty"`
|
||||
EggWeightStd float64 `json:"egg_weight_std"`
|
||||
HenHouseAct float64 `json:"hen_housed_act,omitempty"`
|
||||
HenHouseStd float64 `json:"hen_housed_std"`
|
||||
}
|
||||
|
||||
type ClosingSalesGroupDTO struct {
|
||||
|
||||
@@ -99,9 +99,31 @@ func (s closingService) GetAll(c *fiber.Ctx, params *validation.Query) ([]dto.Cl
|
||||
}
|
||||
|
||||
offset := (params.Page - 1) * params.Limit
|
||||
statusFilter := ""
|
||||
if params.ProjectStatus != nil {
|
||||
switch *params.ProjectStatus {
|
||||
case 1:
|
||||
statusFilter = "Pengajuan"
|
||||
case 2:
|
||||
statusFilter = "Aktif"
|
||||
}
|
||||
}
|
||||
|
||||
closings, total, err := s.Repository.GetAll(c.Context(), offset, params.Limit, func(db *gorm.DB) *gorm.DB {
|
||||
db = s.withClosingRelations(db)
|
||||
if params.LocationID != nil {
|
||||
db = db.Where("location_id = ?", *params.LocationID)
|
||||
}
|
||||
if statusFilter != "" {
|
||||
latestApprovalSubQuery := s.Repository.DB().
|
||||
WithContext(c.Context()).
|
||||
Table("approvals").
|
||||
Select("DISTINCT ON (approvable_id) approvable_id, step_name, id").
|
||||
Where("approvable_type = ?", utils.ApprovalWorkflowProjectFlock.String()).
|
||||
Order("approvable_id, id DESC")
|
||||
db = db.Joins("JOIN (?) AS latest_approval ON latest_approval.approvable_id = project_flocks.id", latestApprovalSubQuery).
|
||||
Where("LOWER(latest_approval.step_name) = LOWER(?)", statusFilter)
|
||||
}
|
||||
if params.Search != "" {
|
||||
return db.Where("flock_name ILIKE ?", "%"+params.Search+"%")
|
||||
}
|
||||
@@ -930,19 +952,19 @@ func (s closingService) GetClosingDataProduksi(c *fiber.Ctx, projectFlockID uint
|
||||
if !isGrowing {
|
||||
if targetAverages.HenDayCount > 0 {
|
||||
henDayAct := targetAverages.HenDayAvg
|
||||
performance.HenDayAct = &henDayAct
|
||||
performance.HenDayAct = henDayAct
|
||||
}
|
||||
if targetAverages.HenHouseCount > 0 {
|
||||
henHouseAct := targetAverages.HenHouseAvg
|
||||
performance.HenHouseAct = &henHouseAct
|
||||
performance.HenHouseAct = henHouseAct
|
||||
}
|
||||
if targetAverages.EggWeightCount > 0 {
|
||||
eggWeight := targetAverages.EggWeightAvg
|
||||
performance.EggWeight = &eggWeight
|
||||
performance.EggWeight = eggWeight
|
||||
}
|
||||
if targetAverages.EggMassCount > 0 {
|
||||
eggMass := targetAverages.EggMassAvg
|
||||
performance.EggMass = &eggMass
|
||||
performance.EggMass = eggMass
|
||||
}
|
||||
}
|
||||
performance.DeffFcr = performance.FcrStd - performance.FcrAct
|
||||
|
||||
@@ -9,9 +9,11 @@ type Update struct {
|
||||
}
|
||||
|
||||
type Query struct {
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
Page int `query:"page" validate:"omitempty,number,min=1,gt=0"`
|
||||
Limit int `query:"limit" validate:"omitempty,number,min=1,max=100,gt=0"`
|
||||
Search string `query:"search" validate:"omitempty,max=50"`
|
||||
ProjectStatus *int `query:"project_status" validate:"omitempty,oneof=1 2"`
|
||||
LocationID *uint `query:"location_id" validate:"omitempty,gt=0"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -285,7 +285,7 @@ func (r *DashboardRepositoryImpl) SumEggProductionWeightGrams(ctx context.Contex
|
||||
|
||||
db := r.DB().WithContext(ctx).
|
||||
Table("recording_eggs AS re").
|
||||
Select("COALESCE(SUM(re.qty * re.weight), 0)").
|
||||
Select("COALESCE(SUM(re.weight * 1000), 0)").
|
||||
Joins("JOIN recordings AS r ON r.id = re.recording_id").
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
@@ -648,7 +648,7 @@ func (r *DashboardRepositoryImpl) GetEggWeightWeeklyGrams(ctx context.Context, s
|
||||
Table("recording_eggs AS re").
|
||||
Select(`
|
||||
((r.day - 1) / 7 + 1) AS week,
|
||||
COALESCE(SUM(re.qty * re.weight), 0) AS egg_weight_grams`).
|
||||
COALESCE(SUM(re.weight * 1000), 0) AS egg_weight_grams`).
|
||||
Joins("JOIN recordings AS r ON r.id = re.recording_id").
|
||||
Joins("JOIN project_flock_kandangs AS pfk ON pfk.id = r.project_flock_kandangs_id").
|
||||
Joins("JOIN kandangs AS k ON k.id = pfk.kandang_id").
|
||||
|
||||
@@ -171,6 +171,7 @@ func (r *RecordingRepositoryImpl) GenerateNextDay(tx *gorm.DB, projectFlockKanda
|
||||
var days []int
|
||||
if err := tx.Model(&entity.Recording{}).
|
||||
Where("project_flock_kandangs_id = ?", projectFlockKandangId).
|
||||
Where("deleted_at IS NULL").
|
||||
Where("day IS NOT NULL").
|
||||
Pluck("day", &days).Error; err != nil {
|
||||
return 0, err
|
||||
@@ -399,7 +400,7 @@ func (r *RecordingRepositoryImpl) GetEggSummaryByRecording(tx *gorm.DB, recordin
|
||||
}
|
||||
err = tx.
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0) AS total_qty, COALESCE(SUM(recording_eggs.qty * COALESCE(recording_eggs.weight, 0)), 0) AS total_weight_grams").
|
||||
Select("COALESCE(SUM(recording_eggs.qty), 0) AS total_qty, COALESCE(SUM(COALESCE(recording_eggs.weight, 0) * 1000), 0) AS total_weight_grams").
|
||||
Where("recording_eggs.recording_id = ?", recordingID).
|
||||
Scan(&result).Error
|
||||
if err != nil {
|
||||
@@ -485,7 +486,7 @@ func (r *RecordingRepositoryImpl) GetTotalEggProductionWeightByProjectFlockID(ct
|
||||
var result float64
|
||||
err := r.DB().WithContext(ctx).
|
||||
Table("recording_eggs").
|
||||
Select("COALESCE(SUM(recording_eggs.qty * recording_eggs.weight), 0) / 1000").
|
||||
Select("COALESCE(SUM(recording_eggs.weight), 0)").
|
||||
Joins("JOIN recordings ON recordings.id = recording_eggs.recording_id").
|
||||
Joins("JOIN project_flock_kandangs ON project_flock_kandangs.id = recordings.project_flock_kandangs_id").
|
||||
Where("project_flock_kandangs.project_flock_id = ?", projectFlockID).
|
||||
|
||||
@@ -1157,7 +1157,7 @@ func eggsMatch(existing []entity.RecordingEgg, incoming []validation.Egg) bool {
|
||||
}
|
||||
current := existingTotals[egg.ProductWarehouseId]
|
||||
current.Qty += egg.Qty
|
||||
current.Weight += float64(egg.Qty) * weight
|
||||
current.Weight += weight
|
||||
existingTotals[egg.ProductWarehouseId] = current
|
||||
}
|
||||
|
||||
@@ -1169,7 +1169,7 @@ func eggsMatch(existing []entity.RecordingEgg, incoming []validation.Egg) bool {
|
||||
}
|
||||
current := incomingTotals[egg.ProductWarehouseId]
|
||||
current.Qty += egg.Qty
|
||||
current.Weight += float64(egg.Qty) * weight
|
||||
current.Weight += weight
|
||||
incomingTotals[egg.ProductWarehouseId] = current
|
||||
}
|
||||
|
||||
@@ -1328,7 +1328,7 @@ func (s *recordingService) computeAndUpdateMetrics(ctx context.Context, tx *gorm
|
||||
|
||||
var eggMass float64
|
||||
if remainingChick > 0 && totalEggWeightGrams > 0 {
|
||||
eggMass = totalEggWeightGrams / remainingChick
|
||||
eggMass = (totalEggWeightGrams / remainingChick) / 1000
|
||||
updates["egg_mass"] = eggMass
|
||||
recording.EggMass = &eggMass
|
||||
} else {
|
||||
|
||||
@@ -844,6 +844,11 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
pwID uint
|
||||
qty float64
|
||||
}, 0, len(prepared))
|
||||
fifoSubs := make([]struct {
|
||||
itemID uint
|
||||
pwID uint
|
||||
qty float64
|
||||
}, 0, len(prepared))
|
||||
|
||||
for _, prep := range prepared {
|
||||
item := prep.item
|
||||
@@ -877,9 +882,18 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
totalQtyDeltas[item.Id] += deltaQty
|
||||
}
|
||||
case deltaQty < 0 && newPWID != nil:
|
||||
deltas[*newPWID] += deltaQty // negative
|
||||
affected[*newPWID] = struct{}{}
|
||||
totalQtyDeltas[item.Id] += deltaQty
|
||||
if s.FifoSvc != nil {
|
||||
fifoSubs = append(fifoSubs, struct {
|
||||
itemID uint
|
||||
pwID uint
|
||||
qty float64
|
||||
}{itemID: item.Id, pwID: *newPWID, qty: deltaQty})
|
||||
affected[*newPWID] = struct{}{}
|
||||
} else {
|
||||
deltas[*newPWID] += deltaQty // negative
|
||||
affected[*newPWID] = struct{}{}
|
||||
totalQtyDeltas[item.Id] += deltaQty
|
||||
}
|
||||
}
|
||||
|
||||
dateCopy := prep.receivedDate
|
||||
@@ -919,10 +933,6 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return err
|
||||
}
|
||||
|
||||
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(priceUpdates) > 0 {
|
||||
if err := repoTx.UpdatePricing(c.Context(), purchase.Id, priceUpdates); err != nil {
|
||||
return err
|
||||
@@ -967,6 +977,26 @@ func (s *purchaseService) ReceiveProducts(c *fiber.Ctx, id uint, req *validation
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, adj := range fifoSubs {
|
||||
if adj.pwID == 0 || adj.qty >= 0 {
|
||||
continue
|
||||
}
|
||||
if err := s.FifoSvc.AdjustStockableQuantity(c.Context(), commonSvc.StockAdjustRequest{
|
||||
StockableKey: fifo.StockableKeyPurchaseItems,
|
||||
StockableID: adj.itemID,
|
||||
ProductWarehouseID: adj.pwID,
|
||||
Quantity: adj.qty,
|
||||
Tx: tx,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if len(affected) > 0 {
|
||||
if err := pwRepoTx.CleanupEmpty(c.Context(), affected); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
||||
Reference in New Issue
Block a user