Skip to content

Commit e748db1

Browse files
committed
[IMP] sale_timesheet_invoice_link: link pending timesheets from invoice form
After invoicing a sale order with fixed price, milestone, or manual billing policies, timesheets are never linked to the invoice automatically. This adds a warning alert on the invoice form that detects unlinked timesheets and offers "Link All", "Select Manually", or "Dismiss" actions. - Add computed fields timesheet_pending_ids, timesheet_pending_count, timesheet_alert_dismissed to account.move - Add warning alert on invoice form with Link All / Select Manually / Dismiss buttons - Add "Link to Invoice" header button on timesheet list view - Reuse _timesheet_domain_get_invoiced_lines for domain logic Assisted-by: OpenCode + DeepSeek V4 Flash MT-14588 @moduon
1 parent 38a38cc commit e748db1

14 files changed

Lines changed: 593 additions & 2 deletions

File tree

sale_timesheet_invoice_link/README.rst

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,10 @@ This module extends the functionality of timesheets to support linking
3636
them to preexisting invoices and to allow you to anticipate invoicing,
3737
even before the work hours are actually done.
3838

39+
It also adds a warning alert on invoices that detects timesheets linked
40+
to the invoice's sale order lines that are not yet linked to any
41+
invoice, allowing you to link them all at once or select them manually.
42+
3943
.. IMPORTANT::
4044
This is an alpha version, the data model and design can change at any time without warning.
4145
Only for development or testing purpose, do not use in production.
@@ -78,6 +82,17 @@ To use this module, you need to:
7882
4. Link the timesheet lines with the invoices you want from the same
7983
customer.
8084

85+
Additionally, when you open an invoice that has timesheets linked to its
86+
sale order lines but not yet linked to the invoice itself, a warning
87+
alert will appear at the top of the invoice form. From there you can:
88+
89+
- Click **Link All** to automatically link all pending timesheets to
90+
this invoice.
91+
- Click **Select Manually** to open a filtered list of pending
92+
timesheets where you can select specific ones and click **Link to
93+
Invoice**.
94+
- Click **Dismiss** to hide the alert for this invoice.
95+
8196
Known issues / Roadmap
8297
======================
8398

@@ -106,6 +121,7 @@ Contributors
106121
------------
107122

108123
- Jairo Llopis (`Moduon <https://www.moduon.team/>`__)
124+
- Emilio Pascual (`Moduon <https://www.moduon.team/>`__)
109125

110126
Maintainers
111127
-----------
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import models

