feat(invoicing): managers (+QM+Owner) can create customer invoices

Grant Odoo Billing (account.group_account_invoice) to group_fp_manager via
implied_ids; Quality Manager + Owner inherit it. Billing only (not Accountant);
the SO-origin workflow gate in fusion_plating_jobs is unchanged, so managers
invoice from the Sale Order's Create Invoice action. Tests assert Manager/Owner
get Billing and Shop Manager does not.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
gsinghpal
2026-05-29 20:33:40 -04:00
parent b2186ab032
commit 1e9ffccd6b
5 changed files with 118 additions and 3 deletions

View File

@@ -0,0 +1,71 @@
# Managers Can Create Invoices (grant Billing group)
**Date:** 2026-05-29
**Status:** Approved — implementing directly (single security record; no separate plan)
**Module:** `fusion_plating_invoicing`
## Goal
Let the plating **Manager** role (and Quality Manager + Owner, who inherit it) create customer
invoices from a confirmed Sale Order.
## Background — why managers can't invoice today (verified 2026-05-29)
- Creating an `account.move` of a customer type requires one of Odoo's accounting groups
(minimum: **Billing** = `account.group_account_invoice`).
- **No plating role grants any Odoo accounting group.** A repo-wide grep for
`account.group_account*` in `fusion_plating*/security/` returns nothing.
- `group_fp_manager` implies only the legacy plating-internal `group_fp_accounting`
(`fusion_plating_invoicing/security/fp_invoicing_security.xml:18-20`), which itself chains
to the deprecated `group_fusion_plating_supervisor`**not** to any Odoo accounting group.
So it grants no `account.move` rights.
- Result: when a Manager opens a confirmed SO and clicks **Create Invoice**, Odoo's
`_create_invoices()` tries to create an `account.move` and the Manager hits an `AccessError`
for lacking Billing.
- Separately, `fusion_plating_jobs/models/account_move.py::_fp_validate_customer_invoice`
blocks off-SO customer-invoice creation for **all** users (parent-number audit trail). This
is a *workflow* gate, not a permission gate — it is **not** the manager blocker and stays
unchanged.
## Decision
Grant `account.group_account_invoice` (**Billing**) to `group_fp_manager` via `implied_ids`.
- **Cascade:** Quality Manager → implies Manager, Owner → implies Quality Manager, so QM and
Owner inherit Billing automatically. Shop Manager, Sales Manager, Technician, Operator, and
below are **unaffected**.
- **Level — Billing only:** create/edit/post customer invoices + credit notes. Explicitly
**not** Accountant (`account.group_account_user`): no vendor bills, manual journal entries,
bank reconciliation, accounting reports, or period close.
- **Path unchanged:** managers invoice **from the SO** (`Create Invoice` action), which the
`_fp_validate_customer_invoice` workflow gate already permits via `fp_from_so_invoice`.
Standalone off-SO invoices remain blocked for everyone.
## Implementation
Extend the existing additive `implied_ids` write on `group_fp_manager` in
`fusion_plating_invoicing/security/fp_invoicing_security.xml`:
```xml
<record id="fusion_plating.group_fp_manager" model="res.groups">
<field name="implied_ids" eval="[(4, ref('fusion_plating_invoicing.group_fp_accounting')),
(4, ref('account.group_account_invoice'))]"/>
</record>
```
`(4, id)` (Command.link) is additive + idempotent across install/`-u`. `fusion_plating_invoicing`
already depends transitively on `account` (it overrides `account.move`), so `ref('account.…')`
resolves. Bump the module `version`.
## Testing
`TransactionCase` in `fusion_plating_invoicing/tests/`: create a user holding **only**
`group_fp_manager`, assert `user.has_group('account.group_account_invoice')` is `True` (proves
the implication landed). Then a live entech check: a Manager creates an invoice from a
confirmed SO without an AccessError.
## Out of scope
- Relaxing the SO-origin workflow gate (intentional; preserves the parent-number audit trail).
- Accountant-level access (vendor bills, journals, reconciliation, reports).
- Granting invoicing to Shop Manager / Sales Manager / below.