Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
42 changes: 42 additions & 0 deletions api/externals/controller/financial_record_controller.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package controller

import (
"encoding/csv"
"fmt"
"net/http"

"github.com/NUTFes/FinanSu/api/generated"
Expand All @@ -17,6 +19,7 @@ type FinancialRecordController interface {
CreateFinancialRecord(echo.Context) error
UpdateFinancialRecord(echo.Context) error
DestroyFinancialRecord(echo.Context) error
DownloadFinancialRecordsCSV(echo.Context) error
}

func NewFinancialRecordController(u usecase.FinancialRecordUseCase) FinancialRecordController {
Expand Down Expand Up @@ -81,5 +84,44 @@ func (f *financialRecordController) DestroyFinancialRecord(c echo.Context) error
return c.String(http.StatusOK, "Destroy FinancialRecord")
}

func (f *financialRecordController) DownloadFinancialRecordsCSV(c echo.Context) error {
year := c.QueryParam("year")
var err error

records, err := f.u.GetFinancialRecordDetailForCSV(c.Request().Context(), year)
if err != nil {
return err
}

// ヘッダーの設定
w := c.Response().Writer
fileName := fmt.Sprintf("予算_%s.csv", year)
attachment := fmt.Sprintf(`attachment; filename="%s"`, fileName)
w.Header().Set("Content-Type", "text/csv")
w.Header().Set("Content-Disposition", attachment)

if err := makeCSV(w, records); err != nil {
return err
}

return nil
}

func makeCSV(writer http.ResponseWriter, records [][]string) error {
csvWriter := csv.NewWriter(writer)
for _, record := range records {
if err := csvWriter.Write(record); err != nil {
http.Error(writer, "CSVの書き込み中にエラーが発生しました", http.StatusInternalServerError)
return err
}
}
csvWriter.Flush()
if err := csvWriter.Error(); err != nil {
http.Error(writer, "CSVのフラッシュ中にエラーが発生しました", http.StatusInternalServerError)
return err
}
return nil
}

type FinancialRecordDetails = generated.FinancialRecordDetails
type FinancialRecord = generated.FinancialRecord
57 changes: 57 additions & 0 deletions api/externals/repository/financial_record_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ type FinancialRecordRepository interface {
Update(context.Context, string, generated.FinancialRecord) error
Delete(context.Context, string) error
FindLatestRecord(context.Context) (*sql.Row, error)
AllForCSV(context.Context, string) (*sql.Rows, error)
}

func NewFinancialRecordRepository(c db.Client, ac abstract.Crud) FinancialRecordRepository {
Expand Down Expand Up @@ -126,6 +127,27 @@ func (frr *financialRecordRepository) FindLatestRecord(c context.Context) (*sql.
return frr.crud.ReadByID(c, query)
}

// 年度別に取得
func (frr *financialRecordRepository) AllForCSV(
c context.Context,
year string,
) (*sql.Rows, error) {
ds := selectFinancialRecordQueryForCSV
dsExceptItem := selectFinancialRecordQueryForCsvExceptItem
if year != "" {
ds = ds.Where(goqu.Ex{"years.year": year})
dsExceptItem = dsExceptItem.Where(goqu.Ex{"years.year": year})
}
// 2つのdsをUNION
sql := ds.Union(dsExceptItem).Order(goqu.I("id").Asc())
query, _, err := sql.ToSQL()

Comment on lines +141 to +144
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

局や部門だけ登録されている場合にも、予算0として、csvに書き出したいため、UNIONでselectの結果を結合
また、idでソートする(局ごとまとめたいため)

if err != nil {
return nil, err
}
return frr.crud.Read(c, query)
}

var selectFinancialRecordQuery = dialect.Select(
"financial_records.id",
"financial_records.name", "years.year",
Expand All @@ -139,3 +161,38 @@ var selectFinancialRecordQuery = dialect.Select(
LeftJoin(goqu.I("item_budgets"), goqu.On(goqu.I("festival_items.id").Eq(goqu.I("item_budgets.festival_item_id")))).
LeftJoin(goqu.I("buy_reports"), goqu.On(goqu.I("festival_items.id").Eq(goqu.I("buy_reports.festival_item_id")))).
GroupBy("financial_records.id")

// 予算・部門がないものを取得するds
Copy link
Collaborator

Choose a reason for hiding this comment

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

このコメント要らないかも?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

個人的にはコメントで説明があったほうが後々助かるかと思いますので必要かなって思います!

var selectFinancialRecordQueryForCSV = dialect.Select(
"financial_records.id",
"financial_records.name",
"divisions.name",
"festival_items.name",
goqu.COALESCE(goqu.SUM("item_budgets.amount"), 0).As("budget"),
goqu.COALESCE(goqu.SUM("buy_reports.amount"), 0).As("expense")).
From("festival_items").
InnerJoin(goqu.I("divisions"), goqu.On(goqu.I("festival_items.division_id").Eq(goqu.I("divisions.id")))).
InnerJoin(goqu.I("financial_records"), goqu.On(goqu.I("divisions.financial_record_id").Eq(goqu.I("financial_records.id")))).
InnerJoin(goqu.I("years"), goqu.On(goqu.I("financial_records.year_id").Eq(goqu.I("years.id")))).
LeftJoin(goqu.I("item_budgets"), goqu.On(goqu.I("festival_items.id").Eq(goqu.I("item_budgets.festival_item_id")))).
LeftJoin(goqu.I("buy_reports"), goqu.On(goqu.I("festival_items.id").Eq(goqu.I("buy_reports.festival_item_id")))).GroupBy("festival_items.id")

// 予算・部門がないものを取得するds
var selectFinancialRecordQueryForCsvExceptItem = dialect.Select(
"financial_records.id",
"financial_records.name",
goqu.COALESCE(goqu.I("divisions.name"), "").As("divisionName"),
goqu.L("''").As("festivalItemName"),
goqu.L("0").As("budget"),
goqu.L("0").As("expense")).
From("financial_records").
LeftJoin(goqu.I("divisions"), goqu.On(goqu.I("divisions.financial_record_id").Eq(goqu.I("financial_records.id")))).
InnerJoin(goqu.I("years"), goqu.On(goqu.I("financial_records.year_id").Eq(goqu.I("years.id")))).
Where(goqu.Or(
goqu.Ex{
"divisions.id": goqu.Op{"notIn": dialect.Select("festival_items.division_id").From("festival_items")},
},
goqu.Ex{
"financial_records.id": goqu.Op{"notIn": dialect.Select("divisions.financial_record_id").From("divisions")},
},
))
28 changes: 28 additions & 0 deletions api/generated/openapi_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions api/internals/domain/financial_record.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,12 @@ type FinancialRecord struct {
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}

type FinancialRecordData struct {
FinancialRecordID int `json:"financialRecordId"`
FinancialRecordName string `json:"financialRecordName"`
DivisionName string `json:"divisionName"`
FestivalItemName string `json:"festivalItemName"`
BudgetAmount int `json:"budgetAmount"`
ReportAmount int `json:"reportAmount"`
}
49 changes: 49 additions & 0 deletions api/internals/usecase/financial_record_usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@ package usecase

import (
"context"
"strconv"

rep "github.com/NUTFes/FinanSu/api/externals/repository"
"github.com/NUTFes/FinanSu/api/generated"
"github.com/NUTFes/FinanSu/api/internals/domain"
"github.com/pkg/errors"
)

Expand All @@ -25,6 +27,7 @@ type FinancialRecordUseCase interface {
FinancialRecord,
) (FinancialRecordWithBalance, error)
DestroyFinancialRecord(context.Context, string) error
GetFinancialRecordDetailForCSV(context.Context, string) ([][]string, error)
}

func NewFinancialRecordUseCase(rep rep.FinancialRecordRepository) FinancialRecordUseCase {
Expand Down Expand Up @@ -198,7 +201,53 @@ func (fru *financialRecordUseCase) DestroyFinancialRecord(c context.Context, id
return err
}

func (fru *financialRecordUseCase) GetFinancialRecordDetailForCSV(
c context.Context,
year string,
) ([][]string, error) {
header := []string{"局", "部門", "物品", "予算申請金額", "購入金額"}
csvData := [][]string{header}
var financialRecords []FinancialRecordData

rows, err := fru.rep.AllForCSV(c, year)
if err != nil {
return csvData, errors.Wrapf(err, "can not connect SQL")
}

defer rows.Close()

for rows.Next() {
var financialRecord FinancialRecordData
err := rows.Scan(
&financialRecord.FinancialRecordID,
&financialRecord.FinancialRecordName,
&financialRecord.DivisionName,
&financialRecord.FestivalItemName,
&financialRecord.BudgetAmount,
&financialRecord.ReportAmount,
)

if err != nil {
return csvData, errors.Wrapf(err, "scan error")
}
financialRecords = append(financialRecords, financialRecord)
}

for _, financialRecord := range financialRecords {
csvData = append(csvData, []string{
financialRecord.FinancialRecordName,
financialRecord.DivisionName,
financialRecord.FestivalItemName,
strconv.Itoa(financialRecord.BudgetAmount),
strconv.Itoa(financialRecord.ReportAmount),
})
}

return csvData, err
}

type FinancialRecordDetails = generated.FinancialRecordDetails
type FinancialRecord = generated.FinancialRecord
type FinancialRecordWithBalance = generated.FinancialRecordWithBalance
type Total = generated.Total
type FinancialRecordData = domain.FinancialRecordData
1 change: 1 addition & 0 deletions api/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ func (r router) ProvideRouter(e *echo.Echo) {
e.POST("/financial_records", r.financialRecordController.CreateFinancialRecord)
e.PUT("/financial_records/:id", r.financialRecordController.UpdateFinancialRecord)
e.DELETE("/financial_records/:id", r.financialRecordController.DestroyFinancialRecord)
e.GET("/financial_records/csv/download", r.financialRecordController.DownloadFinancialRecordsCSV)

// fund informations
e.GET("/fund_informations", r.fundInformationController.IndexFundInformation)
Expand Down
25 changes: 25 additions & 0 deletions openapi/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -1387,6 +1387,31 @@ paths:
application/json:
schema:
type: object
/financial_records/csv/download:
get:
tags:
- financial_record
description: financial_recordの年度予算のCSVをダウンロード
parameters:
- name: year
in: query
description: year
schema:
type: integer
required: true
responses:
"200":
description: financial_recordの年度予算のCSV取得
headers:
Content-Disposition:
schema:
type: string
example: attachment; filename=financial_record.csv
content:
text/csv:
schema:
type: string
format: binary
/fund_informations:
get:
tags:
Expand Down
59 changes: 59 additions & 0 deletions view/next-project/src/generated/hooks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ import type {
GetFestivalItemsDetailsUserIdParams,
GetFestivalItemsParams,
GetFestivalItemsUsersParams,
GetFinancialRecordsCsvDownloadParams,
GetFinancialRecordsParams,
GetFundInformations200,
GetFundInformationsDetails200,
Expand Down Expand Up @@ -3897,6 +3898,64 @@ export const useDeleteFinancialRecordsId = <TError = unknown>(
}
}

/**
* financial_recordの年度予算のCSVをダウンロード
*/
export type getFinancialRecordsCsvDownloadResponse = {
data: Blob;
status: number;
headers: Headers;
}

export const getGetFinancialRecordsCsvDownloadUrl = (params: GetFinancialRecordsCsvDownloadParams,) => {
const normalizedParams = new URLSearchParams();

Object.entries(params || {}).forEach(([key, value]) => {

if (value !== undefined) {
normalizedParams.append(key, value === null ? 'null' : value.toString())
}
});

return normalizedParams.size ? `/financial_records/csv/download?${normalizedParams.toString()}` : `/financial_records/csv/download`
}

export const getFinancialRecordsCsvDownload = async (params: GetFinancialRecordsCsvDownloadParams, options?: RequestInit): Promise<getFinancialRecordsCsvDownloadResponse> => {

return customFetch<Promise<getFinancialRecordsCsvDownloadResponse>>(getGetFinancialRecordsCsvDownloadUrl(params),
{
...options,
method: 'GET'


}
);}




export const getGetFinancialRecordsCsvDownloadKey = (params: GetFinancialRecordsCsvDownloadParams,) => [`/financial_records/csv/download`, ...(params ? [params]: [])] as const;

export type GetFinancialRecordsCsvDownloadQueryResult = NonNullable<Awaited<ReturnType<typeof getFinancialRecordsCsvDownload>>>
export type GetFinancialRecordsCsvDownloadQueryError = unknown

export const useGetFinancialRecordsCsvDownload = <TError = unknown>(
params: GetFinancialRecordsCsvDownloadParams, options?: { swr?:SWRConfiguration<Awaited<ReturnType<typeof getFinancialRecordsCsvDownload>>, TError> & { swrKey?: Key, enabled?: boolean }, request?: SecondParameter<typeof customFetch> }
) => {
const {swr: swrOptions, request: requestOptions} = options ?? {}

const isEnabled = swrOptions?.enabled !== false
const swrKey = swrOptions?.swrKey ?? (() => isEnabled ? getGetFinancialRecordsCsvDownloadKey(params) : null);
const swrFn = () => getFinancialRecordsCsvDownload(params, requestOptions)

const query = useSwr<Awaited<ReturnType<typeof swrFn>>, TError>(swrKey, swrFn, swrOptions)

return {
swrKey,
...query
}
}

/**
* fund_informationの一覧を取得
*/
Expand Down
Loading