diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 000000000..9776560de
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,54 @@
+FROM python:3.12-bookworm
+
+ENV PYTHONDONTWRITEBYTECODE=1
+ENV PYTHONUNBUFFERED=1
+
+# System dependencies
+RUN apt-get update && apt-get install -y \
+ build-essential \
+ gcc \
+ g++ \
+ git \
+ curl \
+ npm \
+ libpq-dev \
+ libldap2-dev \
+ libsasl2-dev \
+ libxml2-dev \
+ libxslt1-dev \
+ libjpeg62-turbo-dev \
+ zlib1g-dev \
+ libffi-dev \
+ libssl-dev \
+ liblcms2-dev \
+ wkhtmltopdf \
+ && rm -rf /var/lib/apt/lists/*
+
+# Less compiler used by Odoo
+RUN npm install -g less less-plugin-clean-css
+
+# Create odoo user
+RUN useradd -m -d /opt/odoo -U -r -s /bin/bash odoo
+
+WORKDIR /opt/odoo/odoo18
+
+# Copy project
+COPY . .
+
+# Upgrade pip tools
+RUN pip install --upgrade pip setuptools wheel
+
+# Install requirements
+RUN pip install --no-cache-dir -r requirements.txt
+
+# PostgreSQL driver
+RUN pip install psycopg2-binary
+
+# Permissions
+RUN chown -R odoo:odoo /opt/odoo
+
+USER odoo
+
+EXPOSE 8069
+
+CMD ["python3", "odoo-bin", "-c", "odoo.conf"]
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/__init__.py b/addons_extensions/business_travel_expense_management/__init__.py
new file mode 100644
index 000000000..3f3abeb49
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/__init__.py
@@ -0,0 +1,2 @@
+from . import models
+from . import wizard
diff --git a/addons_extensions/business_travel_expense_management/__manifest__.py b/addons_extensions/business_travel_expense_management/__manifest__.py
new file mode 100644
index 000000000..1f612318e
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/__manifest__.py
@@ -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,
+}
diff --git a/addons_extensions/business_travel_expense_management/data/trip_sequence.xml b/addons_extensions/business_travel_expense_management/data/trip_sequence.xml
new file mode 100644
index 000000000..07447e1c7
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/data/trip_sequence.xml
@@ -0,0 +1,9 @@
+
+
+
+ Travel Trip
+ travel.trip
+ TRIP/%(year)s/
+ 4
+
+
diff --git a/addons_extensions/business_travel_expense_management/data/users.xml b/addons_extensions/business_travel_expense_management/data/users.xml
new file mode 100644
index 000000000..8e0e658f2
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/data/users.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/models/__init__.py b/addons_extensions/business_travel_expense_management/models/__init__.py
new file mode 100644
index 000000000..246afe4cc
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/__init__.py
@@ -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
diff --git a/addons_extensions/business_travel_expense_management/models/hr_job.py b/addons_extensions/business_travel_expense_management/models/hr_job.py
new file mode 100644
index 000000000..5b78fa389
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/hr_job.py
@@ -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
diff --git a/addons_extensions/business_travel_expense_management/models/travel_activity.py b/addons_extensions/business_travel_expense_management/models/travel_activity.py
new file mode 100644
index 000000000..5eb9b1504
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_activity.py
@@ -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'))
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/models/travel_city_category.py b/addons_extensions/business_travel_expense_management/models/travel_city_category.py
new file mode 100644
index 000000000..88b52b9b7
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_city_category.py
@@ -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)
diff --git a/addons_extensions/business_travel_expense_management/models/travel_daily_allowance.py b/addons_extensions/business_travel_expense_management/models/travel_daily_allowance.py
new file mode 100644
index 000000000..913092244
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_daily_allowance.py
@@ -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)
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/models/travel_expense.py b/addons_extensions/business_travel_expense_management/models/travel_expense.py
new file mode 100644
index 000000000..bf48811e5
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_expense.py
@@ -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}."
+ )
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/models/travel_group.py b/addons_extensions/business_travel_expense_management/models/travel_group.py
new file mode 100644
index 000000000..37835298e
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_group.py
@@ -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"
+ )
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/models/travel_mode_policy.py b/addons_extensions/business_travel_expense_management/models/travel_mode_policy.py
new file mode 100644
index 000000000..231e9a37b
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_mode_policy.py
@@ -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)
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/models/travel_stay_policy.py b/addons_extensions/business_travel_expense_management/models/travel_stay_policy.py
new file mode 100644
index 000000000..f7666e71f
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_stay_policy.py
@@ -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)
diff --git a/addons_extensions/business_travel_expense_management/models/travel_trip.py b/addons_extensions/business_travel_expense_management/models/travel_trip.py
new file mode 100644
index 000000000..3a1fcdf47
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/models/travel_trip.py
@@ -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 {rec.name} 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 {rec.name} 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
+ )
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/security/ir.model.access.csv b/addons_extensions/business_travel_expense_management/security/ir.model.access.csv
new file mode 100644
index 000000000..2a6a06ea3
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/security/ir.model.access.csv
@@ -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
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/security/travel_groups.xml b/addons_extensions/business_travel_expense_management/security/travel_groups.xml
new file mode 100644
index 000000000..6ea3e36fc
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/security/travel_groups.xml
@@ -0,0 +1,18 @@
+
+
+ Travel - Employee
+
+
+
+
+ Travel - Manager
+
+
+
+
+
+ Travel - Finance
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/security/travel_trip_rules.xml b/addons_extensions/business_travel_expense_management/security/travel_trip_rules.xml
new file mode 100644
index 000000000..ab38fc66d
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/security/travel_trip_rules.xml
@@ -0,0 +1,27 @@
+
+
+
+
+ Travel Trip: Employee Own
+
+ [('employee_id.user_id', '=', user.id)]
+
+
+
+
+
+ Travel Trip: Manager Team
+
+ [('manager_id.user_id', '=', user.id)]
+
+
+
+
+
+ Travel Trip: Admin All
+
+ [(1,'=',1)]
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/static/decription/banner.png b/addons_extensions/business_travel_expense_management/static/decription/banner.png
new file mode 100644
index 000000000..234fa7fa3
Binary files /dev/null and b/addons_extensions/business_travel_expense_management/static/decription/banner.png differ
diff --git a/addons_extensions/business_travel_expense_management/static/decription/icon.png b/addons_extensions/business_travel_expense_management/static/decription/icon.png
new file mode 100644
index 000000000..234fa7fa3
Binary files /dev/null and b/addons_extensions/business_travel_expense_management/static/decription/icon.png differ
diff --git a/addons_extensions/business_travel_expense_management/views/hr_job_view.xml b/addons_extensions/business_travel_expense_management/views/hr_job_view.xml
new file mode 100644
index 000000000..2a9471f5f
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/hr_job_view.xml
@@ -0,0 +1,16 @@
+
+
+ hr.job.form.inherit.travel.group
+ hr.job
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_activity_views.xml b/addons_extensions/business_travel_expense_management/views/travel_activity_views.xml
new file mode 100644
index 000000000..67672bb3f
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_activity_views.xml
@@ -0,0 +1,134 @@
+
+
+
+
+ travel.activity.form
+ travel.activity
+
+
+
+
+
+
+
+ Activity
+ travel.activity
+ form
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_city_category_views.xml b/addons_extensions/business_travel_expense_management/views/travel_city_category_views.xml
new file mode 100644
index 000000000..aae34a044
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_city_category_views.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+ travel.city.category.tree
+ travel.city.category
+
+
+
+
+
+
+
+
+
+
+
+ travel.city.category.form
+ travel.city.category
+
+
+
+
+
+
+
+ City Categories
+ travel.city.category
+ list,form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_daily_allowance_view.xml b/addons_extensions/business_travel_expense_management/views/travel_daily_allowance_view.xml
new file mode 100644
index 000000000..e557ea59a
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_daily_allowance_view.xml
@@ -0,0 +1,44 @@
+
+
+
+
+ travel.daily.allowance.tree
+ travel.daily.allowance
+
+
+
+
+
+
+
+
+
+
+
+
+
+ travel.daily.allowance.form
+ travel.daily.allowance
+
+
+
+
+
+
+
+ Daily Allowance Policies
+ travel.daily.allowance
+ list,form
+
+
+
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/views/travel_expense_views.xml b/addons_extensions/business_travel_expense_management/views/travel_expense_views.xml
new file mode 100644
index 000000000..1fb6fa0b4
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_expense_views.xml
@@ -0,0 +1,66 @@
+
+
+
+
+ travel.expense.form
+ travel.expense
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_group_view.xml b/addons_extensions/business_travel_expense_management/views/travel_group_view.xml
new file mode 100644
index 000000000..1ae366959
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_group_view.xml
@@ -0,0 +1,43 @@
+
+
+
+
+ travel.group.tree
+ travel.group
+
+
+
+
+
+
+
+
+
+
+ travel.group.form
+ travel.group
+
+
+
+
+
+
+
+ Travel Groups
+ travel.group
+ list,form
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_menu.xml b/addons_extensions/business_travel_expense_management/views/travel_menu.xml
new file mode 100644
index 000000000..cd4758eda
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_menu.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_mode_policy_view.xml b/addons_extensions/business_travel_expense_management/views/travel_mode_policy_view.xml
new file mode 100644
index 000000000..f4b822c3f
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_mode_policy_view.xml
@@ -0,0 +1,41 @@
+
+
+
+
+ travel.mode.policy.tree
+ travel.mode.policy
+
+
+
+
+
+
+
+
+
+
+
+
+ travel.mode.policy.form
+ travel.mode.policy
+
+
+
+
+
+
+ Travel Mode Policies
+ travel.mode.policy
+ list,form
+
+
+
\ No newline at end of file
diff --git a/addons_extensions/business_travel_expense_management/views/travel_stay_policy_view.xml b/addons_extensions/business_travel_expense_management/views/travel_stay_policy_view.xml
new file mode 100644
index 000000000..01f83ecd4
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_stay_policy_view.xml
@@ -0,0 +1,42 @@
+
+
+ travel.stay.policy.tree
+ travel.stay.policy
+
+
+
+
+
+
+
+
+
+
+
+
+
+ travel.stay.policy.form
+ travel.stay.policy
+
+
+
+
+
+
+
+ Stay Policies
+ travel.stay.policy
+ list,form
+
+
diff --git a/addons_extensions/business_travel_expense_management/views/travel_trip_views.xml b/addons_extensions/business_travel_expense_management/views/travel_trip_views.xml
new file mode 100644
index 000000000..ffd780953
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/views/travel_trip_views.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+ travel.trip.form
+ travel.trip
+
+
+
+
+
+
+
+ travel.trip.tree
+ travel.trip
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Trips
+ travel.trip
+ list,form
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/addons_extensions/business_travel_expense_management/wizard/__init__.py b/addons_extensions/business_travel_expense_management/wizard/__init__.py
new file mode 100644
index 000000000..d05c3e001
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/wizard/__init__.py
@@ -0,0 +1 @@
+from . import trip_reject_wizard
diff --git a/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard.py b/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard.py
new file mode 100644
index 000000000..11338a0a4
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard.py
@@ -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
+ })
diff --git a/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard_view.xml b/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard_view.xml
new file mode 100644
index 000000000..cacb308d7
--- /dev/null
+++ b/addons_extensions/business_travel_expense_management/wizard/trip_reject_wizard_view.xml
@@ -0,0 +1,31 @@
+
+
+
+
+ trip.reject.wizard.form
+ trip.reject.wizard
+
+
+
+
+
+
+ Reject Trip
+ trip.reject.wizard
+ form
+ new
+
+
+
+
diff --git a/addons_extensions/employee_it_declaration/models/employee_payslip_download_wiz.py b/addons_extensions/employee_it_declaration/models/employee_payslip_download_wiz.py
index 9c5664a17..358b0da2f 100644
--- a/addons_extensions/employee_it_declaration/models/employee_payslip_download_wiz.py
+++ b/addons_extensions/employee_it_declaration/models/employee_payslip_download_wiz.py
@@ -14,7 +14,6 @@ class EmployeePayslipDownloadWizard(models.TransientModel):
'hr.employee',
required=True,
default=lambda self: self.env.user.employee_id.id,
- readonly=True,
)
download_type = fields.Selection(
selection=[
@@ -39,6 +38,13 @@ class EmployeePayslipDownloadWizard(models.TransientModel):
string='Available Payslips',
compute='_compute_payslip_count',
)
+ is_hr_manager = fields.Boolean(compute="_compute_is_hr_manager")
+
+ def _compute_is_hr_manager(self):
+ for rec in self:
+ import pdb
+ pdb.set_trace()
+ rec.is_hr_manager = self.env.user.has_group('hr.group_hr_manager')
@api.onchange('download_type', 'period_id')
def _onchange_download_type_period_id(self):
diff --git a/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py b/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py
index 574227e74..6ebe1fd82 100644
--- a/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py
+++ b/addons_extensions/employee_it_declaration/models/it_tax_statement_wiz.py
@@ -18,9 +18,9 @@ class ITTaxStatementWizard(models.TransientModel):
contract_id = fields.Many2one('hr.contract', related='employee_id.contract_id', required=True)
currency_id = fields.Many2one('res.currency', related='employee_id.company_id.currency_id')
- period_id = fields.Many2one('payroll.period', required=True)
- period_line = fields.Many2one('payroll.period.line',
- domain="[('period_id', '=', period_id), ('to_date', '<', fields.Date.today())]")
+ period_id = fields.Many2one('payroll.period', required=True)
+ period_line = fields.Many2one('payroll.period.line',
+ domain="[('period_id', '=', period_id), ('to_date', '<', fields.Date.today())]")
# Taxpayer profile
taxpayer_name = fields.Char(related='employee_id.name')
@@ -95,33 +95,38 @@ class ITTaxStatementWizard(models.TransientModel):
('old', 'Old Regime'),
('new', 'New Regime')
], string="Beneficial Regime", readonly=True)
+ is_hr_manager = fields.Boolean(compute="_compute_is_hr_manager")
- def _get_age_category(self, age):
- if age < 60:
- return 'below_60'
- elif age < 80:
- return '60_to_80'
- return 'above_80'
-
- def _get_effective_period_start(self):
- self.ensure_one()
- period_start = self.period_id.from_date if self.period_id else False
- if not period_start:
- return False
- if self.emp_doj and self.period_id.to_date and self.period_id.from_date <= self.emp_doj <= self.period_id.to_date:
- return max(period_start, self.emp_doj.replace(day=1))
- return period_start
-
- def _get_effective_period_lines(self):
- self.ensure_one()
- if not self.period_id:
- return self.env['payroll.period.line']
-
- period_lines = self.period_id.period_line_ids.sorted('from_date')
- effective_start = self._get_effective_period_start()
- if not effective_start:
- return period_lines
- return period_lines.filtered(lambda line: line.to_date and line.to_date >= effective_start)
+ def _compute_is_hr_manager(self):
+ for rec in self:
+ rec.is_hr_manager = self.env.user.has_group('hr.group_hr_manager')
+
+ def _get_age_category(self, age):
+ if age < 60:
+ return 'below_60'
+ elif age < 80:
+ return '60_to_80'
+ return 'above_80'
+
+ def _get_effective_period_start(self):
+ self.ensure_one()
+ period_start = self.period_id.from_date if self.period_id else False
+ if not period_start:
+ return False
+ if self.emp_doj and self.period_id.to_date and self.period_id.from_date <= self.emp_doj <= self.period_id.to_date:
+ return max(period_start, self.emp_doj.replace(day=1))
+ return period_start
+
+ def _get_effective_period_lines(self):
+ self.ensure_one()
+ if not self.period_id:
+ return self.env['payroll.period.line']
+
+ period_lines = self.period_id.period_line_ids.sorted('from_date')
+ effective_start = self._get_effective_period_start()
+ if not effective_start:
+ return period_lines
+ return period_lines.filtered(lambda line: line.to_date and line.to_date >= effective_start)
def _find_applicable_slab(self, regime, period_id, age, residence_type):
"""Find the applicable tax slab without forcing both regimes to exist."""
@@ -136,7 +141,7 @@ class ITTaxStatementWizard(models.TransientModel):
('residence_type', '=', 'both')
], limit=1)
- def _get_applicable_slab(self, regime, period_id, age, residence_type):
+ def _get_applicable_slab(self, regime, period_id, age, residence_type):
"""Get the applicable tax slab based on regime, age, and residence type"""
age_category = self._get_age_category(age)
slab_master = self._find_applicable_slab(regime, period_id, age, residence_type)
@@ -145,21 +150,21 @@ class ITTaxStatementWizard(models.TransientModel):
"No tax slab found for %s Regime with Age Category: %s and Residence Type: %s"
) % (regime.capitalize(), age_category.replace('_', ' ').title(), residence_type))
- return slab_master
-
- @api.onchange('employee_id', 'period_id')
- def _onchange_employee_id_period_id(self):
- domain_by_record = {}
- for rec in self:
- domain = [('period_id', '=', rec.period_id.id), ('to_date', '<', fields.Date.today())] if rec.period_id else []
- if rec.emp_doj:
- domain.append(('to_date', '>=', rec.emp_doj.replace(day=1)))
-
- if rec.period_line and rec.period_line not in rec._get_effective_period_lines():
- rec.period_line = False
- domain_by_record[rec.id] = domain
- if len(self) == 1:
- return {'domain': {'period_line': domain_by_record.get(self.id, [])}}
+ return slab_master
+
+ @api.onchange('employee_id', 'period_id')
+ def _onchange_employee_id_period_id(self):
+ domain_by_record = {}
+ for rec in self:
+ domain = [('period_id', '=', rec.period_id.id), ('to_date', '<', fields.Date.today())] if rec.period_id else []
+ if rec.emp_doj:
+ domain.append(('to_date', '>=', rec.emp_doj.replace(day=1)))
+
+ if rec.period_line and rec.period_line not in rec._get_effective_period_lines():
+ rec.period_line = False
+ domain_by_record[rec.id] = domain
+ if len(self) == 1:
+ return {'domain': {'period_line': domain_by_record.get(self.id, [])}}
def _get_standard_deduction(self, regime, slab_master=False):
if slab_master:
@@ -329,7 +334,7 @@ class ITTaxStatementWizard(models.TransientModel):
return list(grouped.values())
- def fetch_salary_components(self):
+ def fetch_salary_components(self):
"""fetch salary components from payroll data"""
for rec in self:
data = {
@@ -345,10 +350,10 @@ class ITTaxStatementWizard(models.TransientModel):
}
if not rec.employee_id or not rec.contract_id or not rec.period_id or not rec.period_line:
return data
- period_lines = rec._get_effective_period_lines()
-
- for line in period_lines:
- components = rec._get_salary_components_for_period_line(line)
+ period_lines = rec._get_effective_period_lines()
+
+ for line in period_lines:
+ components = rec._get_salary_components_for_period_line(line)
if line.from_date and rec.period_line.from_date and line.from_date <= rec.period_line.from_date:
data['basic_salary']['actual'].append(components['basic_salary'])
data['hra_salary']['actual'].append(components['hra_salary'])
@@ -412,7 +417,7 @@ class ITTaxStatementWizard(models.TransientModel):
)
rec.standard_deduction = rec._get_standard_deduction(rec.tax_regime, slab_master)
- def fetch_deduction_components(self):
+ def fetch_deduction_components(self):
for rec in self:
data = {
'professional_tax': {'actual': [], 'projected': []},
@@ -421,12 +426,12 @@ class ITTaxStatementWizard(models.TransientModel):
if not rec.employee_id or not rec.contract_id or not rec.period_id or not rec.period_line:
return data
- for line in rec._get_effective_period_lines():
- rule_amounts = rec._get_rule_amounts_for_period_line(line, ['PT', 'PFE'])
- bucket = 'actual' if line.from_date <= rec.period_line.from_date else 'projected'
- data['professional_tax'][bucket].append(rule_amounts['PT'])
- data['nps_employer_contribution'][bucket].append(rule_amounts['PFE'])
- return data
+ for line in rec._get_effective_period_lines():
+ rule_amounts = rec._get_rule_amounts_for_period_line(line, ['PT', 'PFE'])
+ bucket = 'actual' if line.from_date <= rec.period_line.from_date else 'projected'
+ data['professional_tax'][bucket].append(rule_amounts['PT'])
+ data['nps_employer_contribution'][bucket].append(rule_amounts['PFE'])
+ return data
@api.onchange('employee_id')
@@ -521,6 +526,14 @@ class ITTaxStatementWizard(models.TransientModel):
tax_with_surcharge = total_before_mr - mr
return surcharge, mr, tax_with_surcharge
+ def fetch_current_employer_deducted_tax(self):
+ for rec in self:
+ payslip_ids = self.env['hr.payslip'].sudo().search([('employee_id','=',rec.employee_id.id),('state','in',['done','paid']),('date_from','>=',rec.period_id.from_date),('date_to','<=',rec.period_id.to_date)])
+ amount_deducted = 0.0
+ for payslip in payslip_ids:
+ amount_deducted += sum(payslip.line_ids.filtered(lambda l:l.salary_rule_id.code == 'TDS').mapped('amount'))
+
+ return amount_deducted
def _compute_tax_old_regime(self, taxable, slab_master=False):
# Get applicable slab
slab_master = slab_master or self._get_applicable_slab(
@@ -549,6 +562,8 @@ class ITTaxStatementWizard(models.TransientModel):
total_tax = tax_with_surcharge + cess
+ current_employer_deducted_tax = self.fetch_current_employer_deducted_tax()
+
return {
'taxable_income': taxable,
'slab_tax': slab_tax,
@@ -558,7 +573,9 @@ class ITTaxStatementWizard(models.TransientModel):
'marginal_relief': marginal_relief,
'tax_with_surcharge': tax_with_surcharge,
'cess_4pct': cess,
- 'total_tax': total_tax
+ 'total_tax': total_tax,
+ 'current_employer_deducted_tax': current_employer_deducted_tax,
+ 'balance_tax': total_tax - (-current_employer_deducted_tax)
}
def _compute_tax_new_regime(self, taxable, slab_master=False):
@@ -587,6 +604,7 @@ class ITTaxStatementWizard(models.TransientModel):
cess = tax_with_surcharge * cess_rate[0] / 100
total_tax = tax_with_surcharge + cess
+ current_employer_deducted_tax = self.fetch_current_employer_deducted_tax()
return {
'taxable_income': taxable,
'slab_tax': slab_tax,
@@ -596,7 +614,9 @@ class ITTaxStatementWizard(models.TransientModel):
'marginal_relief': marginal_relief,
'tax_with_surcharge': tax_with_surcharge,
'cess_4pct': cess,
- 'total_tax': total_tax
+ 'total_tax': total_tax,
+ 'current_employer_deducted_tax': current_employer_deducted_tax,
+ 'balance_tax': total_tax - (-current_employer_deducted_tax)
}
def _compute_house_property_income(self):
@@ -759,19 +779,19 @@ class ITTaxStatementWizard(models.TransientModel):
'target': 'current',
}
- def _prepare_income_tax_data(self, include_comparison=False):
- """Prepare data for the tax statement report"""
- today = date.today()
- display_fy_start = self.period_id.from_date
- fy_end = self.period_id.to_date
- effective_fy_start = self._get_effective_period_start() or display_fy_start
- total_months = ((fy_end.year - effective_fy_start.year) * 12 +
- (fy_end.month - effective_fy_start.month) + 1)
-
- line_start = self.period_line.from_date
- current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
- (line_start.month - effective_fy_start.month) + 1)
- values = self._get_tax_base_values(include_comparison=include_comparison)
+ def _prepare_income_tax_data(self, include_comparison=False):
+ """Prepare data for the tax statement report"""
+ today = date.today()
+ display_fy_start = self.period_id.from_date
+ fy_end = self.period_id.to_date
+ effective_fy_start = self._get_effective_period_start() or display_fy_start
+ total_months = ((fy_end.year - effective_fy_start.year) * 12 +
+ (fy_end.month - effective_fy_start.month) + 1)
+
+ line_start = self.period_line.from_date
+ current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
+ (line_start.month - effective_fy_start.month) + 1)
+ values = self._get_tax_base_values(include_comparison=include_comparison)
salary_components_data = values['salary_components_data']
annual_gross_salary = values['annual_gross_salary']
gross_salary_actual = values['gross_salary_actual']
@@ -806,16 +826,16 @@ class ITTaxStatementWizard(models.TransientModel):
# Prepare data structure matching screenshot format
# Financial year (period_id)
- display_fy_start = self.period_id.from_date
- fy_end = self.period_id.to_date
- effective_fy_start = self._get_effective_period_start() or display_fy_start
- total_months = ((fy_end.year - effective_fy_start.year) * 12 +
- (fy_end.month - effective_fy_start.month) + 1)
-
- # Current month (period_line)
- line_start = self.period_line.from_date
- current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
- (line_start.month - effective_fy_start.month) + 1)
+ display_fy_start = self.period_id.from_date
+ fy_end = self.period_id.to_date
+ effective_fy_start = self._get_effective_period_start() or display_fy_start
+ total_months = ((fy_end.year - effective_fy_start.year) * 12 +
+ (fy_end.month - effective_fy_start.month) + 1)
+
+ # Current month (period_line)
+ line_start = self.period_line.from_date
+ current_month_index = ((line_start.year - effective_fy_start.year) * 12 +
+ (line_start.month - effective_fy_start.month) + 1)
tax_result['roundoff_taxable_income'] = float(round(tax_result["taxable_income"] / 10) * 10)
birthday = self.employee_id.birthday
if birthday:
@@ -835,8 +855,8 @@ class ITTaxStatementWizard(models.TransientModel):
'total': total,
})
data = {
- 'financial_year': f"{display_fy_start.year}-{fy_end.year}",
- 'assessment_year': fy_end.year + 1,
+ 'financial_year': f"{display_fy_start.year}-{fy_end.year}",
+ 'assessment_year': fy_end.year,
'report_time': today.strftime('%d-%m-%Y %H:%M'),
'user': 'ESS',
'emp_code': self.employee_id.employee_id,
diff --git a/addons_extensions/employee_it_declaration/report/it_tax_template.xml b/addons_extensions/employee_it_declaration/report/it_tax_template.xml
index 70acae040..d44989933 100644
--- a/addons_extensions/employee_it_declaration/report/it_tax_template.xml
+++ b/addons_extensions/employee_it_declaration/report/it_tax_template.xml
@@ -38,6 +38,8 @@
+
+
@@ -378,7 +380,7 @@
| Less: Tax deducted current employer (up to previous month) |
0 |
- 0 |
+ |
| Less: Tax deducted from previous Employer / Self Tax Paid |
@@ -389,7 +391,7 @@
| Balance Tax for the year |
0 |
- |
+ |
| Less: Adhoc tax deducted in Off-Cycle in current month |
@@ -399,7 +401,7 @@
| Balance Tax |
0 |
- |
+ |
| Tax deducted from current month salary |
diff --git a/addons_extensions/employee_it_declaration/security/ir.model.access.csv b/addons_extensions/employee_it_declaration/security/ir.model.access.csv
index 00e9aa80a..b55d8b1c5 100644
--- a/addons_extensions/employee_it_declaration/security/ir.model.access.csv
+++ b/addons_extensions/employee_it_declaration/security/ir.model.access.csv
@@ -54,14 +54,14 @@ access_nsc_interest_entry_user,nsc.interest.entry,model_nsc_interest_entry,base.
access_house_rent_declaration_user,access.house.rent.declaration.user,model_house_rent_declaration,base.group_user,1,1,1,1
-access_it_tax_statement,it.tax.statement,model_it_tax_statement,base.group_user,1,0,0,0
-access_it_tax_statement_wizard,it.tax.statement.wizard,model_it_tax_statement_wizard,base.group_user,1,0,0,0
-access_employee_payslip_download_wizard,employee.payslip.download.wizard,model_employee_payslip_download_wizard,base.group_user,1,1,1,0
-
-access_it_tax_statement_manager,it.tax.statement,model_it_tax_statement,hr.group_hr_manager,1,1,1,1
+access_it_tax_statement,it.tax.statement,model_it_tax_statement,base.group_user,1,0,0,0
+access_it_tax_statement_wizard,it.tax.statement.wizard,model_it_tax_statement_wizard,base.group_user,1,1,1,0
+access_employee_payslip_download_wizard,employee.payslip.download.wizard,model_employee_payslip_download_wizard,base.group_user,1,1,1,0
+
+access_it_tax_statement_manager,it.tax.statement,model_it_tax_statement,hr.group_hr_manager,1,1,1,1
access_it_tax_statement_wizard_manager,it.tax.statement.wizard,model_it_tax_statement_wizard,hr.group_hr_manager,1,1,1,1
access_it_slab_master,it.slab.master,model_it_slab_master,base.group_user,1,1,1,1
access_it_slab_master_rules,it.slab.master.rules,model_it_slab_master_rules,base.group_user,1,1,1,1
-access_it_sur_charge_rules,it.sur.charge.rules.user,model_it_sur_charge_rules,base.group_user,1,1,1,1
+access_it_sur_charge_rules,it.sur.charge.rules.user,model_it_sur_charge_rules,base.group_user,1,1,1,1
diff --git a/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml b/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml
index 62a666ea4..cf0b7b0cf 100644
--- a/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml
+++ b/addons_extensions/employee_it_declaration/views/employee_payslip_download_wizard_views.xml
@@ -21,7 +21,8 @@
-
+
+
diff --git a/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml b/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml
index 604ca120d..59bfaa342 100644
--- a/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml
+++ b/addons_extensions/employee_it_declaration/views/it_tax_menu_and_wizard_view.xml
@@ -43,7 +43,8 @@
-
+
+
@@ -88,11 +89,13 @@
it.tax.statement.wizard
tax-statement
form
+ [("activity_ids.active", "in", [True, False])]
Create a new employment type
+