package repository import ( "context" "fmt" "time" "gitlab.com/mbugroup/lti-api.git/internal/common/repository" entity "gitlab.com/mbugroup/lti-api.git/internal/entities" validation "gitlab.com/mbugroup/lti-api.git/internal/modules/closings/validations" "gitlab.com/mbugroup/lti-api.git/internal/utils" "gorm.io/gorm" ) type ClosingRepository interface { repository.BaseRepository[entity.ProjectFlock] ListProjectFlockKandangsForSapronak(ctx context.Context, params *validation.SapronakQuery) ([]entity.ProjectFlockKandang, error) MapSapronakStartDates(ctx context.Context, pfkIDs []uint) (map[uint]time.Time, error) FetchSapronakIncoming(ctx context.Context, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) FetchSapronakUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error) FetchSapronakUsageDetails(ctx context.Context, pfkID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) FetchSapronakAdjustments(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) FetchSapronakTransfers(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) } type ClosingRepositoryImpl struct { *repository.BaseRepositoryImpl[entity.ProjectFlock] } func NewClosingRepository(db *gorm.DB) ClosingRepository { return &ClosingRepositoryImpl{ BaseRepositoryImpl: repository.NewBaseRepository[entity.ProjectFlock](db), } } type SapronakIncomingRow struct { ProductID uint ProductName string Flag string Qty float64 Value float64 DefaultPrice float64 } type SapronakUsageRow struct { ProductID uint ProductName string Flag string Qty float64 DefaultPrice float64 } type SapronakDetailRow struct { ProductID uint ProductName string Flag string Date *time.Time Reference string QtyIn float64 QtyOut float64 Price float64 } func (r *ClosingRepositoryImpl) ListProjectFlockKandangsForSapronak(ctx context.Context, params *validation.SapronakQuery) ([]entity.ProjectFlockKandang, error) { db := r.DB(). WithContext(ctx). Preload("ProjectFlock"). Preload("Kandang") if params != nil { if params.ProjectFlockID > 0 { db = db.Where("project_flock_kandangs.project_flock_id = ?", params.ProjectFlockID) } if params.KandangID > 0 { db = db.Where("project_flock_kandangs.kandang_id = ?", params.KandangID) } if params.ProjectFlockKandangID > 0 { db = db.Where("project_flock_kandangs.id = ?", params.ProjectFlockKandangID) } } var pfks []entity.ProjectFlockKandang if err := db.Find(&pfks).Error; err != nil { return nil, err } return pfks, nil } func (r *ClosingRepositoryImpl) MapSapronakStartDates(ctx context.Context, pfkIDs []uint) (map[uint]time.Time, error) { result := make(map[uint]time.Time, len(pfkIDs)) if len(pfkIDs) == 0 { return result, nil } var rows []struct { ProjectFlockKandangID uint `gorm:"column:project_flock_kandang_id"` StartDate *time.Time `gorm:"column:start_date"` } if err := r.DB(). WithContext(ctx). Table("project_chickins"). Select("project_flock_kandang_id, MIN(chick_in_date) AS start_date"). Where("project_flock_kandang_id IN ?", pfkIDs). Group("project_flock_kandang_id"). Scan(&rows).Error; err != nil { return nil, err } for _, row := range rows { if row.StartDate != nil { result[row.ProjectFlockKandangID] = row.StartDate.UTC() } } return result, nil } func (r *ClosingRepositoryImpl) FetchSapronakIncoming(ctx context.Context, kandangID uint, start, end *time.Time) ([]SapronakIncomingRow, error) { rows := make([]SapronakIncomingRow, 0) db := r.DB(). WithContext(ctx). Table("purchase_items AS pi"). Select(` pi.product_id AS product_id, p.name AS product_name, f.name AS flag, COALESCE(SUM(pi.total_qty), 0) AS qty, COALESCE(SUM(pi.total_qty * pi.price), 0) AS value, COALESCE(p.product_price, 0) AS default_price `). Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL"). Joins("JOIN products p ON p.id = pi.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pi.warehouse_id"). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}). Where("pi.received_date IS NOT NULL") if start != nil { db = db.Where("pi.received_date >= ?", *start) } if end != nil { db = db.Where("pi.received_date < ?", *end) } if err := db.Group("pi.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *ClosingRepositoryImpl) FetchSapronakUsage(ctx context.Context, pfkID uint, start, end *time.Time) ([]SapronakUsageRow, error) { rows := make([]SapronakUsageRow, 0) if pfkID == 0 { return rows, nil } db := r.DB(). WithContext(ctx). Table("recording_stocks AS rs"). Select(` pw.product_id AS product_id, p.name AS product_name, f.name AS flag, COALESCE(SUM(rs.usage_qty), 0) AS qty, COALESCE(p.product_price, 0) AS default_price `). Joins("JOIN recordings r ON r.id = rs.recording_id AND r.deleted_at IS NULL"). Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where("r.project_flock_kandangs_id = ?", pfkID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}) if start != nil { db = db.Where("r.record_datetime >= ?", *start) } if end != nil { db = db.Where("r.record_datetime < ?", *end) } if err := db.Group("pw.product_id, p.name, f.name, p.product_price").Scan(&rows).Error; err != nil { return nil, err } return rows, nil } func (r *ClosingRepositoryImpl) FetchSapronakIncomingDetails(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) { rows := make([]SapronakDetailRow, 0) db := r.DB(). WithContext(ctx). Table("purchase_items AS pi"). Select(` pi.product_id AS product_id, p.name AS product_name, f.name AS flag, pi.received_date AS date, COALESCE(po.po_number, '') AS reference, COALESCE(pi.total_qty,0) AS qty_in, 0 AS qty_out, COALESCE(pi.price,0) AS price `). Joins("JOIN purchases po ON po.id = pi.purchase_id AND po.deleted_at IS NULL"). Joins("JOIN products p ON p.id = pi.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pi.warehouse_id"). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}). Where("pi.received_date IS NOT NULL") if start != nil { db = db.Where("pi.received_date >= ?", *start) } if end != nil { db = db.Where("pi.received_date < ?", *end) } if err := db.Scan(&rows).Error; err != nil { return nil, err } result := make(map[uint][]SapronakDetailRow) for _, row := range rows { result[row.ProductID] = append(result[row.ProductID], row) } return result, nil } func (r *ClosingRepositoryImpl) FetchSapronakUsageDetails(ctx context.Context, pfkID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, error) { rows := make([]SapronakDetailRow, 0) db := r.DB(). WithContext(ctx). Table("recording_stocks AS rs"). Select(` pw.product_id AS product_id, p.name AS product_name, f.name AS flag, r.record_datetime AS date, CAST(r.id AS TEXT) AS reference, 0 AS qty_in, COALESCE(rs.usage_qty,0) AS qty_out, COALESCE(p.product_price,0) AS price `). Joins("JOIN recordings r ON r.id = rs.recording_id AND r.deleted_at IS NULL"). Joins("JOIN product_warehouses pw ON pw.id = rs.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Where("r.project_flock_kandangs_id = ?", pfkID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}) if start != nil { db = db.Where("r.record_datetime >= ?", *start) } if end != nil { db = db.Where("r.record_datetime < ?", *end) } if err := db.Scan(&rows).Error; err != nil { return nil, err } result := make(map[uint][]SapronakDetailRow) for _, row := range rows { result[row.ProductID] = append(result[row.ProductID], row) } return result, nil } func (r *ClosingRepositoryImpl) FetchSapronakAdjustments(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { incoming := make(map[uint][]SapronakDetailRow) outgoing := make(map[uint][]SapronakDetailRow) rows := make([]struct { ID uint ProductID uint ProductName string Flag string CreatedAt *time.Time Increase float64 Decrease float64 Price float64 }, 0) db := r.DB(). WithContext(ctx). Table("stock_logs sl"). Select(` sl.id AS id, pw.product_id AS product_id, p.name AS product_name, f.name AS flag, sl.created_at AS created_at, COALESCE(sl.increase,0) AS increase, COALESCE(sl.decrease,0) AS decrease, COALESCE(p.product_price,0) AS price `). Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). Where("sl.loggable_type = ?", entity.LogTypeAdjustment). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}) if start != nil { db = db.Where("sl.created_at >= ?", *start) } if end != nil { db = db.Where("sl.created_at < ?", *end) } if err := db.Scan(&rows).Error; err != nil { return nil, nil, err } for _, row := range rows { ref := fmt.Sprintf("ADJ-%d", row.ID) if row.Increase > 0 { incoming[row.ProductID] = append(incoming[row.ProductID], SapronakDetailRow{ ProductID: row.ProductID, ProductName: row.ProductName, Flag: row.Flag, Date: row.CreatedAt, Reference: ref, QtyIn: row.Increase, QtyOut: 0, Price: row.Price, }) } if row.Decrease > 0 { outgoing[row.ProductID] = append(outgoing[row.ProductID], SapronakDetailRow{ ProductID: row.ProductID, ProductName: row.ProductName, Flag: row.Flag, Date: row.CreatedAt, Reference: ref, QtyIn: 0, QtyOut: row.Decrease, Price: row.Price, }) } } return incoming, outgoing, nil } func (r *ClosingRepositoryImpl) FetchSapronakTransfers(ctx context.Context, kandangID uint, start, end *time.Time) (map[uint][]SapronakDetailRow, map[uint][]SapronakDetailRow, error) { incoming := make(map[uint][]SapronakDetailRow) outgoing := make(map[uint][]SapronakDetailRow) rows := make([]struct { ID uint ProductID uint ProductName string Flag string CreatedAt *time.Time Increase float64 Decrease float64 Price float64 }, 0) db := r.DB(). WithContext(ctx). Table("stock_logs sl"). Select(` sl.id AS id, pw.product_id AS product_id, p.name AS product_name, f.name AS flag, sl.created_at AS created_at, COALESCE(sl.increase,0) AS increase, COALESCE(sl.decrease,0) AS decrease, COALESCE(p.product_price,0) AS price `). Joins("JOIN product_warehouses pw ON pw.id = sl.product_warehouse_id"). Joins("JOIN products p ON p.id = pw.product_id"). Joins("JOIN flags f ON f.flagable_id = p.id AND f.flagable_type = ?", entity.FlagableTypeProduct). Joins("JOIN warehouses w ON w.id = pw.warehouse_id"). Where("sl.loggable_type = ?", entity.LogTypeTransfer). Where("w.kandang_id = ?", kandangID). Where("f.name IN ?", []string{string(utils.FlagDOC), string(utils.FlagPakan), string(utils.FlagOVK)}) if start != nil { db = db.Where("sl.created_at >= ?", *start) } if end != nil { db = db.Where("sl.created_at < ?", *end) } if err := db.Scan(&rows).Error; err != nil { return nil, nil, err } for _, row := range rows { ref := fmt.Sprintf("TRF-%d", row.ID) if row.Increase > 0 { incoming[row.ProductID] = append(incoming[row.ProductID], SapronakDetailRow{ ProductID: row.ProductID, ProductName: row.ProductName, Flag: row.Flag, Date: row.CreatedAt, Reference: ref, QtyIn: row.Increase, QtyOut: 0, Price: row.Price, }) } if row.Decrease > 0 { outgoing[row.ProductID] = append(outgoing[row.ProductID], SapronakDetailRow{ ProductID: row.ProductID, ProductName: row.ProductName, Flag: row.Flag, Date: row.CreatedAt, Reference: ref, QtyIn: 0, QtyOut: row.Decrease, Price: row.Price, }) } } return incoming, outgoing, nil }