diff --git a/revenue_recognition/__init__.py b/revenue_recognition/__init__.py
new file mode 100644
index 00000000000..cf3a78e262c
--- /dev/null
+++ b/revenue_recognition/__init__.py
@@ -0,0 +1,2 @@
+# Revenue Recognition Management Module
+from . import models
diff --git a/revenue_recognition/__manifest__.py b/revenue_recognition/__manifest__.py
new file mode 100644
index 00000000000..ece63ff9a0d
--- /dev/null
+++ b/revenue_recognition/__manifest__.py
@@ -0,0 +1,15 @@
+{
+ 'name': 'Revenue Recognition Management',
+ 'version': '1.0',
+ 'category': 'Accounting',
+ 'summary': 'Automatic revenue recognition for projects with date correction',
+ 'author': 'Odoo',
+ 'depends': ['project', 'sale', 'account', 'sale_project'],
+ 'data': [
+ 'views/project_revenue_recognition_views.xml',
+ 'wizard/account_automatic_entry_wizard_views.xml',
+ ],
+ 'installable': True,
+ 'application': False,
+ 'license': 'LGPL-3',
+}
diff --git a/revenue_recognition/models/__init__.py b/revenue_recognition/models/__init__.py
new file mode 100644
index 00000000000..ac54e1bd4a6
--- /dev/null
+++ b/revenue_recognition/models/__init__.py
@@ -0,0 +1,2 @@
+from . import project_project
+from . import account_automatic_entry_wizard
diff --git a/revenue_recognition/models/account_automatic_entry_wizard.py b/revenue_recognition/models/account_automatic_entry_wizard.py
new file mode 100644
index 00000000000..3a09ad0be38
--- /dev/null
+++ b/revenue_recognition/models/account_automatic_entry_wizard.py
@@ -0,0 +1,39 @@
+from odoo import models, fields
+
+
+class AccountAutomaticEntryWizard(models.TransientModel):
+ _inherit = 'account.automatic.entry.wizard'
+
+ date = fields.Date(
+ required=True,
+ default=lambda self: self._get_default_date(),
+ readonly=False
+ )
+
+ def _get_default_date(self):
+ project_id = (
+ self.env.context.get('project_id') or self.env.context.get('create_for_project_id')
+ )
+
+ if project_id:
+ project = self.env['project.project'].browse(project_id)
+ if project.exists() and project.date_start:
+ return project.date_start
+
+ if self.env.context.get('active_model') == 'account.move.line':
+ for line in self.env['account.move.line'].browse(
+ self.env.context.get('active_ids', [])
+ ):
+ if line.analytic_distribution:
+ for analytic_id_str in line.analytic_distribution:
+ try:
+ project = self.env['project.project'].search(
+ [('account_id', '=', int(analytic_id_str))],
+ limit=1
+ )
+ if project and project.date_start:
+ return project.date_start
+ except (ValueError, TypeError):
+ pass
+
+ return fields.Date.context_today(self)
diff --git a/revenue_recognition/models/project_project.py b/revenue_recognition/models/project_project.py
new file mode 100644
index 00000000000..c825ac85a38
--- /dev/null
+++ b/revenue_recognition/models/project_project.py
@@ -0,0 +1,109 @@
+from odoo import models, fields, api
+
+
+class ProjectProject(models.Model):
+ _inherit = 'project.project'
+
+ has_unrecognized_entries = fields.Boolean(
+ string='Has Unrecognized Entries',
+ compute='_compute_has_unrecognized_entries',
+ store=False,
+ )
+
+ unrecognized_entries_message = fields.Char(
+ string='Unrecognized Entries Message',
+ compute='_compute_unrecognized_entries_message',
+ )
+
+ @api.depends('date_start', 'sale_order_id')
+ def _compute_has_unrecognized_entries(self):
+ for project in self:
+ project.has_unrecognized_entries = False
+
+ if not project.sale_order_id or not project.date_start:
+ continue
+
+ invoices = project.sale_order_id.invoice_ids
+
+ if not invoices:
+ continue
+
+ generated_entries = self.env['account.move'].search([
+ ('adjusting_entry_origin_move_ids', 'in', invoices.ids),
+ ('state', '=', 'posted')
+ ])
+
+ has_current_recognition = generated_entries.filtered(
+ lambda m: m.date == project.date_start
+ )
+ project.has_unrecognized_entries = not bool(has_current_recognition)
+
+ @api.depends('has_unrecognized_entries', 'date_start')
+ def _compute_unrecognized_entries_message(self):
+ for project in self:
+ if project.has_unrecognized_entries and project.date_start:
+ project.unrecognized_entries_message = (
+ f"You still have journal items that need to be recognised "
+ f"from {project.date_start.strftime('%m/%d/%Y')}"
+ )
+ else:
+ project.unrecognized_entries_message = False
+
+ def _get_original_invoice_lines(self, move_lines):
+ generated_entries = move_lines.filtered(
+ lambda l: bool(l.move_id.adjusting_entry_origin_move_ids)
+ )
+
+ if not generated_entries:
+ return move_lines.filtered(
+ lambda l: l.account_id.account_type == 'income'
+ )
+
+ origin_moves = generated_entries.mapped(
+ 'move_id.adjusting_entry_origin_move_ids'
+ )
+
+ invoice_lines = self.env['account.move.line'].search([
+ ('move_id', 'in', origin_moves.ids),
+ ('parent_state', '=', 'posted'),
+ ])
+
+ return invoice_lines.filtered(
+ lambda l: l.account_id.account_type == 'income'
+ )
+
+ def action_recognize_invoices(self):
+ self.ensure_one()
+
+ if not self.account_id:
+ return False
+
+ move_lines = self.env['account.move.line'].search([('parent_state', '=', 'posted')]).filtered(lambda l: l.analytic_distribution and str(self.account_id.id) in l.analytic_distribution)
+
+ original_invoice_lines = self._get_original_invoice_lines(move_lines)
+
+ if not original_invoice_lines:
+ return {
+ 'type': 'ir.actions.client',
+ 'tag': 'display_notification',
+ 'params': {
+ 'title': 'No Journal Items',
+ 'message': 'No journal items found for this project.',
+ 'type': 'warning',
+ 'sticky': False,
+ }
+ }
+
+ return {
+ 'name': 'Create Automatic Entries',
+ 'type': 'ir.actions.act_window',
+ 'res_model': 'account.automatic.entry.wizard',
+ 'view_mode': 'form',
+ 'target': 'new',
+ 'context': {
+ 'active_model': 'account.move.line',
+ 'active_ids': original_invoice_lines.ids,
+ 'project_id': self.id,
+ 'default_action': 'change_period',
+ },
+ }
diff --git a/revenue_recognition/views/project_revenue_recognition_views.xml b/revenue_recognition/views/project_revenue_recognition_views.xml
new file mode 100644
index 00000000000..54aacefb79b
--- /dev/null
+++ b/revenue_recognition/views/project_revenue_recognition_views.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+ project.project.form.revenue.recognition
+ project.project
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/revenue_recognition/wizard/account_automatic_entry_wizard_views.xml b/revenue_recognition/wizard/account_automatic_entry_wizard_views.xml
new file mode 100644
index 00000000000..02e198df543
--- /dev/null
+++ b/revenue_recognition/wizard/account_automatic_entry_wizard_views.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+