business travel expenses
This commit is contained in:
parent
064bd90c58
commit
12ec001b38
|
|
@ -0,0 +1,2 @@
|
|||
from . import models
|
||||
from . import wizard
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
{
|
||||
'name': 'Business Travel & Expense Management',
|
||||
'version': '1.0',
|
||||
'summary': 'Enterprise Business Travel & Expense Management',
|
||||
'description': """
|
||||
Business Travel (Trips) & Expense Management Module.
|
||||
- Pre-approved Trips
|
||||
- Trip lifecycle management
|
||||
- Expense tracking per Trip
|
||||
- Manager & Finance approvals
|
||||
- Reimbursement workflow
|
||||
""",
|
||||
'category': 'Human Resources',
|
||||
'author': 'Karuna',
|
||||
'depends': ['base', 'hr'],
|
||||
'data': [
|
||||
'security/travel_groups.xml',
|
||||
'security/travel_trip_rules.xml',
|
||||
'security/ir.model.access.csv',
|
||||
# 'data/users.xml',
|
||||
'data/trip_sequence.xml',
|
||||
'wizard/trip_reject_wizard_view.xml',
|
||||
'views/hr_job_view.xml', # 👈 hr extension BEFORE menus
|
||||
'views/travel_trip_views.xml',
|
||||
'views/travel_city_category_views.xml',
|
||||
'views/travel_group_view.xml',
|
||||
'views/travel_stay_policy_view.xml', # 👈 ADD HERE
|
||||
'views/travel_daily_allowance_view.xml',
|
||||
'views/travel_mode_policy_view.xml',
|
||||
'views/travel_expense_views.xml',
|
||||
'views/travel_activity_views.xml',
|
||||
'views/travel_menu.xml',
|
||||
|
||||
],
|
||||
'images': ['static/description/banner.png'],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="seq_travel_trip" model="ir.sequence">
|
||||
<field name="name">Travel Trip</field>
|
||||
<field name="code">travel.trip</field>
|
||||
<field name="prefix">TRIP/%(year)s/</field>
|
||||
<field name="padding">4</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<!--<odoo>-->
|
||||
<!-- <record id="user_travel_finance" model="res.users">-->
|
||||
<!-- <field name="name">Finance User</field>-->
|
||||
<!-- <field name="login">finance@test.com</field>-->
|
||||
<!-- <field name="email">finance@test.com</field>-->
|
||||
|
||||
<!-- <!– Attach Travel - Finance group –>-->
|
||||
<!-- <field name="groups_id" eval="[(4, ref('business_travel_expense_management.group_travel_finance'))]"/>-->
|
||||
<!-- </record>-->
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
from . import travel_trip
|
||||
from . import travel_expense
|
||||
from . import travel_activity
|
||||
from . import travel_city_category
|
||||
from . import travel_group
|
||||
from . import hr_job
|
||||
from . import travel_stay_policy
|
||||
from . import travel_daily_allowance
|
||||
from . import travel_mode_policy
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
class HrJob(models.Model):
|
||||
_inherit = 'hr.job'
|
||||
|
||||
designation_level = fields.Selection([
|
||||
('a', 'Level A'),
|
||||
('b', 'Level B'),
|
||||
('c', 'Level C'),
|
||||
], string="Designation Level")
|
||||
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
compute='_compute_travel_group',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
@api.depends('designation_level')
|
||||
def _compute_travel_group(self):
|
||||
for rec in self:
|
||||
if rec.designation_level:
|
||||
group = self.env['travel.group'].search([
|
||||
('level_code', '=', rec.designation_level)
|
||||
], limit=1)
|
||||
rec.travel_group_id = group.id
|
||||
else:
|
||||
rec.travel_group_id = False
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
from odoo import models, fields, api
|
||||
|
||||
|
||||
class TravelActivity(models.Model):
|
||||
_name = 'travel.activity'
|
||||
_description = 'Travel Activity'
|
||||
_order = 'sequence, id'
|
||||
|
||||
# --------------------
|
||||
# BASIC
|
||||
# --------------------
|
||||
sequence = fields.Integer(default=10)
|
||||
name = fields.Char(string="Activity Title", required=True)
|
||||
|
||||
trip_id = fields.Many2one(
|
||||
'travel.trip',
|
||||
required=True,
|
||||
ondelete='cascade'
|
||||
)
|
||||
|
||||
activity_type = fields.Selection([
|
||||
('travel', 'Travel'),
|
||||
('stay', 'Stay'),
|
||||
('meeting', 'Meeting'),
|
||||
('local', 'Local Travel'),
|
||||
], required=True)
|
||||
|
||||
start_datetime = fields.Datetime("Start Time")
|
||||
end_datetime = fields.Datetime("End Time")
|
||||
|
||||
# --------------------
|
||||
# COMPUTED GROUP (VERY IMPORTANT)
|
||||
# --------------------
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
string="Travel Group",
|
||||
compute="_compute_travel_group",
|
||||
store=True
|
||||
)
|
||||
|
||||
@api.depends('trip_id')
|
||||
def _compute_travel_group(self):
|
||||
for rec in self:
|
||||
rec.travel_group_id = rec.trip_id.travel_group_id
|
||||
|
||||
# --------------------
|
||||
# TRAVEL MODE (FILTERED BY GROUP)
|
||||
# --------------------
|
||||
travel_mode_policy_id = fields.Many2one(
|
||||
'travel.mode.policy',
|
||||
string="Travel Mode",
|
||||
domain="""
|
||||
[
|
||||
('travel_group_id', '=', travel_group_id),
|
||||
('mode_type', '=', activity_type),
|
||||
('active', '=', True)
|
||||
]
|
||||
"""
|
||||
)
|
||||
|
||||
from_location = fields.Char()
|
||||
to_location = fields.Char()
|
||||
travel_details = fields.Char()
|
||||
|
||||
# --------------------
|
||||
# OTHER FIELDS
|
||||
# --------------------
|
||||
stay_type = fields.Selection([
|
||||
('hotel', 'Hotel'),
|
||||
('guest', 'Guest House'),
|
||||
])
|
||||
|
||||
hotel_name = fields.Char()
|
||||
city = fields.Char()
|
||||
# city_category_id = fields.Many2one(
|
||||
# 'travel.city.category',
|
||||
# string="City Category"
|
||||
# )
|
||||
checkin = fields.Datetime()
|
||||
checkout = fields.Datetime()
|
||||
|
||||
meeting_title = fields.Char()
|
||||
meeting_location = fields.Char()
|
||||
notes = fields.Text()
|
||||
|
||||
local_travel_mode = fields.Selection([
|
||||
('cab', 'Cab'),
|
||||
('own', 'Own Vehicle'),
|
||||
], string="Local Travel Mode")
|
||||
|
||||
attachment_ids = fields.Many2many(
|
||||
'ir.attachment',
|
||||
'travel_activity_attachment_rel',
|
||||
'activity_id',
|
||||
'attachment_id',
|
||||
string="Documents"
|
||||
)
|
||||
|
||||
expense_ids = fields.One2many(
|
||||
'travel.expense',
|
||||
'activity_id',
|
||||
string="Expenses"
|
||||
)
|
||||
|
||||
total_amount = fields.Float(
|
||||
string='Activity Total',
|
||||
compute='_compute_total_amount',
|
||||
store=True
|
||||
)
|
||||
|
||||
@api.depends('expense_ids.amount')
|
||||
def _compute_total_amount(self):
|
||||
for rec in self:
|
||||
rec.total_amount = sum(rec.expense_ids.mapped('amount'))
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
from odoo import models, fields
|
||||
|
||||
class TravelCityCategory(models.Model):
|
||||
_name = 'travel.city.category'
|
||||
_description = 'Travel City Category'
|
||||
|
||||
name = fields.Char(
|
||||
string="City Category",
|
||||
required=True
|
||||
)
|
||||
|
||||
code = fields.Char(
|
||||
string="Code",
|
||||
help="Short code like AP_TG_HYD, AP_TG_OTHER, ROI"
|
||||
)
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class TravelDailyAllowance(models.Model):
|
||||
_name = 'travel.daily.allowance'
|
||||
_description = 'Daily Allowance Policy'
|
||||
_rec_name = 'travel_group_id'
|
||||
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
string="Travel Group",
|
||||
required=True
|
||||
)
|
||||
|
||||
city_category_id = fields.Many2one(
|
||||
'travel.city.category',
|
||||
string="City Category",
|
||||
required=True
|
||||
)
|
||||
|
||||
amount = fields.Float(string="Allowance Amount")
|
||||
|
||||
actuals_allowed = fields.Boolean(string="Actuals Allowed")
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,112 @@
|
|||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
from odoo.exceptions import ValidationError
|
||||
|
||||
|
||||
|
||||
class TravelExpense(models.Model):
|
||||
_name = 'travel.expense'
|
||||
_description = 'Travel Expense'
|
||||
_order = 'expense_date desc, id desc'
|
||||
|
||||
name = fields.Char(string="Expense Description", required=True)
|
||||
expense_date = fields.Date(default=fields.Date.today)
|
||||
amount = fields.Monetary(required=True)
|
||||
|
||||
activity_id = fields.Many2one(
|
||||
'travel.activity',
|
||||
string="Activity",
|
||||
required=True,
|
||||
ondelete='cascade'
|
||||
)
|
||||
receipt = fields.Binary()
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency',
|
||||
default=lambda self: self.env.company.currency_id
|
||||
)
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('submitted', 'Submitted'),
|
||||
('approved', 'Approved'),
|
||||
('rejected', 'Rejected'),
|
||||
], default='draft')
|
||||
|
||||
# ---------------- Actions ----------------
|
||||
|
||||
@api.depends('expense_ids.amount')
|
||||
def _compute_total_amount(self):
|
||||
for rec in self:
|
||||
rec.total_amount = sum(rec.expense_ids.mapped('amount'))
|
||||
|
||||
@api.onchange('expense_ids')
|
||||
def _onchange_expense_ids(self):
|
||||
self.total_amount = sum(self.expense_ids.mapped('amount'))
|
||||
|
||||
def action_submit(self):
|
||||
for rec in self:
|
||||
if rec.state != 'draft':
|
||||
raise UserError("Only Draft expenses can be submitted.")
|
||||
rec.state = 'submitted'
|
||||
rec.message_post(body="🟡 Expense submitted.")
|
||||
|
||||
def action_approve(self):
|
||||
for rec in self:
|
||||
if rec.state != 'submitted':
|
||||
raise UserError("Only Submitted expenses can be approved.")
|
||||
|
||||
manager_user = rec.activity_id.trip_id.manager_id.sudo().user_id
|
||||
if manager_user != self.env.user:
|
||||
raise UserError("Only the reporting manager can approve.")
|
||||
|
||||
rec.state = 'approved'
|
||||
rec.message_post(body="🟢 Expense approved.")
|
||||
|
||||
def action_mark_reimbursed(self):
|
||||
for rec in self:
|
||||
if rec.state != 'approved':
|
||||
raise UserError("Only Approved expenses can be reimbursed.")
|
||||
|
||||
if not self.env.user.has_group(
|
||||
'business_travel_expense_management.group_travel_finance'
|
||||
):
|
||||
raise UserError("Only Finance can reimburse.")
|
||||
|
||||
rec.state = 'reimbursed'
|
||||
rec.message_post(body="💰 Expense reimbursed.")
|
||||
|
||||
@api.constrains('amount', 'activity_id')
|
||||
def _check_stay_policy(self):
|
||||
for record in self:
|
||||
|
||||
activity = record.activity_id
|
||||
|
||||
if not activity or activity.activity_type != 'stay':
|
||||
continue
|
||||
|
||||
trip = activity.trip_id
|
||||
group = trip.travel_group_id
|
||||
city_category = trip.city_category_id # 👈 NOW FROM TRIP
|
||||
|
||||
if not group:
|
||||
raise ValidationError("Trip must have a Travel Group.")
|
||||
|
||||
if not city_category:
|
||||
raise ValidationError("Trip must have a City Category selected.")
|
||||
|
||||
policy = self.env['travel.stay.policy'].search([
|
||||
('travel_group_id', '=', group.id),
|
||||
('city_category_id', '=', city_category.id),
|
||||
('active', '=', True)
|
||||
], limit=1)
|
||||
|
||||
if not policy:
|
||||
raise ValidationError(
|
||||
f"No Stay Policy configured for Group '{group.name}' and City '{city_category.name}'."
|
||||
)
|
||||
|
||||
if record.amount > policy.max_amount:
|
||||
raise ValidationError(
|
||||
f"Stay expense exceeds allowed limit of {policy.max_amount}."
|
||||
)
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
from odoo import models, fields
|
||||
|
||||
class TravelGroup(models.Model):
|
||||
_name = 'travel.group'
|
||||
_description = 'Travel Group'
|
||||
|
||||
name = fields.Char(string='Travel Group', required=True)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
job_ids = fields.One2many(
|
||||
'hr.job',
|
||||
'travel_group_id',
|
||||
string="Designations"
|
||||
)
|
||||
|
||||
level_code = fields.Selection([
|
||||
('a', 'Level A'),
|
||||
('b', 'Level B'),
|
||||
('c', 'Level C'),
|
||||
], required=True)
|
||||
|
||||
allowed_travel_mode_ids = fields.Many2many(
|
||||
'travel.mode',
|
||||
string="Allowed Travel Modes"
|
||||
)
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
from odoo import models, fields
|
||||
|
||||
|
||||
class TravelModePolicy(models.Model):
|
||||
_name = 'travel.mode.policy'
|
||||
_description = 'Travel Mode Policy'
|
||||
_rec_name = 'travel_mode' # This makes dropdown show Flight/2AC etc
|
||||
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
string="Travel Group",
|
||||
required=True,
|
||||
ondelete='cascade'
|
||||
)
|
||||
|
||||
mode_type = fields.Selection([
|
||||
('travel', 'Travel'),
|
||||
('local', 'Local Travel'),
|
||||
], string="Mode Type", required=True)
|
||||
|
||||
travel_mode = fields.Selection([
|
||||
('flight', 'Flight'),
|
||||
('2ac', 'II AC'),
|
||||
('3ac', 'III AC'),
|
||||
('1st_class', '1st Class'),
|
||||
('car', 'Car'),
|
||||
('taxi', 'Taxi'),
|
||||
('auto', 'Auto'),
|
||||
], string="Travel Mode", required=True)
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
from odoo import models, fields
|
||||
|
||||
class TravelStayPolicy(models.Model):
|
||||
_name = 'travel.stay.policy'
|
||||
_description = 'Travel Stay Policy'
|
||||
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
string='Travel Group',
|
||||
required=True
|
||||
)
|
||||
city_category_id = fields.Many2one(
|
||||
'travel.city.category',
|
||||
string='City Category',
|
||||
required=True
|
||||
)
|
||||
|
||||
min_amount = fields.Float(string='Min Amount')
|
||||
max_amount = fields.Float(string='Max Amount')
|
||||
|
||||
is_actuals = fields.Boolean(
|
||||
string='Actuals Allowed',
|
||||
help='If checked, actual hotel cost is allowed'
|
||||
)
|
||||
|
||||
active = fields.Boolean(default=True)
|
||||
|
|
@ -0,0 +1,179 @@
|
|||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class TravelTrip(models.Model):
|
||||
_name = 'travel.trip'
|
||||
_description = 'Business Travel Trip'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'id desc'
|
||||
|
||||
name = fields.Char(
|
||||
string='Trip Reference',
|
||||
required=True,
|
||||
copy=False,
|
||||
readonly=True,
|
||||
default='New',
|
||||
tracking=True
|
||||
)
|
||||
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
string='Employee',
|
||||
required=True,
|
||||
tracking=True
|
||||
)
|
||||
|
||||
department_id = fields.Many2one(
|
||||
'hr.department',
|
||||
compute='_compute_emp_details',
|
||||
store=True,
|
||||
readonly=True,
|
||||
tracking=True,
|
||||
compute_sudo=True, # IMPORTANT
|
||||
)
|
||||
|
||||
manager_id = fields.Many2one(
|
||||
'hr.employee',
|
||||
compute='_compute_emp_details',
|
||||
store=True,
|
||||
readonly=True,
|
||||
tracking=True,
|
||||
compute_sudo=True, # IMPORTANT
|
||||
)
|
||||
|
||||
purpose = fields.Text(tracking=True)
|
||||
from_location = fields.Char(tracking=True)
|
||||
to_location = fields.Char(tracking=True)
|
||||
start_date = fields.Date(tracking=True)
|
||||
end_date = fields.Date(tracking=True)
|
||||
estimated_cost = fields.Float(tracking=True)
|
||||
|
||||
reject_reason = fields.Text(string="Reject Reason", tracking=True)
|
||||
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('submitted', 'Submitted'),
|
||||
('approved', 'Approved'),
|
||||
('completed', 'Completed'),
|
||||
('reimbursed', 'Reimbursed'),
|
||||
], default='draft', tracking=True)
|
||||
|
||||
# expense_ids = fields.One2many(
|
||||
# 'travel.expense',
|
||||
# 'trip_id',
|
||||
# string='Expenses'
|
||||
# )
|
||||
|
||||
trave_activity_ids = fields.One2many(
|
||||
'travel.activity', # child model
|
||||
'trip_id', # inverse field in travel.activity
|
||||
string="Activities"
|
||||
)
|
||||
total_expense = fields.Float(
|
||||
string='Activity Total',
|
||||
compute='_compute_total_expense',
|
||||
store=True
|
||||
)
|
||||
|
||||
travel_group_id = fields.Many2one(
|
||||
'travel.group',
|
||||
string='Travel Group',
|
||||
related='employee_id.job_id.travel_group_id',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
city_category_id = fields.Many2one(
|
||||
'travel.city.category',
|
||||
string="City Category",
|
||||
required=True
|
||||
)
|
||||
|
||||
@api.depends('trave_activity_ids.total_amount')
|
||||
def _compute_total_expense(self):
|
||||
for rec in self:
|
||||
rec.total_expense = sum(rec.trave_activity_ids.mapped('total_amount'))
|
||||
|
||||
|
||||
# ---------------- COMPUTE ----------------
|
||||
|
||||
@api.depends('employee_id')
|
||||
def _compute_emp_details(self):
|
||||
for rec in self:
|
||||
if rec.employee_id:
|
||||
emp = rec.employee_id.sudo()
|
||||
rec.department_id = emp.department_id
|
||||
rec.manager_id = emp.parent_id
|
||||
else:
|
||||
rec.department_id = False
|
||||
rec.manager_id = False
|
||||
|
||||
# ---------------- CREATE ----------------
|
||||
|
||||
@api.model
|
||||
def create(self, vals):
|
||||
if vals.get('name', 'New') == 'New':
|
||||
vals['name'] = self.env['ir.sequence'].next_by_code('travel.trip') or 'New'
|
||||
return super().create(vals)
|
||||
|
||||
# ---------------- ACTIONS ----------------
|
||||
|
||||
def action_submit(self):
|
||||
for rec in self:
|
||||
# Submit all DRAFT expenses inside ALL activities
|
||||
activities = rec.trave_activity_ids
|
||||
|
||||
expenses = activities.mapped('expense_ids').filtered(
|
||||
lambda e: e.state == 'draft'
|
||||
)
|
||||
|
||||
expenses.write({'state': 'submitted'})
|
||||
|
||||
rec.state = 'submitted'
|
||||
|
||||
def action_approve(self):
|
||||
for rec in self:
|
||||
if rec.state != 'submitted':
|
||||
raise UserError('Only Submitted trips can be approved.')
|
||||
|
||||
# Only reporting manager or admin can approve
|
||||
manager_user = rec.manager_id.sudo().user_id
|
||||
if not self.env.is_admin() and (not manager_user or manager_user != self.env.user):
|
||||
raise UserError("Only the reporting manager can approve this trip.")
|
||||
|
||||
rec.state = 'approved'
|
||||
|
||||
rec.message_post(
|
||||
body=f"Trip <b>{rec.name}</b> has been approved by {self.env.user.name}.",
|
||||
subtype_xmlid="mail.mt_comment"
|
||||
)
|
||||
|
||||
|
||||
|
||||
def action_mark_completed(self):
|
||||
for rec in self:
|
||||
if rec.state != 'approved':
|
||||
raise UserError('Only Approved trips can be marked as Completed.')
|
||||
|
||||
rec.state = 'completed'
|
||||
|
||||
rec.message_post(
|
||||
body=f"Trip <b>{rec.name}</b> has been marked as Completed.",
|
||||
subtype_xmlid="mail.mt_comment"
|
||||
)
|
||||
|
||||
is_current_user_manager = fields.Boolean(
|
||||
compute="_compute_is_current_user_manager",
|
||||
store=False
|
||||
)
|
||||
|
||||
def _compute_is_current_user_manager(self):
|
||||
for rec in self:
|
||||
rec.is_current_user_manager = (
|
||||
rec.manager_id
|
||||
and rec.manager_id.sudo().user_id
|
||||
and rec.manager_id.sudo().user_id.id == self.env.user.id
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_travel_trip,travel.trip,model_travel_trip,base.group_user,1,1,1,1
|
||||
access_travel_expense,travel.expense,model_travel_expense,base.group_user,1,1,1,1
|
||||
access_trip_reject_wizard,trip.reject.wizard,model_trip_reject_wizard,base.group_user,1,1,1,1
|
||||
access_travel_activity_employee,travel.activity employee,model_travel_activity,base.group_user,1,1,1,1
|
||||
access_travel_activity_user,travel.activity user,model_travel_activity,base.group_user,1,1,1,1
|
||||
access_travel_city_category,travel.city.category,model_travel_city_category,base.group_user,1,1,1,1
|
||||
access_travel_group_user,travel.group user,model_travel_group,base.group_user,1,1,1,1
|
||||
access_travel_stay_policy,travel.stay.policy,model_travel_stay_policy,base.group_user,1,1,1,1
|
||||
access_travel_daily_allowance_user,access_travel_daily_allowance_user,model_travel_daily_allowance,base.group_user,1,1,1,1
|
||||
access_travel_mode_policy_user,access_travel_mode_policy_user,model_travel_mode_policy,base.group_user,1,1,1,1
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
<odoo>
|
||||
<record id="group_travel_employee" model="res.groups">
|
||||
<field name="name">Travel - Employee</field>
|
||||
<field name="category_id" ref="base.module_category_human_resources"/>
|
||||
</record>
|
||||
|
||||
<record id="group_travel_manager" model="res.groups">
|
||||
<field name="name">Travel - Manager</field>
|
||||
<field name="category_id" ref="base.module_category_human_resources"/>
|
||||
<field name="implied_ids" eval="[(4, ref('group_travel_employee'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="group_travel_finance" model="res.groups">
|
||||
<field name="name">Travel - Finance</field>
|
||||
<field name="category_id" ref="base.module_category_human_resources"/>
|
||||
<field name="implied_ids" eval="[(4, ref('group_travel_manager'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
<odoo>
|
||||
|
||||
<!-- Employees: only their own trips -->
|
||||
<record id="travel_trip_rule_employee_own" model="ir.rule">
|
||||
<field name="name">Travel Trip: Employee Own</field>
|
||||
<field name="model_id" ref="model_travel_trip"/>
|
||||
<field name="domain_force">[('employee_id.user_id', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('business_travel_expense_management.group_travel_employee'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- Managers: trips of their team -->
|
||||
<record id="travel_trip_rule_manager_team" model="ir.rule">
|
||||
<field name="name">Travel Trip: Manager Team</field>
|
||||
<field name="model_id" ref="model_travel_trip"/>
|
||||
<field name="domain_force">[('manager_id.user_id', '=', user.id)]</field>
|
||||
<field name="groups" eval="[(4, ref('business_travel_expense_management.group_travel_manager'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- System / Admin: everything -->
|
||||
<record id="travel_trip_rule_admin_all" model="ir.rule">
|
||||
<field name="name">Travel Trip: Admin All</field>
|
||||
<field name="model_id" ref="model_travel_trip"/>
|
||||
<field name="domain_force">[(1,'=',1)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_system'))]"/>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 30 KiB |
|
|
@ -0,0 +1,16 @@
|
|||
<odoo>
|
||||
<record id="view_hr_job_form_inherit_travel_group" model="ir.ui.view">
|
||||
<field name="name">hr.job.form.inherit.travel.group</field>
|
||||
<field name="model">hr.job</field>
|
||||
<field name="inherit_id" ref="hr.view_hr_job_form"/>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<!-- Insert Travel Group below Department -->
|
||||
<xpath expr="//field[@name='department_id']" position="after">
|
||||
<field name="designation_level"/>
|
||||
<field name="travel_group_id" readonly="1"/>
|
||||
</xpath>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_travel_activity_form" model="ir.ui.view">
|
||||
<field name="name">travel.activity.form</field>
|
||||
<field name="model">travel.activity</field>
|
||||
<field name="arch" type="xml">
|
||||
|
||||
<form string="Activity">
|
||||
|
||||
<sheet>
|
||||
|
||||
<!-- TITLE -->
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="name" placeholder="Activity title"/>
|
||||
</h1>
|
||||
</div>
|
||||
|
||||
<!-- ACTIVITY TYPE -->
|
||||
<group>
|
||||
<field name="activity_type"/>
|
||||
</group>
|
||||
|
||||
<!-- TRAVEL DETAILS -->
|
||||
<group string="Travel Details"
|
||||
invisible="activity_type != 'travel'">
|
||||
|
||||
<group>
|
||||
<field name="travel_mode_policy_id"/>
|
||||
<field name="from_location"/>
|
||||
<field name="to_location"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="start_datetime"/>
|
||||
<field name="end_datetime"/>
|
||||
</group>
|
||||
|
||||
<!-- <field name="travel_details" colspan="2"/>-->
|
||||
</group>
|
||||
|
||||
<!-- STAY DETAILS -->
|
||||
<group string="Accommodation Details"
|
||||
invisible="activity_type != 'stay'">
|
||||
|
||||
<group>
|
||||
<field name="stay_type"/>
|
||||
<field name="hotel_name"/>
|
||||
<field name="city"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="checkin"/>
|
||||
<field name="checkout"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- MEETING DETAILS -->
|
||||
<group string="Meeting Details"
|
||||
invisible="activity_type != 'meeting'">
|
||||
|
||||
<group>
|
||||
<field name="meeting_title"/>
|
||||
<field name="meeting_location"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="start_datetime"/>
|
||||
<field name="end_datetime"/>
|
||||
</group>
|
||||
|
||||
<!-- <field name="notes" colspan="2"/>-->
|
||||
</group>
|
||||
|
||||
<!-- LOCAL TRAVEL -->
|
||||
<group string="Local Commute Details"
|
||||
invisible="activity_type != 'local'">
|
||||
|
||||
<group>
|
||||
<field name="local_travel_mode"/>
|
||||
<field name="from_location"/>
|
||||
<field name="to_location"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="start_datetime"/>
|
||||
<field name="end_datetime"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<!-- DOCUMENTS -->
|
||||
<separator string="Documents"/>
|
||||
|
||||
<field name="attachment_ids">
|
||||
<list editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="datas"/>
|
||||
</list>
|
||||
</field>
|
||||
|
||||
|
||||
<!-- EXPENSES -->
|
||||
<separator string="Expenses"/>
|
||||
|
||||
<field name="expense_ids"
|
||||
context="{'default_activity_id': id}">
|
||||
<list editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="expense_date"/>
|
||||
<field name="amount"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
|
||||
<!-- TOTAL -->
|
||||
<group>
|
||||
<field name="total_amount" readonly="1"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
</field>
|
||||
</record>
|
||||
<record id="action_travel_activity" model="ir.actions.act_window">
|
||||
<field name="name">Activity</field>
|
||||
<field name="res_model">travel.activity</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="view_id" ref="view_travel_activity_form"/>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- List View -->
|
||||
<record id="view_travel_city_category_tree" model="ir.ui.view">
|
||||
<field name="name">travel.city.category.tree</field>
|
||||
<field name="model">travel.city.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="view_travel_city_category_form" model="ir.ui.view">
|
||||
<field name="name">travel.city.category.form</field>
|
||||
<field name="model">travel.city.category</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="City Category">
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="code"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_travel_city_category" model="ir.actions.act_window">
|
||||
<field name="name">City Categories</field>
|
||||
<field name="res_model">travel.city.category</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu -->
|
||||
<!-- <menuitem id="menu_travel_config_root"-->
|
||||
<!-- name="Travel Configuration"-->
|
||||
<!-- parent="menu_travel_root"-->
|
||||
<!-- sequence="50"/>-->
|
||||
|
||||
<!-- <menuitem id="menu_travel_city_category"-->
|
||||
<!-- name="City Categories"-->
|
||||
<!-- parent="menu_travel_config_root"-->
|
||||
<!-- action="action_travel_city_category"-->
|
||||
<!-- sequence="10"/>-->
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<odoo>
|
||||
|
||||
<!-- TREE VIEW -->
|
||||
<record id="view_travel_daily_allowance_tree" model="ir.ui.view">
|
||||
<field name="name">travel.daily.allowance.tree</field>
|
||||
<field name="model">travel.daily.allowance</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="city_category_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="actuals_allowed"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- FORM VIEW -->
|
||||
<record id="view_travel_daily_allowance_form" model="ir.ui.view">
|
||||
<field name="name">travel.daily.allowance.form</field>
|
||||
<field name="model">travel.daily.allowance</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="city_category_id"/>
|
||||
<field name="amount"/>
|
||||
<field name="actuals_allowed"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ACTION -->
|
||||
<record id="action_travel_daily_allowance" model="ir.actions.act_window">
|
||||
<field name="name">Daily Allowance Policies</field>
|
||||
<field name="res_model">travel.daily.allowance</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_travel_expense_form" model="ir.ui.view">
|
||||
<field name="name">travel.expense.form</field>
|
||||
<field name="model">travel.expense</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Expense">
|
||||
|
||||
<header>
|
||||
<button name="action_submit"
|
||||
string="Submit"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
invisible="state != 'draft'"/>
|
||||
|
||||
<button name="action_approve"
|
||||
string="Approve"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
invisible="state != 'submitted'"/>
|
||||
|
||||
<button name="action_mark_reimbursed"
|
||||
string="Reimburse"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
invisible="state != 'approved'"/>
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
|
||||
<div class="oe_title"
|
||||
style="display:flex; justify-content:space-between;">
|
||||
<h1>
|
||||
<field name="name" placeholder="Expense Description"/>
|
||||
</h1>
|
||||
<field name="state"
|
||||
widget="statusbar"
|
||||
statusbar_visible="draft,submitted,approved,reimbursed"/>
|
||||
</div>
|
||||
|
||||
<group>
|
||||
<group>
|
||||
<field name="activity_id"/>
|
||||
<field name="expense_date"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="amount"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group string="Documents">
|
||||
<field name="receipt" widget="binary"/>
|
||||
</group>
|
||||
|
||||
</sheet>
|
||||
|
||||
<!-- <chatter>-->
|
||||
<!-- <field name="message_ids"/>-->
|
||||
<!-- </chatter>-->
|
||||
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<odoo>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_travel_group_tree" model="ir.ui.view">
|
||||
<field name="name">travel.group.tree</field>
|
||||
<field name="model">travel.group</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="view_travel_group_form" model="ir.ui.view">
|
||||
<field name="name">travel.group.form</field>
|
||||
<field name="model">travel.group</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="level_code"/> <!-- ADD THIS -->
|
||||
<field name="active"/>
|
||||
</group>
|
||||
<group string="Designations">
|
||||
<field name="job_ids" widget="many2many_tags" readonly="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_travel_group" model="ir.actions.act_window">
|
||||
<field name="name">Travel Groups</field>
|
||||
<field name="res_model">travel.group</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- ROOT MENU (MUST HAVE ACTION) -->
|
||||
<menuitem id="menu_travel_root"
|
||||
name="Business Travel"
|
||||
action="action_travel_trip"
|
||||
sequence="50"
|
||||
web_icon="business_travel_expense_management,static/decription/icon.png"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<!-- TRIPS -->
|
||||
<menuitem id="menu_travel_trip"
|
||||
name="Trips"
|
||||
parent="menu_travel_root"
|
||||
action="action_travel_trip"
|
||||
sequence="10"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<!-- CONFIG ROOT -->
|
||||
<menuitem id="menu_travel_config_root"
|
||||
name="Travel Configuration"
|
||||
parent="menu_travel_root"
|
||||
sequence="50"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<!-- CITY CATEGORY -->
|
||||
<menuitem id="menu_travel_city_category"
|
||||
name="City Categories"
|
||||
parent="menu_travel_config_root"
|
||||
action="action_travel_city_category"
|
||||
sequence="10"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<!-- Menu -->
|
||||
<menuitem id="menu_travel_group"
|
||||
name="Travel Groups"
|
||||
parent="menu_travel_config_root"
|
||||
action="action_travel_group"
|
||||
sequence="20"
|
||||
groups="base.group_user"/>
|
||||
|
||||
<menuitem id="menu_travel_stay_policy"
|
||||
name="Stay Policies"
|
||||
parent="menu_travel_config_root"
|
||||
action="action_travel_stay_policy"
|
||||
sequence="30"/>
|
||||
|
||||
<menuitem id="menu_travel_daily_allowance"
|
||||
name="Daily Allowance Policies"
|
||||
parent="menu_travel_config_root"
|
||||
action="action_travel_daily_allowance"
|
||||
sequence="20"/>
|
||||
|
||||
<menuitem id="menu_travel_mode_policy"
|
||||
name="Travel Mode Policies"
|
||||
parent="menu_travel_config_root"
|
||||
action="action_travel_mode_policy"
|
||||
sequence="30"/>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<odoo>
|
||||
|
||||
<!-- TREE -->
|
||||
<record id="view_travel_mode_policy_tree" model="ir.ui.view">
|
||||
<field name="name">travel.mode.policy.tree</field>
|
||||
<field name="model">travel.mode.policy</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="mode_type"/>
|
||||
<field name="travel_mode"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- FORM -->
|
||||
<record id="view_travel_mode_policy_form" model="ir.ui.view">
|
||||
<field name="name">travel.mode.policy.form</field>
|
||||
<field name="model">travel.mode.policy</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="mode_type"/>
|
||||
<field name="travel_mode"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_travel_mode_policy" model="ir.actions.act_window">
|
||||
<field name="name">Travel Mode Policies</field>
|
||||
<field name="res_model">travel.mode.policy</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
<odoo>
|
||||
<record id="view_travel_stay_policy_tree" model="ir.ui.view">
|
||||
<field name="name">travel.stay.policy.tree</field>
|
||||
<field name="model">travel.stay.policy</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="city_category_id"/>
|
||||
<field name="min_amount"/>
|
||||
<field name="max_amount"/>
|
||||
<field name="is_actuals"/>
|
||||
<field name="active"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="view_travel_stay_policy_form" model="ir.ui.view">
|
||||
<field name="name">travel.stay.policy.form</field>
|
||||
<field name="model">travel.stay.policy</field>
|
||||
<field name="arch" type="xml">
|
||||
<form>
|
||||
<sheet>
|
||||
<group>
|
||||
<field name="travel_group_id"/>
|
||||
<field name="city_category_id"/>
|
||||
<field name="is_actuals"/>
|
||||
<field name="min_amount"/>
|
||||
<field name="max_amount"/>
|
||||
<field name="active"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
|
||||
<record id="action_travel_stay_policy" model="ir.actions.act_window">
|
||||
<field name="name">Stay Policies</field>
|
||||
<field name="res_model">travel.stay.policy</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1,170 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Trip Form View -->
|
||||
<record id="view_travel_trip_form" model="ir.ui.view">
|
||||
<field name="name">travel.trip.form</field>
|
||||
<field name="model">travel.trip</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Trip">
|
||||
<header>
|
||||
|
||||
<!-- Employee -->
|
||||
<button name="action_submit"
|
||||
string="Submit Trip"
|
||||
type="object"
|
||||
class="btn-primary"
|
||||
invisible="state != 'draft'"/>
|
||||
|
||||
<!-- Manager only -->
|
||||
<button name="action_approve"
|
||||
string="Approve"
|
||||
type="object"
|
||||
class="btn-success"
|
||||
invisible="state != 'submitted' or not is_current_user_manager"/>
|
||||
|
||||
<button name="%(action_trip_reject_wizard)d"
|
||||
string="Reject"
|
||||
type="action"
|
||||
class="btn-danger"
|
||||
invisible="state != 'submitted' or not is_current_user_manager"/>
|
||||
|
||||
<!-- Employee after approval -->
|
||||
<button name="action_mark_completed"
|
||||
string="Mark Completed"
|
||||
type="object"
|
||||
class="btn-secondary"
|
||||
invisible="state != 'approved'"/>
|
||||
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="draft,submitted,approved,completed,reimbursed,rejected"/>
|
||||
</header>
|
||||
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name" readonly="1"/>
|
||||
<field name="employee_id" readonly="state != 'draft'"/>
|
||||
<xpath expr="//field[@name='employee_id']" position="after">
|
||||
<field name="travel_group_id" readonly="1"/>
|
||||
</xpath>
|
||||
|
||||
<field name="department_id" readonly="1"/>
|
||||
<field name="manager_id" readonly="1"/>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="from_location" readonly="state != 'draft'"/>
|
||||
<field name="to_location" readonly="state != 'draft'"/>
|
||||
<field name="start_date" readonly="state != 'draft'"/>
|
||||
<field name="end_date" readonly="state != 'draft'"/>
|
||||
<field name="estimated_cost" readonly="state != 'draft'"/>
|
||||
<field name="total_expense" string="Actual Cost"/>
|
||||
<field name="city_category_id"/>
|
||||
</group>
|
||||
</group>
|
||||
|
||||
<group>
|
||||
<field name="purpose" readonly="state != 'draft'"/>
|
||||
</group>
|
||||
|
||||
<!-- Show reject reason only when present -->
|
||||
<group invisible="not reject_reason">
|
||||
<field name="reject_reason" readonly="1"/>
|
||||
</group>
|
||||
|
||||
<notebook>
|
||||
<!-- <page string="Expenses">-->
|
||||
<!-- <field name="expense_ids"-->
|
||||
<!-- readonly="state != 'draft'"-->
|
||||
<!-- context="{'form_view_ref': 'business_travel_expense_management.view_travel_expense_form'}">-->
|
||||
|
||||
<!-- <!– Only LIST here –>-->
|
||||
<!-- <list>-->
|
||||
<!-- <field name="name"/>-->
|
||||
<!-- <field name="category"/>-->
|
||||
<!-- <field name="transport_detail"/>-->
|
||||
<!-- <field name="distance_km"/>-->
|
||||
<!-- <field name="amount"/>-->
|
||||
<!-- <field name="expense_date"/>-->
|
||||
<!-- <field name="state"/>-->
|
||||
<!-- </list>-->
|
||||
|
||||
<!-- </field>-->
|
||||
<page string="Activities">
|
||||
<field name="trave_activity_ids"
|
||||
context="{'default_trip_id': id}">
|
||||
<list>
|
||||
<field name="sequence" widget="handle"/>
|
||||
<field name="name" string="Activity"/>
|
||||
<field name="activity_type"/>
|
||||
<field name="start_datetime"/>
|
||||
<field name="end_datetime"/>
|
||||
<!-- <field name="currency_id" invisible="1"/>-->
|
||||
|
||||
<field name="total_amount"
|
||||
string="Activity Total"
|
||||
widget="monetary"/>
|
||||
<!-- options="{'currency_field': 'currency_id'}"/>-->
|
||||
</list>
|
||||
</field>
|
||||
</page>
|
||||
|
||||
|
||||
</notebook>
|
||||
<!-- <group>-->
|
||||
<!-- <field name="total_amount"-->
|
||||
<!-- widget="monetary"-->
|
||||
<!-- options="{'currency_field': 'currency_id'}"-->
|
||||
<!-- readonly="1"/>-->
|
||||
<!-- </group>-->
|
||||
|
||||
|
||||
<chatter>
|
||||
<field name="message_follower_ids"/>
|
||||
<!-- <field name="activity_ids"/>-->
|
||||
<field name="message_ids"/>
|
||||
</chatter>
|
||||
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_travel_trip_tree" model="ir.ui.view">
|
||||
<field name="name">travel.trip.tree</field>
|
||||
<field name="model">travel.trip</field>
|
||||
<field name="arch" type="xml">
|
||||
<list>
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="from_location"/>
|
||||
<field name="to_location"/>
|
||||
<field name="start_date"/>
|
||||
<field name="end_date"/>
|
||||
<field name="estimated_cost"/>
|
||||
<field name="state"/>
|
||||
</list>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Action -->
|
||||
<record id="action_travel_trip" model="ir.actions.act_window">
|
||||
<field name="name">Trips</field>
|
||||
<field name="res_model">travel.trip</field>
|
||||
<field name="view_mode">list,form</field>
|
||||
</record>
|
||||
|
||||
<!-- <!– Menu –>-->
|
||||
<!-- <menuitem id="menu_travel_root"-->
|
||||
<!-- name="Business Travel"-->
|
||||
<!-- sequence="50"/>-->
|
||||
|
||||
<!-- <menuitem id="menu_travel_trip"-->
|
||||
<!-- name="Trips"-->
|
||||
<!-- parent="menu_travel_root"-->
|
||||
<!-- action="action_travel_trip"-->
|
||||
<!-- sequence="10"/>-->
|
||||
|
||||
</odoo>
|
||||
|
|
@ -0,0 +1 @@
|
|||
from . import trip_reject_wizard
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
from odoo import models, fields
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class TripRejectWizard(models.TransientModel):
|
||||
_name = 'trip.reject.wizard'
|
||||
_description = 'Reject Trip Wizard'
|
||||
|
||||
reason = fields.Text(string="Reason for Rejection", required=True)
|
||||
|
||||
def action_confirm_reject(self):
|
||||
trip = self.env['travel.trip'].browse(self.env.context.get('active_id'))
|
||||
if not trip:
|
||||
raise UserError("No Trip found.")
|
||||
|
||||
trip.write({
|
||||
'state': 'draft',
|
||||
'reject_reason': self.reason
|
||||
})
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<record id="view_trip_reject_wizard" model="ir.ui.view">
|
||||
<field name="name">trip.reject.wizard.form</field>
|
||||
<field name="model">trip.reject.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Reject Trip">
|
||||
<group>
|
||||
<field name="reason"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button string="Confirm Reject"
|
||||
type="object"
|
||||
name="action_confirm_reject"
|
||||
class="btn-danger"/>
|
||||
<button string="Cancel" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_trip_reject_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Reject Trip</field>
|
||||
<field name="res_model">trip.reject.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
|
||||
|
||||
</odoo>
|
||||
Loading…
Reference in New Issue