Skip to content

feat: invocie line backend pt.1#4100

Merged
turip merged 4 commits intomainfrom
feat/invoice-line-backend
Apr 9, 2026
Merged

feat: invocie line backend pt.1#4100
turip merged 4 commits intomainfrom
feat/invoice-line-backend

Conversation

@turip
Copy link
Copy Markdown
Member

@turip turip commented Apr 8, 2026

Overview

Note: this is the first part of a series of patches, the engine interface defintion has TODOs for the next steps.

This PR introduces a new billing line-engine architecture that routes pending and standard invoice lines through explicit LineEngineType implementations instead of keeping all behavior in the core billing service.

It adds an engine discriminator to persisted billing invoice lines, wires a default invoice engine, and moves split-line handling, billability checks, and standard-line building behind engine-specific interfaces.

It also extracts charge-specific invoice materialization for flat-fee and credit-purchase charges into dedicated line engines, removing the old charge-side InvoicePendingLines wrapper and related post-create credit-allocation flow. In parallel, it restores billing-side tax snapshot hydration on invoice reads by backfilling scalar tax config fields and reattaching matching resolved tax-code references in the billing mapper.

The overall goal is to make charge-backed billing flows own their own invoice-line behavior while shrinking the billing service toward orchestration and shared infrastructure concerns.

Notes for reviewer

The splitlinegroup still lives in billing's adapter. Eventually we should move that into the invoicelineengine.

Right now the charge adapters are seperate packages, once we see the structure those might end up being part of the service itself.

Usage-based has no credit_then_invoice support, thus there's no lineengine added.

Summary by CodeRabbit

  • New Features

    • Added tracking of invoice line source/type information for improved auditability and billing transparency.
    • Implemented specialized processing engines for different charge types (flat-fee, usage-based, credit purchase) enabling type-specific billing logic.
  • Bug Fixes

    • Improved invoice generation reliability by consolidating logic into a single billing service.
  • Refactor

    • Restructured invoice creation workflow to enhance separation of concerns between charge and billing systems.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

This PR introduces a pluggable line-engine architecture to the billing system. Charge packages (flat-fee, credit-purchase, usage-based) now register their own LineEngine implementations with the billing service, replacing the centralized InvoicePendingLinesService approach. Gathering lines are routed to their respective engines, which handle billability checks, splitting, and standard invoice line construction.

Changes

Cohort / File(s) Summary
Line Engine Foundation
openmeter/billing/lineengine.go, openmeter/billing/lineengine/...
New LineEngine interface and core Engine implementation for the invoice line type, including split-line-group handling, quantity snapshots, and standard invoice line building.
Charge-Specific Engines
openmeter/billing/charges/flatfee/lineengine/..., openmeter/billing/charges/creditpurchase/lineengine/...
New flat-fee and credit-purchase line engine implementations that handle charge-specific billability, splitting, and line generation logic with credit realization support.
Engine Registration & Registry
openmeter/billing/service/lineengine.go, openmeter/billing/service/service.go
Internal registry system for managing line engines and logic to group gathering lines by engine type for dispatch.
Invoice Creation Refactor
openmeter/billing/service/gatheringinvoicependinglines.go, openmeter/billing/service/invoicecalc/details.go
Shifted from centralized line-conversion logic to engine-grouped building; removed now-unneeded split/quantity-snapshot helpers from service.
Charges Service Cleanup
openmeter/billing/charges/service.go, openmeter/billing/charges/service/invoicependinglines.go
Removed InvoiceService interface embedding and deleted the entire InvoicePendingLines implementation; callers now use billingService directly.
Charging Flows Updated
openmeter/billing/charges/flatfee/service/create.go, openmeter/billing/charges/creditpurchase/service/create.go, openmeter/billing/charges/usagebased/service/create.go
Set Engine field on gathering lines during creation to identify which engine should process them.
HTTP Driver & Worker Refactors
openmeter/billing/httpdriver/..., openmeter/billing/worker/collect/collect.go, openmeter/billing/worker/subscriptionsync/service/...
Updated to depend on billing.Service instead of the removed InvoicePendingLinesService; subscription-sync reconciler now uses LineEngineType instead of BackendType.
Data Model & Schema Changes
openmeter/billing/gatheringinvoice.go, openmeter/billing/stdinvoiceline.go, openmeter/billing/derived.gen.go, openmeter/ent/schema/billing.go, tools/migrate/migrations/...
Added Engine field to gathering and standard line models; updated equality checks; added DB migration with default value invoicing.
Adapter & Mapper Updates
openmeter/billing/adapter/gatheringlines.go, openmeter/billing/adapter/stdinvoicelinemapper.go, openmeter/billing/adapter/stdinvoicelines.go
Persist and restore Engine values; refined tax config backfill logic for mapped lines.
Line Engine Registry Integration
openmeter/server/router/router.go, openmeter/billing/service/stdinvoiceline.go, openmeter/billing/service/invoice.go, openmeter/billing/service/quantitysnapshot.go
Router no longer requires BillingInvoicePendingLines injection; service validates and populates engine values during pending-line creation and invoice updates.
Wiring & Test Updates
app/common/billing.go, app/common/charges.go, cmd/server/main.go, openmeter/billing/charges/service/base_test.go, openmeter/billing/charges/testutils/service.go, test/billing/..., test/credits/..., test/subscription/...
Updated all wiring to register flat-fee and credit-purchase engines; test doubles extended with RegisterLineEngine method.
Documentation
.agents/skills/charges/SKILL.md
Documented new billing line engine architecture, enum values, registration requirements, and operational constraints.

