This commit is contained in:
@@ -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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
Reference in New Issue
Block a user