diff options
author | T <t@tjp.lol> | 2025-08-13 15:25:23 -0600 |
---|---|---|
committer | T <t@tjp.lol> | 2025-08-13 16:02:05 -0600 |
commit | e9e6eb4e456ee53da5a6ef743251410d4d3d8381 (patch) | |
tree | d722145a5f7a3dd07623e96045078983b3a14e4c /internal/commands/report.go | |
parent | d6781f3e5b431057c23b2deaa943f273699e37f5 (diff) |
report generation modal
Diffstat (limited to 'internal/commands/report.go')
-rw-r--r-- | internal/commands/report.go | 440 |
1 files changed, 50 insertions, 390 deletions
diff --git a/internal/commands/report.go b/internal/commands/report.go index bcc0e5f..30972b4 100644 --- a/internal/commands/report.go +++ b/internal/commands/report.go @@ -1,15 +1,11 @@ package commands import ( - "context" - "database/sql" "fmt" - "path/filepath" "time" punchctx "punchcard/internal/context" "punchcard/internal/database" - "punchcard/internal/queries" "punchcard/internal/reports" "github.com/spf13/cobra" @@ -97,147 +93,32 @@ func runInvoiceCommand(cmd *cobra.Command, args []string) error { } } - // Generate invoice based on client or project - var invoiceData *reports.InvoiceData - if clientName != "" { - invoiceData, err = generateClientInvoice(q, clientName, dateRange) - } else { - invoiceData, err = generateProjectInvoice(q, projectName, dateRange) - } - if err != nil { - return err - } - - // Generate output filename if not specified - if outputPath == "" { - outputPath = reports.GenerateDefaultInvoiceFilename(invoiceData.ClientName, invoiceData.ProjectName, dateRange) + // Create report parameters + params := reports.ReportParams{ + ClientName: clientName, + ProjectName: projectName, + DateRange: dateRange, + OutputPath: outputPath, } - // Convert to absolute path - outputPath, err = filepath.Abs(outputPath) - if err != nil { - return fmt.Errorf("failed to resolve output path: %w", err) + // Generate invoice using high-level API + var result *reports.ReportResult + if projectName == "" { + result, err = reports.GenerateClientInvoice(cmd.Context(), q, params) + } else { + result, err = reports.GenerateProjectInvoice(cmd.Context(), q, params) } - - // Generate PDF - err = reports.GenerateInvoicePDF(invoiceData, outputPath) if err != nil { - return fmt.Errorf("failed to generate invoice PDF: %w", err) - } - - if _, err := q.CreateInvoice(cmd.Context(), queries.CreateInvoiceParams{ - Year: int64(invoiceData.DateRange.Start.Year()), - Month: int64(invoiceData.DateRange.Start.Month()), - Number: invoiceData.InvoiceNumber, - ClientID: invoiceData.ClientID, - TotalAmount: int64(invoiceData.TotalAmount * 100), - }); err != nil { - return fmt.Errorf("failed to record invoice in database: %w", err) + return err } - fmt.Printf("Invoice generated successfully: %s\n", outputPath) - fmt.Printf("Total hours: %.2f\n", invoiceData.TotalHours) - fmt.Printf("Total amount: $%.2f\n", invoiceData.TotalAmount) + fmt.Printf("Invoice generated successfully: %s\n", result.OutputPath) + fmt.Printf("Total hours: %.2f\n", result.TotalHours) + fmt.Printf("Total amount: $%.2f\n", result.TotalAmount) return nil } -func generateClientInvoice(q *queries.Queries, clientName string, dateRange reports.DateRange) (*reports.InvoiceData, error) { - // Find client - client, err := findClient(context.Background(), q, clientName) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("client not found: %s", clientName) - } - return nil, fmt.Errorf("failed to find client: %w", err) - } - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - highestNumber, err := q.GetHighestInvoiceNumber(context.Background(), queries.GetHighestInvoiceNumberParams{ - Year: int64(dateRange.Start.Year()), - Month: int64(dateRange.Start.Month()), - }) - if err != nil { - return nil, fmt.Errorf("failed to get highest invoice number: %w", err) - } - - // Get invoice data - entries, err := q.GetInvoiceDataByClient(context.Background(), queries.GetInvoiceDataByClientParams{ - ClientID: client.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get invoice data: %w", err) - } - - if len(entries) == 0 { - return nil, fmt.Errorf("no completed time entries found for client %s in the specified date range", client.Name) - } - - return reports.GenerateInvoiceData(entries, client.ID, client.Name, "", contractor, highestNumber+1, dateRange) -} - -func generateProjectInvoice(q *queries.Queries, projectName string, dateRange reports.DateRange) (*reports.InvoiceData, error) { - // We need to find the project, but we need the client info too - // Let's first find all projects with this name - project, err := findProject(context.Background(), q, projectName) - if err != nil { - return nil, fmt.Errorf("failed to find project: %w", err) - } - - // Get client info - clients, err := q.FindClient(context.Background(), queries.FindClientParams{ - ID: project.ClientID, - Name: "", - }) - if err != nil || len(clients) == 0 { - return nil, fmt.Errorf("failed to find client for project") - } - client := clients[0] - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - highestNumber, err := q.GetHighestInvoiceNumber(context.Background(), queries.GetHighestInvoiceNumberParams{ - Year: int64(dateRange.Start.Year()), - Month: int64(dateRange.Start.Month()), - }) - if err != nil { - return nil, fmt.Errorf("failed to get highest invoice number: %w", err) - } - - // Get invoice data - entries, err := q.GetInvoiceDataByProject(context.Background(), queries.GetInvoiceDataByProjectParams{ - ProjectID: project.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get invoice data: %w", err) - } - - if len(entries) == 0 { - return nil, fmt.Errorf("no completed time entries found for project %s in the specified date range", projectName) - } - - return reports.GenerateInvoiceData(entries, client.ID, client.Name, projectName, contractor, highestNumber+1, dateRange) -} - func NewReportTimesheetCmd() *cobra.Command { cmd := &cobra.Command{ Use: "timesheet", @@ -319,120 +200,33 @@ func runTimesheetCommand(cmd *cobra.Command, args []string) error { } } - // Generate timesheet based on client or project - var timesheetData *reports.TimesheetData - if clientName != "" { - timesheetData, err = generateClientTimesheet(q, clientName, dateRange, loc) - } else { - timesheetData, err = generateProjectTimesheet(q, projectName, dateRange, loc) - } - if err != nil { - return err - } - - // Generate output filename if not specified - if outputPath == "" { - outputPath = reports.GenerateDefaultTimesheetFilename(timesheetData.ClientName, timesheetData.ProjectName, dateRange) + // Create report parameters + params := reports.ReportParams{ + ClientName: clientName, + ProjectName: projectName, + DateRange: dateRange, + OutputPath: outputPath, + Timezone: loc, } - // Convert to absolute path - outputPath, err = filepath.Abs(outputPath) - if err != nil { - return fmt.Errorf("failed to resolve output path: %w", err) + // Generate timesheet using high-level API + var result *reports.ReportResult + if projectName == "" { + result, err = reports.GenerateClientTimesheet(cmd.Context(), q, params) + } else { + result, err = reports.GenerateProjectTimesheet(cmd.Context(), q, params) } - - // Generate PDF - err = reports.GenerateTimesheetPDF(timesheetData, outputPath) if err != nil { - return fmt.Errorf("failed to generate timesheet PDF: %w", err) + return err } - fmt.Printf("Timesheet generated successfully: %s\n", outputPath) - fmt.Printf("Total hours: %.2f\n", timesheetData.TotalHours) - fmt.Printf("Total entries: %d\n", len(timesheetData.Entries)) + fmt.Printf("Timesheet generated successfully: %s\n", result.OutputPath) + fmt.Printf("Total hours: %.2f\n", result.TotalHours) + fmt.Printf("Total entries: %d\n", result.TotalEntries) return nil } -func generateClientTimesheet(q *queries.Queries, clientName string, dateRange reports.DateRange, loc *time.Location) (*reports.TimesheetData, error) { - // Find client - client, err := findClient(context.Background(), q, clientName) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("client not found: %s", clientName) - } - return nil, fmt.Errorf("failed to find client: %w", err) - } - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - // Get timesheet data - entries, err := q.GetTimesheetDataByClient(context.Background(), queries.GetTimesheetDataByClientParams{ - ClientID: client.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get timesheet data: %w", err) - } - - if len(entries) == 0 { - return nil, fmt.Errorf("no completed time entries found for client %s in the specified date range", client.Name) - } - - return reports.GenerateTimesheetData(entries, client.ID, client.Name, "", contractor, dateRange, loc) -} - -func generateProjectTimesheet(q *queries.Queries, projectName string, dateRange reports.DateRange, loc *time.Location) (*reports.TimesheetData, error) { - // Find project - project, err := findProject(context.Background(), q, projectName) - if err != nil { - return nil, fmt.Errorf("failed to find project: %w", err) - } - - // Get client info - clients, err := q.FindClient(context.Background(), queries.FindClientParams{ - ID: project.ClientID, - Name: "", - }) - if err != nil || len(clients) == 0 { - return nil, fmt.Errorf("failed to find client for project") - } - client := clients[0] - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - // Get timesheet data - entries, err := q.GetTimesheetDataByProject(context.Background(), queries.GetTimesheetDataByProjectParams{ - ProjectID: project.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get timesheet data: %w", err) - } - - if len(entries) == 0 { - return nil, fmt.Errorf("no completed time entries found for project %s in the specified date range", projectName) - } - - return reports.GenerateTimesheetData(entries, client.ID, client.Name, projectName, contractor, dateRange, loc) -} - func NewReportUnifiedCmd() *cobra.Command { cmd := &cobra.Command{ Use: "unified", @@ -514,163 +308,29 @@ func runUnifiedCommand(cmd *cobra.Command, args []string) error { } } - // Generate unified report based on client or project - var unifiedData *reports.UnifiedReportData - if clientName != "" { - unifiedData, err = generateClientUnified(q, clientName, dateRange, loc) - } else { - unifiedData, err = generateProjectUnified(q, projectName, dateRange, loc) - } - if err != nil { - return err - } - - // Generate output filename if not specified - if outputPath == "" { - outputPath = reports.GenerateDefaultUnifiedFilename(unifiedData.InvoiceData.ClientName, unifiedData.InvoiceData.ProjectName, dateRange) + // Create report parameters + params := reports.ReportParams{ + ClientName: clientName, + ProjectName: projectName, + DateRange: dateRange, + OutputPath: outputPath, + Timezone: loc, } - // Convert to absolute path - outputPath, err = filepath.Abs(outputPath) - if err != nil { - return fmt.Errorf("failed to resolve output path: %w", err) + // Generate unified report using high-level API + var result *reports.ReportResult + if projectName == "" { + result, err = reports.GenerateClientUnifiedReport(cmd.Context(), q, params) + } else { + result, err = reports.GenerateProjectUnifiedReport(cmd.Context(), q, params) } - - // Generate PDF - err = reports.GenerateUnifiedPDF(unifiedData, outputPath) if err != nil { - return fmt.Errorf("failed to generate unified PDF: %w", err) - } - - // Record invoice in database - if _, err := q.CreateInvoice(cmd.Context(), queries.CreateInvoiceParams{ - Year: int64(unifiedData.InvoiceData.DateRange.Start.Year()), - Month: int64(unifiedData.InvoiceData.DateRange.Start.Month()), - Number: unifiedData.InvoiceData.InvoiceNumber, - ClientID: unifiedData.InvoiceData.ClientID, - TotalAmount: int64(unifiedData.InvoiceData.TotalAmount * 100), - }); err != nil { - return fmt.Errorf("failed to record invoice in database: %w", err) + return err } - fmt.Printf("Unified report generated successfully: %s\n", outputPath) - fmt.Printf("Invoice total: $%.2f (%d hours)\n", unifiedData.InvoiceData.TotalAmount, int(unifiedData.InvoiceData.TotalHours)) - fmt.Printf("Timesheet total: %.2f hours (%d entries)\n", unifiedData.TimesheetData.TotalHours, len(unifiedData.TimesheetData.Entries)) + fmt.Printf("Unified report generated successfully: %s\n", result.OutputPath) + fmt.Printf("Invoice total: $%.2f (%.0f hours)\n", result.TotalAmount, result.TotalHours) + fmt.Printf("Timesheet total: %.2f hours (%d entries)\n", result.TotalHours, result.TotalEntries) return nil } - -func generateClientUnified(q *queries.Queries, clientName string, dateRange reports.DateRange, loc *time.Location) (*reports.UnifiedReportData, error) { - // Find client - client, err := findClient(context.Background(), q, clientName) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("client not found: %s", clientName) - } - return nil, fmt.Errorf("failed to find client: %w", err) - } - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - // Get invoice number - highestNumber, err := q.GetHighestInvoiceNumber(context.Background(), queries.GetHighestInvoiceNumberParams{ - Year: int64(dateRange.Start.Year()), - Month: int64(dateRange.Start.Month()), - }) - if err != nil { - return nil, fmt.Errorf("failed to get highest invoice number: %w", err) - } - - // Get data for both invoice and timesheet - invoiceEntries, err := q.GetInvoiceDataByClient(context.Background(), queries.GetInvoiceDataByClientParams{ - ClientID: client.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get invoice data: %w", err) - } - - timesheetEntries, err := q.GetTimesheetDataByClient(context.Background(), queries.GetTimesheetDataByClientParams{ - ClientID: client.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get timesheet data: %w", err) - } - - if len(invoiceEntries) == 0 || len(timesheetEntries) == 0 { - return nil, fmt.Errorf("no completed time entries found for client %s in the specified date range", client.Name) - } - - return reports.GenerateUnifiedReportData(invoiceEntries, client.ID, client.Name, "", contractor, highestNumber+1, dateRange, loc) -} - -func generateProjectUnified(q *queries.Queries, projectName string, dateRange reports.DateRange, loc *time.Location) (*reports.UnifiedReportData, error) { - // Find project - project, err := findProject(context.Background(), q, projectName) - if err != nil { - return nil, fmt.Errorf("failed to find project: %w", err) - } - - // Get client info - clients, err := q.FindClient(context.Background(), queries.FindClientParams{ - ID: project.ClientID, - Name: "", - }) - if err != nil || len(clients) == 0 { - return nil, fmt.Errorf("failed to find client for project") - } - client := clients[0] - - // Get contractor data - contractor, err := q.GetContractor(context.Background()) - if err != nil { - if err == sql.ErrNoRows { - return nil, fmt.Errorf("no contractor information found - please add contractor details first") - } - return nil, fmt.Errorf("failed to get contractor information: %w", err) - } - - // Get invoice number - highestNumber, err := q.GetHighestInvoiceNumber(context.Background(), queries.GetHighestInvoiceNumberParams{ - Year: int64(dateRange.Start.Year()), - Month: int64(dateRange.Start.Month()), - }) - if err != nil { - return nil, fmt.Errorf("failed to get highest invoice number: %w", err) - } - - // Get data for both invoice and timesheet - invoiceEntries, err := q.GetInvoiceDataByProject(context.Background(), queries.GetInvoiceDataByProjectParams{ - ProjectID: project.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get invoice data: %w", err) - } - - timesheetEntries, err := q.GetTimesheetDataByProject(context.Background(), queries.GetTimesheetDataByProjectParams{ - ProjectID: project.ID, - StartTime: dateRange.Start, - EndTime: dateRange.End, - }) - if err != nil { - return nil, fmt.Errorf("failed to get timesheet data: %w", err) - } - - if len(invoiceEntries) == 0 || len(timesheetEntries) == 0 { - return nil, fmt.Errorf("no completed time entries found for project %s in the specified date range", projectName) - } - - return reports.GenerateUnifiedReportData(invoiceEntries, client.ID, client.Name, projectName, contractor, highestNumber+1, dateRange, loc) -} |