@@ -332,7 +332,7 @@ type Invoice struct {
332332 InvoiceBase `json:",inline"`
333333
334334 // Entities external to the invoice itself
335- Lines LineChildren `json:"lines,omitempty"`
335+ Lines InvoiceLines `json:"lines,omitempty"`
336336 ValidationIssues ValidationIssues `json:"validationIssues,omitempty"`
337337
338338 Totals Totals `json:"totals"`
@@ -410,7 +410,7 @@ func (i *Invoice) FlattenLinesByID() map[string]*Line {
410410 for _ , line := range i .Lines .OrEmpty () {
411411 out [line .ID ] = line
412412
413- for _ , child := range line .Children . OrEmpty () {
413+ for _ , child := range line .Children {
414414 out [child .ID ] = child
415415 }
416416 }
@@ -477,7 +477,7 @@ func (i *Invoice) SortLines() {
477477
478478 sortLines (lines )
479479
480- i .Lines = NewLineChildren (lines )
480+ i .Lines = NewInvoiceLines (lines )
481481}
482482
483483func sortLines (lines []* Line ) {
@@ -512,17 +512,106 @@ func sortLines(lines []*Line) {
512512 })
513513
514514 for idx , line := range lines {
515- if line .Type == InvoiceLineTypeUsageBased && line .Children .IsPresent () {
516- children := line .Children .OrEmpty ()
517- sortLines (children )
518-
519- line .Children = NewLineChildren (children )
515+ if line .Type == InvoiceLineTypeUsageBased {
516+ sortLines (line .Children )
520517 }
521518
522519 lines [idx ] = line
523520 }
524521}
525522
523+ type InvoiceLines struct {
524+ mo.Option [[]* Line ]
525+ }
526+
527+ func NewInvoiceLines (children []* Line ) InvoiceLines {
528+ // Note: this helps with test equality checks
529+ if len (children ) == 0 {
530+ children = nil
531+ }
532+
533+ return InvoiceLines {mo .Some (children )}
534+ }
535+
536+ func (i InvoiceLines ) Validate () error {
537+ return errors .Join (lo .Map (i .OrEmpty (), func (line * Line , idx int ) error {
538+ return ValidationWithFieldPrefix (fmt .Sprintf ("%d" , idx ), line .Validate ())
539+ })... )
540+ }
541+
542+ func (c InvoiceLines ) Map (fn func (* Line ) * Line ) InvoiceLines {
543+ if ! c .IsPresent () {
544+ return c
545+ }
546+
547+ return InvoiceLines {
548+ mo .Some (
549+ lo .Map (c .OrEmpty (), func (item * Line , _ int ) * Line {
550+ return fn (item )
551+ }),
552+ ),
553+ }
554+ }
555+
556+ func (c InvoiceLines ) Clone () InvoiceLines {
557+ return c .Map (func (l * Line ) * Line {
558+ return l .Clone ()
559+ })
560+ }
561+
562+ func (c InvoiceLines ) GetByID (id string ) * Line {
563+ return lo .FindOrElse (c .Option .OrEmpty (), nil , func (line * Line ) bool {
564+ return line .ID == id
565+ })
566+ }
567+
568+ func (c * InvoiceLines ) ReplaceByID (id string , newLine * Line ) bool {
569+ if c .IsAbsent () {
570+ return false
571+ }
572+
573+ lines := c .OrEmpty ()
574+
575+ for i , line := range lines {
576+ if line .ID == id {
577+ // Let's preserve the DB state of the original line (as we are only replacing the current state)
578+ originalDBState := line .DBState
579+
580+ lines [i ] = newLine
581+ lines [i ].DBState = originalDBState
582+ return true
583+ }
584+ }
585+
586+ return false
587+ }
588+
589+ // NonDeletedLineCount returns the number of lines that are not deleted and have a valid status (e.g. we are ignoring split lines)
590+ func (c InvoiceLines ) NonDeletedLineCount () int {
591+ return lo .CountBy (c .OrEmpty (), func (l * Line ) bool {
592+ return l .DeletedAt == nil && l .Status == InvoiceLineStatusValid
593+ })
594+ }
595+
596+ func (c * InvoiceLines ) Append (l ... * Line ) {
597+ c .Option = mo .Some (append (c .OrEmpty (), l ... ))
598+ }
599+
600+ func (c * InvoiceLines ) RemoveByID (id string ) bool {
601+ toBeRemoved := c .GetByID (id )
602+ if toBeRemoved == nil {
603+ return false
604+ }
605+
606+ c .Option = mo .Some (
607+ lo .Filter (c .Option .OrEmpty (), func (l * Line , _ int ) bool {
608+ return l .ID != id
609+ }),
610+ )
611+
612+ return true
613+ }
614+
526615type InvoiceExternalIDs struct {
527616 Invoicing string `json:"invoicing,omitempty"`
528617 Payment string `json:"payment,omitempty"`
@@ -959,7 +1048,7 @@ type SimulateInvoiceInput struct {
9591048
9601049 Number * string
9611050 Currency currencyx.Code
962- Lines LineChildren
1051+ Lines InvoiceLines
9631052}
9641053
9651054func (i SimulateInvoiceInput ) Validate () error {
@@ -991,7 +1080,7 @@ func (i SimulateInvoiceInput) Validate() error {
9911080 return errors .New ("currency is required" )
9921081 }
9931082
994- if i . Lines . IsAbsent () || len (i .Lines .OrEmpty ()) == 0 {
1083+ if len (i .Lines .OrEmpty ()) == 0 {
9951084 return errors .New ("lines are required" )
9961085 }
9971086
0 commit comments