Compare commits

..

No commits in common. "19.0" and "main" have entirely different histories.
19.0 ... main

13 changed files with 83 additions and 95 deletions

22
.gitignore vendored
View File

@ -1,22 +0,0 @@
# Python
__pycache__/
*.py[cod]
*$py.class
# Odoo
*.log
.env
.venv/
venv/
*.pot
*.po~
# VSCode
.vscode/
# PyCharm
.idea/
# OS
.DS_Store
Thumbs.db

View File

@ -17,6 +17,9 @@
'views/sign_item_views.xml', 'views/sign_item_views.xml',
], ],
'assets': { 'assets': {
'web.assets_frontend': [
'sign_sequence_field/static/src/xml/sign_item_sequence.xml',
],
'web.assets_backend': [ 'web.assets_backend': [
'sign_sequence_field/static/src/xml/sign_item_sequence.xml', 'sign_sequence_field/static/src/xml/sign_item_sequence.xml',
'sign_sequence_field/static/src/js/sign_item_custom_popover_patch.js', 'sign_sequence_field/static/src/js/sign_item_custom_popover_patch.js',

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -93,7 +93,7 @@ class SignController(Sign):
except Exception: except Exception:
return request.not_found() return request.not_found()
@http.route(['/sign/sign/<int:request_id>/<token>'], type='jsonrpc', auth='public') @http.route(['/sign/sign/<int:request_id>/<token>'], type='json', auth='public')
def sign_document(self, request_id, token, signature=None, items=None, **kwargs): def sign_document(self, request_id, token, signature=None, items=None, **kwargs):
# Intercept items to prevent saving the URL as value # Intercept items to prevent saving the URL as value
if items: if items:

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -5,9 +5,3 @@ class SignItemType(models.Model):
_inherit = "sign.item.type" _inherit = "sign.item.type"
item_type = fields.Selection(selection_add=[('sequence', "Sequence")], ondelete={'sequence': 'cascade'}) item_type = fields.Selection(selection_add=[('sequence', "Sequence")], ondelete={'sequence': 'cascade'})
def _compute_dimensions(self):
super(SignItemType, self.filtered(lambda r: r.item_type != 'sequence'))._compute_dimensions()
for record in self.filtered(lambda r: r.item_type == 'sequence'):
record.default_width = 0.15
record.default_height = 0.015

View File

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

View File

@ -1,39 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<templates xml:space="preserve"> <templates xml:space="preserve">
<t t-name="sign.SignItemCustomPopover.sequence" t-inherit="sign.SignItemCustomPopover" t-inherit-mode="extension"> <t t-name="sign.SignItemCustomPopover.sequence" t-inherit="sign.SignItemCustomPopover" t-inherit-mode="extension">
<xpath expr="//div[hasclass('popover-body')]/div[hasclass('d-flex', 'align-items-center', 'justify-content-between')]" position="before"> <xpath expr="//div[hasclass('d-flex', 'p-2', 'flex-column')]/div[1]" position="before">
<t t-if="props.type === 'sequence'"> <!-- Insert inside the main column, before the placeholder input -->
<div class="mb-3 border-top pt-3"> <div t-if="props.type === 'sequence'" class="mb-3 border-bottom pb-3">
<label class="fw-bold mb-2">Sequence Settings</label> <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"> <div class="mb-2">
<label class="form-label">Sequence</label> <label class="fw-bold">Sequence</label>
<div class="o_field_many2one_selection"> <div class="o_field_widget d-block">
<Many2XAutocomplete <Many2OneField t-props="getMany2XProps(record, 'sequence_id')" update="(value) => record.update(value)"/>
resModel="'ir.sequence'"
value="state.sequence_value"
update="(val) => this.onChangeSequence(val)"
placeholder="'Select a sequence'"
getDomain="() => []"
activeActions="{}"
fieldString="'Sequence'"
/>
</div> </div>
</div> </div>
<t t-if="state.sequence_id"> <!-- 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"> <div class="mb-2">
<label class="form-label">Prefix</label> <label class="fw-bold">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 class="o_field_widget d-block">
<CharField name="'sequence_prefix'" record="record" readonly="false"/>
</div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Next Number</label> <label class="fw-bold">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 class="o_field_widget d-block">
<IntegerField name="'sequence_number_next'" record="record" readonly="false"/>
</div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label class="form-label">Padding</label> <label class="fw-bold">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 class="o_field_widget d-block">
<IntegerField name="'sequence_padding'" record="record" readonly="false"/>
</div>
</div> </div>
</t> </t>
</div>
</t> </t>
</xpath> </xpath>
</t> </t>