Sequence Diagram

sequenceDiagram
    participant Client
    participant BillingService
    participant LineEngineRegistry
    participant ChargeEngine as FlatFee/CreditPurchase<br/>LineEngine
    participant RatingService
    participant Adapter

    Client->>BillingService: InvoicePendingLines(customer, asOf)
    BillingService->>BillingService: gatherInScopeLines(customer, asOf)
    BillingService->>LineEngineRegistry: Get(line.Engine)
    LineEngineRegistry-->>BillingService: ChargeEngine
    BillingService->>ChargeEngine: IsLineBillableAsOf(line, asOf)
    ChargeEngine-->>BillingService: true/false
    BillingService->>BillingService: groupGatheringLinesByEngine()
    loop Per Engine Type
        BillingService->>ChargeEngine: BuildStandardInvoiceLines(invoice, lines)
        ChargeEngine->>ChargeEngine: Convert GatheringLines→StandardLines
        ChargeEngine->>RatingService: GenerateDetailedLines()
        RatingService-->>ChargeEngine: DetailedLines
        ChargeEngine->>ChargeEngine: MergeGeneratedDetailedLines()
        ChargeEngine-->>BillingService: StandardLines
    end
    BillingService->>Adapter: CreateStandardInvoice(lines)
    Adapter-->>BillingService: StandardInvoice
    BillingService-->>Client: StandardInvoice[]
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

This diff introduces a significant new abstraction (line engines) with multiple implementations across charge packages, refactors core invoice-creation logic to dispatch through engines, and requires updates throughout the codebase (wiring, tests, HTTP driver, worker services, reconcilers). The changes are heterogeneous—each file touches different concerns (new interfaces, implementations, refactored control flow, data model updates, schema migrations)—and the logic density is moderate-to-high, particularly in the engine implementations and the refactored gatheringinvoicependinglines.go flow.

Possibly related PRs

  • feat: add wire for charges #4058: Continues the billing/charges registry wiring refactoring introduced there—both adjust how InvoicePendingLinesService is sourced and how line engines are registered and invoked.
  • feat: usage based patching #4053: Shares changes to subscription-sync reconciler types and constructor signatures (e.g., patchchargeusagebased), reflecting the shift from BackendType to LineEngineType.
  • feat: charges advancer #4065: Both modify app-level billing/charges wiring and registry logic to use ChargesServiceOrNil() and adjust component injection patterns.

Suggested labels

release-note/feature

Suggested reviewers

  • tothandras
  • GAlexIHU
🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Title check ⚠️ Warning The title references the main change (line engine backend architecture part 1) but contains a typo ('invocie' instead of 'invoice'), making it less clear and professional. Fix the typo: change 'invocie' to 'invoice' so the title reads 'feat: invoice line backend pt.1'
Docstring Coverage ⚠️ Warning Docstring coverage is 26.47% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/invoice-line-backend

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@turip turip force-pushed the feat/invoice-line-backend branch from 661b481 to 29f8fae Compare April 9, 2026 04:09
@turip turip force-pushed the feat/invoice-line-backend branch from 29f8fae to 122befc Compare April 9, 2026 08:23
@turip turip changed the title feat: invocie line backend feat: invocie line backend pt.1 Apr 9, 2026
@turip turip force-pushed the feat/invoice-line-backend branch from 122befc to b328672 Compare April 9, 2026 08:36
@turip turip added release-note/misc Miscellaneous changes area/billing labels Apr 9, 2026
@turip turip marked this pull request as ready for review April 9, 2026 08:37
@turip turip force-pushed the feat/invoice-line-backend branch from b328672 to b9a6f6f Compare April 9, 2026 08:37
@turip turip requested a review from a team as a code owner April 9, 2026 08:37
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
openmeter/billing/gatheringinvoice.go (1)

