620 lines
17 KiB
Go
620 lines
17 KiB
Go
package handler
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"accounting-app/pkg/api"
|
|
"accounting-app/internal/models"
|
|
"accounting-app/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// ReportHandler handles HTTP requests for reports
|
|
type ReportHandler struct {
|
|
reportService *service.ReportService
|
|
pdfExportService *service.PDFExportService
|
|
excelExportService *service.ExcelExportService
|
|
}
|
|
|
|
// NewReportHandler creates a new ReportHandler instance
|
|
func NewReportHandler(reportService *service.ReportService, pdfExportService *service.PDFExportService, excelExportService *service.ExcelExportService) *ReportHandler {
|
|
return &ReportHandler{
|
|
reportService: reportService,
|
|
pdfExportService: pdfExportService,
|
|
excelExportService: excelExportService,
|
|
}
|
|
}
|
|
|
|
// GetTransactionSummaryRequest represents the request for transaction summary
|
|
type GetTransactionSummaryRequest struct {
|
|
StartDate string `form:"start_date" binding:"required"`
|
|
EndDate string `form:"end_date" binding:"required"`
|
|
TargetCurrency *string `form:"target_currency"`
|
|
ConversionDate *string `form:"conversion_date"`
|
|
}
|
|
|
|
// GetCategorySummaryRequest represents the request for category summary
|
|
type GetCategorySummaryRequest struct {
|
|
StartDate string `form:"start_date" binding:"required"`
|
|
EndDate string `form:"end_date" binding:"required"`
|
|
TransactionType string `form:"type" binding:"required,oneof=income expense"`
|
|
TargetCurrency *string `form:"target_currency"`
|
|
ConversionDate *string `form:"conversion_date"`
|
|
}
|
|
|
|
// GetTrendDataRequest represents the request for trend data
|
|
type GetTrendDataRequest struct {
|
|
StartDate string `form:"start_date" binding:"required"`
|
|
EndDate string `form:"end_date" binding:"required"`
|
|
Period string `form:"period" binding:"required,oneof=day week month year"`
|
|
Currency *string `form:"currency"`
|
|
}
|
|
|
|
// GetComparisonDataRequest represents the request for comparison data
|
|
type GetComparisonDataRequest struct {
|
|
StartDate string `form:"start_date" binding:"required"`
|
|
EndDate string `form:"end_date" binding:"required"`
|
|
Currency *string `form:"currency"`
|
|
}
|
|
|
|
// GetAssetsSummaryRequest represents the request for assets summary
|
|
type GetAssetsSummaryRequest struct {
|
|
TargetCurrency *string `form:"target_currency"`
|
|
ConversionDate *string `form:"conversion_date"`
|
|
}
|
|
|
|
// GetTransactionSummary handles GET /api/v1/reports/summary
|
|
func (h *ReportHandler) GetTransactionSummary(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetTransactionSummaryRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse optional target currency
|
|
var targetCurrency *models.Currency
|
|
if req.TargetCurrency != nil && *req.TargetCurrency != "" {
|
|
currency := models.Currency(*req.TargetCurrency)
|
|
// Validate currency
|
|
if !isValidCurrency(currency) {
|
|
api.BadRequest(c, "Invalid target_currency")
|
|
return
|
|
}
|
|
targetCurrency = ¤cy
|
|
}
|
|
|
|
// Parse optional conversion date
|
|
var conversionDate *time.Time
|
|
if req.ConversionDate != nil && *req.ConversionDate != "" {
|
|
date, err := time.Parse("2006-01-02", *req.ConversionDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid conversion_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
conversionDate = &date
|
|
}
|
|
|
|
// Get summary
|
|
summary, err := h.reportService.GetTransactionSummary(userId.(uint), startDate, endDate, targetCurrency, conversionDate)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get transaction summary: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, summary)
|
|
}
|
|
|
|
// GetCategorySummary handles GET /api/v1/reports/category
|
|
func (h *ReportHandler) GetCategorySummary(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetCategorySummaryRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse transaction type
|
|
var transactionType models.TransactionType
|
|
switch req.TransactionType {
|
|
case "income":
|
|
transactionType = models.TransactionTypeIncome
|
|
case "expense":
|
|
transactionType = models.TransactionTypeExpense
|
|
default:
|
|
api.BadRequest(c, "Invalid transaction type")
|
|
return
|
|
}
|
|
|
|
// Parse optional target currency
|
|
var targetCurrency *models.Currency
|
|
if req.TargetCurrency != nil && *req.TargetCurrency != "" {
|
|
currency := models.Currency(*req.TargetCurrency)
|
|
// Validate currency
|
|
if !isValidCurrency(currency) {
|
|
api.BadRequest(c, "Invalid target_currency")
|
|
return
|
|
}
|
|
targetCurrency = ¤cy
|
|
}
|
|
|
|
// Parse optional conversion date
|
|
var conversionDate *time.Time
|
|
if req.ConversionDate != nil && *req.ConversionDate != "" {
|
|
date, err := time.Parse("2006-01-02", *req.ConversionDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid conversion_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
conversionDate = &date
|
|
}
|
|
|
|
// Get summary
|
|
summary, err := h.reportService.GetCategorySummary(userId.(uint), startDate, endDate, transactionType, targetCurrency, conversionDate)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get category summary: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, summary)
|
|
}
|
|
|
|
// GetTrendData handles GET /api/v1/reports/trend
|
|
func (h *ReportHandler) GetTrendData(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetTrendDataRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse period type
|
|
var period service.PeriodType
|
|
switch req.Period {
|
|
case "day":
|
|
period = service.PeriodTypeDay
|
|
case "week":
|
|
period = service.PeriodTypeWeek
|
|
case "month":
|
|
period = service.PeriodTypeMonth
|
|
case "year":
|
|
period = service.PeriodTypeYear
|
|
default:
|
|
api.BadRequest(c, "Invalid period type")
|
|
return
|
|
}
|
|
|
|
// Parse optional currency
|
|
var currency *models.Currency
|
|
if req.Currency != nil && *req.Currency != "" {
|
|
curr := models.Currency(*req.Currency)
|
|
// Validate currency
|
|
if !isValidCurrency(curr) {
|
|
api.BadRequest(c, "Invalid currency")
|
|
return
|
|
}
|
|
currency = &curr
|
|
}
|
|
|
|
// Get trend data
|
|
trendData, err := h.reportService.GetTrendData(userId.(uint), startDate, endDate, period, currency)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get trend data: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, trendData)
|
|
}
|
|
|
|
// GetComparisonData handles GET /api/v1/reports/comparison
|
|
func (h *ReportHandler) GetComparisonData(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetComparisonDataRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse optional currency
|
|
var currency *models.Currency
|
|
if req.Currency != nil && *req.Currency != "" {
|
|
curr := models.Currency(*req.Currency)
|
|
// Validate currency
|
|
if !isValidCurrency(curr) {
|
|
api.BadRequest(c, "Invalid currency")
|
|
return
|
|
}
|
|
currency = &curr
|
|
}
|
|
|
|
// Get comparison data
|
|
comparisonData, err := h.reportService.GetComparisonData(userId.(uint), startDate, endDate, currency)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get comparison data: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, comparisonData)
|
|
}
|
|
|
|
// GetAssetsSummary handles GET /api/v1/reports/assets
|
|
func (h *ReportHandler) GetAssetsSummary(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetAssetsSummaryRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse optional target currency
|
|
var targetCurrency *models.Currency
|
|
if req.TargetCurrency != nil && *req.TargetCurrency != "" {
|
|
currency := models.Currency(*req.TargetCurrency)
|
|
// Validate currency
|
|
if !isValidCurrency(currency) {
|
|
api.BadRequest(c, "Invalid target_currency")
|
|
return
|
|
}
|
|
targetCurrency = ¤cy
|
|
}
|
|
|
|
// Parse optional conversion date
|
|
var conversionDate *time.Time
|
|
if req.ConversionDate != nil && *req.ConversionDate != "" {
|
|
date, err := time.Parse("2006-01-02", *req.ConversionDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid conversion_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
conversionDate = &date
|
|
}
|
|
|
|
// Get assets summary
|
|
summary, err := h.reportService.GetAssetsSummary(userId.(uint), targetCurrency, conversionDate)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get assets summary: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, summary)
|
|
}
|
|
|
|
// GetConsumptionHabitsRequest represents the request for consumption habits analysis
|
|
type GetConsumptionHabitsRequest struct {
|
|
StartDate string `form:"start_date" binding:"required"`
|
|
EndDate string `form:"end_date" binding:"required"`
|
|
Currency *string `form:"currency"`
|
|
}
|
|
|
|
// GetConsumptionHabits handles GET /api/v1/reports/consumption-habits
|
|
func (h *ReportHandler) GetConsumptionHabits(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetConsumptionHabitsRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse optional currency
|
|
var currency *models.Currency
|
|
if req.Currency != nil && *req.Currency != "" {
|
|
curr := models.Currency(*req.Currency)
|
|
// Validate currency
|
|
if !isValidCurrency(curr) {
|
|
api.BadRequest(c, "Invalid currency")
|
|
return
|
|
}
|
|
currency = &curr
|
|
}
|
|
|
|
// Get consumption habits
|
|
habits, err := h.reportService.GetConsumptionHabits(userId.(uint), startDate, endDate, currency)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get consumption habits: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, habits)
|
|
}
|
|
|
|
// GetAssetLiabilityAnalysisRequest represents the request for asset liability analysis
|
|
type GetAssetLiabilityAnalysisRequest struct {
|
|
IncludeTrend bool `form:"include_trend"`
|
|
TrendStartDate *string `form:"trend_start_date"`
|
|
TrendEndDate *string `form:"trend_end_date"`
|
|
}
|
|
|
|
// GetAssetLiabilityAnalysis handles GET /api/v1/reports/asset-liability-analysis
|
|
func (h *ReportHandler) GetAssetLiabilityAnalysis(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req GetAssetLiabilityAnalysisRequest
|
|
if err := c.ShouldBindQuery(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse optional trend dates
|
|
var trendStartDate, trendEndDate *time.Time
|
|
if req.IncludeTrend {
|
|
if req.TrendStartDate == nil || req.TrendEndDate == nil {
|
|
api.BadRequest(c, "trend_start_date and trend_end_date are required when include_trend is true")
|
|
return
|
|
}
|
|
|
|
startDate, err := time.Parse("2006-01-02", *req.TrendStartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid trend_start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
trendStartDate = &startDate
|
|
|
|
endDate, err := time.Parse("2006-01-02", *req.TrendEndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid trend_end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
trendEndDate = &endDate
|
|
|
|
// Validate date range
|
|
if trendEndDate.Before(*trendStartDate) {
|
|
api.BadRequest(c, "trend_end_date must be after trend_start_date")
|
|
return
|
|
}
|
|
}
|
|
|
|
// Get asset liability analysis
|
|
analysis, err := h.reportService.GetAssetLiabilityAnalysis(userId.(uint), req.IncludeTrend, trendStartDate, trendEndDate)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to get asset liability analysis: "+err.Error())
|
|
return
|
|
}
|
|
|
|
api.Success(c, analysis)
|
|
}
|
|
|
|
// isValidCurrency checks if a currency is supported
|
|
func isValidCurrency(currency models.Currency) bool {
|
|
supportedCurrencies := models.SupportedCurrencies()
|
|
for _, c := range supportedCurrencies {
|
|
if c == currency {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// ExportReportRequest represents the request for exporting a report
|
|
type ExportReportRequest struct {
|
|
StartDate string `json:"start_date" binding:"required"`
|
|
EndDate string `json:"end_date" binding:"required"`
|
|
TargetCurrency *string `json:"target_currency"`
|
|
Format string `json:"format" binding:"required,oneof=pdf excel"`
|
|
}
|
|
|
|
// ExportReport handles POST /api/v1/reports/export
|
|
func (h *ReportHandler) ExportReport(c *gin.Context) {
|
|
userId, exists := c.Get("user_id")
|
|
if !exists {
|
|
api.Unauthorized(c, "User not authenticated")
|
|
return
|
|
}
|
|
|
|
var req ExportReportRequest
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
api.ValidationError(c, "Invalid request parameters: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Parse dates
|
|
startDate, err := time.Parse("2006-01-02", req.StartDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid start_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
endDate, err := time.Parse("2006-01-02", req.EndDate)
|
|
if err != nil {
|
|
api.BadRequest(c, "Invalid end_date format, expected YYYY-MM-DD")
|
|
return
|
|
}
|
|
|
|
// Validate date range
|
|
if endDate.Before(startDate) {
|
|
api.BadRequest(c, "end_date must be after start_date")
|
|
return
|
|
}
|
|
|
|
// Parse optional target currency
|
|
var targetCurrency *models.Currency
|
|
if req.TargetCurrency != nil && *req.TargetCurrency != "" {
|
|
currency := models.Currency(*req.TargetCurrency)
|
|
// Validate currency
|
|
if !isValidCurrency(currency) {
|
|
api.BadRequest(c, "Invalid target_currency")
|
|
return
|
|
}
|
|
targetCurrency = ¤cy
|
|
}
|
|
|
|
// Handle different export formats
|
|
switch req.Format {
|
|
case "pdf":
|
|
// Export as PDF
|
|
exportReq := service.ExportReportRequest{
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
TargetCurrency: targetCurrency,
|
|
IncludeCharts: true,
|
|
}
|
|
|
|
pdfData, err := h.pdfExportService.ExportReportToPDF(userId.(uint), exportReq)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to export report as PDF: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Set response headers for PDF download
|
|
filename := fmt.Sprintf("report_%s_to_%s.pdf", startDate.Format("20060102"), endDate.Format("20060102"))
|
|
c.Header("Content-Type", "application/pdf")
|
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
|
c.Data(200, "application/pdf", pdfData)
|
|
|
|
case "excel":
|
|
// Export as Excel
|
|
exportReq := service.ExportReportRequest{
|
|
StartDate: startDate,
|
|
EndDate: endDate,
|
|
TargetCurrency: targetCurrency,
|
|
IncludeCharts: false, // Charts not supported in Excel yet
|
|
}
|
|
|
|
excelData, err := h.excelExportService.ExportReportToExcel(userId.(uint), exportReq)
|
|
if err != nil {
|
|
api.InternalError(c, "Failed to export report as Excel: "+err.Error())
|
|
return
|
|
}
|
|
|
|
// Set response headers for Excel download
|
|
filename := fmt.Sprintf("report_%s_to_%s.xlsx", startDate.Format("20060102"), endDate.Format("20060102"))
|
|
c.Header("Content-Type", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")
|
|
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", filename))
|
|
c.Data(200, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", excelData)
|
|
|
|
default:
|
|
api.BadRequest(c, "Unsupported export format")
|
|
return
|
|
}
|
|
}
|