[16.0][ADD] sale_partner_sale_contact[_on_project]: Add sale contact to orders, invoices and projects#4039
[16.0][ADD] sale_partner_sale_contact[_on_project]: Add sale contact to orders, invoices and projects#4039
Conversation
3d8b2e9 to
27c72b2
Compare
|
@maisim are you sure we can achieve similar features with built-in? |
I looked, I didn't found anything |
alexey-pelykh
left a comment
There was a problem hiding this comment.
Thanks for this contribution, Simon. Useful B2B use case. I went through both modules in detail and have feedback on a few areas.
Functional overlap question
I see @rousseldenis and @rrebollo already raised this -- standard Odoo 16 does have invoice/delivery/contact addresses on partners, and the SO form shows invoice and delivery address fields. However, I understand this module targets a different concept: a named commercial contact person (procurement manager, project lead) that travels across SO → invoice → project, distinct from the billing/shipping addresses. If that understanding is correct, it would help to explain that distinction explicitly in the module description to avoid future confusion.
Code-level findings
1. Duplicated onchange logic across 3 models
The _onchange_partner_id_clear_sale_contact method is copy-pasted identically in sale.order, account.move, and project.project. This is a maintenance burden -- any bug fix or behavior change would need to be applied in three places.
Consider extracting this into a mixin model (e.g. sale.contact.mixin) that all three inherit. The field definition and onchange would live in one place.
2. Domain uses child_of but onchange checks parent_id
The field domain uses ('id', 'child_of', partner_id) which includes all descendants (children, grandchildren, etc.), but the onchange validation only checks self.sale_contact_partner_id.parent_id != self.partner_id -- this would incorrectly clear a valid grandchild contact. The validation should match the domain, e.g. using commercial_partner_id or walking up the parent chain.
3. Auto-switch onchange may conflict with standard onchange_partner_id
In sale.order, the standard onchange_partner_id already fires when partner_id changes and updates fiscal position, pricelist, payment terms, etc. Your onchange modifies partner_id during an onchange on partner_id, which can cause infinite recursion or unpredictable sequencing depending on the Odoo version. This is fragile -- the auto-switch from contact → company should probably be handled differently, perhaps via an @api.onchange('partner_id') with a guard flag, or by overriding the standard onchange_partner_id instead of adding a second one.
Same concern applies to account.move where the standard _onchange_partner_id is already complex.
4. Report templates use different HTML elements
The sale order report uses a <p> tag while the invoice report uses a <div> tag for the same content block. Should be consistent.
5. module_* fields in res.config.settings reference non-existent modules
The settings form includes checkboxes for module_sale_partner_id_company_only, module_account_invoice_partner_id_company_only, and module_project_partner_id_company_only. These reference modules from other PRs that aren't merged yet (and project_partner_id_company_only is in a completely different repo). Including auto-install triggers for modules that don't exist in this repo's dependency tree is risky -- if a user toggles the checkbox, Odoo will try to install a module that may not be available, resulting in an error. These should either be removed from this PR or the related modules should be merged first.
6. Missing copy=False consideration
When duplicating a sale order or invoice, sale_contact_partner_id will be copied by default. This might be desired, but it's worth being explicit about the choice -- add copy=True (to document intent) or copy=False if the contact should not carry over to duplicates.
7. account.move view uses attrs for readonly instead of states
The invoice view uses attrs="{'readonly': [('state', '!=', 'draft')]}" but the sale order view does not have any readonly condition. Both should be consistent. For the SO view, you'd typically want readonly in locked/done states too.
Test coverage
The tests are solid for the happy path. A couple of gaps:
- No test for the auto-switch behavior (selecting a contact person as partner → auto-switch to company)
- No test for the
_prepare_invoicepropagation whensale_contact_partner_idis False (theifguard -- the testtest_06covers this indirectly but doesn't assert the dict key is absent) - The
codecov/patchcheck is failing, so some lines aren't covered
OCA conventions
- Missing
i18n/*.potexport viaoca-gen-addon-readme(the .pot files look hand-crafted rather than exported) - The copyright header says "Odoo Community Association (OCA)" but the author is "OpenStudio SAS" -- for OCA contributions, copyright typically belongs to the contributing company. The copyright line in source files should match the
authorin manifest.
Overall a useful feature, but the duplicated onchange logic and the domain/validation mismatch should be addressed before merge.
|
@maisim Can you change the module to LGPL? Being a quite low level feature can be benefitial to both CE and EE users. |
No worries, I'll do it right away |
|
Hi @alexey-pelykh, |
27c72b2 to
1e57658
Compare
| sale_contact_partner_id = fields.Many2one( | ||
| # Copied on duplication: the sale contact is part of the commercial | ||
| # context of the order and should carry over when duplicating. | ||
| copy=True, |
There was a problem hiding this comment.
By default, this already has copy=True.
There was a problem hiding this comment.
Hi @MiquelRForgeFlow,
It's a request from @alexey-pelykh be be explicit on the behavior
There was a problem hiding this comment.
Well, you can put it in the mixin instead :S
| sale_contact_partner_id = fields.Many2one( | ||
| # Copied on duplication: when cloning a project for the same customer | ||
| # the sale contact should carry over, just like the partner itself. | ||
| copy=True, |
There was a problem hiding this comment.
same as previous comment
| # Copied on duplication: when cloning a project for the same customer | ||
| # the sale contact should carry over, just like the partner itself. | ||
| copy=True, | ||
| help="Contact person for this project from the sale order. " |
There was a problem hiding this comment.
By the way, the point of the mixing is avoid declaring this field everywhere, but just to customize this help, you are redeclaring it again anyway, so the mixin loses part of its purpose.
There was a problem hiding this comment.
The help in the mixin could be: Contact person. Only child contacts of the customer can be selected., this way you can avoid customizing for each model.
There was a problem hiding this comment.
So it is better to just remove the field here?
I readd it here because I had a db error, the orm try to recreate the field, but I think I change the way I call the mixin between, let me do some tests to double check
There was a problem hiding this comment.
Seems to be ok, let me do some functional tests from the ui
There was a problem hiding this comment.
The last changes broke the "auto switch" feature from the UI.
I first tried to re-add @api.onchange decorator but it seems to brake the MRO.
I had to use a new method name (instead of override _onchange_partner_id) , then remove the super()
Both onchange are now called on partner_id change.
It works.
Do you think this is the good way to do that?
54316c1 to
14a3f2f
Compare
Any change to get concerned module merged instead of remove the refs? |
e80d565 to
9284df8
Compare
|
Repo was greenified in #4230 so please rebase. Also, don't add last commit, thus modules are not dependencies. Also, I think it would be better to squash commits, there are too many. |
It was to ease functionnal test of modules installation from the settings. I will remove it
I did it it seprate commits to get one per asked modification. I will squash them in one commit per addon |
66efc34 to
ea7df6b
Compare
| # Module installation checkboxes | ||
| module_sale_order_partner_company_only = fields.Boolean( | ||
| string="Restrict customer selection to company only in quotation and order", | ||
| ) | ||
| module_account_invoice_partner_company_only = fields.Boolean( | ||
| string="Restrict customer selection to company only on invoice", | ||
| ) | ||
| module_project_partner_company_only = fields.Boolean( | ||
| string="Restrict customer selection to company only in projects", | ||
| ) |
There was a problem hiding this comment.
I think this have to be removed, this should be added when merging the corresponding PRs.
There was a problem hiding this comment.
There, it's done.
I took a quick look and don't see any other references to the additional modules in the commits.
I think it's not the right approach anyway. It's more of a configuration issue and doesn't really make sense, as @rrebollo kindly pointed out to me. OCA/account-invoicing#2180
Either we do without it here and let everyone override their views to limit the display to companies, or I'll look into integrating it later as a configuration option in this module.
What do you think?
ea7df6b to
8a197e9
Compare
… invoices This module adds the ability to specify a contact person for sale orders and invoices, separate from the main customer. Features: * Add Sale Contact field on sale orders and invoices * Propagate sale contact from sale order to invoice * Display sale contact on PDF reports (configurable) * Configuration option in Settings > Sales to enable/disable display * Domain restriction: only child contacts of the customer can be selected * OnChange logic to clear incompatible contacts when customer changes Technical implementation: * New field sale_contact_partner_id on sale.order and account.move * Company-level configuration field sale_display_contact_on_reports * Report template inheritance for sale orders and invoices * French translations included
…act to projects This module extends sale_partner_sale_contact to automatically propagate the sale contact from sale orders to the projects created from those orders. Features: * Propagate sale contact from sale order to project * Display sale contact field on project form view * Automatic propagation when project is created from sale order line Technical implementation: * Override _timesheet_create_project_prepare_values on sale.order.line * Add sale_contact_partner_id field on project.project * Tree and form view customization for projects * French translations included Depends on: sale_partner_sale_contact (in this PR)
8a197e9 to
161880c
Compare

New modules: Sale Contact Management
This PR introduces two complementary modules for managing sale contacts across the sales workflow:
sale_partner_sale_contact
sale_partner_sale_contact_on_project
Use case
In B2B scenarios, you often deal with specific contacts within a customer organization (procurement managers, project leads, etc.). While the invoice must go to the company, you need to track who you're actually working with for follow-ups, communication, and relationship management.
Design choices
Benefits
Related PRs
You can optionally use this module to limit partner_id selection to companies