diff --git a/api/externals/handler/campus_donation_handler.go b/api/externals/handler/campus_donation_handler.go index 20c67fdf3..acf83f3f0 100644 --- a/api/externals/handler/campus_donation_handler.go +++ b/api/externals/handler/campus_donation_handler.go @@ -1,12 +1,52 @@ package handler import ( + "database/sql" + "errors" "net/http" "strconv" + "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.GET(baseURL+"/campus_donations/buildings/:year", wrapper.GetCampusDonationsBuildingsYear) func (h *Handler) GetCampusDonationsBuildingsYear(c echo.Context, year int) error { yearStr := strconv.Itoa(year) @@ -17,3 +57,21 @@ func (h *Handler) GetCampusDonationsBuildingsYear(c echo.Context, year int) erro return c.JSON(http.StatusOK, buildingTotals) } + +// 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) +} diff --git a/api/externals/repository/campus_donation_repository.go b/api/externals/repository/campus_donation_repository.go index a0f4f8170..9a0ac0c52 100644 --- a/api/externals/repository/campus_donation_repository.go +++ b/api/externals/repository/campus_donation_repository.go @@ -5,25 +5,174 @@ import ( "database/sql" "github.com/NUTFes/FinanSu/api/drivers/db" - "github.com/NUTFes/FinanSu/api/externals/repository/abstract" + "github.com/NUTFes/FinanSu/api/internals/domain" goqu "github.com/doug-martin/goqu/v9" ) type campusDonationRepository struct { client db.Client - crud abstract.Crud } 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) AllBuildingTotalsByYear(context.Context, string) (*sql.Rows, error) } -func NewCampusDonationRepository(c db.Client, ac abstract.Crud) CampusDonationRepository { - return &campusDonationRepository{c, ac} +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")) + + 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.I("donation_totals.total_price").As("total_price"), + 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 (cdr *campusDonationRepository) AllBuildingTotalsByYear( - c context.Context, + ctx context.Context, year string, ) (*sql.Rows, error) { donationBuildingsDataset := dialect. @@ -74,5 +223,18 @@ func (cdr *campusDonationRepository) AllBuildingTotalsByYear( return nil, err } - return cdr.client.DB().QueryContext(c, query, args...) + 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"), + ) } diff --git a/api/generated/openapi_gen.go b/api/generated/openapi_gen.go index 6026b6cca..cd7403df0 100644 --- a/api/generated/openapi_gen.go +++ b/api/generated/openapi_gen.go @@ -53,6 +53,23 @@ const ( N2 BuyReportInformationStatus = "清算完了" ) +// Defines values for CampusDonationBuildingGroupKey. +const ( + Administration CampusDonationBuildingGroupKey = "administration" + AnalysisInstrumentationCenter CampusDonationBuildingGroupKey = "analysis_instrumentation_center" + Biology CampusDonationBuildingGroupKey = "biology" + ElectricalEngineering CampusDonationBuildingGroupKey = "electrical_engineering" + EnvironmentalSystem CampusDonationBuildingGroupKey = "environmental_system" + ExtremeEnergyDensityResearchCenter CampusDonationBuildingGroupKey = "extreme_energy_density_research_center" + GeneralResearch CampusDonationBuildingGroupKey = "general_research" + LargeExperiment CampusDonationBuildingGroupKey = "large_experiment" + MachineShop CampusDonationBuildingGroupKey = "machine_shop" + MaterialsManagementInformation CampusDonationBuildingGroupKey = "materials_management_information" + MechanicalCivilEngineering CampusDonationBuildingGroupKey = "mechanical_civil_engineering" + NuclearSystemSafety CampusDonationBuildingGroupKey = "nuclear_system_safety" + Other CampusDonationBuildingGroupKey = "other" +) + // Defines values for IncomeReceiveOption. const ( Hand IncomeReceiveOption = "hand" @@ -283,6 +300,66 @@ type BuyReportWithDivisionId struct { PaidByUserId *int `json:"paidByUserId,omitempty"` } +// CampusDonation defines model for campusDonation. +type CampusDonation struct { + // Id 学内募金ID + Id int `json:"id"` + + // Price 募金金額 + Price int `json:"price"` + + // ReceivedAt 受領日 + ReceivedAt openapi_types.Date `json:"receivedAt"` + + // TeacherId 募金対象の教員ID + TeacherId int `json:"teacherId"` + + // UserId 募金を登録・更新したユーザーID + UserId int `json:"userId"` + + // YearId 年度ID + YearId int `json:"yearId"` +} + +// CampusDonationBuildingFloor 各号棟の指定フロア教員情報 +type CampusDonationBuildingFloor struct { + BuildingId int `json:"buildingId"` + BuildingName string `json:"buildingName"` + Donations []CampusDonationTeacher `json:"donations"` + FloorNumber string `json:"floorNumber"` + UnitNumber int `json:"unitNumber"` +} + +// CampusDonationBuildingGroupKey 学内募金で表示する棟グループのキー +type CampusDonationBuildingGroupKey string + +// CampusDonationRequest defines model for campusDonationRequest. +type CampusDonationRequest struct { + // Price 募金金額 + Price int `json:"price"` + + // ReceivedAt 受領日 + ReceivedAt openapi_types.Date `json:"receivedAt"` + + // TeacherId 募金対象の教員ID + TeacherId int `json:"teacherId"` + + // UserId 募金を登録・更新したユーザーID + UserId int `json:"userId"` + + // YearId 年度ID + YearId int `json:"yearId"` +} + +// CampusDonationTeacher 棟・階ごとの教員別募金情報 +type CampusDonationTeacher struct { + IsBlack bool `json:"isBlack"` + RoomName string `json:"roomName"` + TeacherId int `json:"teacherId"` + TeacherName string `json:"teacherName"` + TotalPrice *int `json:"totalPrice"` +} + // DestroyTeacherIDs defines model for destroyTeacherIDs. type DestroyTeacherIDs struct { DeleteIDs []float32 `json:"deleteIDs"` @@ -603,6 +680,12 @@ type PutBuyReportsIdMultipartBody struct { File *openapi_types.File `json:"file,omitempty"` } +// GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams defines parameters for GetCampusDonationsYearsYearGroupKeysGroupKeyFloors. +type GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams struct { + // FloorNumber floor_number + FloorNumber *string `form:"floor_number,omitempty" json:"floor_number,omitempty"` +} + // GetCurrentUserParams defines parameters for GetCurrentUser. type GetCurrentUserParams struct { AccessToken *string `json:"Access-Token,omitempty"` @@ -903,6 +986,12 @@ type PostBuyReportsMultipartRequestBody PostBuyReportsMultipartBody // PutBuyReportsIdMultipartRequestBody defines body for PutBuyReportsId for multipart/form-data ContentType. type PutBuyReportsIdMultipartRequestBody PutBuyReportsIdMultipartBody +// PostCampusDonationsJSONRequestBody defines body for PostCampusDonations for application/json ContentType. +type PostCampusDonationsJSONRequestBody = CampusDonationRequest + +// PutCampusDonationsIdJSONRequestBody defines body for PutCampusDonationsId for application/json ContentType. +type PutCampusDonationsIdJSONRequestBody = CampusDonationRequest + // PostDivisionsJSONRequestBody defines body for PostDivisions for application/json ContentType. type PostDivisionsJSONRequestBody = Division @@ -1068,9 +1157,18 @@ type ServerInterface interface { // (PUT /buy_reports/{id}) PutBuyReportsId(ctx echo.Context, id int) error + // (POST /campus_donations) + PostCampusDonations(ctx echo.Context) error + // (GET /campus_donations/buildings/{year}) GetCampusDonationsBuildingsYear(ctx echo.Context, year int) error + // (GET /campus_donations/years/{year}/group_keys/{group_key}/floors) + GetCampusDonationsYearsYearGroupKeysGroupKeyFloors(ctx echo.Context, year int, groupKey CampusDonationBuildingGroupKey, params GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams) error + + // (PUT /campus_donations/{id}) + PutCampusDonationsId(ctx echo.Context, id int) error + // (GET /current_user) GetCurrentUser(ctx echo.Context, params GetCurrentUserParams) error @@ -1900,6 +1998,15 @@ func (w *ServerInterfaceWrapper) PutBuyReportsId(ctx echo.Context) error { return err } +// PostCampusDonations converts echo context to params. +func (w *ServerInterfaceWrapper) PostCampusDonations(ctx echo.Context) error { + var err error + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PostCampusDonations(ctx) + return err +} + // GetCampusDonationsBuildingsYear converts echo context to params. func (w *ServerInterfaceWrapper) GetCampusDonationsBuildingsYear(ctx echo.Context) error { var err error @@ -1916,6 +2023,55 @@ func (w *ServerInterfaceWrapper) GetCampusDonationsBuildingsYear(ctx echo.Contex return err } +// GetCampusDonationsYearsYearGroupKeysGroupKeyFloors converts echo context to params. +func (w *ServerInterfaceWrapper) GetCampusDonationsYearsYearGroupKeysGroupKeyFloors(ctx echo.Context) error { + var err error + // ------------- Path parameter "year" ------------- + var year int + + err = runtime.BindStyledParameterWithOptions("simple", "year", ctx.Param("year"), &year, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter year: %s", err)) + } + + // ------------- Path parameter "group_key" ------------- + var groupKey CampusDonationBuildingGroupKey + + err = runtime.BindStyledParameterWithOptions("simple", "group_key", ctx.Param("group_key"), &groupKey, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter group_key: %s", err)) + } + + // Parameter object where we will unmarshal all parameters from the context + var params GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams + // ------------- Optional query parameter "floor_number" ------------- + + err = runtime.BindQueryParameter("form", true, false, "floor_number", ctx.QueryParams(), ¶ms.FloorNumber) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter floor_number: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.GetCampusDonationsYearsYearGroupKeysGroupKeyFloors(ctx, year, groupKey, params) + return err +} + +// PutCampusDonationsId converts echo context to params. +func (w *ServerInterfaceWrapper) PutCampusDonationsId(ctx echo.Context) error { + var err error + // ------------- Path parameter "id" ------------- + var id int + + err = runtime.BindStyledParameterWithOptions("simple", "id", ctx.Param("id"), &id, runtime.BindStyledParameterOptions{ParamLocation: runtime.ParamLocationPath, Explode: false, Required: true}) + if err != nil { + return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter id: %s", err)) + } + + // Invoke the callback with all the unmarshaled arguments + err = w.Handler.PutCampusDonationsId(ctx, id) + return err +} + // GetCurrentUser converts echo context to params. func (w *ServerInterfaceWrapper) GetCurrentUser(ctx echo.Context) error { var err error @@ -3539,7 +3695,10 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL router.DELETE(baseURL+"/buy_reports/:id", wrapper.DeleteBuyReportsId) router.GET(baseURL+"/buy_reports/:id", wrapper.GetBuyReportsId) router.PUT(baseURL+"/buy_reports/:id", wrapper.PutBuyReportsId) + router.POST(baseURL+"/campus_donations", wrapper.PostCampusDonations) router.GET(baseURL+"/campus_donations/buildings/:year", wrapper.GetCampusDonationsBuildingsYear) + router.GET(baseURL+"/campus_donations/years/:year/group_keys/:group_key/floors", wrapper.GetCampusDonationsYearsYearGroupKeysGroupKeyFloors) + router.PUT(baseURL+"/campus_donations/:id", wrapper.PutCampusDonationsId) router.GET(baseURL+"/current_user", wrapper.GetCurrentUser) router.GET(baseURL+"/departments", wrapper.GetDepartments) router.POST(baseURL+"/departments", wrapper.PostDepartments) diff --git a/api/internals/di/wire_gen.go b/api/internals/di/wire_gen.go index e8e062225..7ab63596e 100644 --- a/api/internals/di/wire_gen.go +++ b/api/internals/di/wire_gen.go @@ -43,7 +43,7 @@ func InitializeServer() (*ServerComponents, error) { objectHandleRepository := repository.NewObjectHandleRepository(mcClient) incomeExpenditureManagementRepository := repository.NewIncomeExpenditureManagementRepository(client, crud) buyReportUseCase := usecase.NewBuyReportUseCase(buyReportRepository, transactionRepository, objectHandleRepository, incomeExpenditureManagementRepository) - campusDonationRepository := repository.NewCampusDonationRepository(client, crud) + campusDonationRepository := repository.NewCampusDonationRepository(client) campusDonationUseCase := usecase.NewCampusDonationUseCase(campusDonationRepository) departmentRepository := repository.NewDepartmentRepository(client, crud) departmentUseCase := usecase.NewDepartmentUseCase(departmentRepository) diff --git a/api/internals/domain/campus_donation.go b/api/internals/domain/campus_donation.go new file mode 100644 index 000000000..e0e492a80 --- /dev/null +++ b/api/internals/domain/campus_donation.go @@ -0,0 +1,10 @@ +package domain + +type CampusDonation struct { + ID int + UserID int + TeacherID int + YearID int + Price int + ReceivedAt string +} diff --git a/api/internals/usecase/campus_donation_usecase.go b/api/internals/usecase/campus_donation_usecase.go index 2d7fd5366..d46b9e1f7 100644 --- a/api/internals/usecase/campus_donation_usecase.go +++ b/api/internals/usecase/campus_donation_usecase.go @@ -2,10 +2,14 @@ package usecase import ( "context" + "fmt" "log" + "time" rep "github.com/NUTFes/FinanSu/api/externals/repository" "github.com/NUTFes/FinanSu/api/generated" + "github.com/NUTFes/FinanSu/api/internals/domain" + openapiTypes "github.com/oapi-codegen/runtime/types" "github.com/pkg/errors" ) @@ -51,11 +55,144 @@ type campusDonationUseCase struct { } type CampusDonationUseCase interface { + CreateCampusDonation(context.Context, generated.CampusDonationRequest) (generated.CampusDonation, error) + UpdateCampusDonation(context.Context, int, generated.CampusDonationRequest) (generated.CampusDonation, error) + GetBuildingFloorDonationsByYear(context.Context, int, *string, *string) ([]generated.CampusDonationBuildingFloor, error) GetBuildingTotalsByYear(context.Context, string) ([]generated.BuildingTotal, error) } func NewCampusDonationUseCase(rep rep.CampusDonationRepository) CampusDonationUseCase { - return &campusDonationUseCase{rep} + return &campusDonationUseCase{rep: rep} +} + +func (cdu *campusDonationUseCase) CreateCampusDonation( + ctx context.Context, + request generated.CampusDonationRequest, +) (generated.CampusDonation, error) { + donation := campusDonationRequestToDomain(request) + + id, err := cdu.rep.Create(ctx, donation) + if err != nil { + return generated.CampusDonation{}, err + } + + createdDonation, err := cdu.rep.FindByID(ctx, id) + if err != nil { + return generated.CampusDonation{}, err + } + + return domainCampusDonationToGenerated(createdDonation) +} + +func (cdu *campusDonationUseCase) UpdateCampusDonation( + ctx context.Context, + id int, + request generated.CampusDonationRequest, +) (generated.CampusDonation, error) { + donation := campusDonationRequestToDomain(request) + + if err := cdu.rep.Update(ctx, id, donation); err != nil { + return generated.CampusDonation{}, err + } + + updatedDonation, err := cdu.rep.FindByID(ctx, id) + if err != nil { + return generated.CampusDonation{}, err + } + + return domainCampusDonationToGenerated(updatedDonation) +} + +func (cdu *campusDonationUseCase) GetBuildingFloorDonationsByYear( + ctx context.Context, + year int, + groupKey *string, + floorNumber *string, +) ([]generated.CampusDonationBuildingFloor, error) { + rows, err := cdu.rep.GetBuildingFloorDonationsByYear(ctx, year, groupKey, floorNumber) + if err != nil { + return nil, err + } + defer func() { + if err := rows.Close(); err != nil { + log.Println(err) + } + }() + + buildingFloors := make([]generated.CampusDonationBuildingFloor, 0) + buildingFloorIndex := make(map[string]int) + + for rows.Next() { + var buildingFloor generated.CampusDonationBuildingFloor + var donation generated.CampusDonationTeacher + if err := rows.Scan( + &buildingFloor.BuildingId, + &buildingFloor.BuildingName, + &buildingFloor.UnitNumber, + &buildingFloor.FloorNumber, + &donation.RoomName, + &donation.TeacherId, + &donation.TeacherName, + &donation.TotalPrice, + &donation.IsBlack, + ); err != nil { + return nil, errors.Wrap(err, "failed to scan campus donation building floor row") + } + + buildingFloors = appendBuildingFloorDonation(buildingFloors, buildingFloorIndex, buildingFloor, donation) + } + + if err := rows.Err(); err != nil { + return nil, errors.Wrap(err, "failed to iterate campus donation building floors") + } + + return buildingFloors, nil +} + +func appendBuildingFloorDonation( + buildingFloors []generated.CampusDonationBuildingFloor, + buildingFloorIndex map[string]int, + buildingFloor generated.CampusDonationBuildingFloor, + donation generated.CampusDonationTeacher, +) []generated.CampusDonationBuildingFloor { + indexKey := fmt.Sprintf("%d:%s", buildingFloor.BuildingId, buildingFloor.FloorNumber) + index, ok := buildingFloorIndex[indexKey] + if !ok { + buildingFloor.Donations = []generated.CampusDonationTeacher{} + buildingFloors = append(buildingFloors, buildingFloor) + index = len(buildingFloors) - 1 + buildingFloorIndex[indexKey] = index + } + + buildingFloors[index].Donations = append(buildingFloors[index].Donations, donation) + + return buildingFloors +} + +func campusDonationRequestToDomain(request generated.CampusDonationRequest) domain.CampusDonation { + return domain.CampusDonation{ + UserID: request.UserId, + TeacherID: request.TeacherId, + YearID: request.YearId, + Price: request.Price, + ReceivedAt: request.ReceivedAt.String(), + } +} + +func domainCampusDonationToGenerated(donation domain.CampusDonation) (generated.CampusDonation, error) { + receivedAt, err := time.Parse(openapiTypes.DateFormat, donation.ReceivedAt) + if err != nil { + return generated.CampusDonation{}, err + } + + return generated.CampusDonation{ + Id: donation.ID, + UserId: donation.UserID, + TeacherId: donation.TeacherID, + YearId: donation.YearID, + Price: donation.Price, + ReceivedAt: openapiTypes.Date{Time: receivedAt}, + }, nil } func (cdu *campusDonationUseCase) GetBuildingTotalsByYear( diff --git a/api/internals/usecase/campus_donation_usecase_test.go b/api/internals/usecase/campus_donation_usecase_test.go index 48f987648..2ce1afab4 100644 --- a/api/internals/usecase/campus_donation_usecase_test.go +++ b/api/internals/usecase/campus_donation_usecase_test.go @@ -1,11 +1,119 @@ package usecase import ( + "reflect" "testing" "github.com/NUTFes/FinanSu/api/generated" ) +func intPtr(value int) *int { + return &value +} + +func TestAppendBuildingFloorDonation(t *testing.T) { + t.Parallel() + + buildingFloorIndex := make(map[string]int) + var buildingFloors []generated.CampusDonationBuildingFloor + + buildingFloors = appendBuildingFloorDonation( + buildingFloors, + buildingFloorIndex, + generated.CampusDonationBuildingFloor{ + BuildingId: 1, + BuildingName: "機械・建設棟", + UnitNumber: 1, + FloorNumber: "5", + }, + generated.CampusDonationTeacher{ + RoomName: "501", + TeacherId: 1, + TeacherName: "学内募金API確認教員A", + TotalPrice: intPtr(5000), + IsBlack: false, + }, + ) + buildingFloors = appendBuildingFloorDonation( + buildingFloors, + buildingFloorIndex, + generated.CampusDonationBuildingFloor{ + BuildingId: 1, + BuildingName: "機械・建設棟", + UnitNumber: 1, + FloorNumber: "5", + }, + generated.CampusDonationTeacher{ + RoomName: "502", + TeacherId: 2, + TeacherName: "学内募金API確認教員B", + TotalPrice: nil, + IsBlack: true, + }, + ) + buildingFloors = appendBuildingFloorDonation( + buildingFloors, + buildingFloorIndex, + generated.CampusDonationBuildingFloor{ + BuildingId: 2, + BuildingName: "機械・建設棟", + UnitNumber: 2, + FloorNumber: "5", + }, + generated.CampusDonationTeacher{ + RoomName: "501", + TeacherId: 3, + TeacherName: "学内募金API確認教員C", + TotalPrice: intPtr(7000), + IsBlack: false, + }, + ) + + want := []generated.CampusDonationBuildingFloor{ + { + BuildingId: 1, + BuildingName: "機械・建設棟", + UnitNumber: 1, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 1, + TeacherName: "学内募金API確認教員A", + TotalPrice: intPtr(5000), + IsBlack: false, + }, + { + RoomName: "502", + TeacherId: 2, + TeacherName: "学内募金API確認教員B", + TotalPrice: nil, + IsBlack: true, + }, + }, + }, + { + BuildingId: 2, + BuildingName: "機械・建設棟", + UnitNumber: 2, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 3, + TeacherName: "学内募金API確認教員C", + TotalPrice: intPtr(7000), + IsBlack: false, + }, + }, + }, + } + + if !reflect.DeepEqual(buildingFloors, want) { + t.Fatalf("unexpected result: got %+v, want %+v", buildingFloors, want) + } +} + func TestGroupBuildingTotalsByDisplayGroup(t *testing.T) { t.Parallel() diff --git a/api/test/fixtures/buildings.yml b/api/test/fixtures/buildings.yml new file mode 100644 index 000000000..29d2c9e83 --- /dev/null +++ b/api/test/fixtures/buildings.yml @@ -0,0 +1,14 @@ +- id: 1 + name: 学内募金API確認棟 + unit_number: 1 + group_key: campus_donation_test + +- id: 2 + name: 学内募金API確認棟 + unit_number: 2 + group_key: campus_donation_test + +- id: 3 + name: 学内募金API確認別棟 + unit_number: 1 + group_key: campus_donation_other_test diff --git a/api/test/fixtures/campus_donations.yml b/api/test/fixtures/campus_donations.yml new file mode 100644 index 000000000..ac122a5a3 --- /dev/null +++ b/api/test/fixtures/campus_donations.yml @@ -0,0 +1,41 @@ +- id: 1 + user_id: 1 + teacher_id: 1 + year_id: 1 + price: 3000 + received_at: 2025-05-01 + +- id: 2 + user_id: 1 + teacher_id: 1 + year_id: 1 + price: 2000 + received_at: 2025-05-02 + +- id: 3 + user_id: 2 + teacher_id: 3 + year_id: 1 + price: 7000 + received_at: 2025-05-03 + +- id: 4 + user_id: 2 + teacher_id: 4 + year_id: 1 + price: 9000 + received_at: 2025-05-04 + +- id: 5 + user_id: 1 + teacher_id: 5 + year_id: 1 + price: 4000 + received_at: 2025-05-05 + +- id: 6 + user_id: 1 + teacher_id: 3 + year_id: 2 + price: 8000 + received_at: 2024-05-06 diff --git a/api/test/fixtures/departments.yml b/api/test/fixtures/departments.yml new file mode 100644 index 000000000..a82425ec8 --- /dev/null +++ b/api/test/fixtures/departments.yml @@ -0,0 +1,2 @@ +- id: 1 + name: 学内募金API確認学科 diff --git a/api/test/fixtures/room_teachers.yml b/api/test/fixtures/room_teachers.yml new file mode 100644 index 000000000..84b336b40 --- /dev/null +++ b/api/test/fixtures/room_teachers.yml @@ -0,0 +1,19 @@ +- id: 1 + room_id: 1 + teacher_id: 1 + +- id: 2 + room_id: 2 + teacher_id: 2 + +- id: 3 + room_id: 3 + teacher_id: 3 + +- id: 4 + room_id: 4 + teacher_id: 4 + +- id: 5 + room_id: 5 + teacher_id: 5 diff --git a/api/test/fixtures/rooms.yml b/api/test/fixtures/rooms.yml new file mode 100644 index 000000000..536c5fd22 --- /dev/null +++ b/api/test/fixtures/rooms.yml @@ -0,0 +1,24 @@ +- id: 1 + building_id: 1 + floor_number: "5" + room_name: "501" + +- id: 2 + building_id: 1 + floor_number: "5" + room_name: "502" + +- id: 3 + building_id: 2 + floor_number: "5" + room_name: "501" + +- id: 4 + building_id: 2 + floor_number: "4" + room_name: "401" + +- id: 5 + building_id: 3 + floor_number: "5" + room_name: "501" diff --git a/api/test/fixtures/teachers.yml b/api/test/fixtures/teachers.yml new file mode 100644 index 000000000..84202ee56 --- /dev/null +++ b/api/test/fixtures/teachers.yml @@ -0,0 +1,34 @@ +- id: 1 + name: 学内募金API確認教員A + position: 教授 + department_id: 1 + is_black: false + remark: 学内募金API確認用 + +- id: 2 + name: 学内募金API確認教員B + position: 准教授 + department_id: 1 + is_black: true + remark: 学内募金API確認用 + +- id: 3 + name: 学内募金API確認教員C + position: 助教 + department_id: 1 + is_black: false + remark: 学内募金API確認用 + +- id: 4 + name: 学内募金API確認教員D + position: 教授 + department_id: 1 + is_black: false + remark: 別フロア確認用 + +- id: 5 + name: 学内募金API確認教員E + position: 教授 + department_id: 1 + is_black: false + remark: 別棟確認用 diff --git a/api/test/fixtures/years.yml b/api/test/fixtures/years.yml new file mode 100644 index 000000000..4f56291d0 --- /dev/null +++ b/api/test/fixtures/years.yml @@ -0,0 +1,5 @@ +- id: 1 + year: 2025 + +- id: 2 + year: 2024 diff --git a/api/test/sample_test.go b/api/test/sample_test.go index f45ae0258..840b3fd13 100644 --- a/api/test/sample_test.go +++ b/api/test/sample_test.go @@ -2,6 +2,7 @@ package test import ( "database/sql" + "encoding/json" "fmt" "io" "log" @@ -11,6 +12,7 @@ import ( "os" "testing" + "github.com/NUTFes/FinanSu/api/generated" "github.com/NUTFes/FinanSu/api/internals/di" testfixtures "github.com/go-testfixtures/testfixtures/v3" "github.com/stretchr/testify/assert" @@ -23,6 +25,10 @@ var ( fixtures *testfixtures.Loader ) +func intPtr(value int) *int { + return &value +} + func TestMain(m *testing.M) { var err error if err = os.Setenv("NUTMEG_DB_USER", "finansu"); err != nil { @@ -40,6 +46,18 @@ func TestMain(m *testing.M) { if err = os.Setenv("NUTMEG_DB_NAME", "finansu_test_db"); err != nil { log.Fatal(err) } + if err = os.Setenv("MINIO_ENDPOINT", "minio:9000"); err != nil { + log.Fatal(err) + } + if err = os.Setenv("MINIO_ACCESS_KEY", "user"); err != nil { + log.Fatal(err) + } + if err = os.Setenv("MINIO_SECRET_KEY", "password"); err != nil { + log.Fatal(err) + } + if err = os.Setenv("MINIO_USE_SSL", "false"); err != nil { + log.Fatal(err) + } // テスト前処理 db, err = sql.Open("mysql", "finansu:password@tcp(nutfes-finansu-db:3306)/finansu_test_db") @@ -196,3 +214,168 @@ func TestAddUserHandler(t *testing.T) { assert.Equal(t, http.StatusCreated, r.StatusCode) assert.Contains(t, string(body), "窪坂駿吾") } + +func TestGetCampusDonationBuildingFloorsHandler(t *testing.T) { + prepareTestDatabase(t) + + serverComponents, err := di.InitializeServer() + if err != nil { + t.Errorf("Error initializing server: %s", err) + return + } + + testServer := httptest.NewServer(serverComponents.Echo) + t.Cleanup(func() { + testServer.Close() + serverComponents.Client.CloseDB() + }) + + r, err := http.Get(testServer.URL + "/campus_donations/years/2025/group_keys/campus_donation_test/floors?floor_number=5") + if err != nil { + t.Errorf("Error making request: %s", err) + return + } + + defer func() { + if err := r.Body.Close(); err != nil { + t.Errorf("Error closing response body: %s", err) + } + }() + + var buildingFloors []generated.CampusDonationBuildingFloor + if err := json.NewDecoder(r.Body).Decode(&buildingFloors); err != nil { + t.Errorf("Error decoding response body: %s", err) + return + } + + assert.Equal(t, http.StatusOK, r.StatusCode) + assert.Equal(t, []generated.CampusDonationBuildingFloor{ + { + BuildingId: 1, + BuildingName: "学内募金API確認棟", + UnitNumber: 1, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 1, + TeacherName: "学内募金API確認教員A", + TotalPrice: intPtr(5000), + IsBlack: false, + }, + { + RoomName: "502", + TeacherId: 2, + TeacherName: "学内募金API確認教員B", + TotalPrice: nil, + IsBlack: true, + }, + }, + }, + { + BuildingId: 2, + BuildingName: "学内募金API確認棟", + UnitNumber: 2, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 3, + TeacherName: "学内募金API確認教員C", + TotalPrice: intPtr(7000), + IsBlack: false, + }, + }, + }, + }, buildingFloors) +} + +func TestGetCampusDonationBuildingFloorsHandlerWithoutFloorNumber(t *testing.T) { + prepareTestDatabase(t) + + serverComponents, err := di.InitializeServer() + if err != nil { + t.Errorf("Error initializing server: %s", err) + return + } + + testServer := httptest.NewServer(serverComponents.Echo) + t.Cleanup(func() { + testServer.Close() + serverComponents.Client.CloseDB() + }) + + r, err := http.Get(testServer.URL + "/campus_donations/years/2025/group_keys/campus_donation_test/floors") + if err != nil { + t.Errorf("Error making request: %s", err) + return + } + + defer func() { + if err := r.Body.Close(); err != nil { + t.Errorf("Error closing response body: %s", err) + } + }() + + var buildingFloors []generated.CampusDonationBuildingFloor + if err := json.NewDecoder(r.Body).Decode(&buildingFloors); err != nil { + t.Errorf("Error decoding response body: %s", err) + return + } + + assert.Equal(t, http.StatusOK, r.StatusCode) + assert.Equal(t, []generated.CampusDonationBuildingFloor{ + { + BuildingId: 1, + BuildingName: "学内募金API確認棟", + UnitNumber: 1, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 1, + TeacherName: "学内募金API確認教員A", + TotalPrice: intPtr(5000), + IsBlack: false, + }, + { + RoomName: "502", + TeacherId: 2, + TeacherName: "学内募金API確認教員B", + TotalPrice: nil, + IsBlack: true, + }, + }, + }, + { + BuildingId: 2, + BuildingName: "学内募金API確認棟", + UnitNumber: 2, + FloorNumber: "4", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "401", + TeacherId: 4, + TeacherName: "学内募金API確認教員D", + TotalPrice: intPtr(9000), + IsBlack: false, + }, + }, + }, + { + BuildingId: 2, + BuildingName: "学内募金API確認棟", + UnitNumber: 2, + FloorNumber: "5", + Donations: []generated.CampusDonationTeacher{ + { + RoomName: "501", + TeacherId: 3, + TeacherName: "学内募金API確認教員C", + TotalPrice: intPtr(7000), + IsBlack: false, + }, + }, + }, + }, buildingFloors) +} diff --git a/mysql/migrations/000005_add_building_group_key.down.sql b/mysql/migrations/000005_add_building_group_key.down.sql new file mode 100644 index 000000000..6e831e509 --- /dev/null +++ b/mysql/migrations/000005_add_building_group_key.down.sql @@ -0,0 +1 @@ +ALTER TABLE buildings DROP COLUMN group_key; diff --git a/mysql/migrations/000005_add_building_group_key.up.sql b/mysql/migrations/000005_add_building_group_key.up.sql new file mode 100644 index 000000000..5ce63bd7f --- /dev/null +++ b/mysql/migrations/000005_add_building_group_key.up.sql @@ -0,0 +1,15 @@ +-- 建物の表示グループを明示するためのキーを追加する +ALTER TABLE buildings ADD COLUMN group_key VARCHAR(255) NOT NULL DEFAULT 'other'; + +UPDATE buildings SET group_key = 'mechanical_civil_engineering' WHERE name = '機械・建設棟'; +UPDATE buildings SET group_key = 'electrical_engineering' WHERE name = '電気棟'; +UPDATE buildings SET group_key = 'biology' WHERE name = '生物棟'; +UPDATE buildings SET group_key = 'environmental_system' WHERE name = '環境・システム棟'; +UPDATE buildings SET group_key = 'materials_management_information' WHERE name = '物質・材料経営情報棟'; +UPDATE buildings SET group_key = 'general_research' WHERE name = '総合研究棟'; +UPDATE buildings SET group_key = 'nuclear_system_safety' WHERE name = '原子力・システム安全棟'; +UPDATE buildings SET group_key = 'administration' WHERE name = '事務局棟'; +UPDATE buildings SET group_key = 'extreme_energy_density_research_center' WHERE name = '極限エネルギ密度工学研究センター'; +UPDATE buildings SET group_key = 'machine_shop' WHERE name = '工作センター'; +UPDATE buildings SET group_key = 'large_experiment' WHERE name = '大型実験棟'; +UPDATE buildings SET group_key = 'analysis_instrumentation_center' WHERE name = '分析計測センター'; diff --git a/mysql/seed/000001_initial_schema_seed.sql b/mysql/seed/000001_initial_schema_seed.sql index 8fc7e4a13..47dfec645 100644 --- a/mysql/seed/000001_initial_schema_seed.sql +++ b/mysql/seed/000001_initial_schema_seed.sql @@ -457,25 +457,25 @@ VALUES (2, 6, NOW(), NOW()); INSERT INTO - buildings (name, unit_number) + buildings (name, unit_number, group_key) VALUES - ('機械・建設棟', 1), - ('機械・建設棟', 2), - ('機械・建設棟', 3), - ('電気棟', 1), - ('電気棟', 2), - ('生物棟', 1), - ('環境・システム棟', 1), - ('物質・材料経営情報棟', 1), - ('物質・材料経営情報棟', 2), - ('総合研究棟', 1), - ('原子力・システム安全棟', 1), - ('事務局棟', 1), - ('極限エネルギ密度工学研究センター', 1), - ('工作センター', 1), - ('大型実験棟', 1), - ('分析計測センター', 1), - ('その他', 1); + ('機械・建設棟', 1, 'mechanical_civil_engineering'), + ('機械・建設棟', 2, 'mechanical_civil_engineering'), + ('機械・建設棟', 3, 'mechanical_civil_engineering'), + ('電気棟', 1, 'electrical_engineering'), + ('電気棟', 2, 'electrical_engineering'), + ('生物棟', 1, 'biology'), + ('環境・システム棟', 1, 'environmental_system'), + ('物質・材料経営情報棟', 1, 'materials_management_information'), + ('物質・材料経営情報棟', 2, 'materials_management_information'), + ('総合研究棟', 1, 'general_research'), + ('原子力・システム安全棟', 1, 'nuclear_system_safety'), + ('事務局棟', 1, 'administration'), + ('極限エネルギ密度工学研究センター', 1, 'extreme_energy_density_research_center'), + ('工作センター', 1, 'machine_shop'), + ('大型実験棟', 1, 'large_experiment'), + ('分析計測センター', 1, 'analysis_instrumentation_center'), + ('その他', 1, 'other'); INSERT INTO campus_donations (user_id, teacher_id, year_id, price, received_at) diff --git a/openapi/openapi.yaml b/openapi/openapi.yaml index f8538528a..6d0845e4d 100644 --- a/openapi/openapi.yaml +++ b/openapi/openapi.yaml @@ -1286,6 +1286,39 @@ paths: schema: type: string format: binary + /campus_donations/years/{year}/group_keys/{group_key}/floors: + get: + tags: + - campus_donation + description: 指定した棟グループの教員別募金情報を取得 + parameters: + - name: year + in: path + description: year + required: true + schema: + type: integer + - name: group_key + in: path + description: group_key + required: true + schema: + $ref: "#/components/schemas/campusDonationBuildingGroupKey" + - name: floor_number + in: query + description: floor_number + required: false + schema: + type: string + responses: + "200": + description: 指定した棟グループの教員別募金情報を取得 + content: + application/json: + schema: + type: array + items: + $ref: "#/components/schemas/campusDonationBuildingFloor" /campus_donations/buildings/{year}: get: tags: @@ -1307,6 +1340,51 @@ paths: type: array items: $ref: "#/components/schemas/buildingTotal" + /campus_donations: + post: + tags: + - campus_donation + description: 学内募金の登録 + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/campusDonationRequest" + required: true + responses: + "200": + description: 登録された学内募金が返ってくる + content: + application/json: + schema: + $ref: "#/components/schemas/campusDonation" + x-codegen-request-body-name: campusDonationRequest + /campus_donations/{id}: + put: + tags: + - campus_donation + description: 学内募金の更新 + parameters: + - name: id + in: path + description: 学内募金ID + required: true + schema: + type: integer + requestBody: + content: + application/json: + schema: + $ref: "#/components/schemas/campusDonationRequest" + required: true + responses: + "200": + description: 更新された学内募金が返ってくる + content: + application/json: + schema: + $ref: "#/components/schemas/campusDonation" + x-codegen-request-body-name: campusDonationRequest /income_expenditure_management/csv/download: get: tags: @@ -2847,6 +2925,140 @@ components: required: - unsettledAmount - unpackedAmount + campusDonationRequest: + type: object + required: + - userId + - teacherId + - yearId + - price + - receivedAt + properties: + userId: + type: integer + description: 募金を登録・更新したユーザーID + example: 1 + teacherId: + type: integer + description: 募金対象の教員ID + example: 1 + yearId: + type: integer + description: 年度ID + example: 1 + price: + type: integer + description: 募金金額 + example: 2000 + receivedAt: + type: string + format: date + description: 受領日 + example: 2026-04-28 + campusDonation: + type: object + required: + - id + - userId + - teacherId + - yearId + - price + - receivedAt + properties: + id: + type: integer + description: 学内募金ID + example: 1 + userId: + type: integer + description: 募金を登録・更新したユーザーID + example: 1 + teacherId: + type: integer + description: 募金対象の教員ID + example: 1 + yearId: + type: integer + description: 年度ID + example: 1 + price: + type: integer + description: 募金金額 + example: 2000 + receivedAt: + type: string + format: date + description: 受領日 + example: 2026-04-28 + campusDonationTeacher: + description: 棟・階ごとの教員別募金情報 + type: object + properties: + roomName: + type: string + example: "501" + teacherId: + type: integer + example: 1 + teacherName: + type: string + example: 技大太郎 + totalPrice: + type: integer + nullable: true + example: 5000 + isBlack: + type: boolean + example: false + required: + - roomName + - teacherId + - teacherName + - totalPrice + - isBlack + campusDonationBuildingFloor: + description: 各号棟の指定フロア教員情報 + type: object + properties: + buildingId: + type: integer + example: 1 + buildingName: + type: string + example: 機械・建設棟 + unitNumber: + type: integer + example: 1 + floorNumber: + type: string + example: "5" + donations: + type: array + items: + $ref: "#/components/schemas/campusDonationTeacher" + required: + - buildingId + - buildingName + - unitNumber + - floorNumber + - donations + campusDonationBuildingGroupKey: + description: 学内募金で表示する棟グループのキー + type: string + enum: + - mechanical_civil_engineering + - electrical_engineering + - biology + - environmental_system + - materials_management_information + - general_research + - nuclear_system_safety + - administration + - extreme_energy_density_research_center + - machine_shop + - large_experiment + - analysis_instrumentation_center + - other division: required: - name diff --git a/view/next-project/src/components/budget_managements/BudgetManagement.tsx b/view/next-project/src/components/budget_managements/BudgetManagement.tsx index 859af2d1a..7e12962d0 100644 --- a/view/next-project/src/components/budget_managements/BudgetManagement.tsx +++ b/view/next-project/src/components/budget_managements/BudgetManagement.tsx @@ -308,7 +308,9 @@ export default function BudgetManagement(props: Props) { displayItems.map((item, index) => (
( }; }; +/** + * 指定した棟グループの教員別募金情報を取得 + */ +export type getCampusDonationsYearsYearGroupKeysGroupKeyFloorsResponse200 = { + data: CampusDonationBuildingFloor[]; + status: 200; +}; + +export type getCampusDonationsYearsYearGroupKeysGroupKeyFloorsResponseSuccess = + getCampusDonationsYearsYearGroupKeysGroupKeyFloorsResponse200 & { + headers: Headers; + }; +export type getCampusDonationsYearsYearGroupKeysGroupKeyFloorsResponse = + getCampusDonationsYearsYearGroupKeysGroupKeyFloorsResponseSuccess; + +export const getGetCampusDonationsYearsYearGroupKeysGroupKeyFloorsUrl = ( + year: number, + groupKey: CampusDonationBuildingGroupKey, + params?: GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams, +) => { + const normalizedParams = new URLSearchParams(); + + Object.entries(params || {}).forEach(([key, value]) => { + if (value !== undefined) { + normalizedParams.append(key, value === null ? 'null' : value.toString()); + } + }); + + const stringifiedParams = normalizedParams.toString(); + + return stringifiedParams.length > 0 + ? `/campus_donations/years/${year}/group_keys/${groupKey}/floors?${stringifiedParams}` + : `/campus_donations/years/${year}/group_keys/${groupKey}/floors`; +}; + +export const getCampusDonationsYearsYearGroupKeysGroupKeyFloors = async ( + year: number, + groupKey: CampusDonationBuildingGroupKey, + params?: GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams, + options?: RequestInit, +): Promise => { + return customFetch( + getGetCampusDonationsYearsYearGroupKeysGroupKeyFloorsUrl(year, groupKey, params), + { + ...options, + method: 'GET', + }, + ); +}; + +export const getGetCampusDonationsYearsYearGroupKeysGroupKeyFloorsKey = ( + year: number, + groupKey: CampusDonationBuildingGroupKey, + params?: GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams, +) => + [ + `/campus_donations/years/${year}/group_keys/${groupKey}/floors`, + ...(params ? [params] : []), + ] as const; + +export type GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsQueryResult = NonNullable< + Awaited> +>; +export type GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsQueryError = unknown; + +export const useGetCampusDonationsYearsYearGroupKeysGroupKeyFloors = ( + year: number, + groupKey: CampusDonationBuildingGroupKey, + params?: GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams, + options?: { + swr?: SWRConfiguration< + Awaited>, + TError + > & { swrKey?: Key; enabled?: boolean }; + request?: SecondParameter; + }, +) => { + const { swr: swrOptions, request: requestOptions } = options ?? {}; + + const isEnabled = swrOptions?.enabled !== false && !!(year && groupKey); + const swrKey = + swrOptions?.swrKey ?? + (() => + isEnabled + ? getGetCampusDonationsYearsYearGroupKeysGroupKeyFloorsKey(year, groupKey, params) + : null); + const swrFn = () => + getCampusDonationsYearsYearGroupKeysGroupKeyFloors(year, groupKey, params, requestOptions); + + const query = useSwr>, TError>(swrKey, swrFn, swrOptions); + + return { + swrKey, + ...query, + }; +}; + /** * 年度ごとの各棟の合計募金額を取得 */ @@ -4155,6 +4257,144 @@ export const useGetCampusDonationsBuildingsYear = ( }; }; +/** + * 学内募金の登録 + */ +export type postCampusDonationsResponse200 = { + data: CampusDonation; + status: 200; +}; + +export type postCampusDonationsResponseSuccess = postCampusDonationsResponse200 & { + headers: Headers; +}; +export type postCampusDonationsResponse = postCampusDonationsResponseSuccess; + +export const getPostCampusDonationsUrl = () => { + return `/campus_donations`; +}; + +export const postCampusDonations = async ( + campusDonationRequest: CampusDonationRequest, + options?: RequestInit, +): Promise => { + return customFetch(getPostCampusDonationsUrl(), { + ...options, + method: 'POST', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify(campusDonationRequest), + }); +}; + +export const getPostCampusDonationsMutationFetcher = ( + options?: SecondParameter, +) => { + return (_: Key, { arg }: { arg: CampusDonationRequest }) => { + return postCampusDonations(arg, options); + }; +}; +export const getPostCampusDonationsMutationKey = () => [`/campus_donations`] as const; + +export type PostCampusDonationsMutationResult = NonNullable< + Awaited> +>; +export type PostCampusDonationsMutationError = unknown; + +export const usePostCampusDonations = (options?: { + swr?: SWRMutationConfiguration< + Awaited>, + TError, + Key, + CampusDonationRequest, + Awaited> + > & { swrKey?: string }; + request?: SecondParameter; +}) => { + const { swr: swrOptions, request: requestOptions } = options ?? {}; + + const swrKey = swrOptions?.swrKey ?? getPostCampusDonationsMutationKey(); + const swrFn = getPostCampusDonationsMutationFetcher(requestOptions); + + const query = useSWRMutation(swrKey, swrFn, swrOptions); + + return { + swrKey, + ...query, + }; +}; + +/** + * 学内募金の更新 + */ +export type putCampusDonationsIdResponse200 = { + data: CampusDonation; + status: 200; +}; + +export type putCampusDonationsIdResponseSuccess = putCampusDonationsIdResponse200 & { + headers: Headers; +}; +export type putCampusDonationsIdResponse = putCampusDonationsIdResponseSuccess; + +export const getPutCampusDonationsIdUrl = (id: number) => { + return `/campus_donations/${id}`; +}; + +export const putCampusDonationsId = async ( + id: number, + campusDonationRequest: CampusDonationRequest, + options?: RequestInit, +): Promise => { + return customFetch(getPutCampusDonationsIdUrl(id), { + ...options, + method: 'PUT', + headers: { 'Content-Type': 'application/json', ...options?.headers }, + body: JSON.stringify(campusDonationRequest), + }); +}; + +export const getPutCampusDonationsIdMutationFetcher = ( + id: number, + options?: SecondParameter, +) => { + return (_: Key, { arg }: { arg: CampusDonationRequest }) => { + return putCampusDonationsId(id, arg, options); + }; +}; +export const getPutCampusDonationsIdMutationKey = (id: number) => + [`/campus_donations/${id}`] as const; + +export type PutCampusDonationsIdMutationResult = NonNullable< + Awaited> +>; +export type PutCampusDonationsIdMutationError = unknown; + +export const usePutCampusDonationsId = ( + id: number, + options?: { + swr?: SWRMutationConfiguration< + Awaited>, + TError, + Key, + CampusDonationRequest, + Awaited> + > & { swrKey?: string }; + request?: SecondParameter; + }, +) => { + const { swr: swrOptions, request: requestOptions } = options ?? {}; + + const swrKey = swrOptions?.swrKey ?? getPutCampusDonationsIdMutationKey(id); + const swrFn = getPutCampusDonationsIdMutationFetcher(id, requestOptions); + + const query = useSWRMutation(swrKey, swrFn, swrOptions); + + return { + swrKey, + ...query, + }; +}; + /** * 収支管理一覧のCSVダウンロード、財務向けのページ */ diff --git a/view/next-project/src/generated/model/campusDonation.ts b/view/next-project/src/generated/model/campusDonation.ts new file mode 100644 index 000000000..89ef02410 --- /dev/null +++ b/view/next-project/src/generated/model/campusDonation.ts @@ -0,0 +1,22 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +export interface CampusDonation { + /** 学内募金ID */ + id: number; + /** 募金を登録・更新したユーザーID */ + userId: number; + /** 募金対象の教員ID */ + teacherId: number; + /** 年度ID */ + yearId: number; + /** 募金金額 */ + price: number; + /** 受領日 */ + receivedAt: string; +} diff --git a/view/next-project/src/generated/model/campusDonationBuildingFloor.ts b/view/next-project/src/generated/model/campusDonationBuildingFloor.ts new file mode 100644 index 000000000..e31cb0bc0 --- /dev/null +++ b/view/next-project/src/generated/model/campusDonationBuildingFloor.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ +import type { CampusDonationTeacher } from './campusDonationTeacher'; + +/** + * 各号棟の指定フロア教員情報 + */ +export interface CampusDonationBuildingFloor { + buildingId: number; + buildingName: string; + unitNumber: number; + floorNumber: string; + donations: CampusDonationTeacher[]; +} diff --git a/view/next-project/src/generated/model/campusDonationBuildingGroupKey.ts b/view/next-project/src/generated/model/campusDonationBuildingGroupKey.ts new file mode 100644 index 000000000..6e0912a06 --- /dev/null +++ b/view/next-project/src/generated/model/campusDonationBuildingGroupKey.ts @@ -0,0 +1,30 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +/** + * 学内募金で表示する棟グループのキー + */ +export type CampusDonationBuildingGroupKey = + (typeof CampusDonationBuildingGroupKey)[keyof typeof CampusDonationBuildingGroupKey]; + +// eslint-disable-next-line @typescript-eslint/no-redeclare +export const CampusDonationBuildingGroupKey = { + mechanical_civil_engineering: 'mechanical_civil_engineering', + electrical_engineering: 'electrical_engineering', + biology: 'biology', + environmental_system: 'environmental_system', + materials_management_information: 'materials_management_information', + general_research: 'general_research', + nuclear_system_safety: 'nuclear_system_safety', + administration: 'administration', + extreme_energy_density_research_center: 'extreme_energy_density_research_center', + machine_shop: 'machine_shop', + large_experiment: 'large_experiment', + analysis_instrumentation_center: 'analysis_instrumentation_center', + other: 'other', +} as const; diff --git a/view/next-project/src/generated/model/campusDonationRequest.ts b/view/next-project/src/generated/model/campusDonationRequest.ts new file mode 100644 index 000000000..c88b05615 --- /dev/null +++ b/view/next-project/src/generated/model/campusDonationRequest.ts @@ -0,0 +1,20 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +export interface CampusDonationRequest { + /** 募金を登録・更新したユーザーID */ + userId: number; + /** 募金対象の教員ID */ + teacherId: number; + /** 年度ID */ + yearId: number; + /** 募金金額 */ + price: number; + /** 受領日 */ + receivedAt: string; +} diff --git a/view/next-project/src/generated/model/campusDonationTeacher.ts b/view/next-project/src/generated/model/campusDonationTeacher.ts new file mode 100644 index 000000000..1cfe41f5e --- /dev/null +++ b/view/next-project/src/generated/model/campusDonationTeacher.ts @@ -0,0 +1,19 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +/** + * 棟・階ごとの教員別募金情報 + */ +export interface CampusDonationTeacher { + roomName: string; + teacherId: number; + teacherName: string; + /** @nullable */ + totalPrice: number | null; + isBlack: boolean; +} diff --git a/view/next-project/src/generated/model/getCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams.ts b/view/next-project/src/generated/model/getCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams.ts new file mode 100644 index 000000000..9e8db3bd7 --- /dev/null +++ b/view/next-project/src/generated/model/getCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams.ts @@ -0,0 +1,14 @@ +/** + * Generated by orval v7.18.0 🍺 + * Do not edit manually. + * NUTFes FinanSu API + * FinanSu APIドキュメント + * OpenAPI spec version: 2.0.0 + */ + +export type GetCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams = { + /** + * floor_number + */ + floor_number?: string; +}; diff --git a/view/next-project/src/generated/model/index.ts b/view/next-project/src/generated/model/index.ts index 10f91029a..a6313678b 100644 --- a/view/next-project/src/generated/model/index.ts +++ b/view/next-project/src/generated/model/index.ts @@ -19,6 +19,11 @@ export * from './buyReportInformation'; export * from './buyReportInformationStatus'; export * from './buyReportSummary'; export * from './buyReportWithDivisionId'; +export * from './campusDonation'; +export * from './campusDonationBuildingFloor'; +export * from './campusDonationBuildingGroupKey'; +export * from './campusDonationRequest'; +export * from './campusDonationTeacher'; export * from './createSponsorshipActivityRequest'; export * from './createSponsorshipActivityRequestSponsorStyleDetailsItem'; export * from './createSponsorshipActivityRequestSponsorStyleDetailsItemCategory'; @@ -90,6 +95,7 @@ export * from './getBuyReportsCsvDownloadParams'; export * from './getBuyReportsDetailsParams'; export * from './getBuyReportsListParams'; export * from './getBuyReportsSummaryParams'; +export * from './getCampusDonationsYearsYearGroupKeysGroupKeyFloorsParams'; export * from './getDepartments200'; export * from './getDepartmentsId200'; export * from './getDivisionsParams';