Skip to main content
iVentureTeam

payment_term_line_ids

Sixteen lines of code to fix one annoyance: a new payment term line vanishing because the user clicked somewhere else before typing anything.

Siddharth JambukiyaSiddharth JambukiyaOdoo Techno-Functional Consultant
August 27, 2026Updated August 27, 20265 min read
Technical namepayment_term_line_ids
Field typesone2many, many2many
Viewsform, with an embedded list
Moduleaccount, present wherever Invoicing or Accounting is installed
Used in core1 occurrence, the installment lines on the payment terms form in account
VersionsOdoo 20.0, Odoo 19.0, Odoo 18.0, Odoo 17.0
No-code setupNo. It is a view XML widget with a single behavioral change
Alternativesone2many, many2many, section_one2many, handle

What the payment term lines does

Odoo's editable lists have a sensible rule: a row you created but never touched is discarded when you move on. It stops a stray click on the Add a line link from leaving empty rows behind, and on most lists nobody notices it.

Payment terms are the exception. A new installment line arrives pre-filled with meaningful defaults, a percentage and a delay, and a user who adds one and then clicks on another line to compare has changed nothing. Odoo's rule then throws the row away, which looks exactly like a bug.

This widget removes the ambiguity. It intercepts the creation of a new inline row and immediately applies an empty update to it, which marks it as modified. From that moment the row is a real edit and Odoo will not discard it.

What this means for your team

Payment terms are configured rarely and by people who are not in Odoo every day, which is exactly the audience that cannot tell the difference between a deliberate rule and a broken screen. A disappearing row on a finance configuration page generates a support ticket and, worse, a suspicion that the rest of the configuration did not save either.

The wider lesson is about defaults. Odoo's abandon rule assumes an untouched row is worthless, and that assumption breaks whenever a new row arrives with defaults that are already correct. Any custom list where a new line is useful as created has the same exposure, and this widget is the smallest fix for it.

Supported options in Odoo 19

The widget declares no supported options, and neither does the x2many descriptor it spreads: the options dictionary is forwarded to the embedded list as CRUD settings. The controls that matter are the attributes on the inner list, shown below and verified against the Odoo 19.0 source and the core payment terms form.

OptionTypeWhat it does
editablestring (list attribute)Set on the embedded <list> element. The core payment terms form uses bottom so new installment lines are typed at the end.
createboolean (list attribute)Set on the embedded <list> element. Turning it off removes the add link, which also removes the situation this widget exists for.(default: true)
deleteboolean (list attribute)Set on the embedded <list> element. Rows kept alive by this widget still have to be deletable on purpose.(default: true)

The behavior is not configurable. Marking new rows as modified is hardcoded, with no option to switch it off. If you want the standard abandon behavior on a list, use the plain one2many widget.

Working examples

The core usage

<field name="line_ids" widget="payment_term_line_ids"
       nolabel="1" colspan="2">
  <list editable="bottom">
    ...
  </list>
</field>

The embedded list is an ordinary editable list. All the widget changes is what happens the moment a new row appears.

Reusing it on your own list

<field name="schedule_ids" widget="payment_term_line_ids">
  <list editable="bottom">
    <field name="percentage"/>
  </list>
</field>

Nothing in the widget is payment-term specific, so any list whose new rows arrive usefully pre-filled benefits.

The standard behavior, for comparison

<field name="line_ids">
  <list editable="bottom"> ... </list>
</field>

A new row here is discarded if the user clicks away without editing it.

Why an empty update is not a no-op

The entire widget is a subclass whose setup replaces one hook. The standard add-inline-record helper creates a new record in the list and hands it to the editor; this version wraps that creation so that after the record exists, an empty update is applied to it.

An empty update sounds like a no-op and is not. Odoo's record model treats any update as a modification, so the record's dirty state flips. The abandon logic that runs when focus leaves an untouched new row checks exactly that state, so the row now survives.

The source comment says as much in one sentence, and it names the two situations it protects: clicking globally, and clicking on an existing record. Both are ordinary things to do while adding an installment line, which is why the fix was needed on this form in particular.

