feat: more options
Some checks failed
Go / build (push) Failing after 1m16s

This commit is contained in:
2025-11-04 20:16:44 +01:00
parent 8f5ae15ef0
commit cfbb475a42
6 changed files with 58 additions and 22 deletions

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"git.schreifuchs.ch/lou-taylor/accounting/internal/email" "git.schreifuchs.ch/lou-taylor/accounting/internal/email"
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice"
) )
type createFlags struct { type createFlags struct {
@@ -30,7 +31,10 @@ func create(arguments []string, c any) {
fmt.Printf("could not get repos: %v", err) fmt.Printf("could not get repos: %v", err)
return return
} }
invoice, report, err := invoicer.Generate(req.Creditor, req.Debtor, time.Duration(req.DurationThreshold), req.HourlyRate, repos)
opts := invoice.DefaultOptions
opts.Mindur = time.Duration(req.DurationThreshold)
invoice, report, err := invoicer.Generate(req.Creditor, req.Debtor, req.HourlyRate, repos, &opts)
if err != nil { if err != nil {
log.Error(fmt.Sprintf("Error while creating invoice: %v", err)) log.Error(fmt.Sprintf("Error while creating invoice: %v", err))
} }

View File

@@ -9,6 +9,7 @@ import (
"time" "time"
"git.schreifuchs.ch/lou-taylor/accounting/internal/email" "git.schreifuchs.ch/lou-taylor/accounting/internal/email"
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice"
) )
const bufSize = 1024 * 1024 // 1Mib const bufSize = 1024 * 1024 // 1Mib
@@ -28,7 +29,9 @@ func (s Service) createInvoice(w http.ResponseWriter, r *http.Request) {
return return
} }
invoice, report, err := s.invoice.Generate(req.Creditor, &req.Debtor, time.Duration(req.DurationThreshold), req.HourlyRate, repos) opts := invoice.DefaultOptions
opts.Mindur = time.Duration(req.DurationThreshold)
invoice, report, err := s.invoice.Generate(req.Creditor, &req.Debtor, req.HourlyRate, repos, &opts)
if err != nil { if err != nil {
s.sendErr(w, http.StatusInternalServerError, "internal server error") s.sendErr(w, http.StatusInternalServerError, "internal server error")
return return
@@ -65,11 +68,15 @@ func (s Service) sendInvoice(w http.ResponseWriter, r *http.Request) {
s.sendErr(w, 500, err.Error()) s.sendErr(w, 500, err.Error())
return return
} }
invoice, report, err := s.invoice.Generate(req.Invoice.Creditor, &req.Invoice.Debtor, time.Duration(req.Invoice.DurationThreshold), req.Invoice.HourlyRate, repos)
opts := invoice.DefaultOptions
opts.Mindur = time.Duration(req.Invoice.DurationThreshold)
invoice, report, err := s.invoice.Generate(req.Invoice.Creditor, &req.Invoice.Debtor, req.Invoice.HourlyRate, repos, &opts)
if err != nil { if err != nil {
s.sendErr(w, http.StatusInternalServerError, "error while processing invoice:", err) s.sendErr(w, http.StatusInternalServerError, "error while processing invoice:", err)
return return
} }
// if no time has to be billed aka if bill for 0 CHF // if no time has to be billed aka if bill for 0 CHF
if report.Total() <= time.Duration(0) { if report.Total() <= time.Duration(0) {
s.sendErr(w, http.StatusNotFound, "no suitable issues to be billed") s.sendErr(w, http.StatusNotFound, "no suitable issues to be billed")

View File

@@ -65,12 +65,12 @@ func (s SendReq) ToEMail() email.Mail {
// MockInvoiceService mocks the invoice.Service interface // MockInvoiceService mocks the invoice.Service interface
type MockInvoiceService struct { type MockInvoiceService struct {
GenerateFunc func(creditor model.Entity, debtor *model.Entity, durationThreshold time.Duration, hourlyRate float64, repos []invoice.Repo) (io.ReadCloser, *report.Report, error) GenerateFunc func(creditor model.Entity, debtor *model.Entity, hourlyRate float64, repos []invoice.Repo, opts *invoice.Options) (io.ReadCloser, *report.Report, error)
} }
func (m *MockInvoiceService) Generate(creditor model.Entity, debtor *model.Entity, durationThreshold time.Duration, hourlyRate float64, repos []invoice.Repo) (io.ReadCloser, *report.Report, error) { func (m *MockInvoiceService) Generate(creditor model.Entity, debtor *model.Entity, hourlyRate float64, repos []invoice.Repo, opts *invoice.Options) (io.ReadCloser, *report.Report, error) {
if m.GenerateFunc != nil { if m.GenerateFunc != nil {
return m.GenerateFunc(creditor, debtor, durationThreshold, hourlyRate, repos) return m.GenerateFunc(creditor, debtor, hourlyRate, repos, opts)
} }
return nil, &report.Report{}, nil return nil, &report.Report{}, nil
} }
@@ -166,7 +166,12 @@ func TestCreateInvoice(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
mockInvoiceService := &MockInvoiceService{ mockInvoiceService := &MockInvoiceService{
GenerateFunc: tt.mockGenerate, GenerateFunc: func(creditor model.Entity, debtor *model.Entity, hourlyRate float64, repos []invoice.Repo, opts *invoice.Options) (io.ReadCloser, *report.Report, error) {
if opts == nil {
opts = &invoice.DefaultOptions
}
return tt.mockGenerate(creditor, debtor, opts.Mindur, hourlyRate, repos)
},
} }
service := Service{invoice: mockInvoiceService, log: dummyLogger} // Pass the dummy logger service := Service{invoice: mockInvoiceService, log: dummyLogger} // Pass the dummy logger
@@ -298,7 +303,12 @@ func TestSendInvoice(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
mockInvoiceService := &MockInvoiceService{ mockInvoiceService := &MockInvoiceService{
GenerateFunc: tt.mockGenerate, GenerateFunc: func(creditor model.Entity, debtor *model.Entity, hourlyRate float64, repos []invoice.Repo, opts *invoice.Options) (io.ReadCloser, *report.Report, error) {
if opts == nil {
opts = &invoice.DefaultOptions
}
return tt.mockGenerate(creditor, debtor, opts.Mindur, hourlyRate, repos)
},
} }
mockEmailService := &MockEmailService{ mockEmailService := &MockEmailService{
SendFunc: tt.mockSend, SendFunc: tt.mockSend,

View File

@@ -3,7 +3,6 @@ package httpinvoce
import ( import (
"io" "io"
"log/slog" "log/slog"
"time"
"git.schreifuchs.ch/lou-taylor/accounting/internal/email" "git.schreifuchs.ch/lou-taylor/accounting/internal/email"
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice" "git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice"
@@ -22,7 +21,7 @@ func New(log *slog.Logger, invoice invoicer, mail mailer) *Service {
} }
type invoicer interface { type invoicer interface {
Generate(creditor model.Entity, deptor *model.Entity, mindur time.Duration, rate float64, repos []invoice.Repo) (document io.ReadCloser, report *report.Report, err error) Generate(creditor model.Entity, deptor *model.Entity, rate float64, repos []invoice.Repo, opts *invoice.Options) (document io.ReadCloser, report *report.Report, err error)
} }
type mailer interface { type mailer interface {

View File

@@ -2,7 +2,6 @@ package invoice
import ( import (
"io" "io"
"time"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice/issue" "git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice/issue"
@@ -10,7 +9,10 @@ import (
"git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice/report" "git.schreifuchs.ch/lou-taylor/accounting/pkg/invoice/report"
) )
func (s *Service) Generate(creditor model.Entity, deptor *model.Entity, mindur time.Duration, rate float64, repos []Repo) (document io.ReadCloser, r *report.Report, err error) { func (s *Service) Generate(creditor model.Entity, deptor *model.Entity, rate float64, repos []Repo, config *Options) (document io.ReadCloser, r *report.Report, err error) {
if config == nil {
config = &DefaultOptions
}
var is []*gitea.Issue var is []*gitea.Issue
for _, repo := range repos { for _, repo := range repos {
iss, _, err := s.gitea.ListRepoIssues( iss, _, err := s.gitea.ListRepoIssues(
@@ -18,9 +20,9 @@ func (s *Service) Generate(creditor model.Entity, deptor *model.Entity, mindur t
repo.Repo, repo.Repo,
gitea.ListIssueOption{ gitea.ListIssueOption{
ListOptions: gitea.ListOptions{Page: 0, PageSize: 99999}, ListOptions: gitea.ListOptions{Page: 0, PageSize: 99999},
Since: time.Now().AddDate(0, -1, 0), Since: config.Since,
Before: time.Now(), Before: config.Before,
State: gitea.StateClosed, State: config.IssueState,
}, },
) )
if err != nil { if err != nil {
@@ -30,13 +32,8 @@ func (s *Service) Generate(creditor model.Entity, deptor *model.Entity, mindur t
is = append(is, iss...) is = append(is, iss...)
} }
is = filter( is = filter(is, config.IssueFilter)
is, issues := issue.FromGiteas(is, config.Mindur)
func(i *gitea.Issue) bool {
return i.Closed != nil && i.Closed.After(time.Now().AddDate(0, -1, 0))
},
)
issues := issue.FromGiteas(is, mindur)
r = report.New( r = report.New(
issues, issues,
creditor, creditor,

View File

@@ -3,10 +3,29 @@ package invoice
import ( import (
"io" "io"
"log/slog" "log/slog"
"time"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
) )
var DefaultOptions = Options{
Mindur: time.Minute * 15,
Since: time.Now().AddDate(0, -1, 0),
Before: time.Now(),
IssueState: gitea.StateClosed,
IssueFilter: func(i *gitea.Issue) bool {
return i.Closed != nil && i.Closed.After(time.Now().AddDate(0, -1, 0))
},
}
type Options struct {
Mindur time.Duration
Since time.Time
Before time.Time
IssueState gitea.StateType
IssueFilter func(i *gitea.Issue) bool
}
type Repo struct { type Repo struct {
Owner string `json:"owner"` Owner string `json:"owner"`
Repo string `json:"repo"` Repo string `json:"repo"`