Skip to content

Commit 09da5f9

Browse files
committed
feat: import overspendingHandling config from YNAB4
1 parent 7177243 commit 09da5f9

File tree

4 files changed

+63
-17
lines changed

4 files changed

+63
-17
lines changed

pkg/importer/creator.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ func Create(db *gorm.DB, budgetName string, resources types.ParsedResources) err
7878
}
7979
}
8080

81+
// Create allocations
8182
for _, a := range resources.Allocations {
8283
allocation := a.Model
8384
allocation.AllocationCreate.EnvelopeID = resources.Categories[a.Category].Envelopes[a.Envelope].Model.ID
@@ -89,6 +90,18 @@ func Create(db *gorm.DB, budgetName string, resources types.ParsedResources) err
8990
}
9091
}
9192

93+
// Create MonthConfigs
94+
for _, m := range resources.MonthConfigs {
95+
mConfig := m.Model
96+
mConfig.EnvelopeID = resources.Categories[m.Category].Envelopes[m.Envelope].Model.ID
97+
98+
err := tx.Create(&mConfig).Error
99+
if err != nil {
100+
tx.Rollback()
101+
return err
102+
}
103+
}
104+
92105
// No errors happened, commit the transaction
93106
tx.Commit()
94107
return nil

pkg/importer/parser/ynab4/parse.go

Lines changed: 42 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ func Parse(f io.Reader) (types.ParsedResources, error) {
6262
return types.ParsedResources{}, fmt.Errorf("error parsing transactions: %w", err)
6363
}
6464

65-
err = parseAllocations(&resources, budget.MonthlyBudgets, envelopeIDNames)
65+
err = parseMonthlyBudgets(&resources, budget.MonthlyBudgets, envelopeIDNames)
6666
if err != nil {
6767
return types.ParsedResources{}, fmt.Errorf("error parsing budget allocations: %w", err)
6868
}
@@ -339,29 +339,55 @@ func parseTransactions(resources *types.ParsedResources, transactions []Transact
339339
return nil
340340
}
341341

342-
func parseAllocations(resources *types.ParsedResources, allocations []MonthlyBudget, envelopeIDNames IDToEnvelopes) error {
343-
for _, monthBudget := range allocations {
342+
func parseMonthlyBudgets(resources *types.ParsedResources, monthlyBudgets []MonthlyBudget, envelopeIDNames IDToEnvelopes) error {
343+
for _, monthBudget := range monthlyBudgets {
344344
month, err := time.Parse("2006-01-02", monthBudget.Month)
345345
if err != nil {
346346
return fmt.Errorf("could not parse date: %w", err)
347347
}
348348

349-
for _, allocation := range monthBudget.MonthlySubCategoryBudgets {
350-
// Ignore the ones that are zero
351-
if allocation.Budgeted.IsZero() {
352-
continue
349+
for _, subCategoryBudget := range monthBudget.MonthlySubCategoryBudgets {
350+
// If something is budgeted, create an allocation for it
351+
if !subCategoryBudget.Budgeted.IsZero() {
352+
resources.Allocations = append(resources.Allocations, types.Allocation{
353+
Model: models.Allocation{
354+
AllocationCreate: models.AllocationCreate{
355+
Month: month,
356+
Amount: subCategoryBudget.Budgeted,
357+
},
358+
},
359+
Category: envelopeIDNames[subCategoryBudget.CategoryID].Category,
360+
Envelope: envelopeIDNames[subCategoryBudget.CategoryID].Envelope,
361+
})
353362
}
354363

355-
resources.Allocations = append(resources.Allocations, types.Allocation{
356-
Model: models.Allocation{
357-
AllocationCreate: models.AllocationCreate{
358-
Month: month,
359-
Amount: allocation.Budgeted,
364+
// If the overspendHandling is configured, work with it
365+
if !(subCategoryBudget.OverspendingHandling == "") {
366+
// This might actually be needed in some use cases, but I could not find one
367+
// when implementing, so we're skipping it here.
368+
if strings.HasPrefix(subCategoryBudget.CategoryID, "Category/PreYNABDebt") {
369+
continue
370+
}
371+
372+
// There's two modes in YNAB4: Confined, which equals AFFECT_ENVELOPE in EZ
373+
// and "AffectsBuffer", which equals AFFECT_AVAILABLE in EZ.
374+
// Since AFFECT_AVAILABLE is the default, we can skip everything that does not
375+
// lead to AFFECT_ENVELOPE.
376+
if subCategoryBudget.OverspendingHandling != "Confined" {
377+
continue
378+
}
379+
380+
resources.MonthConfigs = append(resources.MonthConfigs, types.MonthConfig{
381+
Model: models.MonthConfig{
382+
MonthConfigCreate: models.MonthConfigCreate{
383+
OverspendMode: "AFFECT_ENVELOPE",
384+
},
385+
Month: month,
360386
},
361-
},
362-
Category: envelopeIDNames[allocation.CategoryID].Category,
363-
Envelope: envelopeIDNames[allocation.CategoryID].Envelope,
364-
})
387+
Category: envelopeIDNames[subCategoryBudget.CategoryID].Category,
388+
Envelope: envelopeIDNames[subCategoryBudget.CategoryID].Envelope,
389+
})
390+
}
365391
}
366392
}
367393

pkg/importer/parser/ynab4/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ type Transaction struct {
8484

8585
type MonthlySubCategoryBudget struct {
8686
Budgeted decimal.Decimal `json:"budgeted"`
87-
OverspendingHandling string `json:"overspendingHandling"` // Unused. Needed when implementing https://github.com/envelope-zero/backend/issues/327
87+
OverspendingHandling string `json:"overspendingHandling"`
8888
CategoryID string `json:"categoryId"`
8989
}
9090

pkg/importer/types/types.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ type ParsedResources struct {
1111
Categories map[string]Category
1212
Allocations []Allocation
1313
Transactions []Transaction
14+
MonthConfigs []MonthConfig
1415
}
1516

1617
type Account struct {
@@ -32,6 +33,12 @@ type Allocation struct {
3233
Envelope string
3334
}
3435

36+
type MonthConfig struct {
37+
Model models.MonthConfig
38+
Category string // There is a category here since an envelope with the same name can exist for multiple categories
39+
Envelope string
40+
}
41+
3542
type Transaction struct {
3643
Model models.Transaction
3744
SourceAccount string

0 commit comments

Comments
 (0)