356-363: ⚠️ Potential issue | 🟠 Major

Regenerate the derived equality helpers for Engine.

GatheringLineBase.Equal() is still goderive-backed, so adding Engine here without regenerating billing/derived.gen.go leaves engine changes invisible to equality/diff checks.

As per coding guidelines, "Use Goderive annotations and run 'make generate' to regenerate billing/derived.gen.go".

Also applies to: 553-555

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/gatheringinvoice.go` around lines 356 - 363,
GatheringLineBase has a new Engine field but the auto-generated equality helpers
still omit it; update the goderive annotations and regenerate the derived
helpers so GatheringLineBase.Equal() (and any other derived comparisons) include
Engine — run the code generation step (e.g., add/ensure the proper
//goderive:... annotations near the GatheringLineBase definition and run "make
generate") to refresh billing/derived.gen.go so engine changes are detected;
repeat the same regeneration for the other affected struct(s) mentioned around
lines 553-555.
openmeter/billing/service/gatheringinvoicependinglines.go (1)

262-286: ⚠️ Potential issue | 🟠 Major

Resolve the engine before doing rating-period work.

Right now every line still goes through ResolveBillablePeriod(...) before its engine gets a say. That means flat-fee / credit-purchase lines can fail on feature-meter or rating prerequisites they don’t actually use, so the new engine-based billability is still partially coupled to the old invoice-line path. I’d flip this flow so the engine is resolved first, and only engines that need a resolved period require it.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/service/gatheringinvoicependinglines.go` around lines 262 -
286, Flip the order so the engine is resolved before rating: call
s.lineEngines.Get(line.Engine) first inside the slicesx.MapWithErr mapper, then
ask the engine (eng) whether it requires a pre-resolved billable period (or
attempt IsLineBillableAsOf with no ResolvedBillablePeriod), and only call
s.ratingService.ResolveBillablePeriod(...) when the engine indicates it needs
the period; finally pass lo.FromPtr(period) into eng.IsLineBillableAsOf only
when you actually resolved one and otherwise pass nil/omit
ResolvedBillablePeriod. Update the mapper around
gatheringLineWithBillablePeriod, s.lineEngines.Get,
s.ratingService.ResolveBillablePeriod, and eng.IsLineBillableAsOf to reflect
this conditional flow.
🧹 Nitpick comments (2)
openmeter/billing/invoiceline.go (1)

207-224: Could we add branch-level tests for GetChargeID?

Nice addition. Since this method now drives type-based branching plus nil/error paths, a small table-driven test set would help lock in behavior for standard/gathering/nil/invalid-type cases.

