Skip to content

Commit

Permalink
TA#61910 [ADD][MIG] project_wip_timesheet : migration to 14.0
Browse files Browse the repository at this point in the history
  • Loading branch information
lanto-razafindrabe authored and majouda committed Mar 28, 2024
1 parent ac4b130 commit 6085c3f
Show file tree
Hide file tree
Showing 8 changed files with 126 additions and 56 deletions.
2 changes: 1 addition & 1 deletion project_wip_timesheet/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import models
34 changes: 17 additions & 17 deletions project_wip_timesheet/__manifest__.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

{

Check warning on line 4 in project_wip_timesheet/__manifest__.py

View check run for this annotation

Codacy Production / Codacy Static Code Analysis

project_wip_timesheet/__manifest__.py#L4

Statement seems to have no effect
'name': 'Project WIP Timesheet',
'version': '1.0.0',
'author': 'Numigi',
'maintainer': 'Numigi',
'website': 'https://bit.ly/numigi-com',
'license': 'LGPL-3',
'category': 'Project',
'summary': 'Generate WIP journal entries from timesheets',
'depends': [
'project_wip',
'project_task_analytic_lines',
'hr_timesheet',
'sale_timesheet',
"name": "Project WIP Timesheet",
"version": "14.0.1.0.0",
"author": "Numigi",
"maintainer": "Numigi",
"website": "https://bit.ly/numigi-com",
"license": "LGPL-3",
"category": "Project",
"summary": "Generate WIP journal entries from timesheets",
"depends": [
"project_wip",
"project_task_analytic_lines",
"hr_timesheet",
"sale_timesheet",
],
'data': [
'views/project_type.xml',
"data": [
"views/project_type.xml",
],
'installable': True,
"installable": True,
}
12 changes: 11 additions & 1 deletion project_wip_timesheet/i18n/fr.po
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
#
msgid ""
msgstr ""
"Project-Id-Version: Odoo Server 11.0+e\n"
"Project-Id-Version: Odoo Server 14.0+e\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-12-09 15:31+0000\n"
"PO-Revision-Date: 2020-12-09 10:33-0500\n"
Expand Down Expand Up @@ -121,5 +121,15 @@ msgstr ""
"comptable de travaux en cours ({move_name}) serait renversée. L'écriture "
"comptable est déjà transférée vers le coût des marchandises vendues."

#. module: project_wip_timesheet
#: code:addons/project_wip_timesheet/models/account_analytic_line.py:0
#, python-format
msgid ""
"The entry {move_line} ({amount}) could not be reconciled.You should verify "
"if the Salary entry is partially reconciled."
msgstr ""
"L'écriture de {move_line} ({amount}) ne peut pas être lettrée. Vous devriez vérifier que "
"l'écriture de l'entrée de salaire n'est pas partiellement lettrée."

#~ msgid "(task: {})"
#~ msgstr "(tâche: {})"
2 changes: 1 addition & 1 deletion project_wip_timesheet/models/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from . import account_analytic_line, project_type
77 changes: 66 additions & 11 deletions project_wip_timesheet/models/account_analytic_line.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import api, fields, models, _
Expand All @@ -25,7 +25,6 @@ def create(self, vals):
line.sudo()._create_update_or_reverse_salary_account_move()
return line

@api.multi
def write(self, vals):
"""When updating an analytic line, create / update / delete the wip entry.
Expand All @@ -41,10 +40,9 @@ def write(self, vals):

return True

@api.multi
def unlink(self):
"""Reverse the salary account move entry when a timesheet line is deleted."""
lines_with_moves = self.filtered(lambda l: l.salary_account_move_id)
lines_with_moves = self.filtered(lambda line: line.salary_account_move_id)
for line in lines_with_moves:
line.sudo()._reverse_salary_account_move_for_deleted_timesheet()
return super().unlink()
Expand Down Expand Up @@ -94,7 +92,9 @@ def _update_salary_account_move(self):
)
)

self.salary_account_move_id.state = "draft"
# self.salary_account_move_id.state = "draft"
self.salary_account_move_id.button_draft()
self.salary_account_move_id.name = ""
vals = self._get_salary_account_move_vals()
self.salary_account_move_id.write(vals)
self.salary_account_move_id.post()
Expand All @@ -113,7 +113,17 @@ def _reverse_salary_account_move_for_updated_timesheet(self):
move_name=self.salary_account_move_id.name,
)
)
self.salary_account_move_id.reverse_moves()
reversed_move = self.salary_account_move_id._reverse_moves()
reversed_move.action_post()

# get the reversed move line and reconcile it with the salary move line
reversed_move_line, move_line = self._get_line_reconciliation_data(
self.salary_account_move_id, reversed_move
)

# reconcile the move lines
self._reconcile_move_lines(move_line, reversed_move_line)

self.salary_account_move_id = False

def _reverse_salary_account_move_for_deleted_timesheet(self):
Expand All @@ -129,10 +139,56 @@ def _reverse_salary_account_move_for_deleted_timesheet(self):
move_name=self.salary_account_move_id.name,
)
)
self.salary_account_move_id.reverse_moves()
# reverse the move and post it to allow the reconciliation of the move lines
reversed_move = self.salary_account_move_id._reverse_moves()
reversed_move.action_post()

# get the reversed move line and reconcile it with the salary move line
reversed_move_line, move_line = self._get_line_reconciliation_data(
self.salary_account_move_id, reversed_move
)

# reconcile the move lines
self._reconcile_move_lines(move_line, reversed_move_line)

def _get_line_reconciliation_data(self, move, reversed_move):
"""Get the reconciled move line and the original move line.
In case, we changed project in timesheet, and project has no type to select wip
account, we identify the account move line by task_id linked, that only on wip
account.
:rtype: tuple
"""
move_line = move.line_ids.filtered(
lambda line: (line.account_id == self._get_wip_account()) or line.task_id
)
reversed_move_line = reversed_move.line_ids.filtered(
lambda line: (line.account_id == self._get_wip_account()) or line.task_id
)
return reversed_move_line, move_line

def _reconcile_move_lines(self, move_line, reversal_line):
"""Reconcile the move lines."""
data = [
{
"id": None,
"mv_line_ids": [move_line.id, reversal_line.id],
"new_mv_line_dicts": [],
"type": None,
}
]
self.env["account.reconciliation.widget"].process_move_lines(data)

if move_line.matching_number == "P":
raise ValidationError(
_(
"The entry {move_line} ({amount}) could not be reconciled."
"You should verify if the Salary entry is partially reconciled."
).format(wip_line=move_line.display_name, amount=move_line.balance)
)

def _is_salary_account_move_reconciled(self):
return any(l.reconciled for l in self.salary_account_move_id.line_ids)
return any(line.reconciled for line in self.salary_account_move_id.line_ids)

def _get_salary_account_move_vals(self):
"""Get the values for the wip account move.
Expand Down Expand Up @@ -209,12 +265,11 @@ def _get_salary_journal(self):
:rtype: account.journal
"""
self = self.with_context(force_company=self.company_id.id)
return self.project_id.project_type_id.salary_journal_id
return self.project_id.type_id.salary_journal_id

def _get_salary_account(self):
"""Get the account to use for the salary move line.
:rtype: account.account
"""
self = self.with_context(force_company=self.company_id.id)
return self.project_id.project_type_id.salary_account_id
return self.project_id.type_id.salary_account_id
3 changes: 1 addition & 2 deletions project_wip_timesheet/models/project_type.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

from odoo import api, fields, models, _
Expand Down Expand Up @@ -28,7 +28,6 @@ class ProjectType(models.Model):

@api.constrains("salary_account_id", "salary_journal_id", "wip_account_id")
def _check_required_fields_for_salary_entries(self):
self = self.with_context(force_company=self.env.user.company_id.id)
project_types_with_salary_account = self.filtered(lambda t: t.salary_account_id)
for project_type in project_types_with_salary_account:
if not project_type.wip_account_id:
Expand Down
2 changes: 1 addition & 1 deletion project_wip_timesheet/tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).
50 changes: 28 additions & 22 deletions project_wip_timesheet/tests/test_wip_journal_entries.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# © 2019 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# © 2024 Numigi (tm) and all its contributors (https://bit.ly/numigiens)
# License LGPL-3.0 or later (http://www.gnu.org/licenses/lgpl).

import pytest
Expand All @@ -18,7 +18,10 @@ def setUpClass(cls):
"name": "Manager",
"login": "manager",
"email": "[email protected]",
"groups_id": [(4, cls.env.ref("project.group_project_manager").id)],
"groups_id": [
(4, cls.env.ref("project.group_project_manager").id),
(4, cls.env.ref("project_wip.group_wip_to_cgs").id),
],
"company_id": cls.company.id,
"company_ids": [(4, cls.company.id)],
}
Expand Down Expand Up @@ -58,7 +61,7 @@ def setUpClass(cls):
{
"name": "Work in Progress",
"code": "WIP",
"update_posted": True,
# "update_posted": True,
"type": "general",
"company_id": cls.company.id,
}
Expand Down Expand Up @@ -114,7 +117,7 @@ def setUpClass(cls):
cls.project = cls.env["project.project"].create(
{
"name": "Job 123",
"project_type_id": cls.project_type.id,
"type_id": cls.project_type.id,
"company_id": cls.company.id,
}
)
Expand All @@ -132,7 +135,7 @@ def _create_timesheet(cls, description="/", quantity=1, amount=50, date_=None):
cls.employee.timesheet_cost = amount
line = (
cls.env["account.analytic.line"]
.sudo(cls.timesheet_user)
.with_user(cls.timesheet_user)
.create(
{
"company_id": cls.company.id,
Expand Down Expand Up @@ -223,25 +226,25 @@ def test_salary_move_line_has_no_task(self):
def test_on_change_timesheet_amount__debit_amount_updated(self):
timesheet_line = self._create_timesheet()
expected_amount = 25
timesheet_line.sudo(self.timesheet_user).amount = -expected_amount
timesheet_line.with_user(self.timesheet_user).amount = -expected_amount
wip_line = self._get_wip_move_line(timesheet_line)
assert wip_line.debit == expected_amount

def test_on_change_timesheet_quantity__move_quantity_updated(self):
timesheet_line = self._create_timesheet()
expected_quantity = 5
timesheet_line.sudo(self.timesheet_user).unit_amount = expected_quantity
timesheet_line.with_user(self.timesheet_user).unit_amount = expected_quantity
wip_line = self._get_wip_move_line(timesheet_line)
assert wip_line.quantity == expected_quantity

def test_on_change_timesheet_a_date__account_move_date_updated(self):
timesheet_line = self._create_timesheet()
new_date = datetime.now().date() + timedelta(30)
timesheet_line.sudo(self.timesheet_user).date = new_date
timesheet_line.with_user(self.timesheet_user).date = new_date
assert timesheet_line.salary_account_move_id.date == new_date

def test_if_project_has_no_type__no_account_move_created(self):
self.project.project_type_id = False
self.project.type_id = False
timesheet_line = self._create_timesheet()
assert not timesheet_line.salary_account_move_id

Expand All @@ -264,20 +267,20 @@ def test_reversal_move_wip_line_has_task(self):

def test_if_new_project_requires_no_timesheet__account_move_reversed(self):
timesheet_line = self._create_timesheet()
new_project = self.project.copy({"project_type_id": False})
new_project = self.project.copy({"type_id": False})
new_task = self.task.copy({"project_id": new_project.id})

wip_line = self._get_wip_move_line(timesheet_line)
timesheet_line.sudo(self.timesheet_user).write(
timesheet_line.with_user(self.timesheet_user).write(
{"project_id": new_project.id, "task_id": new_task.id}
)
assert wip_line.reconciled

def test_timesheet_amount_can_be_changed_twice(self):
timesheet_line = self._create_timesheet()
expected_amount = 25
timesheet_line.sudo(self.timesheet_user).amount = -20
timesheet_line.sudo(self.timesheet_user).amount = -expected_amount
timesheet_line.with_user(self.timesheet_user).amount = -20
timesheet_line.with_user(self.timesheet_user).amount = -expected_amount
wip_line = self._get_wip_move_line(timesheet_line)
assert wip_line.debit == expected_amount

Expand All @@ -288,7 +291,7 @@ def test_move_ref_contains_task_id(self):
def test_after_change_task_on_timesheet__move_ref_contains_task_id(self):
timesheet_line = self._create_timesheet()
new_task = self.task.copy()
timesheet_line.sudo(self.timesheet_user).task_id = new_task
timesheet_line.with_user(self.timesheet_user).task_id = new_task
assert str(new_task.id) in timesheet_line.salary_account_move_id.ref

def test_move_ref_contains_project_name(self):
Expand All @@ -297,11 +300,13 @@ def test_move_ref_contains_project_name(self):

def test_after_change_project_on_timesheet__move_ref_contains_project_name(self):
timesheet_line = self._create_timesheet()
new_project = self.project.copy()
self.assertIsNotNone(timesheet_line.salary_account_move_id.ref, msg=None)
new_project = self.project.copy({})
new_task = self.task.copy({"project_id": new_project.id})
timesheet_line.sudo(self.timesheet_user).write(
timesheet_line.with_user(self.timesheet_user).write(
{"project_id": new_project.id, "task_id": new_task.id}
)

assert new_project.name in timesheet_line.salary_account_move_id.ref

def test_if_zero_hour__no_entry_created(self):
Expand All @@ -316,24 +321,25 @@ class TestTimesheetEntryTransferedToWip(WIPJournalEntriesCase):
def setUpClass(cls):
super().setUpClass()
cls.timesheet_line = cls._create_timesheet()
cls.project.sudo().action_wip_to_cgs()
cls.project.with_user(cls.manager).action_wip_to_cgs()

def test_timesheet_amount_can_not_be_changed(self):
with pytest.raises(ValidationError):
self.timesheet_line.sudo(self.timesheet_user).amount = -100
self.timesheet_line.with_user(self.timesheet_user).amount = -100

def test_timesheet_quantity_can_not_be_changed(self):
with pytest.raises(ValidationError):
self.timesheet_line.sudo(self.timesheet_user).unit_amount = 10
self.timesheet_line.with_user(self.timesheet_user).unit_amount = 10

def test_project_with_no_type_can_not_be_set(self):
new_project = self.project.copy({"project_type_id": False})
new_project = self.project.with_user(self.manager).copy()
new_project.type_id = False
new_task = self.task.copy({"project_id": new_project.id})
with pytest.raises(ValidationError):
self.timesheet_line.sudo(self.timesheet_user).write(
self.timesheet_line.with_user(self.timesheet_user).write(
{"project_id": new_project.id, "task_id": new_task.id}
)

def test_timesheet_can_not_be_deleted(self):
with pytest.raises(ValidationError):
self.timesheet_line.sudo(self.timesheet_user).unlink()
self.timesheet_line.with_user(self.timesheet_user).unlink()

0 comments on commit 6085c3f

Please sign in to comment.