Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions api/externals/handler/campus_donation_handler.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package handler

import (
"database/sql"
"errors"
"net/http"

"github.com/NUTFes/FinanSu/api/generated"
"github.com/labstack/echo/v4"
)

// router.POST(baseURL+"/campus_donations", wrapper.PostCampusDonations)
func (h *Handler) PostCampusDonations(c echo.Context) error {
var request generated.PostCampusDonationsJSONRequestBody
if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, "failed to bind")
}

campusDonation, err := h.campusDonationUseCase.CreateCampusDonation(c.Request().Context(), request)
if err != nil {
return c.JSON(http.StatusInternalServerError, err.Error())
}

return c.JSON(http.StatusOK, campusDonation)
}

// router.GET(baseURL+"/campus_donations/years/:year/group_keys/:group_key/floors", wrapper.GetCampusDonationsYearsYearGroupKeysGroupKeyFloors)
func (h *Handler) GetCampusDonationsYearsYearGroupKeysGroupKeyFloors(
c echo.Context,
year int,
groupKey generated.CampusDonationBuildingGroupKey,
params generated.GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams,
) error {
groupKeyValue := string(groupKey)

buildingFloors, err := h.campusDonationUseCase.GetBuildingFloorDonationsByYear(
c.Request().Context(),
year,
&groupKeyValue,
params.FloorNumber,
)
if err != nil {
return err
}

return c.JSON(http.StatusOK, buildingFloors)
}

// router.PUT(baseURL+"/campus_donations/:id", wrapper.PutCampusDonationsId)
func (h *Handler) PutCampusDonationsId(c echo.Context, id int) error {
var request generated.PutCampusDonationsIdJSONRequestBody
if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, "failed to bind")
}

campusDonation, err := h.campusDonationUseCase.UpdateCampusDonation(c.Request().Context(), id, request)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return c.JSON(http.StatusNotFound, "campus donation not found")
}
return c.JSON(http.StatusInternalServerError, err.Error())
}

