summaryrefslogtreecommitdiff
path: root/internal/commands
diff options
context:
space:
mode:
Diffstat (limited to 'internal/commands')
-rw-r--r--internal/commands/report.go440
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)
-}