mirror of
https://github.com/luxfi/captable.git
synced 2026-10-30 17:01:02 +00:00
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package tax
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// DividendProvider supplies dividend distribution data for tax form generation.
|
|
type DividendProvider interface {
|
|
// GetDividendsByYear returns stakeholder ID -> total ordinary dividends for a tax year.
|
|
GetDividendsByYear(ctx context.Context, companyID string, taxYear int) (map[string]float64, error)
|
|
}
|
|
|
|
// Service handles tax form generation (1099-DIV, 1099-B, Schedule K-1).
|
|
type Service struct {
|
|
repo Repository
|
|
dividends DividendProvider
|
|
}
|
|
|
|
// NewService creates a tax service.
|
|
func NewService(repo Repository, dividends DividendProvider) *Service {
|
|
return &Service{repo: repo, dividends: dividends}
|
|
}
|
|
|
|
// Generate1099DIV creates 1099-DIV forms for all dividend recipients in a tax year.
|
|
func (s *Service) Generate1099DIV(ctx context.Context, req *GenerationRequest, payerName, payerEIN string, recipients map[string]RecipientInfo) (*GenerationResult, error) {
|
|
if req.CompanyID == "" {
|
|
return nil, fmt.Errorf("company_id is required")
|
|
}
|
|
if req.TaxYear <= 0 {
|
|
return nil, fmt.Errorf("tax_year must be positive")
|
|
}
|
|
|
|
divs, err := s.dividends.GetDividendsByYear(ctx, req.CompanyID, req.TaxYear)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("get dividends: %w", err)
|
|
}
|
|
|
|
result := &GenerationResult{
|
|
FormType: "1099-DIV",
|
|
TaxYear: req.TaxYear,
|
|
}
|
|
|
|
now := time.Now().UTC()
|
|
for stakeholderID, amount := range divs {
|
|
// IRS minimum reporting threshold for 1099-DIV is $10.
|
|
if amount < 10.0 {
|
|
continue
|
|
}
|
|
|
|
recip, ok := recipients[stakeholderID]
|
|
if !ok {
|
|
result.Errors++
|
|
continue
|
|
}
|
|
|
|
form := &Form1099DIV{
|
|
ID: fmt.Sprintf("1099div_%s_%d_%s", req.CompanyID, req.TaxYear, stakeholderID),
|
|
TaxYear: req.TaxYear,
|
|
PayerName: payerName,
|
|
PayerEIN: payerEIN,
|
|
RecipientName: recip.Name,
|
|
RecipientTIN: recip.TIN,
|
|
RecipientAddress: recip.Address,
|
|
OrdinaryDividends: amount,
|
|
QualifiedDividends: amount, // simplified — in production, track qualified vs. non-qualified
|
|
Status: "generated",
|
|
GeneratedAt: &now,
|
|
}
|
|
|
|
if err := s.repo.CreateForm1099DIV(ctx, form); err != nil {
|
|
result.Errors++
|
|
continue
|
|
}
|
|
result.FormsCreated++
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Compute1099B calculates gain/loss for a sale transaction.
|
|
func (s *Service) Compute1099B(proceeds, costBasis float64, dateAcquired, dateSold time.Time) *Form1099B {
|
|
gainLoss := proceeds - costBasis
|
|
holdingDays := dateSold.Sub(dateAcquired).Hours() / 24
|
|
term := "short"
|
|
if holdingDays > 365 {
|
|
term = "long"
|
|
}
|
|
|
|
return &Form1099B{
|
|
DateAcquired: dateAcquired.Format("2006-01-02"),
|
|
DateSold: dateSold.Format("2006-01-02"),
|
|
Proceeds: proceeds,
|
|
CostBasis: costBasis,
|
|
GainLoss: gainLoss,
|
|
ShortTermLongTerm: term,
|
|
Status: "generated",
|
|
}
|
|
}
|
|
|
|
// RecipientInfo contains the identifying information needed for tax forms.
|
|
type RecipientInfo struct {
|
|
Name string `json:"name"`
|
|
TIN string `json:"tin"` // SSN or EIN
|
|
Address string `json:"address"`
|
|
}
|