Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions hr_expense_trip/README.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
==============
HR Expense Trip
==============

.. |badge1| image:: https://img.shields.io/badge/maturity-Beta-yellow.png
:target: https://odoo-community.org/page/development-status
:alt: Beta
.. |badge2| image:: https://img.shields.io/badge/licence-AGPL--3-blue.png
:target: http://www.gnu.org/licenses/agpl-3.0-standalone.html
:alt: License: AGPL-3
.. |badge3| image:: https://img.shields.io/badge/github-OCA%2Fhr--expense-lightgray.png?logo=github
:target: https://github.com/OCA/hr-expense/tree/19.0/hr_expense_trip
:alt: OCA/hr-expense

|badge1| |badge2| |badge3|

This module allows you to group expenses under business trips.
You can create a trip with a start date and end date, then attach expenses
to it. The expense date column in the trip form is colour-coded:

- **Green** – expense date is *before* the trip start date.
- **Yellow** – expense date is *after* the trip end date.

**Table of contents**

.. contents::
:local:

Usage
=====

#. Go to *Expenses → My Trips* to create and manage your own trips.
#. Go to *Expenses → All Trips* (expense managers only) to see all trips.
#. Open a trip and link expenses to it, or set the *Trip* field directly
on an expense form.

Bug Tracker
===========

Bugs are tracked on `GitHub Issues <https://github.com/OCA/hr-expense/issues>`_.
In case of trouble, please check there if your issue has already been reported.
If you spotted it first, help us smash it by providing a detailed and welcomed
`feedback <https://github.com/OCA/hr-expense/issues/new?body=module:%20hr_expense_trip%0Aversion:%2019.0%0A%0A**Steps%20to%20reproduce**%0A-%20...%0A%0A**Current%20behavior**%0A%0A**Expected%20behavior**>`_.

Credits
=======

Authors
-------

* Odoo Community Association (OCA)

Maintainers
-----------

This module is maintained by the OCA.

.. image:: https://odoo-community.org/logo.png
:alt: Odoo Community Association
:target: https://odoo-community.org

OCA, or the Odoo Community Association, is a nonprofit organization whose
mission is to support the collaborative development of Odoo features and
promote its widespread use.

This module is part of the `OCA/hr-expense <https://github.com/OCA/hr-expense/tree/19.0/hr_expense_trip>`_ project on GitHub.

You are welcome to contribute. To learn how please visit https://odoo-community.org/page/Contribute.
3 changes: 3 additions & 0 deletions hr_expense_trip/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from . import models
25 changes: 25 additions & 0 deletions hr_expense_trip/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Copyright 2024 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

{
"name": "HR Expense Trip",
"version": "19.0.1.0.0",
"category": "Human Resources",
"author": "Odoo Community Association (OCA)",
"license": "LGPL-3",
"website": "https://github.com/OCA/hr-expense",
"depends": ["hr_expense", "mail"],
"data": [
"security/ir.model.access.csv",
"views/hr_trip_views.xml",
"views/hr_expense_views.xml",
"views/report_hr_trip.xml",
"views/res_config_settings_views.xml",
],
"installable": True,
"assets": {
"web.assets_backend": [
"hr_expense_trip/static/src/views/*.js",
],
},
}
5 changes: 5 additions & 0 deletions hr_expense_trip/models/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from . import hr_expense
from . import hr_trip
from . import res_config_settings
55 changes: 55 additions & 0 deletions hr_expense_trip/models/hr_expense.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Copyright 2024 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import fields, models


class HrExpense(models.Model):
_inherit = "hr.expense"

trip_id = fields.Many2one(
comodel_name="hr.trip",
string="Trip",
ondelete="set null",
index=True,
)
# Related fields used by decoration-success / decoration-warning in the trip
# form's expense list to flag dates outside the trip date range.
trip_start_date = fields.Date(
related="trip_id.start_date",
store=False,
)
trip_end_date = fields.Date(
related="trip_id.end_date",
store=False,
)

def action_add_existing_expenses(self):
self.ensure_one()
trip_id = self.env.context.get("default_trip_id")
return {
"type": "ir.actions.act_window",
"name": self.env._("Add: Expenses"),
"res_model": "hr.trip",
"res_id": trip_id,
"view_mode": "form",
"target": "new",
}

