Compare commits

..

3 Commits
main ... 19.0

13 changed files with 95 additions and 86 deletions

22
.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# 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,9 +17,6 @@
'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.

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='json', auth='public') @http.route(['/sign/sign/<int:request_id>/<token>'], type='jsonrpc', 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:

View File

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

View File

@ -1,48 +1,36 @@
<?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('d-flex', 'p-2', 'flex-column')]/div[1]" position="before"> <xpath expr="//div[hasclass('popover-body')]/div[hasclass('d-flex', 'align-items-center', 'justify-content-between')]" 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'"> <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"> <div class="mb-2">
<label class="fw-bold">Sequence</label> <label class="form-label">Sequence</label>
<div class="o_field_widget d-block"> <div class="o_field_many2one_selection">
<Many2OneField t-props="getMany2XProps(record, 'sequence_id')" update="(value) => record.update(value)"/> <Many2XAutocomplete
resModel="'ir.sequence'"
value="state.sequence_value"
update="(val) => this.onChangeSequence(val)"
placeholder="'Select a sequence'"
/>
</div> </div>
</div> </div>
<!-- Only show details if sequence is selected --> <t t-if="state.sequence_id">
<!-- 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="fw-bold">Prefix</label> <label class="form-label">Prefix</label>
<div class="o_field_widget d-block"> <input type="text" class="form-control" t-att-value="state.sequence_prefix" t-on-change="(e) => this.onChange('sequence_prefix', e.target.value)" />
<CharField name="'sequence_prefix'" record="record" readonly="false"/>
</div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label class="fw-bold">Next Number</label> <label class="form-label">Next Number</label>
<div class="o_field_widget d-block"> <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))" />
<IntegerField name="'sequence_number_next'" record="record" readonly="false"/>
</div>
</div> </div>
<div class="mb-2"> <div class="mb-2">
<label class="fw-bold">Padding</label> <label class="form-label">Padding</label>
<div class="o_field_widget d-block"> <input type="number" class="form-control" t-att-value="state.sequence_padding" t-on-change="(e) => this.onChange('sequence_padding', parseInt(e.target.value))" />
<IntegerField name="'sequence_padding'" record="record" readonly="false"/>
</div>
</div> </div>
</t> </t>
</div>
</t> </t>
</xpath> </xpath>
</t> </t>