return c.JSON(http.StatusOK, campusDonation)
}
3 changes: 3 additions & 0 deletions api/externals/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ type Handler struct {
activityStyleUseCase usecase.ActivityStyleUseCase
bureauUseCase usecase.BureauUseCase
buyReportUseCase usecase.BuyReportUseCase
campusDonationUseCase usecase.CampusDonationUseCase
departmentUseCase usecase.DepartmentUseCase
divisionUseCase usecase.DivisionUseCase
festivalItemUseCase usecase.FestivalItemUseCase
Expand All @@ -33,6 +34,7 @@ func NewHandler(
activityStyleUseCase usecase.ActivityStyleUseCase,
bureauUseCase usecase.BureauUseCase,
buyReportUseCase usecase.BuyReportUseCase,
campusDonationUseCase usecase.CampusDonationUseCase,
departmentUseCase usecase.DepartmentUseCase,
divisionUseCase usecase.DivisionUseCase,
festivalItemUseCase usecase.FestivalItemUseCase,
Expand All @@ -55,6 +57,7 @@ func NewHandler(
activityStyleUseCase: activityStyleUseCase,
bureauUseCase: bureauUseCase,
buyReportUseCase: buyReportUseCase,
campusDonationUseCase: campusDonationUseCase,
departmentUseCase: departmentUseCase,
divisionUseCase: divisionUseCase,
festivalItemUseCase: festivalItemUseCase,
Expand Down
184 changes: 184 additions & 0 deletions api/externals/repository/campus_donation_repository.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package repository

import (
"context"
"database/sql"

"github.com/NUTFes/FinanSu/api/drivers/db"
"github.com/NUTFes/FinanSu/api/internals/domain"
goqu "github.com/doug-martin/goqu/v9"
)

type campusDonationRepository struct {
client db.Client
}

type CampusDonationRepository interface {
Create(context.Context, domain.CampusDonation) (int, error)
Update(context.Context, int, domain.CampusDonation) error
FindByID(context.Context, int) (domain.CampusDonation, error)
GetBuildingFloorDonationsByYear(context.Context, int, *string, *string) (*sql.Rows, error)
}

func NewCampusDonationRepository(c db.Client) CampusDonationRepository {
return &campusDonationRepository{client: c}
}

func (cdr *campusDonationRepository) Create(ctx context.Context, donation domain.CampusDonation) (int, error) {
ds := dialect.
Insert(goqu.T("campus_donations")).
Rows(goqu.Record{
"user_id": donation.UserID,
"teacher_id": donation.TeacherID,
"year_id": donation.YearID,
"price": donation.Price,
"received_at": donation.ReceivedAt,
})

query, args, err := ds.ToSQL()
if err != nil {
return 0, err
}

result, err := cdr.client.DB().ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}

id, err := result.LastInsertId()
if err != nil {
return 0, err
}

return int(id), nil
}

func (cdr *campusDonationRepository) Update(ctx context.Context, id int, donation domain.CampusDonation) error {
ds := dialect.
Update(goqu.T("campus_donations")).
Set(goqu.Record{
"user_id": donation.UserID,
"teacher_id": donation.TeacherID,
"year_id": donation.YearID,
"price": donation.Price,
"received_at": donation.ReceivedAt,
}).
Where(goqu.I("id").Eq(id))

query, args, err := ds.ToSQL()
if err != nil {
return err
}

_, err = cdr.client.DB().ExecContext(ctx, query, args...)
return err
}

func (cdr *campusDonationRepository) FindByID(ctx context.Context, id int) (domain.CampusDonation, error) {
ds := selectCampusDonationQuery().Where(goqu.I("campus_donations.id").Eq(id)).Limit(1)

query, args, err := ds.ToSQL()
if err != nil {
return domain.CampusDonation{}, err
}

var donation domain.CampusDonation
err = cdr.client.DB().QueryRowContext(ctx, query, args...).Scan(
&donation.ID,
&donation.UserID,
&donation.TeacherID,
&donation.YearID,
&donation.Price,
&donation.ReceivedAt,
)
if err != nil {
return domain.CampusDonation{}, err
}

return donation, nil
}

func (cdr *campusDonationRepository) GetBuildingFloorDonationsByYear(
ctx context.Context,
year int,
groupKey *string,
floorNumber *string,
) (*sql.Rows, error) {
donationTotalsByTeacher := dialect.
Select(
goqu.I("campus_donations.teacher_id").As("teacher_id"),
goqu.SUM(goqu.I("campus_donations.price")).As("total_price"),
).
From(goqu.T("campus_donations")).
InnerJoin(
goqu.T("years"),
goqu.On(goqu.I("campus_donations.year_id").Eq(goqu.I("years.id"))),
).
Where(goqu.I("years.year").Eq(year)).
GroupBy(goqu.I("campus_donations.teacher_id"))
Comment on lines +108 to +119
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The subquery donationTotalsByTeacher filters by year but does not filter by building or floor. While this is logically correct because it's joined later on teacher_id, it might be more efficient to include building/floor filters in the subquery if the campus_donations table is very large, to reduce the number of rows processed in the aggregation.


queryDataset := dialect.
From(goqu.T("buildings")).
InnerJoin(
goqu.T("rooms"),
goqu.On(goqu.I("rooms.building_id").Eq(goqu.I("buildings.id"))),
).
InnerJoin(
goqu.T("room_teachers"),
goqu.On(goqu.I("room_teachers.room_id").Eq(goqu.I("rooms.id"))),
).
InnerJoin(
goqu.T("teachers"),
goqu.On(goqu.I("teachers.id").Eq(goqu.I("room_teachers.teacher_id"))),
).
LeftJoin(
donationTotalsByTeacher.As("donation_totals"),
goqu.On(goqu.I("donation_totals.teacher_id").Eq(goqu.I("teachers.id"))),
).
Select(
goqu.I("buildings.id").As("building_id"),
goqu.I("buildings.name").As("building_name"),
goqu.I("buildings.unit_number").As("unit_number"),
goqu.I("rooms.floor_number").As("floor_number"),
goqu.I("rooms.room_name").As("room_name"),
goqu.I("teachers.id").As("teacher_id"),
goqu.I("teachers.name").As("teacher_name"),
goqu.COALESCE(goqu.I("donation_totals.total_price"), 0).As("total_price"),
Copy link
Copy Markdown
Collaborator

@Chikuwa0141 Chikuwa0141 May 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

仕様書

### 回収済み/未回収の判定
- 金額が登録済み(または募金レコードが存在)なら回収済み
- 未登録なら未回収

みたいに回収済みの0円なのか未回収なのかわかるようにしたいからnullのままはどう?
それともisDonatedとか追加する?

goqu.COALESCE(goqu.I("teachers.is_black"), false).As("is_black"),
).
Order(
goqu.I("buildings.unit_number").Asc(),
goqu.I("buildings.id").Asc(),
goqu.I("rooms.floor_number").Asc(),
goqu.I("rooms.room_name").Asc(),
goqu.I("teachers.name").Asc(),
)

if groupKey != nil && *groupKey != "" {
queryDataset = queryDataset.Where(goqu.I("buildings.group_key").Eq(*groupKey))
}

if floorNumber != nil && *floorNumber != "" {
queryDataset = queryDataset.Where(goqu.I("rooms.floor_number").Eq(*floorNumber))
}

query, args, err := queryDataset.ToSQL()
if err != nil {
return nil, err
}

return cdr.client.DB().QueryContext(ctx, query, args...)
}

func selectCampusDonationQuery() *goqu.SelectDataset {
return dialect.
From(goqu.T("campus_donations")).
Select(
goqu.I("campus_donations.id").As("id"),
goqu.I("campus_donations.user_id").As("user_id"),
goqu.I("campus_donations.teacher_id").As("teacher_id"),
goqu.I("campus_donations.year_id").As("year_id"),
goqu.I("campus_donations.price").As("price"),
goqu.L("DATE_FORMAT(campus_donations.received_at, '%Y-%m-%d')").As("received_at"),
)
}
1 change: 1 addition & 0 deletions api/externals/repository/wire.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ var RepositoryProviderSet = wire.NewSet(
NewBudgetRepository,
NewBureauRepository,
NewBuyReportRepository,
NewCampusDonationRepository,
NewDepartmentRepository,
NewDivisionRepository,
NewExpenseRepository,
Expand Down
Loading