sale_timesheet_invoice_link/__manifest__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
{
55
"name": "Sale Timesheet Invoice Link",
66
"summary": "Link invoices with timesheet lines",
7-
"version": "19.0.0.1.1",
7+
"version": "19.0.0.1.0",
88
"development_status": "Alpha",
99
"category": "Services/Timesheets",
1010
"website": "https://github.com/OCA/timesheet",
@@ -14,5 +14,6 @@
1414
"depends": ["sale_timesheet"],
1515
"data": [
1616
"views/account_analytic_line_view.xml",
17+
"views/account_move_view.xml",
1718
],
1819
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_timesheet_invoice_link
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 18.0\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2026-07-27 06:22+0000\n"
10+
"PO-Revision-Date: \n"
11+
"Last-Translator: Emilio Pascual <emilio@moduon.team>\n"
12+
"Language-Team: \n"
13+
"Language: es\n"
14+
"MIME-Version: 1.0\n"
15+
"Content-Type: text/plain; charset=UTF-8\n"
16+
"Content-Transfer-Encoding: 8bit\n"
17+
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
18+
"X-Generator: Poedit 3.9\n"
19+
20+
#. module: sale_timesheet_invoice_link
21+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
22+
msgid "Dismiss"
23+
msgstr "Descartar"
24+
25+
#. module: sale_timesheet_invoice_link
26+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
27+
msgid "Dismiss this alert"
28+
msgstr "Ignorar esta alerta"
29+
30+
#. module: sale_timesheet_invoice_link
31+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__display_name
32+
msgid "Display Name"
33+
msgstr ""
34+
35+
#. module: sale_timesheet_invoice_link
36+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__id
37+
msgid "ID"
38+
msgstr ""
39+
40+
#. module: sale_timesheet_invoice_link
41+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_alert_dismissed
42+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_move__timesheet_alert_dismissed
43+
msgid "If unchecked, shows a warning alert about unlinked timesheets"
44+
msgstr ""
45+
"Si está desmarcado, muestra una alerta sobre partes de horas no vinculados"
46+
47+
#. module: sale_timesheet_invoice_link
48+
#: model:ir.model,name:sale_timesheet_invoice_link.model_account_move
49+
msgid "Journal Entry"
50+
msgstr "Asiento contable"
51+
52+
#. module: sale_timesheet_invoice_link
53+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
54+
msgid "Link All"
55+
msgstr "Vincular todos"
56+
57+
#. module: sale_timesheet_invoice_link
58+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_ids
59+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_pending_ids
60+
msgid "Pending Timesheets"
61+
msgstr "Partes de horas pendientes"
62+
63+
#. module: sale_timesheet_invoice_link
64+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_count
65+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_pending_count
66+
msgid "Pending Timesheets Count"
67+
msgstr "N.º de partes de horas pendientes"
68+
69+
#. module: sale_timesheet_invoice_link
70+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
71+
msgid "Select Manually"
72+
msgstr "Seleccionar manualmente"
73+
74+
#. module: sale_timesheet_invoice_link
75+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
76+
msgid "There is/are"
77+
msgstr "Hay"
78+
79+
#. module: sale_timesheet_invoice_link
80+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_alert_dismissed
81+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_alert_dismissed
82+
msgid "Timesheet Alert Dismissed"
83+
msgstr "Alerta de partes descartada"
84+
85+
#. module: sale_timesheet_invoice_link
86+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_ids
87+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_move__timesheet_pending_ids
88+
msgid ""
89+
"Timesheets linked to this invoice's SO lines that are not yet linked to any "
90+
"non-cancelled invoice"
91+
msgstr ""
92+
"Partes de horas vinculados a las líneas de pedido de esta factura que aún no "
93+
"están vinculados a ninguna factura no cancelada"
94+
95+
#. module: sale_timesheet_invoice_link
96+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
97+
msgid "timesheet(s) not linked to this invoice yet."
98+
msgstr "partes de horas no vinculados a esta factura todavía."
Lines changed: 93 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,93 @@
1-
# No translations.
1+
# Translation of Odoo Server.
2+
# This file contains the translation of the following modules:
3+
# * sale_timesheet_invoice_link
4+
#
5+
msgid ""
6+
msgstr ""
7+
"Project-Id-Version: Odoo Server 19.0+e\n"
8+
"Report-Msgid-Bugs-To: \n"
9+
"POT-Creation-Date: 2026-07-27 06:22+0000\n"
10+
"PO-Revision-Date: 2026-07-27 06:22+0000\n"
11+
"Last-Translator: \n"
12+
"Language-Team: \n"
13+
"MIME-Version: 1.0\n"
14+
"Content-Type: text/plain; charset=UTF-8\n"
15+
"Content-Transfer-Encoding: \n"
16+
"Plural-Forms: \n"
17+
18+
#. module: sale_timesheet_invoice_link
19+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
20+
msgid "Dismiss"
21+
msgstr ""
22+
23+
#. module: sale_timesheet_invoice_link
24+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
25+
msgid "Dismiss this alert"
26+
msgstr ""
27+
28+
#. module: sale_timesheet_invoice_link
29+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__display_name
30+
msgid "Display Name"
31+
msgstr ""
32+
33+
#. module: sale_timesheet_invoice_link
34+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__id
35+
msgid "ID"
36+
msgstr ""
37+
38+
#. module: sale_timesheet_invoice_link
39+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_alert_dismissed
40+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_move__timesheet_alert_dismissed
41+
msgid "If unchecked, shows a warning alert about unlinked timesheets"
42+
msgstr ""
43+
44+
#. module: sale_timesheet_invoice_link
45+
#: model:ir.model,name:sale_timesheet_invoice_link.model_account_move
46+
msgid "Journal Entry"
47+
msgstr ""
48+
49+
#. module: sale_timesheet_invoice_link
50+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
51+
msgid "Link All"
52+
msgstr ""
53+
54+
#. module: sale_timesheet_invoice_link
55+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_ids
56+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_pending_ids
57+
msgid "Pending Timesheets"
58+
msgstr ""
59+
60+
#. module: sale_timesheet_invoice_link
61+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_count
62+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_pending_count
63+
msgid "Pending Timesheets Count"
64+
msgstr ""
65+
66+
#. module: sale_timesheet_invoice_link
67+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
68+
msgid "Select Manually"
69+
msgstr ""
70+
71+
#. module: sale_timesheet_invoice_link
72+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
73+
msgid "There is/are"
74+
msgstr ""
75+
76+
#. module: sale_timesheet_invoice_link
77+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_alert_dismissed
78+
#: model:ir.model.fields,field_description:sale_timesheet_invoice_link.field_account_move__timesheet_alert_dismissed
79+
msgid "Timesheet Alert Dismissed"
80+
msgstr ""
81+
82+
#. module: sale_timesheet_invoice_link
83+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_bank_statement_line__timesheet_pending_ids
84+
#: model:ir.model.fields,help:sale_timesheet_invoice_link.field_account_move__timesheet_pending_ids
85+
msgid ""
86+
"Timesheets linked to this invoice's SO lines that are not yet linked to any "
87+
"non-cancelled invoice"
88+
msgstr ""
89+
90+
#. module: sale_timesheet_invoice_link
91+
#: model_terms:ir.ui.view,arch_db:sale_timesheet_invoice_link.account_move_view_form_inherit_timesheet_link
92+
msgid "timesheet(s) not linked to this invoice yet."
93+
msgstr ""
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
from . import account_move
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2024 Moduon Team S.L.
2+
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
3+
4+
from odoo import api, fields, models
5+
6+
7+
class AccountMove(models.Model):
8+
_inherit = "account.move"
9+
10+
timesheet_pending_ids = fields.One2many(
11+
comodel_name="account.analytic.line",
12+
compute="_compute_timesheet_pending_ids",
13+
string="Pending Timesheets",
14+
help="Timesheets linked to this invoice's SO lines that are not yet "
15+
"linked to any non-cancelled invoice",
16+
)
17+
timesheet_pending_count = fields.Integer(
18+
string="Pending Timesheets Count",
19+
compute="_compute_timesheet_pending_ids",
20+
)
21+
timesheet_alert_dismissed = fields.Boolean(
22+
default=False,
23+
help="If unchecked, shows a warning alert about unlinked timesheets",
24+
)
25+
26+
@api.depends("invoice_line_ids.sale_line_ids")
27+
def _compute_timesheet_pending_ids(self):
28+
self.timesheet_pending_ids = False
29+
self.timesheet_pending_count = 0
30+
for invoice in self.filtered_domain([("move_type", "=", "out_invoice")]):
31+
so_lines = invoice.invoice_line_ids.sale_line_ids
32+
if not so_lines:
33+
continue
34+
domain = self.env["account.move.line"]._timesheet_domain_get_invoiced_lines(
35+
so_lines
36+
)
37+
pending = self.env["account.analytic.line"].search(domain)
38+
invoice.timesheet_pending_ids = pending
39+
invoice.timesheet_pending_count = len(pending)
40+
41+
def action_link_timesheets(self):
42+
self.ensure_one()
43+
self.timesheet_pending_ids.write({"timesheet_invoice_id": self.id})
44+
self.timesheet_alert_dismissed = True
45+
46+
def action_dismiss_timesheet_alert(self):
47+
self.ensure_one()
48+
self.timesheet_alert_dismissed = True
49+
50+
def action_select_timesheets(self):
51+
self.ensure_one()
52+
pending = self.timesheet_pending_ids
53+
if not pending:
54+
return
55+
return {
56+
"type": "ir.actions.act_window",
57+
"name": "Select Timesheets to Link",
58+
"res_model": "account.analytic.line",
59+
"view_mode": "list,form",
60+
"domain": [("id", "in", pending.ids)],
61+
"context": {
62+
"default_timesheet_invoice_id": self.id,
63+
},
64+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
11
- Jairo Llopis ([Moduon](https://www.moduon.team/))
2+
- Emilio Pascual ([Moduon](https://www.moduon.team/))
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
11
This module extends the functionality of timesheets to support linking them to
22
preexisting invoices and to allow you to anticipate invoicing, even before the
33
work hours are actually done.
4+
5+
It also adds a warning alert on invoices that detects timesheets linked to the
6+
invoice's sale order lines that are not yet linked to any invoice, allowing you
7+
to link them all at once or select them manually.

sale_timesheet_invoice_link/readme/USAGE.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,13 @@ To use this module, you need to:
44
1. Select the list view.
55
1. Click on the icon in the top-right corner and display the column *Invoice*.
66
1. Link the timesheet lines with the invoices you want from the same customer.
7+
8+
Additionally, when you open an invoice that has timesheets linked to its sale
9+
order lines but not yet linked to the invoice itself, a warning alert will
10+
appear at the top of the invoice form. From there you can:
11+
12+
- Click **Link All** to automatically link all pending timesheets to this
13+
invoice.
14+
- Click **Select Manually** to open a filtered list of pending timesheets where
15+
you can select specific ones and click **Link to Invoice**.
16+
- Click **Dismiss** to hide the alert for this invoice.

0 commit comments

Comments
 (0)