mirror of
https://gitlab.com/mbugroup/lti-api.git
synced 2026-05-20 13:31:56 +00:00
196 lines
6.4 KiB
Go
196 lines
6.4 KiB
Go
package repository
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"gitlab.com/mbugroup/lti-api.git/internal/common/exportprogress"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/common/repository"
|
|
entity "gitlab.com/mbugroup/lti-api.git/internal/entities"
|
|
"gitlab.com/mbugroup/lti-api.git/internal/utils"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type ExpenseRepository interface {
|
|
repository.BaseRepository[entity.Expense]
|
|
IdExists(ctx context.Context, id uint) (bool, error)
|
|
GetNextSequence(ctx context.Context) (int, error)
|
|
GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error)
|
|
WithProjectFlockKandangFilter(pfkID, kandangID uint) func(*gorm.DB) *gorm.DB
|
|
CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID, kandangID uint, isFinished func(*entity.Approval) bool) (int64, error)
|
|
DeleteOne(ctx context.Context, id uint) error
|
|
GetProgressRows(ctx context.Context, startDate, endDate time.Time, allowedLocationIDs []uint, restrict bool) ([]exportprogress.Row, error)
|
|
}
|
|
|
|
type ExpenseRepositoryImpl struct {
|
|
*repository.BaseRepositoryImpl[entity.Expense]
|
|
}
|
|
|
|
func NewExpenseRepository(db *gorm.DB) ExpenseRepository {
|
|
return &ExpenseRepositoryImpl{
|
|
BaseRepositoryImpl: repository.NewBaseRepository[entity.Expense](db),
|
|
}
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) IdExists(ctx context.Context, id uint) (bool, error) {
|
|
return repository.Exists[entity.Expense](ctx, r.DB(), id)
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) GetNextSequence(ctx context.Context) (int, error) {
|
|
var sequence int
|
|
err := r.DB().Raw("SELECT nextval('expenses_ref_seq')").Scan(&sequence).Error
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return sequence, nil
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) GetWithSupplier(ctx context.Context, id uint64) (*entity.Expense, error) {
|
|
var expense entity.Expense
|
|
err := r.DB().WithContext(ctx).
|
|
Where("id = ?", id).
|
|
Preload("Supplier").
|
|
First(&expense).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &expense, nil
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) WithProjectFlockKandangFilter(pfkID, kandangID uint) func(*gorm.DB) *gorm.DB {
|
|
return func(db *gorm.DB) *gorm.DB {
|
|
if pfkID == 0 && kandangID == 0 {
|
|
return db
|
|
}
|
|
q := db.Joins("JOIN expense_nonstocks ON expense_nonstocks.expense_id = expenses.id")
|
|
if pfkID > 0 && kandangID > 0 {
|
|
return q.Where("expense_nonstocks.project_flock_kandang_id = ? OR expense_nonstocks.kandang_id = ?", pfkID, kandangID)
|
|
}
|
|
if pfkID > 0 {
|
|
return q.Where("expense_nonstocks.project_flock_kandang_id = ?", pfkID)
|
|
}
|
|
return q.Where("expense_nonstocks.kandang_id = ?", kandangID)
|
|
}
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) CountUnfinishedByProjectFlockKandang(ctx context.Context, pfkID, kandangID uint, isFinished func(*entity.Approval) bool) (int64, error) {
|
|
if pfkID == 0 && kandangID == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var ids []uint64
|
|
if err := r.DB().WithContext(ctx).
|
|
Table("expenses").
|
|
Scopes(r.WithProjectFlockKandangFilter(pfkID, kandangID)).
|
|
Group("expenses.id").Where("expenses.deleted_at IS NULL").
|
|
Pluck("expenses.id", &ids).Error; err != nil {
|
|
return 0, err
|
|
}
|
|
if len(ids) == 0 {
|
|
return 0, nil
|
|
}
|
|
|
|
var unfinished int64
|
|
for _, id := range ids {
|
|
var latest entity.Approval
|
|
err := r.DB().WithContext(ctx).
|
|
Table("approvals").
|
|
Where("approvable_type = ? AND approvable_id = ?", utils.ApprovalWorkflowExpense.String(), id).
|
|
Order("action_at DESC").
|
|
Limit(1).
|
|
First(&latest).Error
|
|
if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return 0, err
|
|
}
|
|
if isFinished != nil {
|
|
if !isFinished(&latest) {
|
|
unfinished++
|
|
}
|
|
}
|
|
}
|
|
return unfinished, nil
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) DeleteOne(ctx context.Context, id uint) error {
|
|
// Cast to uint64 to match entity.Id type
|
|
id64 := uint64(id)
|
|
deletedAt := time.Now()
|
|
|
|
// Use raw SQL with interpolated integer to avoid type issues
|
|
// Interpolate id directly as integer literal (safe because it's uint64)
|
|
result := r.DB().WithContext(ctx).
|
|
Exec(`UPDATE "expenses" SET "deleted_at" = $1 WHERE "id" = `+fmt.Sprintf("%d", id64)+` AND "deleted_at" IS NULL`,
|
|
deletedAt)
|
|
|
|
if result.Error != nil {
|
|
return result.Error
|
|
}
|
|
if result.RowsAffected == 0 {
|
|
return gorm.ErrRecordNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (r *ExpenseRepositoryImpl) GetProgressRows(ctx context.Context, startDate, endDate time.Time, allowedLocationIDs []uint, restrict bool) ([]exportprogress.Row, error) {
|
|
const unassignedSQL = "'" + exportprogress.UnassignedKandangName + "'"
|
|
query := r.DB().WithContext(ctx).
|
|
Table("expenses AS e").
|
|
Select(`
|
|
'Expenses' AS module,
|
|
COALESCE(pf.flock_name, loc.name, fallback_loc.name, 'Unknown Farm') AS farm_name,
|
|
COALESCE(k.name, `+unassignedSQL+`, 'Unknown Kandang') AS kandang_name,
|
|
CAST(DATE(e.transaction_date) AS TEXT) AS activity_date,
|
|
COUNT(*) AS count
|
|
`).
|
|
Joins("LEFT JOIN (SELECT DISTINCT expense_id, project_flock_kandang_id, kandang_id FROM expense_nonstocks) en ON en.expense_id = e.id").
|
|
Joins("LEFT JOIN project_flock_kandangs pfk ON pfk.id = en.project_flock_kandang_id").
|
|
Joins("LEFT JOIN project_flocks pf ON pf.id = pfk.project_flock_id").
|
|
Joins("LEFT JOIN kandangs k ON k.id = COALESCE(en.kandang_id, pfk.kandang_id)").
|
|
Joins("LEFT JOIN locations loc ON loc.id = k.location_id").
|
|
Joins("LEFT JOIN locations fallback_loc ON fallback_loc.id = e.location_id").
|
|
Where("e.deleted_at IS NULL").
|
|
Where("DATE(e.transaction_date) >= DATE(?)", startDate).
|
|
Where("DATE(e.transaction_date) <= DATE(?)", endDate)
|
|
|
|
if restrict {
|
|
if len(allowedLocationIDs) == 0 {
|
|
return []exportprogress.Row{}, nil
|
|
}
|
|
query = query.Where("e.location_id IN ?", allowedLocationIDs)
|
|
}
|
|
|
|
type progressRowResult struct {
|
|
Module string
|
|
FarmName string
|
|
KandangName string
|
|
ActivityDate string
|
|
Count int
|
|
}
|
|
scanned := make([]progressRowResult, 0)
|
|
err := query.
|
|
Group("DATE(e.transaction_date), COALESCE(pf.flock_name, loc.name, fallback_loc.name, 'Unknown Farm'), COALESCE(k.name, " + unassignedSQL + ", 'Unknown Kandang')").
|
|
Order("activity_date ASC, farm_name ASC, kandang_name ASC").
|
|
Scan(&scanned).Error
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
rows := make([]exportprogress.Row, 0, len(scanned))
|
|
for _, item := range scanned {
|
|
activityDate, err := exportprogress.ParseActivityDate(item.ActivityDate)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
rows = append(rows, exportprogress.Row{
|
|
Module: item.Module,
|
|
FarmName: item.FarmName,
|
|
KandangName: item.KandangName,
|
|
ActivityDate: activityDate,
|
|
Count: item.Count,
|
|
})
|
|
}
|
|
return rows, nil
|
|
}
|