Note what the widget does not do. It does not save anything, it does not validate the row, and it does not stop the user deleting it. The row is simply no longer eligible for silent removal, so an unwanted line has to be removed on purpose, which on a configuration form is the right default.

Version compatibility

VersionStatusNotes
Odoo 20.0In developmentNot released. Byte-identical to Odoo 19 on the development branch.
Odoo 19.0VerifiedVerified against the shipped source. Unchanged in substance since Odoo 17.
Odoo 18.0VerifiedIdentical behavior.
Odoo 17.0VerifiedFirst version.
Odoo 16.0Not availableWidget does not exist. The payment terms form used a plain relational field.

Upgrade note. Nothing to do. The widget appeared in Odoo 17 and the file is unchanged in substance through Odoo 19, differing only by the module marker comment that Odoo 19 dropped everywhere. Databases on Odoo 16 used a plain relational field on the payment terms form, so the behavior arrives with the standard views on upgrade.

What is changing in Odoo 20

Odoo 20 is expected at Odoo Experience in Brussels, 24 to 26 September 2026. The development branch is unstable and this can still change; we re-verify the page after release.

Byte-identical. The file on the development branch matches Odoo 19 exactly, which is notable in a release cycle that rewrote most field widgets onto the new props syntax. It survives untouched because it declares no props and only replaces one hook.

Common problems and fixes

SymptomCause and fix
New rows still disappearThe field is not actually using this widget, most often because an inherited view replaced it. Check the effective view for widget="payment_term_line_ids" on the field.
Empty rows accumulateExpected consequence. Rows are no longer discarded automatically, so unwanted ones must be deleted. Delete the row explicitly, or use the plain one2many widget if the abandon rule suits that list better.
The form is dirty as soon as I add a lineIntended. The empty update marks the record as modified, which is what keeps it alive.
Validation errors on save about empty linesA row was created and never filled, and it is now saved along with the rest. Delete the unwanted row before saving.
Missing widget errorThe accounting module is not installed in that database. Install Invoicing or Accounting, or use the plain one2many widget.

Payment term lines vs the alternatives

WidgetBest forKey difference
payment_term_line_idsEditable lists whose new rows are already useful as createdMarks every new inline row as modified so Odoo does not discard it when focus moves away
one2manyMost embedded listsKeeps the standard rule that an untouched new row is abandoned
many2manyLinking existing records rather than creating themAdds and removes links, so the abandon question does not arise
section_one2manyLists that need section rowsStructural rendering rather than record lifecycle
handleReordering rowsA single column widget, unrelated to creation

Use the plain one2many widget everywhere the abandon rule is helping rather than hurting, which is most lists. Reach for this one when new rows arrive already useful, and remember that the alternative fix, giving the row a required field the user must fill, changes the interface rather than the behavior.

Frequently asked questions

What problem does payment_term_line_ids solve?+
Odoo discards a newly added inline row if the user never edits it. On payment terms the new row already carries useful defaults, so clicking away looked like the row vanishing for no reason.
How does it keep the row?+
It wraps the creation of a new inline record and applies an empty update to it. Any update marks the record as modified, and the abandon logic only removes rows that are untouched.
Does it save the row?+
No. It only stops the row being discarded. Saving still happens when you save the form, and an unwanted row has to be deleted deliberately.
Can I use it on my own list?+
Yes. Nothing in it is payment-term specific, so any editable list whose new rows arrive pre-filled benefits, as long as the accounting module is installed.
Does it support options?+
None of its own. It spreads the x2many descriptor, which declares no options and forwards the whole dictionary to the embedded list.

Payment terms that match how you actually get paid

Installments, early payment discounts and the reminders around them decide how long your cash sits with customers. We configure Odoo payment terms and the follow-up flow that goes with them, on versions 16 through 19.

Book a free consultation

How this page was produced

The replaced hook, the empty update and its effect on the record's modified state were read from payment_term_line_ids.js on the Odoo 19.0 branch, together with the shared add-inline-record helper in web. The usage comes from account/views/account_payment_term_views.xml. Version coverage comes from comparing the file across the 16.0, 17.0 and 18.0 branches and a byte comparison against the public development branch. Spotted an error? Tell us and we will correct the page.