def default_get(self, fields_list):
defaults = super().default_get(fields_list)
if "trip_id" in fields_list and not defaults.get("trip_id"):
expense_date = defaults.get("date")
employee_id = defaults.get("employee_id")
if expense_date and employee_id:
trip = self.env["hr.trip"].search(
[
("employee_id", "=", employee_id),
("start_date", "<=", expense_date),
("end_date", ">=", expense_date),
],
limit=1,
)
if trip:
defaults["trip_id"] = trip.id
return defaults
103 changes: 103 additions & 0 deletions hr_expense_trip/models/hr_trip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Copyright 2024 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

import base64

from odoo import api, fields, models
from odoo.exceptions import ValidationError


class HrTrip(models.Model):
_name = "hr.trip"
_description = "HR Trip"
_order = "start_date desc, name"
_inherit = ["mail.thread.main.attachment", "mail.activity.mixin"]

name = fields.Char(required=True)
start_date = fields.Date(required=True)
end_date = fields.Date(required=True)
reason = fields.Text()
partner_id = fields.Many2one(
comodel_name="res.partner",
)
employee_id = fields.Many2one(
comodel_name="hr.employee",
default=lambda self: self.env.user.employee_id,
)
expense_ids = fields.One2many(
comodel_name="hr.expense",
inverse_name="trip_id",
domain="[('employee_id', '=', employee_id), ('trip_id', '=', False)]",
)
state = fields.Selection(
selection=[
("draft", "Draft"),
("request", "Requested"),
("receipts", "Collect Receipts"),
("done", "Done"),
],
default="draft",
tracking=True,
)

def action_print_trip(self):
self.ensure_one()
return self.env.ref("hr_expense_trip.action_report_hr_trip").report_action(self)

def action_request_approval(self):
self.ensure_one()
auto_approve = (
self.env["ir.config_parameter"]
.sudo()
.get_param("hr_expense_trip.auto_approve", default="True")
)
if auto_approve == "True":
self.state = "receipts"
else:
self.state = "request"
manager_employee = self.employee_id.parent_id
manager = manager_employee.user_id if manager_employee else False
if manager:
activity_type = self.env.ref("mail.mail_activity_data_todo")
self.activity_schedule(
activity_type_id=activity_type.id,
summary=self.env._("Trip Approval Request"),
user_id=manager.id,
)

def action_approve(self):
self.ensure_one()
self.state = "receipts"

def action_done(self):
self.ensure_one()
self.state = "done"
self._attach_trip_report()

def _attach_trip_report(self):
self.ensure_one()
report = self.env.ref("hr_expense_trip.action_report_hr_trip")
pdf_content, _mime = report._render_qweb_pdf(self.ids)
attachment = self.env["ir.attachment"].create(
{
"name": self.env._("%s - Trip Report.pdf", self.name),
"type": "binary",
"datas": base64.b64encode(pdf_content),
"res_model": self._name,
"res_id": self.id,
"mimetype": "application/pdf",
}
)
self.message_post(attachment_ids=[attachment.id])

@api.constrains("start_date", "end_date")
def _check_date_range(self):
for rec in self:
if rec.start_date and rec.end_date and rec.end_date < rec.start_date:
raise ValidationError(
self.env._(
"End date (%(end)s) must not be before start date (%(start)s).",
end=rec.end_date,
start=rec.start_date,
)
)
22 changes: 22 additions & 0 deletions hr_expense_trip/models/res_config_settings.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# Copyright 2024 Odoo Community Association (OCA)
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).

from odoo import api, fields, models


class ResConfigSettings(models.TransientModel):
_inherit = "res.config.settings"

hr_trip_auto_approve = fields.Boolean(
string="Auto Approve Trip Requests",
config_parameter="hr_expense_trip.auto_approve",
)

@api.model
def get_values(self):
res = super().get_values()
params = self.env["ir.config_parameter"].sudo()
value = params.get_param("hr_expense_trip.auto_approve")
# Default to True when the parameter has never been set
res["hr_trip_auto_approve"] = value != "False" if value else True
return res
3 changes: 3 additions & 0 deletions hr_expense_trip/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["whool"]
build-backend = "whool.buildapi"
3 changes: 3 additions & 0 deletions hr_expense_trip/security/ir.model.access.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_hr_trip_user,hr.trip user,model_hr_trip,hr_expense.group_hr_expense_user,1,0,1,0
access_hr_trip_manager,hr.trip manager,model_hr_trip,hr_expense.group_hr_expense_manager,1,1,1,1
Loading
Loading