refactor: Reimplements sequence field management in SignItemCustomPopover using Many2XAutocomplete and direct state updates for improved data handling.

This commit is contained in:
Suherdy Yacob 2026-02-04 17:11:55 +07:00
parent 0238f7d4d4
commit e6023eb2d8
2 changed files with 66 additions and 82 deletions

View File

@ -1,61 +1,57 @@
/** @odoo-module **/
import { SignItemCustomPopover } from "@sign/backend_components/sign_template/sign_item_custom_popover";
import { Many2XAutocomplete } from "@web/views/fields/relational_utils";
import { patch } from "@web/core/utils/patch";
patch(SignItemCustomPopover.prototype, {
setup() {
super.setup();
// Add sequence fields to the loaded fields
this.signItemFieldsGet.sequence_id = { type: "many2one", relation: "ir.sequence", string: "Sequence" };
this.signItemFieldsGet.sequence_prefix = { type: "char", string: "Prefix" };
this.signItemFieldsGet.sequence_padding = { type: "integer", string: "Padding" };
this.signItemFieldsGet.sequence_number_next = { type: "integer", string: "Next Number" };
this.state.sequence_id = this.props.sequence_id || false;
this.state.sequence_prefix = this.props.sequence_prefix || "";
this.state.sequence_padding = this.props.sequence_padding || 0;
this.state.sequence_number_next = this.props.sequence_number_next || 1;
// Initialize state from props
this.state.sequence_id = this.props.sequence_id;
this.state.sequence_prefix = this.props.sequence_prefix;
this.state.sequence_padding = this.props.sequence_padding;
this.state.sequence_number_next = this.props.sequence_number_next;
// Many2XAutocomplete 'value' prop expects the string to display
this.state.sequence_value = "";
if (this.props.sequence_id) {
this.state.sequence_value = "Loading...";
this.orm.read("ir.sequence", [this.props.sequence_id], ["display_name"]).then((res) => {
if (res && res.length) {
this.state.sequence_value = res[0].display_name;
} else {
this.state.sequence_value = "";
}
});
}
},
get recordProps() {
// We need to intercept onRecordChanged to sync changes to this.state
const originalRecordProps = super.recordProps;
const originalOnRecordChanged = originalRecordProps.onRecordChanged;
async onChangeSequence(val) {
// val comes from Many2XAutocomplete's update prop, which usually sends [{id, display_name}] or []/false
const record = Array.isArray(val) && val.length ? val[0] : null;
const sequenceId = record ? record.id : false;
return {
...originalRecordProps,
onRecordChanged: async (record, changes) => {
if (originalOnRecordChanged) {
await originalOnRecordChanged(record, changes);
}
// Sync sequence fields to state if changed
if ("sequence_id" in changes) {
this.state.sequence_id = changes.sequence_id;
// When sequence changes, we might want to reload prefix/padding/etc if they are computed?
// They are related fields, so 'record' should have them updated.
// But 'changes' might only contain the changed field.
// We should check the record data for the others.
this.state.sequence_value = record ? record.display_name : "";
this.state.sequence_id = sequenceId;
// Actually, if they are related fields, the `Record` model updates them.
// But `changes` object passed here contains changed values.
}
if ("sequence_prefix" in changes) this.state.sequence_prefix = changes.sequence_prefix;
if ("sequence_padding" in changes) this.state.sequence_padding = changes.sequence_padding;
if ("sequence_number_next" in changes) this.state.sequence_number_next = changes.sequence_number_next;
if (sequenceId) {
const result = await this.orm.read("ir.sequence", [sequenceId], ["prefix", "padding", "number_next"]);
if (result && result.length) {
this.state.sequence_prefix = result[0].prefix || "";
this.state.sequence_padding = result[0].padding || 0;
this.state.sequence_number_next = result[0].number_next || 1;
}
};
} else {
this.state.sequence_prefix = "";
this.state.sequence_padding = 0;
this.state.sequence_number_next = 1;
}
}
});
import { CharField } from "@web/views/fields/char/char_field";
import { IntegerField } from "@web/views/fields/integer/integer_field";
// Patch static components
const originalComponents = SignItemCustomPopover.components;
// Patch static components to include Many2XAutocomplete
SignItemCustomPopover.components = {
...originalComponents,
CharField,
IntegerField,
...SignItemCustomPopover.components,
Many2XAutocomplete,
};

View File

@ -1,48 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve">
<t t-name="sign.SignItemCustomPopover.sequence" t-inherit="sign.SignItemCustomPopover" t-inherit-mode="extension">
<xpath expr="//div[hasclass('d-flex', 'p-2', 'flex-column')]/div[1]" position="before">
<!-- Insert inside the main column, before the placeholder input -->
<div t-if="props.type === 'sequence'" class="mb-3 border-bottom pb-3">
<label class="fw-bold mb-2">Sequence Settings</label>
<!-- We need to access the record from the scope, but the scope is inside <Record> component -->
<!-- The Record component is below this div in original template? -->
<!-- Wait, the original template has <div t-if="props.debug"> then <Record> -->
<!-- I should insert INSIDE the <Record> component to access the `record` variable -->
</div>
</xpath>
<xpath expr="//div[@id='o_sign_responsible_select_input']/.." position="after">
<t t-if="props.type === 'sequence'">
<div class="mb-2">
<label class="fw-bold">Sequence</label>
<div class="o_field_widget d-block">
<Many2OneField t-props="getMany2XProps(record, 'sequence_id')" update="(value) => record.update(value)"/>
<xpath expr="//div[hasclass('popover-body')]/div[hasclass('d-flex', 'align-items-center', 'justify-content-between')]" position="before">
<t t-if="props.type === 'sequence'">
<div class="mb-3 border-top pt-3">
<label class="fw-bold mb-2">Sequence Settings</label>
<div class="mb-2">
<label class="form-label">Sequence</label>
<div class="o_field_many2one_selection">
<Many2XAutocomplete
resModel="'ir.sequence'"
value="state.sequence_value"
update="(val) => this.onChangeSequence(val)"
placeholder="'Select a sequence'"
/>
</div>
</div>
<t t-if="state.sequence_id">
<div class="mb-2">
<label class="form-label">Prefix</label>
<input type="text" class="form-control" t-att-value="state.sequence_prefix" t-on-change="(e) => this.onChange('sequence_prefix', e.target.value)" />
</div>
<div class="mb-2">
<label class="form-label">Next Number</label>
<input type="number" class="form-control" t-att-value="state.sequence_number_next" t-on-change="(e) => this.onChange('sequence_number_next', parseInt(e.target.value))" />
</div>
<div class="mb-2">
<label class="form-label">Padding</label>
<input type="number" class="form-control" t-att-value="state.sequence_padding" t-on-change="(e) => this.onChange('sequence_padding', parseInt(e.target.value))" />
</div>
</t>
</div>
<!-- Only show details if sequence is selected -->
<!-- Note: record.data.sequence_id is an array [id, display_name] usually in M2O -->
<t t-if="record.data.sequence_id">
<div class="mb-2">
<label class="fw-bold">Prefix</label>
<div class="o_field_widget d-block">
<CharField name="'sequence_prefix'" record="record" readonly="false"/>
</div>
</div>
<div class="mb-2">
<label class="fw-bold">Next Number</label>
<div class="o_field_widget d-block">
<IntegerField name="'sequence_number_next'" record="record" readonly="false"/>
</div>
</div>
<div class="mb-2">
<label class="fw-bold">Padding</label>
<div class="o_field_widget d-block">
<IntegerField name="'sequence_padding'" record="record" readonly="false"/>
</div>
</div>
</t>
</t>
</xpath>
</t>