package repository import ( "apskel-pos-be/internal/entities" "context" "database/sql" "fmt" "github.com/google/uuid" ) type IngredientRepository struct { db *sql.DB } func NewIngredientRepository(db *sql.DB) *IngredientRepository { return &IngredientRepository{db: db} } func (r *IngredientRepository) Create(ctx context.Context, ingredient *entities.Ingredient) error { query := ` INSERT INTO ingredients (id, organization_id, outlet_id, name, unit_id, cost, stock, is_semi_finished, is_active, metadata, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12) ` _, err := r.db.ExecContext(ctx, query, ingredient.ID, ingredient.OrganizationID, ingredient.OutletID, ingredient.Name, ingredient.UnitID, ingredient.Cost, ingredient.Stock, ingredient.IsSemiFinished, ingredient.IsActive, ingredient.Metadata, ingredient.CreatedAt, ingredient.UpdatedAt, ) return err } func (r *IngredientRepository) GetByID(ctx context.Context, id, organizationID uuid.UUID) (*entities.Ingredient, error) { query := ` SELECT i.id, i.organization_id, i.outlet_id, i.name, i.unit_id, i.cost, i.stock, i.is_semi_finished, i.is_active, i.metadata, i.created_at, i.updated_at, u.id, u.organization_id, u.outlet_id, u.name, u.abbreviation, u.is_active, u.created_at, u.updated_at FROM ingredients i LEFT JOIN units u ON i.unit_id = u.id WHERE i.id = $1 AND i.organization_id = $2 ` ingredient := &entities.Ingredient{} unit := &entities.Unit{} err := r.db.QueryRowContext(ctx, query, id, organizationID).Scan( &ingredient.ID, &ingredient.OrganizationID, &ingredient.OutletID, &ingredient.Name, &ingredient.UnitID, &ingredient.Cost, &ingredient.Stock, &ingredient.IsSemiFinished, &ingredient.IsActive, &ingredient.Metadata, &ingredient.CreatedAt, &ingredient.UpdatedAt, &unit.ID, &unit.OrganizationID, &unit.OutletID, &unit.Name, &unit.Abbreviation, &unit.IsActive, &unit.CreatedAt, &unit.UpdatedAt, ) if err != nil { return nil, err } ingredient.Unit = unit return ingredient, nil } func (r *IngredientRepository) GetAll(ctx context.Context, organizationID uuid.UUID, outletID *uuid.UUID, page, limit int, search string, isSemiFinished *bool) ([]*entities.Ingredient, int, error) { offset := (page - 1) * limit // Build WHERE clause whereClause := "WHERE i.organization_id = $1" args := []interface{}{organizationID} argCount := 1 if outletID != nil { argCount++ whereClause += fmt.Sprintf(" AND i.outlet_id = $%d", argCount) args = append(args, *outletID) } if search != "" { argCount++ whereClause += fmt.Sprintf(" AND i.name ILIKE $%d", argCount) args = append(args, "%"+search+"%") } if isSemiFinished != nil { argCount++ whereClause += fmt.Sprintf(" AND i.is_semi_finished = $%d", argCount) args = append(args, *isSemiFinished) } // Count query countQuery := fmt.Sprintf("SELECT COUNT(*) FROM ingredients i %s", whereClause) var total int err := r.db.QueryRowContext(ctx, countQuery, args...).Scan(&total) if err != nil { return nil, 0, err } // Data query argCount++ query := fmt.Sprintf(` SELECT i.id, i.organization_id, i.outlet_id, i.name, i.unit_id, i.cost, i.stock, i.is_semi_finished, i.is_active, i.metadata, i.created_at, i.updated_at, u.id, u.organization_id, u.outlet_id, u.name, u.abbreviation, u.is_active, u.created_at, u.updated_at FROM ingredients i LEFT JOIN units u ON i.unit_id = u.id %s ORDER BY i.created_at DESC LIMIT $%d OFFSET $%d `, whereClause, argCount, argCount+1) args = append(args, limit, offset) rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, 0, err } defer rows.Close() var ingredients []*entities.Ingredient for rows.Next() { ingredient := &entities.Ingredient{} unit := &entities.Unit{} err := rows.Scan( &ingredient.ID, &ingredient.OrganizationID, &ingredient.OutletID, &ingredient.Name, &ingredient.UnitID, &ingredient.Cost, &ingredient.Stock, &ingredient.IsSemiFinished, &ingredient.IsActive, &ingredient.Metadata, &ingredient.CreatedAt, &ingredient.UpdatedAt, &unit.ID, &unit.OrganizationID, &unit.OutletID, &unit.Name, &unit.Abbreviation, &unit.IsActive, &unit.CreatedAt, &unit.UpdatedAt, ) if err != nil { return nil, 0, err } ingredient.Unit = unit ingredients = append(ingredients, ingredient) } return ingredients, total, nil } func (r *IngredientRepository) Update(ctx context.Context, ingredient *entities.Ingredient) error { query := ` UPDATE ingredients SET outlet_id = $1, name = $2, unit_id = $3, cost = $4, stock = $5, is_semi_finished = $6, is_active = $7, metadata = $8, updated_at = $9 WHERE id = $10 AND organization_id = $11 ` result, err := r.db.ExecContext(ctx, query, ingredient.OutletID, ingredient.Name, ingredient.UnitID, ingredient.Cost, ingredient.Stock, ingredient.IsSemiFinished, ingredient.IsActive, ingredient.Metadata, ingredient.UpdatedAt, ingredient.ID, ingredient.OrganizationID, ) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { return sql.ErrNoRows } return nil } func (r *IngredientRepository) UpdateStock(ctx context.Context, id uuid.UUID, quantity float64, organizationID uuid.UUID) error { query := ` UPDATE ingredients SET stock = stock + $1, updated_at = NOW() WHERE id = $2 AND organization_id = $3 ` result, err := r.db.ExecContext(ctx, query, quantity, id, organizationID) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { return sql.ErrNoRows } return nil } func (r *IngredientRepository) Delete(ctx context.Context, id, organizationID uuid.UUID) error { query := `DELETE FROM ingredients WHERE id = $1 AND organization_id = $2` result, err := r.db.ExecContext(ctx, query, id, organizationID) if err != nil { return err } rowsAffected, err := result.RowsAffected() if err != nil { return err } if rowsAffected == 0 { return sql.ErrNoRows } return nil }