Compare commits
No commits in common. "19.0" and "main" have entirely different histories.
22
.gitignore
vendored
22
.gitignore
vendored
@ -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
|
|
||||||
@ -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',
|
||||||
|
|||||||
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
BIN
__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
controllers/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
controllers/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
controllers/__pycache__/main.cpython-312.pyc
Normal file
BIN
controllers/__pycache__/main.cpython-312.pyc
Normal file
Binary file not shown.
@ -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:
|
||||||
|
|||||||
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
BIN
models/__pycache__/__init__.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/sign_item.cpython-312.pyc
Normal file
BIN
models/__pycache__/sign_item.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/sign_item_type.cpython-312.pyc
Normal file
BIN
models/__pycache__/sign_item_type.cpython-312.pyc
Normal file
Binary file not shown.
BIN
models/__pycache__/sign_request.cpython-312.pyc
Normal file
BIN
models/__pycache__/sign_request.cpython-312.pyc
Normal file
Binary file not shown.
@ -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
|
|
||||||
|
|||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user