-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
666d5ee
commit bc8394c
Showing
70 changed files
with
3,679 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import models |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Copyright 2024 Onestein (<http://www.onestein.eu>) | ||
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). | ||
{ | ||
"name": "Hr Expense Fleet", | ||
"version": "16.0.1.0.0", | ||
"category": "Human Resources/Expenses", | ||
"license": "LGPL-3", | ||
"summary": "Allows to create expenses for fleet", | ||
"depends": [ | ||
"hr_expense", | ||
"hr_fleet", | ||
"product_analytic" | ||
], | ||
"data": [ | ||
"data/hr_expense_data.xml", | ||
"views/fleet_vehicle_odometer_view.xml", | ||
"views/fleet_vehicle_view.xml", | ||
"views/hr_expense_view.xml", | ||
"views/product_view.xml", | ||
], | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
<?xml version="1.0" encoding="utf-8"?> | ||
<odoo> | ||
<data noupdate="1"> | ||
<record id="hr_expense.expense_product_mileage" model="product.product"> | ||
<field name="can_be_used_for_fleet" eval="True"/> | ||
<field name="uom_id" ref="uom.product_uom_km"/> | ||
</record> | ||
</data> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from . import fleet_vehicle | ||
from . import fleet_vehicle_odometer | ||
from . import hr_expense | ||
from . import product_template |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class FleetVehicle(models.Model): | ||
_inherit = "fleet.vehicle" | ||
|
||
product_id = fields.Many2one("product.product", string="Expense Category", | ||
domain="[('can_be_expensed', '=', True),('can_be_used_for_fleet', '=', True)]", | ||
help="Defines default expense category for this vehicle's trips") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class FleetVehicleOdometer(models.Model): | ||
_inherit = "fleet.vehicle.odometer" | ||
|
||
partner_id = fields.Many2one("res.partner", "Contact Visited", | ||
help="Defines the contact visited for the trip if any") | ||
from_address = fields.Text("From", help="Defines the from address for the trip") | ||
to_address = fields.Text("To", help="Defines the to address for the trip") | ||
distance = fields.Float("Distance For Single Way Trip", help="Defines the distance covered for the trip") | ||
is_roundtrip = fields.Boolean("Is Roundtrip", help="Defines whether it is a round trip or not") | ||
total_distance = fields.Float("Total Distance", compute="_compute_total_distance", | ||
help="Defines the total distance covered including round trip") | ||
is_private_trip = fields.Boolean("Is Private Trip", help="Defines whether it is a private trip or not") | ||
value = fields.Float("Odometer End Value", group_operator="max", copy=False, ) | ||
start_value = fields.Float("Odometer Start Value", group_operator="max", copy=False, ) | ||
expense_id = fields.Many2one("hr.expense", "Expense", help="Defines expense record for this trip", copy=False) | ||
product_id = fields.Many2one("product.product", string="Expense Category", | ||
domain="[('can_be_expensed', '=', True),('can_be_used_for_fleet', '=', True)]", | ||
help="Defines expense category for this trip") | ||
status = fields.Selection( | ||
[("not_to_expense", "Not To Expense"), ("to_expense", "To Expense"), ("expense_created", "Expense Created")], | ||
"Status", help="Defines expense status for this trip", default="to_expense", copy=False) | ||
|
||
@api.depends("distance", "is_roundtrip") | ||
def _compute_total_distance(self): | ||
for rec in self: | ||
rec.total_distance = rec.distance * 2 if rec.is_roundtrip else rec.distance | ||
|
||
@api.onchange("is_private_trip") | ||
def _onchange_is_private_trip(self): | ||
if self.is_private_trip: | ||
self.status = "not_to_expense" | ||
else: | ||
self.status = "to_expense" | ||
|
||
@api.onchange("vehicle_id") | ||
def _onchange_vehicle(self): | ||
super()._onchange_vehicle() | ||
if self.vehicle_id: | ||
self.product_id = self.vehicle_id.product_id | ||
|
||
def action_create_expense(self): | ||
hr_expense_obj = self.env["hr.expense"] | ||
product_uom_km = self.env.ref("uom.product_uom_km") | ||
product_uom_mi = self.env.ref("uom.product_uom_mile") | ||
to_expense_odometers = self.filtered(lambda o: o.status == "to_expense" and ( | ||
o.driver_employee_id == self.env.user.employee_id or not o.driver_employee_id)) | ||
products = to_expense_odometers.mapped("product_id") | ||
for product in products: | ||
odometers = to_expense_odometers.filtered(lambda o: o.product_id == product) | ||
product_uom = product.uom_id | ||
if product_uom == product_uom_km: | ||
product_unit = "kilometers" | ||
odometer_uom_to_convert = product_uom_mi | ||
else: | ||
product_unit = "miles" | ||
odometer_uom_to_convert = product_uom_km | ||
total_distance = sum( | ||
line.total_distance for line in odometers.filtered(lambda ol: ol.unit == product_unit)) | ||
for odometer in odometers.filtered(lambda ol: ol.unit != product_unit): | ||
total_distance += odometer_uom_to_convert._compute_quantity(odometer.total_distance, product_uom) | ||
ana_accounts = product.product_tmpl_id._get_product_analytic_accounts() | ||
ana_account = ana_accounts["expense"] | ||
hr_expense_rec = hr_expense_obj.create({ | ||
"product_id": product.id, | ||
"quantity": total_distance, | ||
"analytic_distribution": ( | ||
{ana_account.id: 100} if ana_account else False | ||
) | ||
}) | ||
odometers.write({"expense_id": hr_expense_rec.id, "status": "expense_created"}) | ||
return True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class Expense(models.Model): | ||
_inherit = "hr.expense" | ||
|
||
fleet_vehicle_odometer_ids = fields.One2many("fleet.vehicle.odometer", "expense_id", "OdoMeters", copy=False) | ||
odometer_count = fields.Integer(compute="_compute_odometer_count", string="Odometer") | ||
|
||
@api.depends("fleet_vehicle_odometer_ids") | ||
def _compute_odometer_count(self): | ||
for rec in self: | ||
rec.odometer_count = len(rec.fleet_vehicle_odometer_ids) | ||
|
||
def open_odometer(self): | ||
self.ensure_one() | ||
return { | ||
"type": "ir.actions.act_window", | ||
"name": "Odometers", | ||
"res_model": "fleet.vehicle.odometer", | ||
"view_mode":"tree,kanban,form,graph", | ||
"domain": [("id", "in", self.fleet_vehicle_odometer_ids.ids)], | ||
"context": {"create": False, "edit": False}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
from odoo import api, fields, models | ||
|
||
|
||
class ProductProduct(models.Model): | ||
_inherit = "product.template" | ||
|
||
can_be_used_for_fleet = fields.Boolean("Can Be Used For Fleet", | ||
help="Helps to define whether this product is to be used for fleet or not") | ||
uom_id_domain = fields.Binary(string="UOM Domain", | ||
help="Dynamic domain used for the uom that can be set on product to be used for fleet", | ||
compute="_compute_uom_id_domain") | ||
|
||
@api.depends("can_be_used_for_fleet") | ||
def _compute_uom_id_domain(self): | ||
ids = [self.env.ref("uom.product_uom_km").id, self.env.ref("uom.product_uom_mile").id] | ||
for rec in self: | ||
rec.uom_id_domain = [("id", "in", ids)] if rec.can_be_used_for_fleet else [] | ||
|
||
@api.onchange("can_be_used_for_fleet") | ||
def _onchange_can_be_used_for_fleet(self): | ||
if self.can_be_used_for_fleet: | ||
if self.uom_id not in [self.env.ref("uom.product_uom_km"), self.env.ref("uom.product_uom_mile")]: | ||
self.uom_id = self.env.ref("uom.product_uom_km").id |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
from . import test_hr_expense_fleet |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
# -*- coding: utf-8 -*- | ||
from odoo.tests import common | ||
|
||
|
||
class TestHrExpenseFleet(common.SingleTransactionCase): | ||
@classmethod | ||
def setUpClass(cls): | ||
super().setUpClass() | ||
cls.product_mileage = cls.env.ref("hr_expense.expense_product_mileage") | ||
cls.product_expense = cls.env["product.product"].create({ | ||
"name": "Travel", | ||
"default_code": "EXP_TRA", | ||
"standard_price": 0.93, | ||
"can_be_expensed": True, | ||
}) | ||
cls.env.user.groups_id += cls.env.ref("uom.group_uom") | ||
cls.env.user.action_create_employee() | ||
brand = cls.env["fleet.vehicle.model.brand"].create({ | ||
"name": "Audi", | ||
}) | ||
model = cls.env["fleet.vehicle.model"].create({ | ||
"brand_id": brand.id, | ||
"name": "A3", | ||
}) | ||
cls.vehicle_with_km_odometer = cls.env["fleet.vehicle"].create({ | ||
"model_id": model.id, | ||
"driver_id": cls.env.user.partner_id.id, | ||
"product_id": cls.product_mileage.id | ||
}) | ||
model = cls.env["fleet.vehicle.model"].create({ | ||
"brand_id": brand.id, | ||
"name": "A8", | ||
}) | ||
cls.vehicle_with_mi_odometer = cls.env["fleet.vehicle"].create({ | ||
"model_id": model.id, | ||
"driver_id": cls.env.user.partner_id.id, | ||
}) | ||
|
||
def test_01_onchange_product_can_be_used_for_fleet(self): | ||
self.assertEqual(self.product_expense.uom_id, self.env.ref("uom.product_uom_unit")) | ||
self.assertEqual(self.product_expense.uom_id_domain, []) | ||
self.product_expense.can_be_used_for_fleet = True | ||
self.product_expense.product_tmpl_id._onchange_can_be_used_for_fleet() | ||
uom_km = self.env.ref("uom.product_uom_km") | ||
uom_mile = self.env.ref("uom.product_uom_mile") | ||
self.assertEqual(self.product_expense.uom_id, uom_km) | ||
self.assertEqual(self.product_expense.uom_id_domain, [("id", "in", [uom_km.id, uom_mile.id])]) | ||
self.product_expense.uom_id = self.env.ref("uom.product_uom_mile").id | ||
|
||
def test_01_onchange_odometer(self): | ||
self.vehicle_with_mi_odometer.product_id = self.product_expense.id | ||
self.odometer_in_mi = self.env["fleet.vehicle.odometer"].create( | ||
{"vehicle_id": self.vehicle_with_mi_odometer.id, | ||
"from_address": "Breda", "to_address": "Tilburg", "distance": 10.0, "is_roundtrip": True}) | ||
self.assertEqual(self.odometer_in_mi.total_distance, 20.0) | ||
self.odometer_in_mi._onchange_vehicle() | ||
self.assertEqual(self.odometer_in_mi.product_id, self.vehicle_with_mi_odometer.product_id) | ||
self.odometer_in_mi.is_private_trip = True | ||
self.odometer_in_mi._onchange_is_private_trip() | ||
self.assertEqual(self.odometer_in_mi.status, "not_to_expense") | ||
self.odometer_in_mi.is_private_trip = False | ||
self.odometer_in_mi._onchange_is_private_trip() | ||
self.assertEqual(self.odometer_in_mi.status, "to_expense") | ||
|
||
def test_action_create_expense(self): | ||
self.odometer_in_km = self.env["fleet.vehicle.odometer"].create( | ||
{"vehicle_id": self.vehicle_with_km_odometer.id, | ||
"from_address": "Breda", "to_address": "Tilburg", "distance": 10.0}) | ||
self.odometer_in_km._onchange_vehicle() | ||
self.odometer_in_mi = self.env["fleet.vehicle.odometer"].create( | ||
{"vehicle_id": self.vehicle_with_mi_odometer.id, | ||
"from_address": "Breda", "to_address": "Tilburg", "distance": 10.0, "is_roundtrip": True}) | ||
self.odometer_in_mi._onchange_vehicle() | ||
(self.odometer_in_km + self.odometer_in_mi).action_create_expense() | ||
self.assertEqual(self.odometer_in_mi.status, "expense_created") | ||
self.assertEqual(self.odometer_in_mi.expense_id.quantity, 12.43) | ||
self.assertEqual(self.odometer_in_km.expense_id.quantity, 10.0) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- | ||
Copyright 2024 Onestein (<https://www.onestein.eu>) | ||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
--> | ||
<odoo> | ||
<record id="fleet_vehicle_odometer_view_form" model="ir.ui.view"> | ||
<field name="name">fleet.vehicle.odometer.form</field> | ||
<field name="model">fleet.vehicle.odometer</field> | ||
<field name="inherit_id" ref="fleet.fleet_vehicle_odometer_view_form"/> | ||
<field name="arch" type="xml"> | ||
<xpath expr="//form/sheet" position="before"> | ||
<header> | ||
<field name="status" widget="statusbar"/> | ||
</header> | ||
</xpath> | ||
<xpath expr="//label[@for='value']" position="replace"/> | ||
<xpath expr="//div[hasclass('o_row')]" position="replace"/> | ||
<xpath expr="//sheet/group" position="inside"> | ||
<group> | ||
<field name="is_roundtrip" attrs="{'readonly':[('status','=','expense_created')]}"/> | ||
<field name="total_distance"/> | ||
<field name="is_private_trip" attrs="{'readonly':[('status','=','expense_created')]}"/> | ||
<field name="product_id" | ||
attrs="{'required':[('is_private_trip','=',False)],'readonly':[('status','=','expense_created')]}" | ||
options="{'no_create': True, 'no_open': True}"/> | ||
<xpath expr="//label[@for='value']" position="move"/> | ||
<xpath expr="//div[hasclass('o_row')]" position="move"/> | ||
<label for="start_value"/> | ||
<div class="o_row"> | ||
<field name="start_value" class="oe_inline" | ||
attrs="{'readonly':[('status','=','expense_created')]}"/> | ||
<field name="unit" class="ms-2"/> | ||
</div> | ||
<label for="value"/> | ||
<div class="o_row"> | ||
<field name="value" class="oe_inline"/> | ||
<field name="unit" class="ms-2"/> | ||
</div> | ||
<field name="expense_id" readonly="True"/> | ||
</group> | ||
</xpath> | ||
<field name="vehicle_id" position="after"> | ||
<xpath expr="//field[@name='date']" position="move"/> | ||
<field name="partner_id"/> | ||
<field name="from_address" | ||
attrs="{'required':[('is_private_trip','=',False)],'readonly':[('status','=','expense_created')]}"/> | ||
<field name="to_address" | ||
attrs="{'required':[('is_private_trip','=',False)],'readonly':[('status','=','expense_created')]}"/> | ||
<field name="distance" | ||
attrs="{'required':[('is_private_trip','=',False)],'readonly':[('status','=','expense_created')]}"/> | ||
</field> | ||
<field name="date" position="attributes"> | ||
<attribute name="attrs">{'readonly':[('status','=','expense_created')]}</attribute> | ||
</field> | ||
<field name="value" position="attributes"> | ||
<attribute name="attrs">{'readonly':[('status','=','expense_created')]}</attribute> | ||
</field> | ||
<field name="vehicle_id" position="attributes"> | ||
<attribute name="attrs">{'readonly':[('status','=','expense_created')]}</attribute> | ||
</field> | ||
</field> | ||
</record> | ||
|
||
<record id="fleet_vehicle_odometer_view_tree" model="ir.ui.view"> | ||
<field name="name">fleet.vehicle.odometer.tree</field> | ||
<field name="model">fleet.vehicle.odometer</field> | ||
<field name="inherit_id" ref="fleet.fleet_vehicle_odometer_view_tree"/> | ||
<field name="arch" type="xml"> | ||
<xpath expr="//tree" position="attributes"> | ||
<attribute name="editable"/> | ||
</xpath> | ||
<field name="value" position="attributes"> | ||
<attribute name="invisible">True</attribute> | ||
</field> | ||
<field name="unit" position="attributes"> | ||
<attribute name="invisible">True</attribute> | ||
</field> | ||
<field name="date" position="after"> | ||
<field name="partner_id"/> | ||
<field name="total_distance"/> | ||
<field name="product_id" attrs="{'required':[('is_private_trip','=',False)],'readonly':[('status','=','expense_created')]}" options="{'no_create': True, 'no_open': True}"/> | ||
<field name="status" readonly="True"/> | ||
<xpath expr="//field[@name='vehicle_id']" position="move"/> | ||
<xpath expr="//field[@name='driver_id']" position="move"/> | ||
<field name="is_private_trip" optional="hide"/> | ||
<field name="expense_id" optional="hide"/> | ||
</field> | ||
<field name="vehicle_id" position="attributes"> | ||
<attribute name="optional">hide</attribute> | ||
</field> | ||
<field name="driver_id" position="attributes"> | ||
<attribute name="optional">hide</attribute> | ||
</field> | ||
</field> | ||
</record> | ||
|
||
<record id="action_create_expense" model="ir.actions.server"> | ||
<field name="name">Create Expense From Odometer</field> | ||
<field name="model_id" ref="fleet.model_fleet_vehicle_odometer"/> | ||
<field name="binding_model_id" ref="fleet.model_fleet_vehicle_odometer"/> | ||
<field name="binding_view_types">list,form</field> | ||
<field name="state">code</field> | ||
<field name="code"> | ||
records.action_create_expense() | ||
</field> | ||
</record> | ||
</odoo> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
<?xml version="1.0" encoding="utf-8" ?> | ||
<!-- | ||
Copyright 2024 Onestein (<https://www.onestein.eu>) | ||
License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl). | ||
--> | ||
<odoo> | ||
<record id="fleet_vehicle_view_form" model="ir.ui.view"> | ||
<field name="name">fleet.vehicle.form</field> | ||
<field name="model">fleet.vehicle</field> | ||
<field name="inherit_id" ref="fleet.fleet_vehicle_view_form"/> | ||
<field name="arch" type="xml"> | ||
<field name="category_id" position="after"> | ||
<field name="product_id" options="{'no_create': True, 'no_open': True}"/> | ||
</field> | ||
</field> | ||
</record> | ||
</odoo> |
Oops, something went wrong.