As per coding guidelines "**/*_test.go: Make sure the tests are comprehensive and cover the changes."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/invoiceline.go` around lines 207 - 224, Add a table-driven
test file for InvoiceLine.GetChargeID that exercises each branch and error path:
create test cases for InvoiceLineTypeStandard with a non-nil standardLine
(expect its ChargeID), InvoiceLineTypeGathering with a non-nil gatheringLine
(expect its ChargeID), cases where standardLine or gatheringLine is nil (expect
the corresponding "standard line is nil"/"gathering line is nil" error), and an
invalid i.t value (expect the "invalid invoice line type" error). Use
descriptive subtests and assert both returned pointer values and error messages;
place the tests in a *_test.go alongside invoiceline.go and reference the
InvoiceLine, GetChargeID, InvoiceLineTypeStandard, InvoiceLineTypeGathering,
standardLine and gatheringLine symbols to locate the code under test.
openmeter/billing/worker/subscriptionsync/service/base_test.go (1)

61-67: Please cover the engine-routed sync path in a real test.

These setup changes only prove the service still builds. They won’t catch a missing line-engine registration or a bad billing.Service.InvoicePendingLines integration, which is the risky part of this refactor.

As per coding guidelines, "Make sure the tests are comprehensive and cover the changes. Keep a strong focus on unit tests and in-code integration tests."

Also applies to: 81-88

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@openmeter/billing/worker/subscriptionsync/service/base_test.go` around lines
61 - 67, The current test only constructs the service via New(Config{...}) but
doesn’t exercise the engine-routed sync path; add a real unit/integration test
that instantiates the service with a SubscriptionSyncAdapter that routes to the
line-engine, mocks BillingService.InvoicePendingLines to expect and verify
calls, and then invokes the service’s sync entrypoint (the method that triggers
subscription sync) so the test asserts that the line-engine registration and
BillingService.InvoicePendingLines integration are exercised; use the existing
symbols New, Config, SubscriptionSyncAdapter, SubscriptionService and
BillingService.InvoicePendingLines to locate and wire up the mocks and
assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@openmeter/billing/charges/flatfee/lineengine/engine.go`:
- Around line 111-133: The code currently returns early with `continue` when
`realizations` is empty, skipping later materialization steps; instead, remove
the `continue` and only skip assigning credits when empty. Keep the call to
e.ratingService.GenerateDetailedLines(stdLine),
invoicecalc.MergeGeneratedDetailedLines(stdLine, ...), and stdLine.Validate()
always run; set stdLine.CreditsApplied to an empty slice or nil when
len(realizations) == 0 and only call convertCreditRealizations(realizations)
when realizations exist (i.e., if len(realizations) > 0 { stdLine.CreditsApplied
= convertCreditRealizations(realizations) }).

In `@openmeter/billing/service/lineengine.go`:
- Around line 23-25: Ensure Register on engineRegistry guards against a nil
billing.LineEngine: check if eng == nil at the start of engineRegistry.Register
and return a clear error instead of calling eng.GetLineEngineType(); then
proceed to call eng.GetLineEngineType() and engineType.Validate() only when eng
is non-nil. This prevents a panic when a nil engine is passed and keeps the
public API initialization path safe.

---

Outside diff comments:
In `@openmeter/billing/gatheringinvoice.go`:
- Around line 356-363: GatheringLineBase has a new Engine field but the
auto-generated equality helpers still omit it; update the goderive annotations
and regenerate the derived helpers so GatheringLineBase.Equal() (and any other
derived comparisons) include Engine — run the code generation step (e.g.,
add/ensure the proper //goderive:... annotations near the GatheringLineBase
definition and run "make generate") to refresh billing/derived.gen.go so engine
changes are detected; repeat the same regeneration for the other affected
struct(s) mentioned around lines 553-555.

In `@openmeter/billing/service/gatheringinvoicependinglines.go`:
- Around line 262-286: Flip the order so the engine is resolved before rating:
call s.lineEngines.Get(line.Engine) first inside the slicesx.MapWithErr mapper,
then ask the engine (eng) whether it requires a pre-resolved billable period (or
attempt IsLineBillableAsOf with no ResolvedBillablePeriod), and only call
s.ratingService.ResolveBillablePeriod(...) when the engine indicates it needs
the period; finally pass lo.FromPtr(period) into eng.IsLineBillableAsOf only
when you actually resolved one and otherwise pass nil/omit
ResolvedBillablePeriod. Update the mapper around
gatheringLineWithBillablePeriod, s.lineEngines.Get,
s.ratingService.ResolveBillablePeriod, and eng.IsLineBillableAsOf to reflect
this conditional flow.

---

Nitpick comments:
In `@openmeter/billing/invoiceline.go`:
- Around line 207-224: Add a table-driven test file for InvoiceLine.GetChargeID
that exercises each branch and error path: create test cases for
InvoiceLineTypeStandard with a non-nil standardLine (expect its ChargeID),
InvoiceLineTypeGathering with a non-nil gatheringLine (expect its ChargeID),
cases where standardLine or gatheringLine is nil (expect the corresponding
"standard line is nil"/"gathering line is nil" error), and an invalid i.t value
(expect the "invalid invoice line type" error). Use descriptive subtests and
assert both returned pointer values and error messages; place the tests in a
*_test.go alongside invoiceline.go and reference the InvoiceLine, GetChargeID,
InvoiceLineTypeStandard, InvoiceLineTypeGathering, standardLine and
gatheringLine symbols to locate the code under test.

In `@openmeter/billing/worker/subscriptionsync/service/base_test.go`:
- Around line 61-67: The current test only constructs the service via
New(Config{...}) but doesn’t exercise the engine-routed sync path; add a real
unit/integration test that instantiates the service with a
SubscriptionSyncAdapter that routes to the line-engine, mocks
BillingService.InvoicePendingLines to expect and verify calls, and then invokes
the service’s sync entrypoint (the method that triggers subscription sync) so
the test asserts that the line-engine registration and
BillingService.InvoicePendingLines integration are exercised; use the existing
symbols New, Config, SubscriptionSyncAdapter, SubscriptionService and
BillingService.InvoicePendingLines to locate and wire up the mocks and
assertions.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2a08c682-0a48-460c-89c4-6fd8b531769a

📥 Commits

Reviewing files that changed from the base of the PR and between 5d00754 and b9a6f6f.

⛔ Files ignored due to path filters (10)
  • go.sum is excluded by !**/*.sum, !**/*.sum
  • openmeter/ent/db/billinginvoiceline.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline/billinginvoiceline.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline/where.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline_create.go is excluded by !**/ent/db/**
  • openmeter/ent/db/billinginvoiceline_update.go is excluded by !**/ent/db/**
  • openmeter/ent/db/migrate/schema.go is excluded by !**/ent/db/**
  • openmeter/ent/db/mutation.go is excluded by !**/ent/db/**
  • openmeter/ent/db/runtime.go is excluded by !**/ent/db/**
  • tools/migrate/migrations/atlas.sum is excluded by !**/*.sum, !**/*.sum
📒 Files selected for processing (59)
  • app/common/billing.go
  • app/common/charges.go
  • cmd/server/main.go
  • openmeter/billing/adapter/gatheringlines.go
  • openmeter/billing/adapter/stdinvoicelinemapper.go
  • openmeter/billing/adapter/stdinvoicelines.go
  • openmeter/billing/charges/creditpurchase/lineengine/engine.go
  • openmeter/billing/charges/creditpurchase/service/create.go
  • openmeter/billing/charges/flatfee/lineengine/engine.go
  • openmeter/billing/charges/flatfee/lineengine/helpers.go
  • openmeter/billing/charges/flatfee/service/create.go
  • openmeter/billing/charges/service.go
  • openmeter/billing/charges/service/base_test.go
  • openmeter/billing/charges/service/create.go
  • openmeter/billing/charges/service/creditpurchase_test.go
  • openmeter/billing/charges/service/invoicable_test.go
  • openmeter/billing/charges/service/invoicependinglines.go
  • openmeter/billing/charges/testutils/service.go
  • openmeter/billing/charges/usagebased/service/create.go
  • openmeter/billing/derived.gen.go
  • openmeter/billing/gatheringinvoice.go
  • openmeter/billing/httpdriver/handler.go
  • openmeter/billing/httpdriver/invoice.go
  • openmeter/billing/httpdriver/invoiceline.go
  • openmeter/billing/invoiceline.go
  • openmeter/billing/lineengine.go
  • openmeter/billing/lineengine/engine.go
  • openmeter/billing/lineengine/splitlinegroup.go
  • openmeter/billing/lineengine/stdinvoice.go
  • openmeter/billing/service.go
  • openmeter/billing/service/gatheringinvoicependinglines.go
  • openmeter/billing/service/invoice.go
  • openmeter/billing/service/invoicecalc/details.go
  • openmeter/billing/service/lineengine.go
  • openmeter/billing/service/quantitysnapshot.go
  • openmeter/billing/service/service.go
  • openmeter/billing/service/stdinvoiceline.go
  • openmeter/billing/stdinvoiceline.go
  • openmeter/billing/worker/collect/collect.go
  • openmeter/billing/worker/subscriptionsync/service/base_test.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patch.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchcharge.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchchargeflatfee.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchchargeusagebased.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/patchinvoice.go
  • openmeter/billing/worker/subscriptionsync/service/reconciler/reconciler.go
  • openmeter/billing/worker/subscriptionsync/service/service.go
  • openmeter/billing/worker/subscriptionsync/service/sync.go
  • openmeter/ent/schema/billing.go
  • openmeter/server/router/router.go
  • openmeter/server/server_test.go
  • test/billing/adapter_test.go
  • test/billing/invoice_test.go
  • test/billing/subscription_test.go
  • test/billing/ubpflatfee_test.go
  • test/credits/sanity_test.go
  • test/subscription/framework_test.go
  • tools/migrate/migrations/20260407133223_invoice-line-engine.down.sql
  • tools/migrate/migrations/20260407133223_invoice-line-engine.up.sql
💤 Files with no reviewable changes (4)
  • cmd/server/main.go
  • openmeter/server/router/router.go
  • openmeter/billing/charges/service.go
  • openmeter/billing/charges/service/invoicependinglines.go

Comment thread openmeter/billing/charges/flatfee/lineengine/engine.go
Comment thread openmeter/billing/service/lineengine.go
@turip turip force-pushed the feat/invoice-line-backend branch from 7efcf6d to 03170ea Compare April 9, 2026 11:24
@turip turip merged commit 621e99a into main Apr 9, 2026
23 of 24 checks passed
@turip turip deleted the feat/invoice-line-backend branch April 9, 2026 11:31
This was referenced Apr 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants