Compare commits
86 Commits
feature/od
...
feature/sh
| Author | SHA1 | Date |
|---|---|---|
|
|
5e617d3ff8 | |
|
|
625bd67064 | |
|
|
3de90ee7ce | |
|
|
257e05cc34 | |
|
|
80e90ca88a | |
|
|
a1320afd5b | |
|
|
cf0b469b21 | |
|
|
1b34ae2c5c | |
|
|
08569e0ae0 | |
|
|
77827a188f | |
|
|
aecdbab7c2 | |
|
|
f40d78f5dc | |
|
|
3ff97c7c8a | |
|
|
adc4733e15 | |
|
|
a8570dea30 | |
|
|
7f8a627a8c | |
|
|
19669a7a3a | |
|
|
d448713449 | |
|
|
a9a9f08ff9 | |
|
|
7960d1926b | |
|
|
e2c8a25c7b | |
|
|
9ce8c135d0 | |
|
|
8877a4ebc4 | |
|
|
12ec001b38 | |
|
|
064bd90c58 | |
|
|
29af1ebf29 | |
|
|
41f493840b | |
|
|
54541998c5 | |
|
|
68e62956b7 | |
|
|
14e725be4f | |
|
|
adfe801d8e | |
|
|
5c6341d8b7 | |
|
|
71f416e8e8 | |
|
|
b5f643fb18 | |
|
|
3092032fee | |
|
|
f118600ab6 | |
|
|
05bdddc472 | |
|
|
582225e11e | |
|
|
478c1708fb | |
|
|
945aedc0b4 | |
|
|
eb17d717dd | |
|
|
604d556501 | |
|
|
90211776a1 | |
|
|
923304f759 | |
|
|
96493be796 | |
|
|
5460f6c207 | |
|
|
4db7e5ade2 | |
|
|
f2788e025d | |
|
|
0e51ac85e9 | |
|
|
19e5f1db80 | |
|
|
c2e33753bb | |
|
|
f6bfd46f2c | |
|
|
c9746456b8 | |
|
|
b0ec5ee508 | |
|
|
e2d6a8c417 | |
|
|
9c33507a45 | |
|
|
a9a6c683d7 | |
|
|
723dcbe225 | |
|
|
4139e5fa33 | |
|
|
ce93d9601c | |
|
|
1b21175e75 | |
|
|
74526cc1a2 | |
|
|
6a32ac3f37 | |
|
|
fa3833bac3 | |
|
|
73a27d8921 | |
|
|
26923e20b9 | |
|
|
ff806505e2 | |
|
|
b5b276f552 | |
|
|
5d6c2c09aa | |
|
|
4b85cc0f59 | |
|
|
ad5967d420 | |
|
|
53f90a7834 | |
|
|
87824199d0 | |
|
|
92543295d6 | |
|
|
7b6d108ace | |
|
|
6f77059f85 | |
|
|
c93d208990 | |
|
|
bfd7890cbc | |
|
|
d362ef87aa | |
|
|
20d22c1f04 | |
|
|
44e5ee7e2f | |
|
|
0fa84c6d43 | |
|
|
4ce02e58fa | |
|
|
66077d1819 | |
|
|
2dbdb58127 | |
|
|
5f267e96da |
|
|
@ -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"]
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import api, SUPERUSER_ID
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
from . import report
|
||||||
|
from . import wizard
|
||||||
|
|
||||||
|
|
||||||
|
# TODO: Generate Sequence For each company for Equipment
|
||||||
|
def pre_init_hook(env):
|
||||||
|
company_ids = env['res.company'].search([])
|
||||||
|
for company_id in company_ids:
|
||||||
|
sequence_id = env['ir.sequence'].search(
|
||||||
|
[('name', '=', 'Equipment Company Sequence'), ('company_id', '=', company_id.id)])
|
||||||
|
if not sequence_id:
|
||||||
|
env['ir.sequence'].create({
|
||||||
|
'name': 'Equipment Company Sequence',
|
||||||
|
'prefix': company_id.id,
|
||||||
|
'padding': 5,
|
||||||
|
'number_increment': 1,
|
||||||
|
'company_id': company_id.id
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
|
||||||
|
{
|
||||||
|
"name": "QR Code on Equipment",
|
||||||
|
'category': '',
|
||||||
|
"summary": "Add QR Code on equipment .",
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
"price": 00.00,
|
||||||
|
'description': """
|
||||||
|
The Equipment Management Module generates unique QR codes for each asset, offering instant details and direct Odoo profile access for seamless management.
|
||||||
|
""",
|
||||||
|
"author": "Raman Marikanti",
|
||||||
|
"depends": ['account','maintenance'],
|
||||||
|
"external_dependencies": {
|
||||||
|
'python': ['qrcode']
|
||||||
|
},
|
||||||
|
"data": [
|
||||||
|
'views/maintenance_equipment.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'report/custom_qrcode.xml',
|
||||||
|
'wizard/equipment_label_layout_views.xml',
|
||||||
|
],
|
||||||
|
'pre_init_hook': 'pre_init_hook',
|
||||||
|
"application": True,
|
||||||
|
"installable": True,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import maintenance_equipment
|
||||||
|
from . import res_company
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
|
||||||
|
class MaintenanceEquipment(models.Model):
|
||||||
|
_inherit = 'maintenance.equipment'
|
||||||
|
|
||||||
|
qr_code = fields.Binary("QR Code")
|
||||||
|
comp_serial_no = fields.Char("Inventory Serial No", tracking=True)
|
||||||
|
serial_no = fields.Char('Mfg. Serial Number', copy=False)
|
||||||
|
|
||||||
|
def action_print_qrcode_layout(self):
|
||||||
|
action = self.env['ir.actions.act_window']._for_xml_id('aspl_equipment_qrcode_generator.action_open_label_layout_equipment')
|
||||||
|
action['context'] = {'default_equipment_ids': self.ids}
|
||||||
|
return action
|
||||||
|
|
||||||
|
def generate_serial_no(self):
|
||||||
|
for equipment_id in self:
|
||||||
|
if not equipment_id.comp_serial_no:
|
||||||
|
company_id = equipment_id.company_id.id
|
||||||
|
sequence_id = self.env['ir.sequence'].search(
|
||||||
|
[('name', '=', 'Equipment Company Sequence'), ('company_id', '=', company_id)])
|
||||||
|
if sequence_id:
|
||||||
|
data = sequence_id._next()
|
||||||
|
equipment_id.write({
|
||||||
|
'comp_serial_no': data
|
||||||
|
})
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from odoo import models, api
|
||||||
|
|
||||||
|
|
||||||
|
class ResCompany(models.Model):
|
||||||
|
_inherit = 'res.company'
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals):
|
||||||
|
result = super(ResCompany, self).create(vals)
|
||||||
|
sequence_id = self.env['ir.sequence'].search(
|
||||||
|
[('name', '=', 'Equipment Company Sequence'), ('company_id', '=', result.id)])
|
||||||
|
if not sequence_id:
|
||||||
|
self.env['ir.sequence'].create({
|
||||||
|
'name': 'Equipment Company Sequence',
|
||||||
|
'prefix': result.id,
|
||||||
|
'padding': 5,
|
||||||
|
'number_increment': 1,
|
||||||
|
'company_id': result.id
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import custom_qrcode_generator
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<template id="report_equipment_simple_label2x7">
|
||||||
|
<t t-set="qrcode_size" t-value="'height:14mm'"/>
|
||||||
|
<t t-set="table_style" t-value="'width:97mm;height:37.1mm;margin:inherit;' + table_style"/>
|
||||||
|
<td t-att-style="make_invisible and 'visibility:hidden;'" >
|
||||||
|
<div class="o_label_full" t-att-style="table_style">
|
||||||
|
<div class="o_label_name">
|
||||||
|
<strong t-field="equipment.name"/>
|
||||||
|
</div>
|
||||||
|
<div class="o_label_data">
|
||||||
|
<div class="text-center o_label_right_column">
|
||||||
|
<t t-if="equipment.qr_code">
|
||||||
|
<div t-field="equipment.qr_code" t-options="{'widget': 'image'}" style="width: 130px;margin-top: -40px;"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="text-left" style="line-height:normal;word-wrap: break-word;">
|
||||||
|
<span class="text-nowrap" t-field="equipment.serial_no"/>
|
||||||
|
<div class="o_label_extra_data">
|
||||||
|
<span t-field="equipment.comp_serial_no"/>
|
||||||
|
</div>
|
||||||
|
<t t-if="equipment.warranty_date">
|
||||||
|
<strong t-field="equipment.warranty_date"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="o_label_clear"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="report_equipment_simple_label4x7">
|
||||||
|
<t t-set="barcode_size" t-value="'width:80px;'"/>
|
||||||
|
<t t-set="table_style" t-value="'width:47mm;height:37.1mm;margin:inherit;' + table_style"/>
|
||||||
|
<td t-att-style="make_invisible and 'visibility:hidden;'" >
|
||||||
|
<div class="o_label_full" t-att-style="table_style">
|
||||||
|
<div class="o_label_name">
|
||||||
|
<strong t-field="equipment.name"/>
|
||||||
|
</div>
|
||||||
|
<div class= "text-center o_label_right_column">
|
||||||
|
<t t-if="equipment.qr_code">
|
||||||
|
<div t-field="equipment.qr_code" t-options="{'widget': 'image'}" style="width:95px;padding:0px;margin-top:-10px"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
<div class="text-left o_label_left_column" style="line-height:normal;word-wrap: break-word;">
|
||||||
|
<div class="o_label_data">
|
||||||
|
<strong t-field="equipment.serial_no"/>
|
||||||
|
<span t-field="equipment.comp_serial_no"/>
|
||||||
|
<t t-if="equipment.warranty_date">
|
||||||
|
<strong t-field="equipment.warranty_date"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="report_equipment_simple_label2x5">
|
||||||
|
<div class="d-flex flex-column" style="
|
||||||
|
width: 87mm;
|
||||||
|
height:40mm;
|
||||||
|
border: 1.5px solid black;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 6px;
|
||||||
|
font-family: Arial, Roboto, sans-serif;
|
||||||
|
font-size: 11px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
">
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="text-center fw-bold" style="
|
||||||
|
font-size: 15px;
|
||||||
|
border-bottom: 1px solid #ccc;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
white-space: nowrap;
|
||||||
|
">
|
||||||
|
FTPROTECH - Asset Management Team
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="d-flex" style="flex: 1; padding-top: 6px;">
|
||||||
|
|
||||||
|
<!-- Left Column -->
|
||||||
|
<div class="flex-grow-1 pe-2" style="white-space: nowrap;">
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="fw-bold" style="width: 40%;">Asset Tag:</div>
|
||||||
|
<div style="width: 60%; overflow: visible;"><t style="padding-left:5px;" t-esc="equipment.name"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="fw-bold" style="width: 40%;">Serial Number:</div>
|
||||||
|
<div style="width: 60%; overflow: visible;"><t style="padding-left:5px;" t-esc="equipment.serial_no"/></div>
|
||||||
|
</div>
|
||||||
|
<div class="d-flex">
|
||||||
|
<div class="fw-bold" style="width: 40%;">Model Number:</div>
|
||||||
|
<div style="width: 60%; overflow: visible;"><t style="padding-left:5px;" t-esc="equipment.model"/></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right Column - QR Code -->
|
||||||
|
<div class="d-flex justify-content-end align-items-center" style="width: 30%;">
|
||||||
|
<t t-if="equipment.name">
|
||||||
|
<div t-field="equipment.qr_code"
|
||||||
|
t-options="{'widget': 'image'}"
|
||||||
|
style="width: 90px; height: 90px; display: inline-block;">
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="report_quipmentlabel">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-if="columns and rows">
|
||||||
|
<t t-if="columns == 2 and rows == 7">
|
||||||
|
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
|
||||||
|
<t t-set="report_to_call" t-value="'aspl_equipment_qrcode_generator.report_equipment_simple_label2x7'"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="columns == 4 and rows == 7">
|
||||||
|
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
|
||||||
|
<t t-set="report_to_call" t-value="'aspl_equipment_qrcode_generator.report_equipment_simple_label4x7'"/>
|
||||||
|
</t>
|
||||||
|
<t t-if="columns == 2 and rows == 5">
|
||||||
|
<t t-set="padding_page" t-value="'padding: 14mm 3mm'"/>
|
||||||
|
<t t-set="report_to_call" t-value="'aspl_equipment_qrcode_generator.report_equipment_simple_label2x5'"/>
|
||||||
|
</t>
|
||||||
|
<t t-foreach="range(page_numbers)" t-as="page">
|
||||||
|
<div class="o_label_sheet" t-att-style="padding_page">
|
||||||
|
<table class="my-0 table table-sm table-borderless" style="border-spacing: 5mm 3mm;">
|
||||||
|
<t t-foreach="range(rows)" t-as="row">
|
||||||
|
<tr>
|
||||||
|
<t t-foreach="range(columns)" t-as="column">
|
||||||
|
<t t-if="equipment_data">
|
||||||
|
<t t-set="current_data" t-value="equipment_data.popitem()"/>
|
||||||
|
<t t-set="equipment" t-value="current_data[0]"/>
|
||||||
|
<t t-set="table_style" t-value="'border: 1px solid black;'"/>
|
||||||
|
<t t-call="{{report_to_call}}"/>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</tr>
|
||||||
|
</t>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="maintenance_quip">
|
||||||
|
<t t-call="web.basic_layout">
|
||||||
|
<div class="page">
|
||||||
|
<t t-call="aspl_equipment_qrcode_generator.report_quipmentlabel">
|
||||||
|
<t t-set="products" t-value="products"/>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<record id="paperformat_label_sheet_qrcode" model="report.paperformat">
|
||||||
|
<field name="name">A4 Label Sheet</field>
|
||||||
|
<field name="default" eval="True"/>
|
||||||
|
<field name="format">A4</field>
|
||||||
|
<field name="page_height">0</field>
|
||||||
|
<field name="page_width">0</field>
|
||||||
|
<field name="orientation">Portrait</field>
|
||||||
|
<field name="margin_top">0</field>
|
||||||
|
<field name="margin_bottom">0</field>
|
||||||
|
<field name="margin_left">0</field>
|
||||||
|
<field name="margin_right">0</field>
|
||||||
|
<field name="disable_shrinking" eval="True"/>
|
||||||
|
<field name="dpi">96</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="report_equipment_label" model="ir.actions.report">
|
||||||
|
<field name="name">Equipment QR-code (PDF)</field>
|
||||||
|
<field name="model">maintenance.equipment</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">aspl_equipment_qrcode_generator.maintenance_quip</field>
|
||||||
|
<field name="report_file">aspl_equipment_qrcode_generator.maintenance_quip</field>
|
||||||
|
<field name="paperformat_id" ref="aspl_equipment_qrcode_generator.paperformat_label_sheet_qrcode"/>
|
||||||
|
<field name="print_report_name">'Products Labels - %s' % (object.name)</field>
|
||||||
|
<field name="binding_model_id" eval="False"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
import base64
|
||||||
|
import math
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import qrcode
|
||||||
|
from odoo import models
|
||||||
|
|
||||||
|
|
||||||
|
def generate_qr_code(value):
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
box_size=20,
|
||||||
|
border=4,
|
||||||
|
)
|
||||||
|
qr.add_data(value)
|
||||||
|
qr.make(fit=True)
|
||||||
|
img = qr.make_image()
|
||||||
|
temp = BytesIO()
|
||||||
|
img.save(temp, format="PNG")
|
||||||
|
qr_img = base64.b64encode(temp.getvalue())
|
||||||
|
return qr_img
|
||||||
|
|
||||||
|
|
||||||
|
def _prepare_data(env, data):
|
||||||
|
equipment_label_layout_id = env['equipment.label.layout'].browse(data['equipment_label_layout_id'])
|
||||||
|
equipment_dict = {}
|
||||||
|
equipment_ids = equipment_label_layout_id.equipment_ids
|
||||||
|
for equipment in equipment_ids:
|
||||||
|
if not equipment.name:
|
||||||
|
continue
|
||||||
|
equipment_dict[equipment] = 1
|
||||||
|
combine_equipment_detail = ""
|
||||||
|
|
||||||
|
# Generate Equipment Redirect LInk
|
||||||
|
url = env['ir.config_parameter'].sudo().get_param('web.base.url')
|
||||||
|
menuId = env.ref('maintenance.menu_equipment_form').sudo().id
|
||||||
|
actionId = env.ref('maintenance.hr_equipment_action').sudo().id
|
||||||
|
|
||||||
|
equipment_link = url + '/web#id=' + str(equipment.id) + '&menu_id=' + str(menuId) + '&action=' + str(
|
||||||
|
actionId) + '&model=maintenance.equipment&view_type=form'
|
||||||
|
|
||||||
|
# Prepare main Equipment Detail
|
||||||
|
main_equipment_detail = ""
|
||||||
|
main_equipment_detail = main_equipment_detail.join(
|
||||||
|
"Name: " + str(equipment.name) + "\n" +
|
||||||
|
"Model: " + str(equipment.model) + "\n" +
|
||||||
|
"Mfg serial no: " + str(equipment.serial_no) + "\n"
|
||||||
|
"Warranty Exp. Date: " +str(equipment.warranty_date) + "\n"
|
||||||
|
"Category: " +str(equipment.category_id.name)+ "\n"
|
||||||
|
"Contact No: "+str(equipment.technician_user_id.phone) +"\n"
|
||||||
|
"Contact Email: "+str(equipment.technician_user_id.login)
|
||||||
|
)
|
||||||
|
# main_equipment_detail = equipment_link + '\n' + '\n' + main_equipment_detail
|
||||||
|
|
||||||
|
# Prepare Child Equipment Detail
|
||||||
|
combine_equipment_detail = main_equipment_detail
|
||||||
|
|
||||||
|
combine_equipment_detail += '\n' + '\n' + equipment_link
|
||||||
|
|
||||||
|
# Generate Qr Code depends on Details
|
||||||
|
qr_image = generate_qr_code(combine_equipment_detail)
|
||||||
|
equipment.write({
|
||||||
|
'qr_code': qr_image
|
||||||
|
})
|
||||||
|
env.cr.commit()
|
||||||
|
page_numbers = (len(equipment_ids) - 1) // (equipment_label_layout_id.rows * equipment_label_layout_id.columns) + 1
|
||||||
|
|
||||||
|
dict_equipment = {
|
||||||
|
'rows': equipment_label_layout_id.rows,
|
||||||
|
'columns': equipment_label_layout_id.columns,
|
||||||
|
'page_numbers': page_numbers,
|
||||||
|
'equipment_data': equipment_dict
|
||||||
|
}
|
||||||
|
return dict_equipment
|
||||||
|
|
||||||
|
|
||||||
|
class ReportProductTemplateLabel(models.AbstractModel):
|
||||||
|
_name = 'report.aspl_equipment_qrcode_generator.maintenance_quip'
|
||||||
|
_description = 'Equipment QR-code Report'
|
||||||
|
|
||||||
|
def _get_report_values(self, docids, data):
|
||||||
|
return _prepare_data(self.env, data)
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_equipment_label_layout,access.equipment_label_layout,model_equipment_label_layout,,1,1,1,1
|
||||||
|
Binary file not shown.
|
After Width: | Height: | Size: 9.2 KiB |
|
|
@ -0,0 +1,56 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_extend_equipment_tree" model="ir.ui.view">
|
||||||
|
<field name="name">maintenance.equipment.tree.inherit</field>
|
||||||
|
<field name="model">maintenance.equipment</field>
|
||||||
|
<field name="priority" eval="16"/>
|
||||||
|
<field name="inherit_id" ref="maintenance.hr_equipment_view_tree"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//list" position="inside">
|
||||||
|
<header>
|
||||||
|
<button string="Print QR-code" type="object" name="action_print_qrcode_layout"/>
|
||||||
|
</header>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_extend_equipment_form" model="ir.ui.view">
|
||||||
|
<field name="name">maintenance.equipment.form.inherit</field>
|
||||||
|
<field name="model">maintenance.equipment</field>
|
||||||
|
<field name="priority" eval="16"/>
|
||||||
|
<field name="inherit_id" ref="maintenance.hr_equipment_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//page[@name='product_information']" position="inside">
|
||||||
|
<group>
|
||||||
|
<field name="comp_serial_no"/>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//sheet" position="before">
|
||||||
|
<header>
|
||||||
|
<button string="Generate Serial Number" type="object" name="generate_serial_no" class="oe_highlight"/>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="generate_serial_no_action" model="ir.actions.server">
|
||||||
|
<field name="name">Generate Serial Number</field>
|
||||||
|
<field name="model_id" ref="model_maintenance_equipment"/>
|
||||||
|
<field name="binding_model_id" ref="model_maintenance_equipment"/>
|
||||||
|
<field name="binding_view_types">list</field>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">action = records.generate_serial_no()</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="generate_qrcode_no_action" model="ir.actions.server">
|
||||||
|
<field name="name">Print QR-Code</field>
|
||||||
|
<field name="model_id" ref="model_maintenance_equipment"/>
|
||||||
|
<field name="binding_model_id" ref="model_maintenance_equipment"/>
|
||||||
|
<field name="binding_view_types">form</field>
|
||||||
|
<field name="state">code</field>
|
||||||
|
<field name="code">action = records.action_print_qrcode_layout()</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from . import equipment_label_layout
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
|
||||||
|
from odoo import _, api, fields, models
|
||||||
|
|
||||||
|
|
||||||
|
class EquipmentLabelLayout(models.TransientModel):
|
||||||
|
_name = 'equipment.label.layout'
|
||||||
|
_description = 'Choose the sheet layout to print the labels'
|
||||||
|
|
||||||
|
print_format = fields.Selection([
|
||||||
|
('2x5', '2 x 5'),
|
||||||
|
('2x7', '2 x 7'),
|
||||||
|
('4x7', '4 x 7')], string="Format", default='2x5', required=True)
|
||||||
|
equipment_ids = fields.Many2many('maintenance.equipment')
|
||||||
|
rows = fields.Integer(compute='_compute_dimensions')
|
||||||
|
columns = fields.Integer(compute='_compute_dimensions')
|
||||||
|
|
||||||
|
@api.depends('print_format')
|
||||||
|
def _compute_dimensions(self):
|
||||||
|
for wizard in self:
|
||||||
|
if 'x' in wizard.print_format:
|
||||||
|
columns, rows = wizard.print_format.split('x')[:2]
|
||||||
|
wizard.columns = int(columns)
|
||||||
|
wizard.rows = int(rows)
|
||||||
|
else:
|
||||||
|
wizard.columns, wizard.rows = 1, 1
|
||||||
|
|
||||||
|
|
||||||
|
def process_label(self):
|
||||||
|
xml_id = 'aspl_equipment_qrcode_generator.report_equipment_label'
|
||||||
|
data = {
|
||||||
|
'equipment_label_layout_id':self.id
|
||||||
|
}
|
||||||
|
|
||||||
|
return self.env.ref(xml_id).report_action(None, data=data)
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="equipment_label_layout_form" model="ir.ui.view">
|
||||||
|
<field name="name">equipment.label.layout.form</field>
|
||||||
|
<field name="model">equipment.label.layout</field>
|
||||||
|
<field name="mode">primary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="print_format" widget="radio"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<footer>
|
||||||
|
<button name="process_label" string="Confirm" type="object" class="btn-primary"/>
|
||||||
|
<button string="Discard" class="btn-secondary" special="cancel"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_open_label_layout_equipment" model="ir.actions.act_window">
|
||||||
|
<field name="name">Choose Labels Layout</field>
|
||||||
|
<field name="res_model">equipment.label.layout</field>
|
||||||
|
<field name="view_ids"
|
||||||
|
eval="[(5, 0, 0),
|
||||||
|
(0, 0, {'view_mode': 'form', 'view_id': ref('equipment_label_layout_form')})]" />
|
||||||
|
<field name="target">new</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
'name': 'Bench Management',
|
||||||
|
'version': '1.0',
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'summary': 'Bench Management System',
|
||||||
|
'author': 'Team Srivyn',
|
||||||
|
'depends': [
|
||||||
|
'hr',
|
||||||
|
'project',
|
||||||
|
'hr_timesheet',
|
||||||
|
'project_task_timesheet_extended',
|
||||||
|
'hr_employee_extended'
|
||||||
|
],
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'data/sync_team_lines.xml',
|
||||||
|
'views/project.xml',
|
||||||
|
'views/bench_management_view.xml',
|
||||||
|
],
|
||||||
|
|
||||||
|
'installable': True,
|
||||||
|
'application': True,
|
||||||
|
'auto_install': False,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<function model="project.project" name="_sync_all_team_lines_from_members"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import project
|
||||||
|
from . import bench_management
|
||||||
|
|
@ -0,0 +1,174 @@
|
||||||
|
from odoo import models, fields, tools, api
|
||||||
|
|
||||||
|
|
||||||
|
class BenchManagementLine(models.Model):
|
||||||
|
_name = "bench.management.line"
|
||||||
|
_description = "Employee Availability (Bench)"
|
||||||
|
_auto = False
|
||||||
|
_rec_name = 'employee_id'
|
||||||
|
|
||||||
|
employee_id = fields.Many2one("hr.employee", readonly=True)
|
||||||
|
job_id = fields.Many2one("hr.job", readonly=True)
|
||||||
|
company_id = fields.Many2one("res.company", related="employee_id.company_id")
|
||||||
|
|
||||||
|
project_line_ids = fields.Many2many(
|
||||||
|
'project.team.line',
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
string='Project Assignments',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
limited_project_line_ids = fields.Many2many(
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
comodel_name='project.team.line',
|
||||||
|
string='Kanban Projects',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
project_names_tooltip = fields.Text(
|
||||||
|
string="Project Names",
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
project_count = fields.Integer(
|
||||||
|
string="Project Count",
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
active_project_count = fields.Integer(
|
||||||
|
string="Active Projects",
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
future_project_count = fields.Integer(
|
||||||
|
string="Upcoming Projects",
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
completed_project_count = fields.Integer(
|
||||||
|
string="Completed Projects",
|
||||||
|
compute='_compute_bench_details',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
status = fields.Selection([
|
||||||
|
("bench", "Bench"),
|
||||||
|
("partial", "Partial"),
|
||||||
|
("full", "Full"),
|
||||||
|
], readonly=True)
|
||||||
|
|
||||||
|
def _get_line_availability_status(self, line, today):
|
||||||
|
return line.status or 'not_started'
|
||||||
|
|
||||||
|
def _compute_bench_details(self):
|
||||||
|
project_team_line = self.env['project.team.line'].sudo()
|
||||||
|
today = fields.Date.context_today(self)
|
||||||
|
for rec in self:
|
||||||
|
project_lines = project_team_line.search(
|
||||||
|
[('employee_id', '=', rec.employee_id.id)],
|
||||||
|
order='start_date desc, id desc'
|
||||||
|
)
|
||||||
|
active_lines = project_lines.filtered(
|
||||||
|
lambda line: rec._get_line_availability_status(line, today) == 'in_progress'
|
||||||
|
)
|
||||||
|
future_lines = project_lines.filtered(
|
||||||
|
lambda line: rec._get_line_availability_status(line, today) == 'not_started'
|
||||||
|
)
|
||||||
|
completed_lines = project_lines.filtered(
|
||||||
|
lambda line: rec._get_line_availability_status(line, today) == 'done'
|
||||||
|
)
|
||||||
|
project_records = project_lines.mapped('project_id')
|
||||||
|
|
||||||
|
if active_lines:
|
||||||
|
bench_status = 'full'
|
||||||
|
elif future_lines:
|
||||||
|
bench_status = 'partial'
|
||||||
|
else:
|
||||||
|
bench_status = 'bench'
|
||||||
|
|
||||||
|
rec.project_line_ids = project_lines
|
||||||
|
rec.limited_project_line_ids = project_lines[:3]
|
||||||
|
rec.project_count = len(project_records)
|
||||||
|
rec.active_project_count = len(active_lines)
|
||||||
|
rec.future_project_count = len(future_lines)
|
||||||
|
rec.completed_project_count = len(completed_lines)
|
||||||
|
rec.project_names_tooltip = '\n'.join(
|
||||||
|
f"{line.project_id.display_name or 'No Project'} - {dict(line._fields['status'].selection).get(rec._get_line_availability_status(line, today), 'N/A')}"
|
||||||
|
for line in project_lines
|
||||||
|
) or ''
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
tools.drop_view_if_exists(self.env.cr, self._table)
|
||||||
|
|
||||||
|
self.env.cr.execute("""
|
||||||
|
CREATE OR REPLACE VIEW bench_management_line AS (
|
||||||
|
SELECT
|
||||||
|
he.id AS id,
|
||||||
|
he.id AS employee_id,
|
||||||
|
he.job_id AS job_id,
|
||||||
|
CASE
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM project_team_line tpl
|
||||||
|
WHERE tpl.employee_id = he.id
|
||||||
|
AND tpl.status = 'in_progress'
|
||||||
|
) THEN 'full'
|
||||||
|
WHEN EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM project_team_line tpl
|
||||||
|
WHERE tpl.employee_id = he.id
|
||||||
|
AND tpl.status = 'not_started'
|
||||||
|
) THEN 'partial'
|
||||||
|
ELSE 'bench'
|
||||||
|
END AS status
|
||||||
|
FROM hr_employee he
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
|
||||||
|
class ProjectTeamLine(models.Model):
|
||||||
|
_inherit = 'project.team.line'
|
||||||
|
|
||||||
|
line_status_color = fields.Integer(
|
||||||
|
compute='_compute_line_status_color',
|
||||||
|
string='Status Color',
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('status')
|
||||||
|
def _compute_line_status_color(self):
|
||||||
|
color_map = {
|
||||||
|
'not_started': 8,
|
||||||
|
'in_progress': 2,
|
||||||
|
'done': 10,
|
||||||
|
}
|
||||||
|
for line in self:
|
||||||
|
line.line_status_color = color_map.get(line.status, 0)
|
||||||
|
|
||||||
|
def name_get(self):
|
||||||
|
result = []
|
||||||
|
for rec in self:
|
||||||
|
name = rec.project_id.display_name or 'No Project'
|
||||||
|
result.append((rec.id, name))
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _sync_project_members(self):
|
||||||
|
if self.env.context.get('skip_project_team_member_sync'):
|
||||||
|
return True
|
||||||
|
self.mapped('project_id')._sync_members_from_team_lines()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
records = super().create(vals_list)
|
||||||
|
records._sync_project_members()
|
||||||
|
return records
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
projects = self.mapped('project_id')
|
||||||
|
res = super().write(vals)
|
||||||
|
(projects | self.mapped('project_id'))._sync_members_from_team_lines()
|
||||||
|
return res
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
projects = self.mapped('project_id')
|
||||||
|
res = super().unlink()
|
||||||
|
projects._sync_members_from_team_lines()
|
||||||
|
return res
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
from odoo import Command, api, fields, models, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
class ProjectProject(models.Model):
|
||||||
|
_inherit = 'project.project'
|
||||||
|
|
||||||
|
team_line_ids = fields.One2many(
|
||||||
|
'project.team.line',
|
||||||
|
'project_id',
|
||||||
|
string="Team Details"
|
||||||
|
)
|
||||||
|
can_manage_team_lines = fields.Boolean(
|
||||||
|
compute='_compute_can_manage_team_lines',
|
||||||
|
string='Can Manage Team Lines'
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.depends('user_id', 'project_lead')
|
||||||
|
def _compute_can_manage_team_lines(self):
|
||||||
|
current_user = self.env.user
|
||||||
|
for project in self:
|
||||||
|
project.can_manage_team_lines = bool(
|
||||||
|
self.env.is_superuser()
|
||||||
|
or project.user_id == current_user
|
||||||
|
or ('project_lead' in project._fields and project.project_lead == current_user) or (current_user.has_group("project.group_project_manager"))
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.onchange('team_line_ids')
|
||||||
|
def _onchange_team_line_ids(self):
|
||||||
|
for project in self:
|
||||||
|
users = project.team_line_ids.mapped('user_id')
|
||||||
|
project.members_ids = [(6, 0, users.ids)]
|
||||||
|
|
||||||
|
def _sync_members_from_team_lines(self):
|
||||||
|
if self.env.context.get('skip_project_team_member_sync'):
|
||||||
|
return
|
||||||
|
for project in self:
|
||||||
|
users = project.team_line_ids.mapped('user_id')
|
||||||
|
if set(project.members_ids.ids) != set(users.ids):
|
||||||
|
project.with_context(skip_project_team_member_sync=True).sudo().write({
|
||||||
|
'members_ids': [Command.set(users.ids)],
|
||||||
|
})
|
||||||
|
|
||||||
|
def _sync_team_lines_from_members(self):
|
||||||
|
if self.env.context.get('skip_project_team_member_sync'):
|
||||||
|
return
|
||||||
|
|
||||||
|
TeamLine = self.env['project.team.line'].sudo().with_context(skip_project_team_member_sync=True)
|
||||||
|
for project in self.sudo():
|
||||||
|
member_ids = set(project.members_ids.ids)
|
||||||
|
kept_user_ids = set()
|
||||||
|
lines_to_remove = self.env['project.team.line'].sudo()
|
||||||
|
|
||||||
|
for line in project.team_line_ids.sorted('id'):
|
||||||
|
user_id = line.user_id.id
|
||||||
|
if not user_id or user_id not in member_ids or user_id in kept_user_ids:
|
||||||
|
lines_to_remove |= line
|
||||||
|
else:
|
||||||
|
kept_user_ids.add(user_id)
|
||||||
|
|
||||||
|
if lines_to_remove:
|
||||||
|
lines_to_remove.with_context(skip_project_team_member_sync=True).unlink()
|
||||||
|
|
||||||
|
for user_id in member_ids - kept_user_ids:
|
||||||
|
TeamLine.create({
|
||||||
|
'project_id': project.id,
|
||||||
|
'user_id': user_id,
|
||||||
|
})
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _sync_all_team_lines_from_members(self):
|
||||||
|
self.search([])._sync_team_lines_from_members()
|
||||||
|
return True
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
projects = super().create(vals_list)
|
||||||
|
for project, vals in zip(projects, vals_list):
|
||||||
|
if 'team_line_ids' in vals:
|
||||||
|
project._sync_members_from_team_lines()
|
||||||
|
elif 'members_ids' in vals:
|
||||||
|
project._sync_team_lines_from_members()
|
||||||
|
return projects
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
if 'team_line_ids' in vals:
|
||||||
|
self._sync_members_from_team_lines()
|
||||||
|
elif 'members_ids' in vals:
|
||||||
|
self._sync_team_lines_from_members()
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class ProjectTeamLine(models.Model):
|
||||||
|
_name = 'project.team.line'
|
||||||
|
_description = 'Project Team Line'
|
||||||
|
_rec_name = 'project_id'
|
||||||
|
|
||||||
|
project_id = fields.Many2one('project.project', ondelete='cascade')
|
||||||
|
user_id = fields.Many2one('res.users')
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
compute="_compute_employee",
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
job_id = fields.Many2one(
|
||||||
|
'hr.job',
|
||||||
|
related='employee_id.job_id',
|
||||||
|
store=True
|
||||||
|
)
|
||||||
|
|
||||||
|
start_date = fields.Date()
|
||||||
|
end_date = fields.Date()
|
||||||
|
|
||||||
|
status = fields.Selection([
|
||||||
|
('not_started', 'Not Started'),
|
||||||
|
('in_progress', 'In Progress'),
|
||||||
|
('done', 'Completed')
|
||||||
|
], compute='_compute_status', inverse='_inverse_status', store=True, readonly=False)
|
||||||
|
can_edit_assignment = fields.Boolean(
|
||||||
|
compute='_compute_can_edit_assignment',
|
||||||
|
string='Can Edit Assignment'
|
||||||
|
)
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# COMPUTE EMPLOYEE
|
||||||
|
# ------------------------
|
||||||
|
@api.depends('user_id')
|
||||||
|
def _compute_employee(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.employee_id = self.env['hr.employee'].search([
|
||||||
|
('user_id', '=', rec.user_id.id)
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
@api.depends('start_date', 'end_date')
|
||||||
|
def _compute_status(self):
|
||||||
|
today = fields.Date.context_today(self)
|
||||||
|
for rec in self:
|
||||||
|
if rec.end_date and rec.end_date < today:
|
||||||
|
rec.status = 'done'
|
||||||
|
elif rec.start_date and rec.start_date > today:
|
||||||
|
rec.status = 'not_started'
|
||||||
|
else:
|
||||||
|
rec.status = 'in_progress'
|
||||||
|
|
||||||
|
@api.depends('project_id.user_id', 'project_id.project_lead')
|
||||||
|
def _compute_can_edit_assignment(self):
|
||||||
|
current_user = self.env.user
|
||||||
|
for rec in self:
|
||||||
|
project = rec.project_id
|
||||||
|
rec.can_edit_assignment = bool(
|
||||||
|
self.env.is_superuser()
|
||||||
|
or (project and project.user_id == current_user)
|
||||||
|
or (project and 'project_lead' in project._fields and project.project_lead == current_user) or (current_user.has_group("project.group_project_manager"))
|
||||||
|
)
|
||||||
|
|
||||||
|
def _inverse_status(self):
|
||||||
|
# Allow manual edits to the stored computed field.
|
||||||
|
# When start/end dates change later, compute will refresh it again.
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _check_manager_access(self):
|
||||||
|
if self.env.is_superuser():
|
||||||
|
return
|
||||||
|
unauthorized = self.filtered(lambda rec: not rec.can_edit_assignment)
|
||||||
|
if unauthorized:
|
||||||
|
raise ValidationError(_("Only the related project manager can update team assignment dates or status."))
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# SYNC BENCH
|
||||||
|
# ------------------------
|
||||||
|
def _sync_bench(self):
|
||||||
|
# Bench data is read live from SQL view / computed fields,
|
||||||
|
# so there is no separate sync model to refresh here.
|
||||||
|
return True
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# CREATE
|
||||||
|
# ------------------------
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
if not self.env.is_superuser():
|
||||||
|
for vals in vals_list:
|
||||||
|
project_id = vals.get('project_id')
|
||||||
|
if project_id:
|
||||||
|
project = self.env['project.project'].browse(project_id)
|
||||||
|
if not (
|
||||||
|
project.user_id == self.env.user
|
||||||
|
or ('project_lead' in project._fields and project.project_lead == self.env.user)
|
||||||
|
):
|
||||||
|
raise ValidationError(_("Only the related project manager can add team assignments."))
|
||||||
|
records = super().create(vals_list)
|
||||||
|
records._sync_bench()
|
||||||
|
records.mapped('project_id')._sync_members_from_team_lines()
|
||||||
|
return records
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# WRITE
|
||||||
|
# ------------------------
|
||||||
|
def write(self, vals):
|
||||||
|
if any(key in vals for key in ('status', 'start_date', 'end_date', 'user_id', 'project_id')):
|
||||||
|
self._check_manager_access()
|
||||||
|
projects = self.mapped('project_id')
|
||||||
|
res = super().write(vals)
|
||||||
|
self._sync_bench()
|
||||||
|
if any(key in vals for key in ('user_id', 'project_id')):
|
||||||
|
(projects | self.mapped('project_id'))._sync_members_from_team_lines()
|
||||||
|
return res
|
||||||
|
|
||||||
|
# ------------------------
|
||||||
|
# UNLINK
|
||||||
|
# ------------------------
|
||||||
|
def unlink(self):
|
||||||
|
projects = self.mapped('project_id')
|
||||||
|
self._check_manager_access()
|
||||||
|
res = super().unlink()
|
||||||
|
self._sync_bench()
|
||||||
|
projects._sync_members_from_team_lines()
|
||||||
|
return res
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
id,name,model_id:id,group_id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_project_team_line,project.team.line,model_project_team_line,,1,1,1,1
|
||||||
|
access_bench_management_line,bench.management.line,model_bench_management_line,,1,1,1,1
|
||||||
|
|
|
@ -0,0 +1,245 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="view_bench_management_tree" model="ir.ui.view">
|
||||||
|
<field name="name">bench.management.line.list</field>
|
||||||
|
<field name="model">bench.management.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list create="0" string="Bench Management">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="job_id"/>
|
||||||
|
<field name="status"/>
|
||||||
|
<field name="limited_project_line_ids" widget="many2many_tags" options="{'color_field': 'line_status_color'}"/>
|
||||||
|
<field name="active_project_count"/>
|
||||||
|
<field name="future_project_count"/>
|
||||||
|
<field name="completed_project_count"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_bench_management_search" model="ir.ui.view">
|
||||||
|
<field name="name">bench.management.line.search</field>
|
||||||
|
<field name="model">bench.management.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Bench Management">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="job_id"/>
|
||||||
|
<field name="status"/>
|
||||||
|
<filter name="filter_bench" string="Bench" domain="[('status', '=', 'bench')]"/>
|
||||||
|
<filter name="filter_partial" string="Partially Available" domain="[('status', '=', 'partial')]"/>
|
||||||
|
<filter name="filter_full" string="Fully Allocated" domain="[('status', '=', 'full')]"/>
|
||||||
|
<group expand="0" string="Group By">
|
||||||
|
<filter name="group_by_status" string="Status" context="{'group_by': 'status'}"/>
|
||||||
|
<filter name="group_by_job" string="Job Position" context="{'group_by': 'job_id'}"/>
|
||||||
|
</group>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_bench_management_form" model="ir.ui.view">
|
||||||
|
<field name="name">bench.management.line.form</field>
|
||||||
|
<field name="model">bench.management.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form create="0" string="Bench Management">
|
||||||
|
<sheet>
|
||||||
|
|
||||||
|
<group>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="job_id"/>
|
||||||
|
<field name="status"/>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<group string="Project Information">
|
||||||
|
<field name="project_line_ids" nolabel="1" readonly="0">
|
||||||
|
<list create="0" delete="0" editable="bottom">
|
||||||
|
<field name="project_id" readonly="1"/>
|
||||||
|
<field name="can_edit_assignment" column_invisible="1"/>
|
||||||
|
<field name="status" readonly="not can_edit_assignment"/>
|
||||||
|
<field name="start_date" readonly="not can_edit_assignment"/>
|
||||||
|
<field name="end_date" readonly="not can_edit_assignment"/>
|
||||||
|
<field name="job_id" optional="hide" readonly="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_bench_management_kanban" model="ir.ui.view">
|
||||||
|
<field name="name">bench.management.line.kanban</field>
|
||||||
|
<field name="model">bench.management.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<kanban create="0" class="o_kanban_mobile">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="job_id"/>
|
||||||
|
<field name="status"/>
|
||||||
|
<field name="limited_project_line_ids"/>
|
||||||
|
<field name="project_count"/>
|
||||||
|
<field name="active_project_count"/>
|
||||||
|
<field name="future_project_count"/>
|
||||||
|
<field name="completed_project_count"/>
|
||||||
|
<field name="project_names_tooltip"/>
|
||||||
|
<templates>
|
||||||
|
<t t-name="kanban-box">
|
||||||
|
<div class="oe_kanban_card oe_kanban_global_click"
|
||||||
|
style="border-radius:16px;border:1px solid #dbe4ee;background:linear-gradient(180deg, #ffffff 0%, #f8fafc 100%);padding:16px;min-height:260px;box-shadow:0 8px 24px rgba(15, 23, 42, 0.06);">
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="d-flex align-items-center mb-3">
|
||||||
|
<img t-att-src="'/web/image/hr.employee/' + record.employee_id.raw_value + '/avatar_128'"
|
||||||
|
style="
|
||||||
|
width:42px;
|
||||||
|
height:42px;
|
||||||
|
border-radius:50%;
|
||||||
|
object-fit:cover;
|
||||||
|
margin-right:10px;
|
||||||
|
"/>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
|
||||||
|
<div style="
|
||||||
|
font-size:16px;
|
||||||
|
font-weight:600;
|
||||||
|
color:#1f2937;
|
||||||
|
">
|
||||||
|
<field name="employee_id"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="
|
||||||
|
font-size:12px;
|
||||||
|
color:#6b7280;
|
||||||
|
">
|
||||||
|
<field name="job_id"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Status -->
|
||||||
|
<div class="mb-3 d-flex align-items-center justify-content-between">
|
||||||
|
|
||||||
|
<t t-if="record.status.raw_value == 'bench'">
|
||||||
|
<span style="
|
||||||
|
background:#e2e8f0;
|
||||||
|
color:#334155;
|
||||||
|
padding:5px 10px;
|
||||||
|
border-radius:20px;
|
||||||
|
font-size:11px;
|
||||||
|
font-weight:600;
|
||||||
|
">
|
||||||
|
Bench
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-if="record.status.raw_value == 'partial'">
|
||||||
|
<span style="
|
||||||
|
background:#fef3c7;
|
||||||
|
color:#92400e;
|
||||||
|
padding:5px 10px;
|
||||||
|
border-radius:20px;
|
||||||
|
font-size:11px;
|
||||||
|
font-weight:600;
|
||||||
|
">
|
||||||
|
Partially Available
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-if="record.status.raw_value == 'full'">
|
||||||
|
<span style="
|
||||||
|
background:#dcfce7;
|
||||||
|
color:#166534;
|
||||||
|
padding:5px 10px;
|
||||||
|
border-radius:20px;
|
||||||
|
font-size:11px;
|
||||||
|
font-weight:600;
|
||||||
|
">
|
||||||
|
Fully Allocated
|
||||||
|
</span>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<div style="font-size:11px;color:#64748b;">
|
||||||
|
<t t-out="record.active_project_count.raw_value"/> Active
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="d-flex gap-2 mb-3" style="gap:8px;">
|
||||||
|
<div style="flex:1;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px;background:#ffffff;">
|
||||||
|
<div style="font-size:10px;color:#64748b;text-transform:uppercase;">Current</div>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#0f172a;">
|
||||||
|
<t t-out="record.active_project_count.raw_value"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px;background:#ffffff;">
|
||||||
|
<div style="font-size:10px;color:#64748b;text-transform:uppercase;">Upcoming</div>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#0f172a;">
|
||||||
|
<t t-out="record.future_project_count.raw_value"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="flex:1;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px;background:#ffffff;">
|
||||||
|
<div style="font-size:10px;color:#64748b;text-transform:uppercase;">Completed</div>
|
||||||
|
<div style="font-size:18px;font-weight:700;color:#0f172a;">
|
||||||
|
<t t-out="record.completed_project_count.raw_value"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Project Info -->
|
||||||
|
<div t-if="record.project_count.raw_value"
|
||||||
|
style="border-top:1px solid #f3f4f6;padding-top:12px;">
|
||||||
|
|
||||||
|
<div style="
|
||||||
|
font-size:13px;
|
||||||
|
font-weight:600;
|
||||||
|
color:#374151;
|
||||||
|
margin-bottom:8px;
|
||||||
|
">
|
||||||
|
Projects
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div t-att-title="record.project_names_tooltip.raw_value"
|
||||||
|
style="max-height:48px;overflow:hidden;">
|
||||||
|
<field name="limited_project_line_ids"
|
||||||
|
widget="many2many_tags"
|
||||||
|
options="{'no_create': True, 'no_create_edit': True, 'no_open': True, 'color_field': 'line_status_color'}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<t t-if="record.project_count.raw_value > 3">
|
||||||
|
<div t-att-title="record.project_names_tooltip.raw_value"
|
||||||
|
style="margin-top:8px;font-size:11px;color:#2563eb;font-weight:600;">
|
||||||
|
+ <t t-out="record.project_count.raw_value - 3"/> more
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</t>
|
||||||
|
|
||||||
|
</templates>
|
||||||
|
|
||||||
|
</kanban>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_bench_management" model="ir.actions.act_window">
|
||||||
|
<field name="name">Bench Management</field>
|
||||||
|
<field name="res_model">bench.management.line</field>
|
||||||
|
<field name="view_mode">kanban,list,form</field>
|
||||||
|
<field name="domain">[('company_id', 'in', allowed_company_ids)]</field>
|
||||||
|
<field name="search_view_id" ref="view_bench_management_search"/>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_bench_management"
|
||||||
|
name="Employee Bench"
|
||||||
|
parent="hr.menu_hr_root"
|
||||||
|
groups="hr.group_hr_manager"
|
||||||
|
action="action_bench_management"
|
||||||
|
sequence="3"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="project_project_inherit_form_view2_inherit" model="ir.ui.view">
|
||||||
|
<field name="name">project.project.inherit.form.view.inherit</field>
|
||||||
|
<field name="model">project.project</field>
|
||||||
|
<field name="inherit_id" ref="project_task_timesheet_extended.project_project_inherit_form_view2"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//form" position="inside">
|
||||||
|
<field name="can_manage_team_lines" invisible="1"/>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//page[@name='team']/group[field[@name='members_ids']]" position="after">
|
||||||
|
<group string="Project Team Details">
|
||||||
|
<field name="team_line_ids" nolabel="1" readonly="not can_manage_team_lines">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="user_id" string="Employee Name"/>
|
||||||
|
<field name="employee_id" string="Employee" readonly="1" column_invisible="1" invisible="1"/>
|
||||||
|
<field name="job_id" string="Job Position"/>
|
||||||
|
<field name="project_id" column_invisible="1"/>
|
||||||
|
<field name="can_edit_assignment" column_invisible="1"/>
|
||||||
|
<field name="start_date" string="Start Date" readonly="not can_edit_assignment"/>
|
||||||
|
<field name="end_date" string="End date" readonly="not can_edit_assignment"/>
|
||||||
|
<field name="status" string="Status" readonly="not can_edit_assignment"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</group>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -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>
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
/** @odoo-module **/
|
/** @odoo-module **/
|
||||||
import {standardWidgetProps} from "@web/views/widgets/standard_widget_props";
|
import {standardWidgetProps} from "@web/views/widgets/standard_widget_props";
|
||||||
import { Component, onMounted, useRef, useState, onWillStart } from "@odoo/owl";
|
import {Component, onMounted, useRef, useState, onWillStart, onWillUpdateProps} from "@odoo/owl";
|
||||||
import {registry} from "@web/core/registry";
|
import {registry} from "@web/core/registry";
|
||||||
import {useService} from "@web/core/utils/hooks";
|
import {useService} from "@web/core/utils/hooks";
|
||||||
import {loadJS, loadCSS} from "@web/core/assets";
|
import {loadJS, loadCSS} from "@web/core/assets";
|
||||||
|
|
@ -34,6 +34,12 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
console.error("Grid element not found");
|
console.error("Grid element not found");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onWillUpdateProps(async (nextProps) => {
|
||||||
|
if (nextProps.record.data.state !== this.props.record.data.state) {
|
||||||
|
await this.loadGrid();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadDependencies() {
|
async loadDependencies() {
|
||||||
|
|
@ -167,18 +173,6 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
label: 'Refresh',
|
label: 'Refresh',
|
||||||
icon: 'ui-icon-refresh',
|
icon: 'ui-icon-refresh',
|
||||||
listener: () => this.loadGrid()
|
listener: () => this.loadGrid()
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: 'Save Changes',
|
|
||||||
icon: 'ui-icon-disk',
|
|
||||||
listener: () => this.saveChanges()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: 'button',
|
|
||||||
label: 'Recalculate LOP',
|
|
||||||
icon: 'ui-icon-calculator',
|
|
||||||
listener: () => this.recalculateLOP()
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: 'select',
|
type: 'select',
|
||||||
|
|
@ -207,6 +201,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
function updateData(data) {
|
function updateData(data) {
|
||||||
$.ajax({
|
$.ajax({
|
||||||
url: "/slip/update",
|
url: "/slip/update",
|
||||||
|
|
@ -295,7 +290,7 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
async getColumns() {
|
async getColumns() {
|
||||||
const subCols = await this.getSubgridColumns();
|
const subCols = await this.getSubgridColumns();
|
||||||
|
|
||||||
return [
|
const columns = [
|
||||||
{
|
{
|
||||||
title: "Employee",
|
title: "Employee",
|
||||||
dataIndx: "employee",
|
dataIndx: "employee",
|
||||||
|
|
@ -460,11 +455,18 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
];
|
||||||
|
if (this.props.record.data.state !== "paid") {
|
||||||
|
columns.push(
|
||||||
{
|
{
|
||||||
title: "Edit",
|
title: "Edit",
|
||||||
width: 120,
|
width: 120,
|
||||||
editable: false,
|
editable: false,
|
||||||
render: function (ui) {
|
render: function (ui) {
|
||||||
|
if (ui.rowData.state == 'paid') {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
return "<button class='row-btn-edit' type='button'>Edit</button>"
|
return "<button class='row-btn-edit' type='button'>Edit</button>"
|
||||||
},
|
},
|
||||||
postRender: function (ui) {
|
postRender: function (ui) {
|
||||||
|
|
@ -479,7 +481,10 @@ export class ConsolidatedPayslipGrid extends Component {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
];
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return columns
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSubgridColumns() {
|
async getSubgridColumns() {
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,6 @@ class CwfTimesheet(models.Model):
|
||||||
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
external_group_id = self.env.ref("hr_employee_extended.group_external_user")
|
||||||
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
users = self.env["res.users"].search([("groups_id", "=", external_group_id.id)])
|
||||||
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
employees = self.env['hr.employee'].search([('user_id', 'in', users.ids),'|',('doj','=',False),('doj','>=', self.week_start_date)])
|
||||||
print(employees)
|
|
||||||
# Loop through each day of the week and create timesheet lines for each employee
|
# Loop through each day of the week and create timesheet lines for each employee
|
||||||
while current_date <= end_date:
|
while current_date <= end_date:
|
||||||
for employee in employees:
|
for employee in employees:
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||||||
|
{
|
||||||
|
'name': 'Disciplinary',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'category': 'Apps',
|
||||||
|
'summary': 'Disciplinary',
|
||||||
|
'description': 'Employee Disciplinary',
|
||||||
|
'sequence': '10',
|
||||||
|
'author': '',
|
||||||
|
'company': 'FTPROTECH',
|
||||||
|
'website': 'https://www.ftprotech.in',
|
||||||
|
'depends': ['mail', 'hr', 'base', 'website_hr_recruitment', 'contacts', 'point_of_sale'],
|
||||||
|
'demo': [],
|
||||||
|
'data': [
|
||||||
|
'data/sequence.xml',
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/disciplinary_view.xml',
|
||||||
|
'views/employee_displance.xml',
|
||||||
|
'views/mistake_type_views.xml',
|
||||||
|
'views/incident_sub_type.xml',
|
||||||
|
'views/disciplinary_complaint_type.xml',
|
||||||
|
],
|
||||||
|
'installable': True,
|
||||||
|
'application': False,
|
||||||
|
'auto_install': False,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="incident_report_sequence" model="ir.sequence">
|
||||||
|
<field name="name">Employee Disciplinary</field>
|
||||||
|
<field name="code">employee.disciplinary</field>
|
||||||
|
<field name="prefix">IR</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="manage_incident_report_sequence" model="ir.sequence">
|
||||||
|
<field name="name">Manage Incident</field>
|
||||||
|
<field name="code">manage.incident</field>
|
||||||
|
<field name="prefix">MI</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="seq_employee_disciplinary_sequence" model="ir.sequence">
|
||||||
|
<field name="name">Disciplinary Sequence</field>
|
||||||
|
<field name="code">hr.employee.sequence</field>
|
||||||
|
<field name="prefix">ED</field>
|
||||||
|
<field name="padding">5</field>
|
||||||
|
<field name="company_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import disciplinary
|
||||||
|
from . import employee_displane
|
||||||
|
|
@ -0,0 +1,188 @@
|
||||||
|
from datetime import datetime, date
|
||||||
|
from odoo import fields, models, api
|
||||||
|
|
||||||
|
#
|
||||||
|
# class NameChangeHrEmployee(models.Model):
|
||||||
|
# _inherit = "hr.employee"
|
||||||
|
#
|
||||||
|
# employee_name_ids1 = fields.One2many('employee.disciplinary', 'disp_name')
|
||||||
|
# employee_self_service_line_ids = fields.One2many('manage.incident', 'emp_incident', domain=[('state', '=', 'closed')])
|
||||||
|
#
|
||||||
|
# def name_get(self):
|
||||||
|
# result = []
|
||||||
|
# for record in self:
|
||||||
|
# if self.env.context.get('new_custom_name', False):
|
||||||
|
# result.append((record.id, "{} - {}".format(record.name, record.identification_id)))
|
||||||
|
# else:
|
||||||
|
# return super(NameChangeHrEmployee, self).name_get()
|
||||||
|
# return result
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeeDisciplinary(models.Model):
|
||||||
|
_name = 'employee.disciplinary'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
_rec_name = 'incident_type'
|
||||||
|
|
||||||
|
incident_date = fields.Datetime(string='Incident Date & Time', tracking=True, default=datetime.now(), required=True)
|
||||||
|
incident_type = fields.Many2one('incident.employee', string='Incident Type', tracking=True, required=True)
|
||||||
|
incident_sub_type = fields.Many2many('incident.sub.employee', string='Incident Sub Type', tracking=True,
|
||||||
|
required=True)
|
||||||
|
incident_details = fields.Char(string='Incident Details', tracking=True, required=True)
|
||||||
|
seized_items = fields.Char(string='Seized Items', tracking=True)
|
||||||
|
incident_summary = fields.Text(string='Incident Summary', tracking=True, required=True)
|
||||||
|
attach = fields.Many2many('ir.attachment', string='Attachments', tracking=True)
|
||||||
|
emp_many_disp = fields.Many2many('hr.employee', 'new_custom_table', string='Employees Involved in the Incident',
|
||||||
|
tracking=True, required=True)
|
||||||
|
date_action = fields.Date(string='Date')
|
||||||
|
employee = fields.Many2one('manage.incident')
|
||||||
|
employee_code = fields.Many2one("hr.employee", string="Employee Name", required=True)
|
||||||
|
employee_name = fields.Char(related="employee_code.identification_id")
|
||||||
|
disp_name = fields.Many2one('hr.employee')
|
||||||
|
|
||||||
|
@api.onchange('incident_type')
|
||||||
|
def return_incident_sub_type(self):
|
||||||
|
print(self.incident_type.sub_type)
|
||||||
|
listed = []
|
||||||
|
for recs in self.incident_type.sub_type:
|
||||||
|
listed.append(recs.id)
|
||||||
|
return {'domain': {'incident_sub_type': [('id', 'in', listed)]}}
|
||||||
|
|
||||||
|
@api.constrains('incident_type')
|
||||||
|
def create_manage_incidents(self):
|
||||||
|
for rec in self:
|
||||||
|
print('created')
|
||||||
|
self.env['manage.incident'].create({
|
||||||
|
'employee_disciplinary_id': rec.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
@api.constrains('employee')
|
||||||
|
def holds_hr_employee(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.disp_name = rec.employee.employee_code_list1
|
||||||
|
|
||||||
|
|
||||||
|
class IncidentEmployee(models.Model):
|
||||||
|
_name = 'incident.employee'
|
||||||
|
|
||||||
|
name = fields.Char(string='Incident')
|
||||||
|
sub_type = fields.Many2many('incident.sub.employee', string='Sub type')
|
||||||
|
|
||||||
|
|
||||||
|
class IncidentSubEmployee(models.Model):
|
||||||
|
_name = 'incident.sub.employee'
|
||||||
|
|
||||||
|
name = fields.Char(string='Incident Sub')
|
||||||
|
|
||||||
|
class DisciplinaryMistakeType(models.Model):
|
||||||
|
_name = 'disciplinary.mistake.type'
|
||||||
|
_description = 'Disciplinary Mistake Type'
|
||||||
|
|
||||||
|
name = fields.Char(string="Mistake Type", required=True)
|
||||||
|
|
||||||
|
class IncidentSubEmployee(models.Model):
|
||||||
|
_name = 'incident.sub.employee'
|
||||||
|
_description = 'Incident Sub Type'
|
||||||
|
|
||||||
|
name = fields.Char(string="Incident Sub Type", required=True)
|
||||||
|
|
||||||
|
class EmployeeDisciplinaryLines(models.Model):
|
||||||
|
_name = 'employee.disciplinary.line'
|
||||||
|
_rec_name = 'hr_emp_many'
|
||||||
|
|
||||||
|
emp_many = fields.Many2one('employee.disciplinary', string='Employee Disp')
|
||||||
|
hr_emp_many = fields.Many2one('hr.employee', string='Employee Number')
|
||||||
|
hr_emp_many_name = fields.Char(related='hr_emp_many.name', string='Employee Name')
|
||||||
|
|
||||||
|
|
||||||
|
class ManageIncident(models.Model):
|
||||||
|
_name = 'manage.incident'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
|
||||||
|
# employee_name_ids = fields.One2many('employee.disciplinary','employee',string="Employee Name")
|
||||||
|
employee_disciplinary_id = fields.Many2one("employee.disciplinary", string="Employee Disp")
|
||||||
|
employee_code_list1 = fields.Many2many("hr.employee", string="Employees Involved in the Incident",
|
||||||
|
related='employee_disciplinary_id.emp_many_disp', tracking=True)
|
||||||
|
incident_dat = fields.Datetime(related='employee_disciplinary_id.incident_date', string='Incident Date & Time',
|
||||||
|
tracking=True)
|
||||||
|
employee_by_code = fields.Many2one(related='employee_disciplinary_id.employee_code',
|
||||||
|
string="Reported By Employee Name")
|
||||||
|
incident_sum = fields.Text(related='employee_disciplinary_id.incident_summary', string='Incident Summary',
|
||||||
|
tracking=True)
|
||||||
|
incident_typ = fields.Many2one(related='employee_disciplinary_id.incident_type', string="Incident Type",
|
||||||
|
tracking=True)
|
||||||
|
incident_sub_typ = fields.Many2many(related='employee_disciplinary_id.incident_sub_type',
|
||||||
|
string="Incident Sub Type", tracking=True)
|
||||||
|
# corrective_action_emp_id = fields.Many2one(related='employee_disciplinary_id.corrective_action_id',
|
||||||
|
# string="Corrective Action", tracking=True)
|
||||||
|
state = fields.Selection(([
|
||||||
|
('pending_inquiry', 'Pending Inquiry'),
|
||||||
|
('in_progress', 'In Process'),
|
||||||
|
('closed', 'Closed')
|
||||||
|
]), string="Status", default='pending_inquiry', tracking=True)
|
||||||
|
emp_incident = fields.Many2one('hr.employee')
|
||||||
|
employee_inquiry = fields.One2many('manage.incident.line', 'employee_inquiry_state')
|
||||||
|
|
||||||
|
def button_in_progress(self):
|
||||||
|
self.state = 'in_progress'
|
||||||
|
|
||||||
|
# def button_closed(self):
|
||||||
|
# for rec in self:
|
||||||
|
# rec.state = 'closed'
|
||||||
|
|
||||||
|
def button_closed(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.state = 'closed'
|
||||||
|
update_into_employee = rec.env['hr.employee'].search([('id', '=', rec.employee_code_list1.id)])
|
||||||
|
records = {
|
||||||
|
}
|
||||||
|
if records:
|
||||||
|
update_into_employee.write(records)
|
||||||
|
|
||||||
|
print('triggered 2')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class CorrectiveActions(models.Model):
|
||||||
|
_name = "corrective.actions"
|
||||||
|
|
||||||
|
name = fields.Char(string="Name")
|
||||||
|
|
||||||
|
|
||||||
|
class ManageIncidentLine(models.Model):
|
||||||
|
_name = 'manage.incident.line'
|
||||||
|
_inherit = ['mail.thread']
|
||||||
|
_description = 'Manage Incident Line'
|
||||||
|
|
||||||
|
corrective_action_id = fields.Many2one('corrective.actions', string="Corrective Action", tracking=True,
|
||||||
|
required=True)
|
||||||
|
internal_panel = fields.Many2many('hr.employee', string="Internal Panel Members", tracking=True,
|
||||||
|
required=True)
|
||||||
|
external_panel = fields.Char(string="External Panel Members")
|
||||||
|
due_date = fields.Date(string="Due Date")
|
||||||
|
last_action_date = fields.Datetime(string="Last Action Date", compute='_compute_last_action_date',
|
||||||
|
default=date.today())
|
||||||
|
recommendation = fields.Char(string="Recommendation", tracking=True, required=True)
|
||||||
|
venue = fields.Char(string='Venue')
|
||||||
|
inquiry_summary = fields.Char(string='Inquiry Summary', tracking=True, required=True)
|
||||||
|
is_guilty = fields.Selection(([
|
||||||
|
('yes', 'Yes'),
|
||||||
|
('no', 'No'),
|
||||||
|
]), string="Is the Employee Guilt of the Incident", default='no', tracking=True)
|
||||||
|
inquiry_date = fields.Datetime(string="Inquiry Date and Time", required=True)
|
||||||
|
employee_inquiry_state = fields.Many2one('manage.incident')
|
||||||
|
|
||||||
|
@api.depends('inquiry_date')
|
||||||
|
def _compute_last_action_date(self):
|
||||||
|
for line in self:
|
||||||
|
if not line.employee_inquiry_state or line == line.employee_inquiry_state.employee_inquiry[0]:
|
||||||
|
line.last_action_date = False
|
||||||
|
else:
|
||||||
|
previous_line = line.employee_inquiry_state.employee_inquiry.filtered(lambda l: l.inquiry_date < line.inquiry_date)
|
||||||
|
sorted_previous_line = previous_line.sorted(key=lambda l: l.inquiry_date, reverse=True)
|
||||||
|
if sorted_previous_line:
|
||||||
|
line.last_action_date = sorted_previous_line[0].inquiry_date
|
||||||
|
else:
|
||||||
|
line.last_action_date = False
|
||||||
|
|
@ -0,0 +1,179 @@
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
|
||||||
|
class HRDisciplinaryAction(models.Model):
|
||||||
|
_name = 'hr.employee.disciplinary'
|
||||||
|
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||||
|
_description = 'Employee Disciplinary Management'
|
||||||
|
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
name = fields.Char('Reference', copy=False, readonly=True, default=lambda x: _('New'))
|
||||||
|
employee_id = fields.Many2one('hr.employee', string="Employee", required=True)
|
||||||
|
company_id = fields.Many2one('res.company', string="Company", required=True, default=lambda self: self.env.company)
|
||||||
|
employee_code = fields.Char(string='Employee Code', related='employee_id.employee_id',tracking=True,required=True)
|
||||||
|
# unit_id = fields.Many2one('unit.master', string="Unit",tracking=True)
|
||||||
|
department_id = fields.Many2one('hr.department', string="Department",tracking=True)
|
||||||
|
designation_id = fields.Many2one('hr.job', string="Designation",tracking=True)
|
||||||
|
doj = fields.Date(string="Date of Joining",tracking=True)
|
||||||
|
referred_by_id = fields.Many2one('res.users', string="Referred By",tracking=True)
|
||||||
|
loss_of_cost = fields.Float(string="Loss of Cost")
|
||||||
|
# employee_section_id = fields.Many2one('section.master',string='Section')
|
||||||
|
disciplinary_complaint_line_ids = fields.One2many('hr.disciplinary.complaint.line','disciplinary_id',string = 'Complaint Lines')
|
||||||
|
disciplinary_action_line_ids = fields.One2many('hr.disciplinary.action.line','disciplinary_id',string = 'Action Lines')
|
||||||
|
state = fields.Selection([
|
||||||
|
('new', 'New'),
|
||||||
|
('submitted', 'Submitted'),
|
||||||
|
('pending', 'Pending'),
|
||||||
|
('closed', 'Closed'),
|
||||||
|
('cancel', 'Cancel')
|
||||||
|
], default='new',tracking=True,string='State')
|
||||||
|
complaint_name = fields.Text('Complaint', compute='_compute_complaint_name', store=True)
|
||||||
|
name_1 = fields.Char('Name')
|
||||||
|
disciplinary_id = fields.Many2one('hr.employee.disciplinary', string="Disciplinary")
|
||||||
|
complaint_date = fields.Date('Complaint Date')
|
||||||
|
language_id = fields.Many2one('res.lang', 'Language')
|
||||||
|
complaint_type_id = fields.Many2one('disciplinary.complaint.type', string="Complaint Type")
|
||||||
|
mistake_type_id = fields.Many2one('disciplinary.mistake.type', string="Mistake Type", required=True)
|
||||||
|
complaint = fields.Char(string='Complaints')
|
||||||
|
employee_id_2 = fields.Many2one('hr.employee', string='Employee')
|
||||||
|
related_record_count = fields.Integer(string="Disciplinary Action Records Count", compute="_compute_related_record_count")
|
||||||
|
# general_cat = fields.Many2one('general.category', string="General Category", tracking=True)
|
||||||
|
# cat_id = fields.Many2one('hr.category','Category')
|
||||||
|
occurrences = fields.Integer('Occurrences', store=True)
|
||||||
|
severe = fields.Char('Severe')
|
||||||
|
major = fields.Char('Major')
|
||||||
|
less_major = fields.Char('Less Major')
|
||||||
|
negligible = fields.Char('Negligible')
|
||||||
|
normal = fields.Char('Normal')
|
||||||
|
total_mistakes = fields.Char('Total Mistakes')
|
||||||
|
memo = fields.Char('Memo')
|
||||||
|
explanation = fields.Char('Explanation')
|
||||||
|
show_cause = fields.Char('Show Cause')
|
||||||
|
charge_sheet = fields.Char('Charge Sheet')
|
||||||
|
warning = fields.Char('Warning')
|
||||||
|
enquiry_notice = fields.Char('Enquiry Notice')
|
||||||
|
recovery_order = fields.Char('Recovery_ Order')
|
||||||
|
stoppage_of_increment = fields.Char('Stoppage Of Increment')
|
||||||
|
demotion = fields.Char('Demotion')
|
||||||
|
total_actions = fields.Char('Total Actions')
|
||||||
|
normal_action = fields.Char('Normal Actions')
|
||||||
|
suspension = fields.Char('Suspension')
|
||||||
|
total_cost = fields.Float('Total Cost')
|
||||||
|
|
||||||
|
@api.depends('employee_id')
|
||||||
|
def _compute_related_record_count(self):
|
||||||
|
for record in self:
|
||||||
|
record.related_record_count = self.env['hr.employee.disciplinary'].search_count([('employee_id', '=', record.employee_id.id)])
|
||||||
|
|
||||||
|
def action_open_related_records(self):
|
||||||
|
return {
|
||||||
|
'name': 'Disciplinary Action Records',
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'res_model': 'hr.employee.disciplinary',
|
||||||
|
'view_mode': 'list',
|
||||||
|
'domain': [('employee_id', '=', self.employee_id.id)],
|
||||||
|
'context': {'default_employee_id': self.employee_id.id},
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.depends('disciplinary_complaint_line_ids.complaint')
|
||||||
|
def _compute_complaint_name(self):
|
||||||
|
for record in self:
|
||||||
|
complaints = record.disciplinary_complaint_line_ids.mapped('complaint')
|
||||||
|
record.complaint_name = "\n".join(filter(None, complaints))
|
||||||
|
|
||||||
|
def action_set_submitted(self):
|
||||||
|
self.state = 'submitted'
|
||||||
|
|
||||||
|
def action_set_pending(self):
|
||||||
|
self.state = 'pending'
|
||||||
|
|
||||||
|
def action_set_closed(self):
|
||||||
|
self.state = 'closed'
|
||||||
|
|
||||||
|
def action_set_cancel(self):
|
||||||
|
self.state = 'cancel'
|
||||||
|
|
||||||
|
def action_reset_to_new(self):
|
||||||
|
self.state = 'new'
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
for vals in vals_list:
|
||||||
|
if not vals.get('name') or vals['name'] == _('New'):
|
||||||
|
vals['name'] = self.env['ir.sequence'].next_by_code('hr.employee.sequence') or _('New')
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
|
@api.onchange('employee_id')
|
||||||
|
def _onchange_employee_id(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.employee_id:
|
||||||
|
rec.employee_code = rec.employee_id.employee_id or ''
|
||||||
|
rec.department_id = rec.employee_id.department_id.id
|
||||||
|
rec.designation_id = rec.employee_id.job_id.id
|
||||||
|
rec.doj = rec.employee_id.doj
|
||||||
|
rec.company_id = rec.employee_id.company_id.id
|
||||||
|
# rec.unit_id = rec.employee_id.unit_name_hr.id if rec.employee_id.unit_name_hr else False
|
||||||
|
# rec.employee_section_id = rec.employee_id.section_name_hr.id if rec.employee_id.section_name_hr else False
|
||||||
|
else:
|
||||||
|
rec.employee_code = False
|
||||||
|
rec.department_id = False
|
||||||
|
rec.designation_id = False
|
||||||
|
rec.doj = False
|
||||||
|
|
||||||
|
|
||||||
|
class DisciplinaryComplaintLine(models.Model):
|
||||||
|
_name = 'hr.disciplinary.complaint.line'
|
||||||
|
_description = 'Disciplinary Complaint Line'
|
||||||
|
|
||||||
|
name = fields.Char('Name')
|
||||||
|
disciplinary_id = fields.Many2one('hr.employee.disciplinary',string="Disciplinary")
|
||||||
|
complaint_date = fields.Date('Complaint Date')
|
||||||
|
language_id = fields.Many2one('res.lang','Language')
|
||||||
|
complaint_type_id = fields.Many2one('disciplinary.complaint.type',string="Complaint Type")
|
||||||
|
mistake_type_id = fields.Many2one('disciplinary.mistake.type',string="Mistake Type")
|
||||||
|
complaint = fields.Char(string='Complaints')
|
||||||
|
employee_id = fields.Many2one('hr.employee', string='Employee')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class DisciplinaryActionLine(models.Model):
|
||||||
|
_name = 'hr.disciplinary.action.line'
|
||||||
|
_description = 'Disciplinary Action Line'
|
||||||
|
|
||||||
|
name = fields.Char('Name')
|
||||||
|
disciplinary_id = fields.Many2one('hr.employee.disciplinary',string="Disciplinary")
|
||||||
|
action_taken_date = fields.Date('Action On')
|
||||||
|
action_type_id = fields.Many2one('disciplinary.action.type',string="Action Type")
|
||||||
|
action = fields.Char(string='Description')
|
||||||
|
action_name = fields.Char('ActionName')
|
||||||
|
related_complaint_id = fields.Many2one('hr.disciplinary.complaint.line', string="Related Complaint",
|
||||||
|
domain="[('disciplinary_id', '=', disciplinary_id)]")
|
||||||
|
employee_id = fields.Many2one('hr.employee', string='Employee')
|
||||||
|
|
||||||
|
@api.constrains('action_taken_date')
|
||||||
|
def _check_action_taken_date(self):
|
||||||
|
for record in self:
|
||||||
|
if record.action_taken_date and record.action_taken_date > date.today():
|
||||||
|
raise ValidationError("The Action On date cannot be in the future.")
|
||||||
|
|
||||||
|
|
||||||
|
class DisciplinaryActionType(models.Model):
|
||||||
|
_name = 'disciplinary.action.type'
|
||||||
|
_description = 'Action Type'
|
||||||
|
|
||||||
|
name = fields.Char('Name', required=True)
|
||||||
|
|
||||||
|
class DisciplinaryComplaintType(models.Model):
|
||||||
|
_name = 'disciplinary.complaint.type'
|
||||||
|
_description = 'Complaint Type'
|
||||||
|
|
||||||
|
name = fields.Char('Name', required=True)
|
||||||
|
|
||||||
|
class DisciplinaryMistakeType(models.Model):
|
||||||
|
_name = 'disciplinary.mistake.type'
|
||||||
|
_description = 'Mistake Type'
|
||||||
|
|
||||||
|
name = fields.Char('Name', required=True)
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
access_employee_disciplinary,employee_disciplinary,model_employee_disciplinary,,1,1,1,1
|
||||||
|
access_incident_employee,incident_employee,model_incident_employee,,1,1,1,1
|
||||||
|
access_incident_sub_employee,incident_sub_employee,model_incident_sub_employee,,1,1,1,1
|
||||||
|
access_employee_disciplinary_line,employee_disciplinary_line,model_employee_disciplinary_line,,1,1,1,1
|
||||||
|
access_manage_incident,manage_incident,model_manage_incident,,1,1,1,1
|
||||||
|
access_manage_incident_line,manage_incident_line,model_manage_incident_line,,1,1,1,1
|
||||||
|
access_corrective_actions,corrective_actions,model_corrective_actions,,1,1,1,1
|
||||||
|
|
||||||
|
access_hr_employee_disciplinary,hr.employee.disciplinary,model_hr_employee_disciplinary,,1,1,1,1
|
||||||
|
access_hr_disciplinary_complaint_line,hr.disciplinary.complaint.line,model_hr_disciplinary_complaint_line,,1,1,1,1
|
||||||
|
access_hr_disciplinary_action_line,hr.disciplinary.action.line,model_hr_disciplinary_action_line,,1,1,1,1
|
||||||
|
access_disciplinary_action_type,disciplinary.action.type,model_disciplinary_action_type,,1,1,1,1
|
||||||
|
access_disciplinary_complaint_type,disciplinary.complaint.type,model_disciplinary_complaint_type,,1,1,1,1
|
||||||
|
access_disciplinary_mistake_type,disciplinary.mistake.type,model_disciplinary_mistake_type,,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_disciplinary_complaint_type_list" model="ir.ui.view">
|
||||||
|
<field name="name">disciplinary.complaint.type</field>
|
||||||
|
<field name="model">disciplinary.complaint.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_disciplinary_complaint_type_form" model="ir.ui.view">
|
||||||
|
<field name="name">disciplinary.complaint.type.form</field>
|
||||||
|
<field name="model">disciplinary.complaint.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_disciplinary_complaint_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Employee Disciplinary Complaint Type</field>
|
||||||
|
<field name="res_model">disciplinary.complaint.type</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="menu_view_disciplinary_complaint"
|
||||||
|
name="Disciplinary Complaints"
|
||||||
|
action="action_disciplinary_complaint_type"
|
||||||
|
parent="menu_employee_disciplinary_root"
|
||||||
|
sequence="19"/>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_employee_disciplinary_complaint_line_list" model="ir.ui.view">
|
||||||
|
<field name="name">hr.disciplinary.action.line</field>
|
||||||
|
<field name="model">hr.disciplinary.action.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_employee_disciplinary_complaint_line_form" model="ir.ui.view">
|
||||||
|
<field name="name">hr.disciplinary.action.line.form</field>
|
||||||
|
<field name="model">hr.disciplinary.action.line</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name" />
|
||||||
|
<field name="disciplinary_id" />
|
||||||
|
<field name="action_taken_date" />
|
||||||
|
<field name="action_type_id" />
|
||||||
|
<field name="action" />
|
||||||
|
<field name="related_complaint_id" />
|
||||||
|
<field name="employee_id" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_employee_disciplinary_complaint_line" model="ir.actions.act_window">
|
||||||
|
<field name="name">Employee Disciplinary Action</field>
|
||||||
|
<field name="res_model">hr.disciplinary.action.line</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="menu_view_employee__disciplinary_complaint"
|
||||||
|
name="Employee Disciplinary Complaints"
|
||||||
|
action="action_employee_disciplinary_complaint_line"
|
||||||
|
parent="menu_employee_disciplinary_root"
|
||||||
|
sequence="20"/>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_disciplinary_action_type_list" model="ir.ui.view">
|
||||||
|
<field name="name">disciplinary.action.type</field>
|
||||||
|
<field name="model">disciplinary.action.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_disciplinary_action_type_form" model="ir.ui.view">
|
||||||
|
<field name="name">disciplinary.action.type.form</field>
|
||||||
|
<field name="model">disciplinary.action.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name" />
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_disciplinary_action_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Employee Disciplinary Action Type</field>
|
||||||
|
<field name="res_model">disciplinary.action.type</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="menu_view_disciplinary_action_type"
|
||||||
|
name="Disciplinary Action Type"
|
||||||
|
action="action_disciplinary_action_type"
|
||||||
|
parent="menu_employee_disciplinary_root"
|
||||||
|
sequence="21"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,264 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<record id="employee_disciplinary_list" model="ir.ui.view">
|
||||||
|
<field name="name">Employee Disciplinary list</field>
|
||||||
|
<field name="model">employee.disciplinary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="incident_date"/>
|
||||||
|
<field name="incident_type"/>
|
||||||
|
<field name="incident_sub_type" widget="many2many_tags"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="employee_disciplinary_form" model="ir.ui.view">
|
||||||
|
<field name="name">Employee Disciplinary form</field>
|
||||||
|
<field name="model">employee.disciplinary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="incident_date"/>
|
||||||
|
<field name="incident_type" options="{'no_open': True,}"/>
|
||||||
|
<field name="incident_sub_type" widget="many2many_tags" readonly="0"
|
||||||
|
options="{'no_open': True}"/>
|
||||||
|
<label for="employee_code" string="Reported By Employee Code"/>
|
||||||
|
<div class="address_format">
|
||||||
|
<field name="employee_code" style="width: 50%" options="{'no_open': True,}"/>
|
||||||
|
<field name="employee_name" style="width: 50%"/>
|
||||||
|
</div>
|
||||||
|
<field name="incident_details"/>
|
||||||
|
<field name="seized_items"/>
|
||||||
|
<field name="incident_summary"/>
|
||||||
|
<field name="attach" widget="many2many_binary" options="{'preview_image': True}"/>
|
||||||
|
<field name="emp_many_disp" widget="many2many_tags" context="{'new_custom_name': True}"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="manage_incident_list" model="ir.ui.view">
|
||||||
|
<field name="name">Manage Incident list</field>
|
||||||
|
<field name="model">manage.incident</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list create="0">
|
||||||
|
<field name="employee_code_list1" widget="many2many_tags" context="{'new_custom_name': True}"/>
|
||||||
|
<!-- <field name="incident_dat"/>-->
|
||||||
|
<field name="incident_typ"/>
|
||||||
|
<field name="incident_sub_typ" widget="many2many_tags"/>
|
||||||
|
<field name="incident_sum"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="manage_incident_form" model="ir.ui.view">
|
||||||
|
<field name="name">Manage Incident form</field>
|
||||||
|
<field name="model">manage.incident</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form create="0">
|
||||||
|
<header>
|
||||||
|
<button name="button_in_progress" string="In Progress" class="oe_highlight" type="object"/>
|
||||||
|
<!-- states="pending_inquiry"/>-->
|
||||||
|
<!-- attrs="{'invisible' : ('state','!=','pending_inquiry')}"/>-->
|
||||||
|
<button name="button_closed" string="Closed" class="oe_highlight" type="object"/>
|
||||||
|
<!-- states="in_progress"/>-->
|
||||||
|
<!-- attrs="{'invisible' : ('state','!=','in_progress')}"/>-->
|
||||||
|
<field name="state" widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="employee_code_list1" widget="many2many_tags"
|
||||||
|
context="{'new_custom_name': True}"/>
|
||||||
|
<field name="employee_by_code" options="{'no_open': True,}"/>
|
||||||
|
<!-- <field name="incident_dat"/>-->
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="incident_typ" options="{'no_open': True,}"/>
|
||||||
|
<field name="incident_sub_typ" widget="many2many_tags"/>
|
||||||
|
<field name="incident_sum"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<field name="employee_inquiry" string="Manage Incident">
|
||||||
|
<!-- attrs="{'readonly': [('state', '=','closed')]}">-->
|
||||||
|
<list>
|
||||||
|
<field name="inquiry_date"/>
|
||||||
|
<field name="corrective_action_id"/>
|
||||||
|
<field name="due_date"/>
|
||||||
|
<field name="last_action_date"/>
|
||||||
|
</list>
|
||||||
|
<form>
|
||||||
|
<group>
|
||||||
|
<field name="inquiry_date"/>
|
||||||
|
<field name="venue"/>
|
||||||
|
<field name="internal_panel" widget="many2many_tags"
|
||||||
|
context="{'new_custom_name': True}"/>
|
||||||
|
<field name="external_panel"/>
|
||||||
|
<field name="inquiry_summary"/>
|
||||||
|
<field name="is_guilty" widget="radio" options="{'horizontal':true}"/>
|
||||||
|
<field name="corrective_action_id" options="{'no_open': True,}"/>
|
||||||
|
<field name="recommendation"/>
|
||||||
|
<field name="due_date"/>
|
||||||
|
<field name="last_action_date"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- <record id="career_history_tab_sub_menu" model="ir.ui.view">-->
|
||||||
|
<!-- <field name="name">Career History Tab Sub Menu</field>-->
|
||||||
|
<!-- <field name="model">hr.employee</field>-->
|
||||||
|
<!-- <field name="inherit_id" ref="employee_life_cycle.career_history_tab_menu"/>-->
|
||||||
|
<!-- <field name="arch" type="xml">-->
|
||||||
|
<!-- <xpath expr="//page/field[@name='career_history_field']" position="after">-->
|
||||||
|
<!-- <!– <group name="career_hist_sub_menu" string="Disciplinary Actions">–>-->
|
||||||
|
<!-- <!– <field name="employee_name_ids1" string="Manage Incident">–>-->
|
||||||
|
<!-- <!– <list editable="0" create="0">–>-->
|
||||||
|
<!-- <!– <field name="incident_date"/>–>-->
|
||||||
|
<!-- <!– <field name="incident_type"/>–>-->
|
||||||
|
<!-- <!– <field name="incident_sub_type"/>–>-->
|
||||||
|
<!-- <!–<!– <field name="corrective_action_id"/>–>–>-->
|
||||||
|
<!-- <!– </list>–>-->
|
||||||
|
<!-- <!– </field>–>-->
|
||||||
|
<!-- <!– </group>–>-->
|
||||||
|
<!-- <field name="employee_self_service_line_ids" string="Manage Incident" readonly="1">-->
|
||||||
|
<!-- <list>-->
|
||||||
|
<!-- <field name="incident_dat"/>-->
|
||||||
|
<!-- <field name="incident_typ"/>-->
|
||||||
|
<!-- <field name="incident_sub_typ"/>-->
|
||||||
|
<!-- </list>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
|
||||||
|
<!-- <record id="career_history_tab_sub_menu_self" model="ir.ui.view">-->
|
||||||
|
<!-- <field name="name">Career History Tab Sub Menu Self Service</field>-->
|
||||||
|
<!-- <field name="model">hr.employee</field>-->
|
||||||
|
<!-- <field name="inherit_id" ref="employee_self_service.view_employee_form_self_service"/>-->
|
||||||
|
<!-- <field name="arch" type="xml">-->
|
||||||
|
<!-- <xpath expr="//page/field[@name='career_history_field']" position="after">-->
|
||||||
|
<!-- <!– <group name="career_hist_sub_menu" string="Disciplinary Actions">–>-->
|
||||||
|
<!-- <!– <field name="employee_name_ids1" string="Manage Incident">–>-->
|
||||||
|
<!-- <!– <list editable="0" create="0">–>-->
|
||||||
|
<!-- <!– <field name="incident_date"/>–>-->
|
||||||
|
<!-- <!– <field name="incident_type"/>–>-->
|
||||||
|
<!-- <!– <field name="incident_sub_type"/>–>-->
|
||||||
|
<!-- <!– <!– <field name="corrective_action_id"/>–>–>-->
|
||||||
|
<!-- <!– </list>–>-->
|
||||||
|
<!-- <!– </field>–>-->
|
||||||
|
<!-- <!– </group>–>-->
|
||||||
|
<!-- <field name="employee_self_service_line_ids" string="Manage Incident" readonly="1">-->
|
||||||
|
<!-- <list>-->
|
||||||
|
<!-- <field name="incident_dat"/>-->
|
||||||
|
<!-- <field name="incident_typ"/>-->
|
||||||
|
<!-- <field name="incident_sub_typ"/>-->
|
||||||
|
<!-- </list>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </xpath>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </record>-->
|
||||||
|
|
||||||
|
<record id="incident_employee_list" model="ir.ui.view">
|
||||||
|
<field name="name">incident Employee list</field>
|
||||||
|
<field name="model">incident.employee</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sub_type" widget="many2many_tags"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="incident_employee_form" model="ir.ui.view">
|
||||||
|
<field name="name">incident Employee form</field>
|
||||||
|
<field name="model">incident.employee</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<form>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="sub_type" widget="many2many_tags"/>
|
||||||
|
</group>
|
||||||
|
</form>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="employee_disciplinary_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Incident Reporting</field>
|
||||||
|
<field name="res_model">employee.disciplinary</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="manage_incident_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Manage Incident</field>
|
||||||
|
<field name="res_model">manage.incident</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="incident_employee_action" model="ir.actions.act_window">
|
||||||
|
<field name="name">Incident Type</field>
|
||||||
|
<field name="res_model">incident.employee</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_employee_disciplinary" model="ir.actions.act_window">
|
||||||
|
<field name="name">Employee Disciplinary</field>
|
||||||
|
<field name="res_model">hr.employee.disciplinary</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="employee_disciplinary_menu"
|
||||||
|
name="Employee Disciplinary Management"
|
||||||
|
parent="hr.menu_hr_root"
|
||||||
|
action="employee_disciplinary_action"
|
||||||
|
sequence="105"/>
|
||||||
|
|
||||||
|
<!-- Child Menu (moved inside) -->
|
||||||
|
<menuitem id="menu_employee_disciplinary_root"
|
||||||
|
name="Employee Disciplinary Configuration"
|
||||||
|
parent="employee_disciplinary_menu"
|
||||||
|
sequence="10"/>
|
||||||
|
|
||||||
|
<!-- Sub Menu -->
|
||||||
|
<menuitem id="menu_employee_disciplinary"
|
||||||
|
name="Employee Disciplinary"
|
||||||
|
parent="employee_disciplinary_menu"
|
||||||
|
action="action_hr_employee_disciplinary"
|
||||||
|
sequence="01"/>
|
||||||
|
|
||||||
|
<!-- <menuitem id="manage_incident_employee"-->
|
||||||
|
<!-- name="Incident Type"-->
|
||||||
|
<!-- parent="employee_disciplinary_menu"-->
|
||||||
|
<!-- action="incident_employee_action"-->
|
||||||
|
<!-- sequence="3"/>-->
|
||||||
|
|
||||||
|
<!-- <menuitem id="manage_incident_sub_menu"-->
|
||||||
|
<!-- name="Manage Incident"-->
|
||||||
|
<!-- parent="employee_disciplinary_menu"-->
|
||||||
|
<!-- action="manage_incident_action"-->
|
||||||
|
<!-- sequence="2"/>-->
|
||||||
|
|
||||||
|
<!-- <menuitem id="employee_disciplinary_sub_menu"-->
|
||||||
|
<!-- name="Incident Reporting"-->
|
||||||
|
<!-- parent="employee_disciplinary_menu"-->
|
||||||
|
<!-- action="employee_disciplinary_action"-->
|
||||||
|
<!-- sequence="1"/>-->
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
<odoo>
|
||||||
|
<!-- Disciplinary Form View -->
|
||||||
|
<record id="view_hr_employee_disciplinary_form" model="ir.ui.view">
|
||||||
|
<field name="name">employee.disciplinary.form</field>
|
||||||
|
<field name="model">hr.employee.disciplinary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Employee Disciplinary">
|
||||||
|
<header>
|
||||||
|
|
||||||
|
<button name="action_set_submitted"
|
||||||
|
type="object"
|
||||||
|
string="Submit"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'new'"/>
|
||||||
|
|
||||||
|
<button name="action_set_pending"
|
||||||
|
type="object"
|
||||||
|
string="Pending"
|
||||||
|
class="btn-warning"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
|
||||||
|
<button name="action_set_closed"
|
||||||
|
type="object"
|
||||||
|
string="Closed"
|
||||||
|
class="btn-success"
|
||||||
|
invisible="state != 'pending'"/>
|
||||||
|
|
||||||
|
<button name="action_set_cancel" type="object" string="Cancel"
|
||||||
|
class="btn-danger" invisible="state not in ['new', 'submitted', 'pending']"/>
|
||||||
|
<button name="action_reset_to_new" type="object"
|
||||||
|
string="Reset to New" class="btn-secondary" invisible="state != 'cancel'"/>
|
||||||
|
<field name="state" widget="statusbar"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_button_box" name="button_box">
|
||||||
|
<button name="action_open_related_records"
|
||||||
|
type="object"
|
||||||
|
icon="fa-gavel"
|
||||||
|
class="oe_stat_button full-width-button"
|
||||||
|
string="Disciplinary">
|
||||||
|
<field name="related_record_count" widget="statinfo"/>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>
|
||||||
|
<field name="name" readonly="1"/>
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="employee_code"/>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="designation_id"/>
|
||||||
|
<field name="doj"/>
|
||||||
|
<!-- <field name="general_cat"/>-->
|
||||||
|
<field name="referred_by_id"/>
|
||||||
|
<!-- <field name="occurrences"/>-->
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="company_id"/>
|
||||||
|
<!-- <field name="unit_id"/>-->
|
||||||
|
<field name="department_id"/>
|
||||||
|
<!-- <field name="employee_section_id"/>-->
|
||||||
|
<field name="loss_of_cost"/>
|
||||||
|
<!-- <field name="cat_id"/>-->
|
||||||
|
<field name="total_cost"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group string="Complaints" colspan="2">
|
||||||
|
<group colspan="1">
|
||||||
|
<field name="complaint_date"/>
|
||||||
|
<field name="language_id"/>
|
||||||
|
<field name="complaint_type_id"/>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
<group colspan="1">
|
||||||
|
<field name="mistake_type_id"/>
|
||||||
|
<field name="complaint"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<!-- <page name="'complaints" string = "Complaints">-->
|
||||||
|
<!-- <field name="disciplinary_complaint_line_ids">-->
|
||||||
|
<!-- <list string="complaints" editable="bottom">-->
|
||||||
|
<field name="name" column_invisible="1"/>
|
||||||
|
<field name="complaint_date"/>
|
||||||
|
<field name="language_id"/>
|
||||||
|
<field name="complaint_type_id"/>
|
||||||
|
<field name="mistake_type_id"/>
|
||||||
|
<field name="complaint"/>
|
||||||
|
<field name="disciplinary_id" column_invisible="1"/>
|
||||||
|
<field name="employee_id" column_invisible="1"/>
|
||||||
|
<!-- </list>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
|
||||||
|
<!-- </page>-->
|
||||||
|
<page name="'actions" string="Actions">
|
||||||
|
<field name="disciplinary_action_line_ids">
|
||||||
|
<list string="Action Lines" editable="bottom">
|
||||||
|
<field name="name" column_invisible="1"/>
|
||||||
|
<field name="action_name"/>
|
||||||
|
<!-- <field name="action_taken_date"/>-->
|
||||||
|
<field name="action_taken_date"
|
||||||
|
context="{'max_date': time.strftime('%Y-%m-%d')}"/>
|
||||||
|
<field name="action_type_id"/>
|
||||||
|
<field name="action"/>
|
||||||
|
<field name="disciplinary_id" column_invisible="1"/>
|
||||||
|
<field name="employee_id" column_invisible="1"/>
|
||||||
|
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
<chatter/>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
<!-- list View for Employee Disciplinary -->
|
||||||
|
<record id="view_hr_employee_disciplinary_list" model="ir.ui.view">
|
||||||
|
<field name="name">hr.employee.disciplinary.list</field>
|
||||||
|
<field name="model">hr.employee.disciplinary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list string="Employee Disciplinary">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="employee_id" string="Employee"/>
|
||||||
|
<field name="designation_id" string="Designation"/>
|
||||||
|
<field name="doj" string="Date of Joining"/>
|
||||||
|
<field name="company_id" string="Company"/>
|
||||||
|
<!-- <field name="unit_id" string="Unit"/>-->
|
||||||
|
<field name="department_id" string="Department"/>
|
||||||
|
<field name="state"
|
||||||
|
widget="badge"
|
||||||
|
decoration-primary="state == 'new'"
|
||||||
|
decoration-warning="state == 'submitted'"
|
||||||
|
decoration-info="state == 'pending'"
|
||||||
|
decoration-success="state == 'closed'"
|
||||||
|
decoration-danger="state == 'cancel'"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_hr_employee_disciplinary_search" model="ir.ui.view">
|
||||||
|
<field name="name">hr.employee.disciplinary.search</field>
|
||||||
|
<field name="model">hr.employee.disciplinary</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<search string="Search Employee Disciplinary">
|
||||||
|
<field name="employee_id" string="Employee"/>
|
||||||
|
<field name="employee_code" string="Employee Code"/>
|
||||||
|
</search>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_hr_employee_disciplinary" model="ir.actions.act_window">
|
||||||
|
<field name="name">Employee Disciplinary</field>
|
||||||
|
<field name="res_model">hr.employee.disciplinary</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="search_view_id" ref="view_hr_employee_disciplinary_search"/>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- <menuitem id="menu_employee_disciplinary_root" name="Employee Disciplinary" sequence="15" parent="hr.menu_hr_root"/>-->
|
||||||
|
<!-- <menuitem id="menu_employee_disciplinary" name="Employee Disciplinary"-->
|
||||||
|
<!-- parent="menu_employee_disciplinary_root"-->
|
||||||
|
<!-- action="action_hr_employee_disciplinary"-->
|
||||||
|
<!-- sequence="10"/>-->
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
|
||||||
|
<!-- list View -->
|
||||||
|
<record id="view_incident_sub_employee_list" model="ir.ui.view">
|
||||||
|
<field name="name">incident.sub.employee.list</field>
|
||||||
|
<field name="model">incident.sub.employee</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="view_incident_sub_employee_form" model="ir.ui.view">
|
||||||
|
<field name="name">incident.sub.employee.form</field>
|
||||||
|
<field name="model">incident.sub.employee</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Incident Sub Type">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_incident_sub_employee" model="ir.actions.act_window">
|
||||||
|
<field name="name">Incident Sub Type</field>
|
||||||
|
<field name="res_model">incident.sub.employee</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Menu -->
|
||||||
|
<!-- <menuitem id="menu_incident_sub_employee"-->
|
||||||
|
<!-- name="Incident Sub Type"-->
|
||||||
|
<!-- parent="employee_disciplinary_menu"-->
|
||||||
|
<!-- action="action_incident_sub_employee"/>-->
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
<odoo>
|
||||||
|
|
||||||
|
<!-- Tree View -->
|
||||||
|
<record id="view_mistake_type_list" model="ir.ui.view">
|
||||||
|
<field name="name">mistake.type.list</field>
|
||||||
|
<field name="model">disciplinary.mistake.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Form View -->
|
||||||
|
<record id="view_mistake_type_form" model="ir.ui.view">
|
||||||
|
<field name="name">mistake.type.form</field>
|
||||||
|
<field name="model">disciplinary.mistake.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="name"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_mistake_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Mistake Type</field>
|
||||||
|
<field name="res_model">disciplinary.mistake.type</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem
|
||||||
|
id="menu_mistake_type"
|
||||||
|
name="Mistake Type"
|
||||||
|
parent="menu_employee_disciplinary_root"
|
||||||
|
action="action_mistake_type"
|
||||||
|
sequence="02"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import models
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "Document Parser",
|
||||||
|
"summary": "Reusable AI-assisted document text and data extraction",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"category": "Tools",
|
||||||
|
"author": "Pranay",
|
||||||
|
"website": "https://www.ftprotech.com",
|
||||||
|
"license": "LGPL-3",
|
||||||
|
"depends": ["base"],
|
||||||
|
"data": [
|
||||||
|
"views/res_config_settings_views.xml",
|
||||||
|
],
|
||||||
|
"installable": True,
|
||||||
|
"application": False,
|
||||||
|
"auto_install": False,
|
||||||
|
"external_dependencies": {
|
||||||
|
"python": ["requests","python-docx"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
from . import document_parser_service
|
||||||
|
from . import res_config_settings
|
||||||
|
|
@ -0,0 +1,529 @@
|
||||||
|
import base64
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import mimetypes
|
||||||
|
import re
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from odoo import _, api, models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
try:
|
||||||
|
import pytesseract
|
||||||
|
except Exception: # pragma: no cover - optional dependency
|
||||||
|
pytesseract = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from PIL import Image
|
||||||
|
except Exception: # pragma: no cover - optional dependency
|
||||||
|
Image = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pdf2image import convert_from_bytes
|
||||||
|
except Exception: # pragma: no cover - optional dependency
|
||||||
|
convert_from_bytes = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from pypdf import PdfReader
|
||||||
|
except Exception: # pragma: no cover - optional dependency
|
||||||
|
PdfReader = None
|
||||||
|
|
||||||
|
try:
|
||||||
|
from docx import Document
|
||||||
|
except Exception: # pragma: no cover - optional dependency
|
||||||
|
Document = None
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class DocumentParserService(models.AbstractModel):
|
||||||
|
_name = "document.parser.service"
|
||||||
|
_description = "Document Parser Service"
|
||||||
|
|
||||||
|
TOGETHER_ENDPOINT = "https://api.together.xyz/v1/chat/completions"
|
||||||
|
OPENROUTER_ENDPOINT = "https://openrouter.ai/api/v1/chat/completions"
|
||||||
|
|
||||||
|
TOGETHER_MODELS = [
|
||||||
|
"Qwen/Qwen2.5-7B-Instruct-Turbo",
|
||||||
|
"meta-llama/Meta-Llama-3.1-70B-Instruct-Turbo",
|
||||||
|
]
|
||||||
|
OPENROUTER_MODELS = [
|
||||||
|
"qwen/qwen-2.5-7b-instruct",
|
||||||
|
"qwen/qwen-2.5-7b-instruct:free",
|
||||||
|
"deepseek/deepseek-chat:free",
|
||||||
|
]
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def parse_document(
|
||||||
|
self,
|
||||||
|
file_content,
|
||||||
|
filename=None,
|
||||||
|
required_fields=None,
|
||||||
|
extra_instructions=None,
|
||||||
|
json_schema=None,
|
||||||
|
):
|
||||||
|
if not file_content:
|
||||||
|
raise UserError(_("No document provided."))
|
||||||
|
if not filename:
|
||||||
|
raise UserError(_("Filename is required."))
|
||||||
|
|
||||||
|
binary = self._decode_file_content(file_content)
|
||||||
|
mimetype = self._detect_mimetype(binary, filename)
|
||||||
|
text_content = self._extract_text(binary, mimetype)
|
||||||
|
fields_spec = self._normalize_required_fields(required_fields or {})
|
||||||
|
|
||||||
|
if not text_content.strip():
|
||||||
|
return {
|
||||||
|
"filename": filename,
|
||||||
|
"mimetype": mimetype,
|
||||||
|
"text": "",
|
||||||
|
"result": {},
|
||||||
|
"provider": False,
|
||||||
|
"errors": [_("No text could be extracted from the document.")],
|
||||||
|
"error": _("No text could be extracted from the document."),
|
||||||
|
}
|
||||||
|
|
||||||
|
schema_text = json_schema or self._build_json_schema_text(fields_spec)
|
||||||
|
ai_result, provider_used, provider_errors = self._send_to_ai(
|
||||||
|
text_content=text_content[:45000],
|
||||||
|
schema_text=schema_text,
|
||||||
|
extra_instructions=extra_instructions,
|
||||||
|
)
|
||||||
|
|
||||||
|
if not ai_result:
|
||||||
|
ai_result = self._extract_with_heuristics(text_content, fields_spec)
|
||||||
|
|
||||||
|
ai_result = ai_result or {}
|
||||||
|
error_message = False
|
||||||
|
if not ai_result and provider_errors:
|
||||||
|
error_message = "; ".join(provider_errors[:3])
|
||||||
|
|
||||||
|
return {
|
||||||
|
"filename": filename,
|
||||||
|
"mimetype": mimetype,
|
||||||
|
"text": text_content,
|
||||||
|
"result": ai_result,
|
||||||
|
"provider": provider_used,
|
||||||
|
"errors": provider_errors,
|
||||||
|
"error": error_message,
|
||||||
|
}
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def extract_requested_data(self, file_content, filename, required_fields, extra_instructions=None, json_schema=None):
|
||||||
|
return self.parse_document(
|
||||||
|
file_content=file_content,
|
||||||
|
filename=filename,
|
||||||
|
required_fields=required_fields,
|
||||||
|
extra_instructions=extra_instructions,
|
||||||
|
json_schema=json_schema,
|
||||||
|
)["result"]
|
||||||
|
|
||||||
|
def _decode_file_content(self, file_content):
|
||||||
|
if isinstance(file_content, bytes):
|
||||||
|
if file_content.startswith((b"%PDF", b"\xFF\xD8", b"\x89PNG", b"PK")):
|
||||||
|
return file_content
|
||||||
|
try:
|
||||||
|
return base64.b64decode(file_content)
|
||||||
|
except Exception:
|
||||||
|
return file_content
|
||||||
|
if isinstance(file_content, str):
|
||||||
|
try:
|
||||||
|
return base64.b64decode(file_content)
|
||||||
|
except Exception as exc:
|
||||||
|
raise UserError(_("Invalid base64 document.")) from exc
|
||||||
|
raise UserError(_("Unsupported file format."))
|
||||||
|
|
||||||
|
def _detect_mimetype(self, binary, filename):
|
||||||
|
if filename:
|
||||||
|
guessed = mimetypes.guess_type(filename)[0]
|
||||||
|
if guessed:
|
||||||
|
return guessed
|
||||||
|
if binary.startswith(b"%PDF"):
|
||||||
|
return "application/pdf"
|
||||||
|
if binary.startswith(b"\xFF\xD8"):
|
||||||
|
return "image/jpeg"
|
||||||
|
if binary.startswith(b"\x89PNG"):
|
||||||
|
return "image/png"
|
||||||
|
if binary[:2] == b"PK":
|
||||||
|
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document"
|
||||||
|
return "application/octet-stream"
|
||||||
|
|
||||||
|
def _extract_text(self, binary, mimetype):
|
||||||
|
text_content = ""
|
||||||
|
try:
|
||||||
|
if mimetype == "application/pdf":
|
||||||
|
text_content = self._extract_text_from_pdf(binary)
|
||||||
|
elif mimetype in {"image/png", "image/jpeg", "image/jpg"}:
|
||||||
|
text_content = self._extract_text_from_image(binary)
|
||||||
|
elif mimetype == "application/vnd.openxmlformats-officedocument.wordprocessingml.document":
|
||||||
|
text_content = self._extract_text_from_docx(binary)
|
||||||
|
elif mimetype.startswith("text/"):
|
||||||
|
text_content = binary.decode("utf-8", errors="ignore")
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.exception("Document text extraction failed: %s", exc)
|
||||||
|
return (text_content or "").strip()
|
||||||
|
|
||||||
|
def _extract_text_from_pdf(self, binary):
|
||||||
|
extracted_parts = []
|
||||||
|
if PdfReader:
|
||||||
|
try:
|
||||||
|
reader = PdfReader(BytesIO(binary))
|
||||||
|
extracted_parts.extend(page.extract_text() or "" for page in reader.pages)
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.warning("PdfReader extraction failed: %s", exc)
|
||||||
|
text_content = "\n".join(part for part in extracted_parts if part).strip()
|
||||||
|
if text_content:
|
||||||
|
return text_content
|
||||||
|
if convert_from_bytes and pytesseract:
|
||||||
|
try:
|
||||||
|
images = convert_from_bytes(binary, dpi=300)
|
||||||
|
return "\n".join(
|
||||||
|
pytesseract.image_to_string(image)
|
||||||
|
for image in images
|
||||||
|
).strip()
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.warning("PDF OCR extraction failed: %s", exc)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _extract_text_from_image(self, binary):
|
||||||
|
if not pytesseract or not Image:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
image = Image.open(BytesIO(binary))
|
||||||
|
return pytesseract.image_to_string(image).strip()
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.warning("Image OCR extraction failed: %s", exc)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _extract_text_from_docx(self, binary):
|
||||||
|
if not Document:
|
||||||
|
return ""
|
||||||
|
try:
|
||||||
|
document = Document(BytesIO(binary))
|
||||||
|
return "\n".join(
|
||||||
|
paragraph.text for paragraph in document.paragraphs if paragraph.text
|
||||||
|
).strip()
|
||||||
|
except Exception as exc:
|
||||||
|
_logger.warning("DOCX extraction failed: %s", exc)
|
||||||
|
return ""
|
||||||
|
|
||||||
|
def _send_to_ai(self, text_content, schema_text, extra_instructions=None):
|
||||||
|
prompt = self._build_prompt(text_content, schema_text, extra_instructions)
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
together_key = self._get_param("document_parser.together_ai_key") or self._get_param("document_parser.together_api_key")
|
||||||
|
openrouter_key = self._get_param("document_parser.openrouter_ai_key") or self._get_param("document_parser.openrouter_api_key")
|
||||||
|
|
||||||
|
if together_key:
|
||||||
|
result, provider_errors = self._call_provider(
|
||||||
|
provider_name="Together",
|
||||||
|
endpoint=self.TOGETHER_ENDPOINT,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {together_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
models=self.TOGETHER_MODELS,
|
||||||
|
prompt=prompt,
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
return result, "together", errors
|
||||||
|
errors.extend(provider_errors)
|
||||||
|
else:
|
||||||
|
errors.append(_("Together AI key is not configured."))
|
||||||
|
|
||||||
|
if openrouter_key:
|
||||||
|
result, provider_errors = self._call_provider(
|
||||||
|
provider_name="OpenRouter",
|
||||||
|
endpoint=self.OPENROUTER_ENDPOINT,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {openrouter_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"HTTP-Referer": self._get_param("web.base.url") or "odoo.local",
|
||||||
|
"X-Title": "Document Parser",
|
||||||
|
},
|
||||||
|
models=self.OPENROUTER_MODELS,
|
||||||
|
prompt=prompt,
|
||||||
|
)
|
||||||
|
if result:
|
||||||
|
return result, "openrouter", errors
|
||||||
|
errors.extend(provider_errors)
|
||||||
|
else:
|
||||||
|
errors.append(_("OpenRouter key is not configured."))
|
||||||
|
|
||||||
|
return {}, False, errors
|
||||||
|
|
||||||
|
def _build_prompt(self, text_content, schema_text, extra_instructions=None):
|
||||||
|
return f"""
|
||||||
|
You are a strict JSON generator.
|
||||||
|
|
||||||
|
RULES:
|
||||||
|
- Output ONLY valid raw JSON.
|
||||||
|
- No explanation.
|
||||||
|
- No markdown.
|
||||||
|
- No backticks.
|
||||||
|
- No extra text.
|
||||||
|
- Follow schema strictly.
|
||||||
|
- If a field is missing in text, return null.
|
||||||
|
- Scan the entire document carefully before answering.
|
||||||
|
- Extract ONLY what exists in text.
|
||||||
|
- FOR ANY DATES CHANGE FORMAT TO %Y-%m-%d
|
||||||
|
|
||||||
|
FIELD RULES:
|
||||||
|
- If "skills" exists, extract only explicit technical skills written in the document.
|
||||||
|
- Do NOT infer similar skills from role names, responsibilities, or projects.
|
||||||
|
- Normalize names like "Expert Python" to "Python".
|
||||||
|
- Exclude soft skills and business phrases.
|
||||||
|
- Exclude responsibility-style phrases like Cross-Functional Collaboration, Cost Saving, Resource Utilization, Documentation, Reporting, and Team Handling.
|
||||||
|
- Prefer concrete tools, methods, technologies, platforms, certifications, engineering/process methods, and domain techniques explicitly written in the resume.
|
||||||
|
- If the resume explicitly mentions items like AutoCAD, Root Cause Analysis, Project Management, Manufacturing Processes, Lean, Six Sigma, or Quality Control, include them.
|
||||||
|
- Remove duplicates and return each skill only once.
|
||||||
|
- If "email" exists, return one valid normalized email.
|
||||||
|
- If "name" exists, prefer the full name at the top and exclude titles, companies, and addresses.
|
||||||
|
- If "phone" exists, return the most complete phone number found.
|
||||||
|
- If "experience" exists, return only clearly supported numeric values.
|
||||||
|
|
||||||
|
Schema:
|
||||||
|
{schema_text}
|
||||||
|
|
||||||
|
Instructions:
|
||||||
|
{extra_instructions or "None"}
|
||||||
|
|
||||||
|
Document:
|
||||||
|
{text_content}
|
||||||
|
"""
|
||||||
|
|
||||||
|
def _call_provider(self, provider_name, endpoint, headers, models, prompt):
|
||||||
|
errors = []
|
||||||
|
for model in models:
|
||||||
|
payload = {
|
||||||
|
"model": model,
|
||||||
|
"messages": [{"role": "user", "content": prompt}],
|
||||||
|
"temperature": 0,
|
||||||
|
"max_tokens": 1500,
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
response = requests.post(endpoint, headers=headers, json=payload, timeout=90)
|
||||||
|
if response.status_code != 200:
|
||||||
|
message = _("%(provider)s model %(model)s failed with %(status)s: %(body)s") % {
|
||||||
|
"provider": provider_name,
|
||||||
|
"model": model,
|
||||||
|
"status": response.status_code,
|
||||||
|
"body": (response.text or "")[:300],
|
||||||
|
}
|
||||||
|
_logger.warning(message)
|
||||||
|
errors.append(message)
|
||||||
|
continue
|
||||||
|
|
||||||
|
body = response.json()
|
||||||
|
content = self._extract_message_content(body)
|
||||||
|
parsed = self._safe_json_load(content)
|
||||||
|
if parsed:
|
||||||
|
return parsed, errors
|
||||||
|
|
||||||
|
message = _("%(provider)s model %(model)s returned invalid JSON.") % {
|
||||||
|
"provider": provider_name,
|
||||||
|
"model": model,
|
||||||
|
}
|
||||||
|
_logger.warning(message)
|
||||||
|
errors.append(message)
|
||||||
|
except Exception as exc:
|
||||||
|
message = _("%(provider)s model %(model)s error: %(error)s") % {
|
||||||
|
"provider": provider_name,
|
||||||
|
"model": model,
|
||||||
|
"error": str(exc),
|
||||||
|
}
|
||||||
|
_logger.warning(message)
|
||||||
|
errors.append(message)
|
||||||
|
return {}, errors
|
||||||
|
|
||||||
|
def _extract_message_content(self, response_body):
|
||||||
|
try:
|
||||||
|
content = response_body["choices"][0]["message"]["content"]
|
||||||
|
except Exception:
|
||||||
|
return ""
|
||||||
|
if isinstance(content, list):
|
||||||
|
parts = []
|
||||||
|
for item in content:
|
||||||
|
if isinstance(item, dict):
|
||||||
|
if item.get("type") == "text":
|
||||||
|
parts.append(item.get("text", ""))
|
||||||
|
elif item.get("text"):
|
||||||
|
parts.append(item.get("text"))
|
||||||
|
else:
|
||||||
|
parts.append(str(item))
|
||||||
|
return "\n".join(part for part in parts if part)
|
||||||
|
if isinstance(content, dict):
|
||||||
|
return content.get("text", "")
|
||||||
|
return content or ""
|
||||||
|
|
||||||
|
def _safe_json_load(self, content):
|
||||||
|
if not content:
|
||||||
|
return {}
|
||||||
|
content = content.strip().replace("```json", "").replace("```", "").strip()
|
||||||
|
try:
|
||||||
|
return json.loads(content)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
match = re.search(r"\{[\s\S]*\}", content)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
return json.loads(match.group(0))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
_logger.warning("JSON parse failed for provider response: %s", content[:500])
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _extract_with_heuristics(self, text_content, fields):
|
||||||
|
result = {}
|
||||||
|
email_match = re.search(r"([A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,})", text_content or "", re.I)
|
||||||
|
phone_match = re.search(r"(\+?\d[\d\-\s()]{7,}\d)", text_content or "")
|
||||||
|
linkedin_match = re.search(r"(https?://(?:www\.)?linkedin\.com/[^\s]+)", text_content or "", re.I)
|
||||||
|
name_guess = self._guess_name(text_content or "")
|
||||||
|
skills_guess = self._guess_skills(text_content or "")
|
||||||
|
|
||||||
|
for field_name, field_spec in fields.items():
|
||||||
|
field_type = field_spec.get("type", "string")
|
||||||
|
if field_name in {"email", "email_from"}:
|
||||||
|
result[field_name] = email_match.group(1).lower() if email_match else None
|
||||||
|
elif field_name in {"phone", "mobile", "partner_phone"}:
|
||||||
|
result[field_name] = phone_match.group(1).strip() if phone_match else None
|
||||||
|
elif field_name in {"linkedin_profile", "linkedin"}:
|
||||||
|
result[field_name] = linkedin_match.group(1).strip() if linkedin_match else None
|
||||||
|
elif field_name in {"name", "full_name", "partner_name"}:
|
||||||
|
result[field_name] = name_guess
|
||||||
|
elif field_name == "skills" and field_type == "list":
|
||||||
|
result[field_name] = skills_guess
|
||||||
|
else:
|
||||||
|
result[field_name] = None
|
||||||
|
return result
|
||||||
|
|
||||||
|
def _guess_name(self, text_content):
|
||||||
|
for line in [line.strip() for line in (text_content or "").splitlines() if line.strip()][:12]:
|
||||||
|
cleaned = re.sub(r"[^A-Za-z .'-]", "", line).strip()
|
||||||
|
if len(cleaned.split()) in {2, 3, 4} and not re.search(r"(resume|cv|email|phone|linkedin|skills|experience)", cleaned, re.I):
|
||||||
|
return cleaned
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _guess_skills(self, text_content):
|
||||||
|
section = re.search(r"(skills|technical skills|core competencies)(.*?)(experience|education|projects|certifications|$)", text_content or "", re.I | re.S)
|
||||||
|
if not section:
|
||||||
|
return []
|
||||||
|
parts = re.split(r"[,;\n|•]", section.group(2))
|
||||||
|
cleaned = []
|
||||||
|
for part in parts:
|
||||||
|
value = re.sub(r"\s+", " ", part).strip(" -:\t\r\n")
|
||||||
|
if value and 1 < len(value) < 50 and not re.search(r"^(skills?|experience|education)$", value, re.I):
|
||||||
|
cleaned.append(value)
|
||||||
|
return list(dict.fromkeys(cleaned[:25]))
|
||||||
|
|
||||||
|
def _get_param(self, key):
|
||||||
|
return self.env["ir.config_parameter"].sudo().get_param(key)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def validate_explicit_skills(self, resume_text, skills):
|
||||||
|
if not skills:
|
||||||
|
return []
|
||||||
|
|
||||||
|
prompt = f"""
|
||||||
|
You are validating resume skills.
|
||||||
|
|
||||||
|
Resume:
|
||||||
|
{resume_text[:30000]}
|
||||||
|
|
||||||
|
Extracted Skills:
|
||||||
|
{json.dumps(skills)}
|
||||||
|
|
||||||
|
Keep ONLY skills explicitly claimed by the candidate.
|
||||||
|
|
||||||
|
A skill is explicit if:
|
||||||
|
- It is presented as the candidate's expertise.
|
||||||
|
- It appears in a skill list or competency list.
|
||||||
|
|
||||||
|
Reject skills appearing only in:
|
||||||
|
- job responsibilities
|
||||||
|
- project descriptions
|
||||||
|
- achievements
|
||||||
|
- employer history
|
||||||
|
|
||||||
|
Return ONLY JSON.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
{{
|
||||||
|
"skills": ["Python", "Django"]
|
||||||
|
}}
|
||||||
|
"""
|
||||||
|
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
together_key = self._get_param(
|
||||||
|
"document_parser.together_ai_key"
|
||||||
|
) or self._get_param(
|
||||||
|
"document_parser.together_api_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
openrouter_key = self._get_param(
|
||||||
|
"document_parser.openrouter_ai_key"
|
||||||
|
) or self._get_param(
|
||||||
|
"document_parser.openrouter_api_key"
|
||||||
|
)
|
||||||
|
|
||||||
|
if together_key:
|
||||||
|
result, provider_errors = self._call_provider(
|
||||||
|
provider_name="Together",
|
||||||
|
endpoint=self.TOGETHER_ENDPOINT,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {together_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
models=self.TOGETHER_MODELS,
|
||||||
|
prompt=prompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return result.get("skills", skills)
|
||||||
|
|
||||||
|
errors.extend(provider_errors)
|
||||||
|
|
||||||
|
if openrouter_key:
|
||||||
|
result, provider_errors = self._call_provider(
|
||||||
|
provider_name="OpenRouter",
|
||||||
|
endpoint=self.OPENROUTER_ENDPOINT,
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {openrouter_key}",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"HTTP-Referer": self._get_param("web.base.url") or "odoo.local",
|
||||||
|
"X-Title": "Document Parser",
|
||||||
|
},
|
||||||
|
models=self.OPENROUTER_MODELS,
|
||||||
|
prompt=prompt,
|
||||||
|
)
|
||||||
|
|
||||||
|
if result:
|
||||||
|
return result.get("skills", skills)
|
||||||
|
|
||||||
|
errors.extend(provider_errors)
|
||||||
|
|
||||||
|
return skills
|
||||||
|
def _normalize_required_fields(self, fields):
|
||||||
|
if isinstance(fields, dict):
|
||||||
|
normalized = {}
|
||||||
|
for field_name, field_value in fields.items():
|
||||||
|
if isinstance(field_value, dict):
|
||||||
|
normalized[field_name] = {
|
||||||
|
"type": field_value.get("type", "string"),
|
||||||
|
"description": field_value.get("description", field_name.replace("_", " ").title()),
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
normalized[field_name] = {
|
||||||
|
"type": "string",
|
||||||
|
"description": str(field_value or field_name.replace("_", " ").title()),
|
||||||
|
}
|
||||||
|
return normalized
|
||||||
|
if isinstance(fields, list):
|
||||||
|
return {field_name: {"type": "string", "description": field_name.replace("_", " ").title()} for field_name in fields}
|
||||||
|
return {}
|
||||||
|
|
||||||
|
def _build_json_schema_text(self, fields):
|
||||||
|
return json.dumps(fields, ensure_ascii=True)
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
import requests
|
||||||
|
|
||||||
|
from odoo import _, fields, models
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class ResConfigSettings(models.TransientModel):
|
||||||
|
_inherit = "res.config.settings"
|
||||||
|
|
||||||
|
together_ai_key = fields.Char(
|
||||||
|
string="Together AI Key",
|
||||||
|
config_parameter="document_parser.together_ai_key",
|
||||||
|
)
|
||||||
|
openrouter_ai_key = fields.Char(
|
||||||
|
string="OpenRouter AI Key",
|
||||||
|
config_parameter="document_parser.openrouter_ai_key",
|
||||||
|
)
|
||||||
|
|
||||||
|
def action_test_together_ai_connection(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.together_ai_key:
|
||||||
|
raise UserError(_("Please add the Together AI key first."))
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
"https://api.together.xyz/v1/models",
|
||||||
|
headers={"Authorization": f"Bearer {self.together_ai_key}"},
|
||||||
|
timeout=20,
|
||||||
|
)
|
||||||
|
if response.ok:
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.client",
|
||||||
|
"tag": "display_notification",
|
||||||
|
"params": {
|
||||||
|
"title": _("Together AI Connection"),
|
||||||
|
"message": _("Connection successful."),
|
||||||
|
"type": "success",
|
||||||
|
"sticky": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raise UserError(_("Together AI connection failed: %s") % (response.text or response.reason))
|
||||||
|
|
||||||
|
def action_test_openrouter_ai_connection(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.openrouter_ai_key:
|
||||||
|
raise UserError(_("Please add the OpenRouter key first."))
|
||||||
|
|
||||||
|
response = requests.get(
|
||||||
|
"https://openrouter.ai/api/v1/models",
|
||||||
|
headers={
|
||||||
|
"Authorization": f"Bearer {self.openrouter_ai_key}",
|
||||||
|
"HTTP-Referer": self.env["ir.config_parameter"].sudo().get_param("web.base.url", ""),
|
||||||
|
"X-Title": "Odoo Document Parser",
|
||||||
|
},
|
||||||
|
timeout=20,
|
||||||
|
)
|
||||||
|
if response.ok:
|
||||||
|
return {
|
||||||
|
"type": "ir.actions.client",
|
||||||
|
"tag": "display_notification",
|
||||||
|
"params": {
|
||||||
|
"title": _("OpenRouter Connection"),
|
||||||
|
"message": _("Connection successful."),
|
||||||
|
"type": "success",
|
||||||
|
"sticky": False,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
raise UserError(_("OpenRouter connection failed: %s") % (response.text or response.reason))
|
||||||
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<record id="res_config_settings_view_form_document_parser" model="ir.ui.view">
|
||||||
|
<field name="name">res.config.settings.view.form.document.parser</field>
|
||||||
|
<field name="model">res.config.settings</field>
|
||||||
|
<field name="priority" eval="80"/>
|
||||||
|
<field name="inherit_id" ref="base.res_config_settings_view_form"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//form" position="inside">
|
||||||
|
<app string="Document Parser" name="document_parser" groups="base.group_system">
|
||||||
|
<block title="AI Providers" name="document_parser_ai_provider_block">
|
||||||
|
<setting string="Together AI Key"
|
||||||
|
help="Primary provider used first for structured document extraction."
|
||||||
|
id="document_parser_together_ai_key">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<field name="together_ai_key" password="True" placeholder="together.ai API key"/>
|
||||||
|
<button name="action_test_together_ai_connection"
|
||||||
|
string="Test Connection"
|
||||||
|
type="object"
|
||||||
|
class="btn btn-secondary"/>
|
||||||
|
</div>
|
||||||
|
</setting>
|
||||||
|
<setting string="OpenRouter AI Key"
|
||||||
|
help="Fallback provider used when Together AI is unavailable or quota is exhausted."
|
||||||
|
id="document_parser_openrouter_ai_key">
|
||||||
|
<div class="d-flex align-items-center gap-2">
|
||||||
|
<field name="openrouter_ai_key" password="True" placeholder="openrouter API key"/>
|
||||||
|
<button name="action_test_openrouter_ai_connection"
|
||||||
|
string="Test Connection"
|
||||||
|
type="object"
|
||||||
|
class="btn btn-secondary"/>
|
||||||
|
</div>
|
||||||
|
</setting>
|
||||||
|
</block>
|
||||||
|
</app>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from . import models
|
||||||
|
from . import wizards
|
||||||
|
from . import controllers
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': "Payroll Tax Management",
|
||||||
|
|
||||||
|
'summary': "Manage Income Tax Declarations for Employees in Payroll",
|
||||||
|
'description': """
|
||||||
|
Payroll IT Declarations
|
||||||
|
========================
|
||||||
|
|
||||||
|
This module allows HR and payroll departments to manage and track Income Tax (IT) declarations submitted by employees.
|
||||||
|
|
||||||
|
Features:
|
||||||
|
---------
|
||||||
|
- Employee-wise tax declaration submission
|
||||||
|
- HR approval workflow for declarations
|
||||||
|
- Category-wise declaration limits (e.g. 80C, HRA, LTA, etc.)
|
||||||
|
- Auto-calculation of eligible deductions
|
||||||
|
- Integration with Odoo Payroll for accurate tax computation
|
||||||
|
- Attach supporting documents (PDFs, images)
|
||||||
|
- Employee self-service through portal
|
||||||
|
|
||||||
|
Built with usability and compliance in mind, this module streamlines the IT declaration process and ensures transparency and efficiency across the organization.
|
||||||
|
|
||||||
|
Developed by: Pranay
|
||||||
|
""",
|
||||||
|
|
||||||
|
'author': "Pranay",
|
||||||
|
'website': "https://www.ftprotech.com",
|
||||||
|
|
||||||
|
# Categories can be used to filter modules in modules listing
|
||||||
|
# Check https://github.com/odoo/odoo/blob/15.0/odoo/addons/base/data/ir_module_category_data.xml
|
||||||
|
# for the full list
|
||||||
|
'category': 'Human Resources',
|
||||||
|
'version': '0.1',
|
||||||
|
|
||||||
|
# any module necessary for this one to work correctly
|
||||||
|
'depends': ['base','hr','hr_payroll','hr_employee_extended','l10n_in_hr_payroll'],
|
||||||
|
|
||||||
|
# always loaded
|
||||||
|
'data': [
|
||||||
|
'security/ir.model.access.csv',
|
||||||
|
'views/payroll_periods.xml',
|
||||||
|
'views/investment_types.xml',
|
||||||
|
# 'views/payroll_periods.xml',
|
||||||
|
'views/slab_master.xml',
|
||||||
|
'views/emp_it_declaration.xml',
|
||||||
|
'views/report_it_tax_statement.xml',
|
||||||
|
'report/report_action.xml',
|
||||||
|
'report/it_tax_template.xml',
|
||||||
|
'views/it_tax_menu_and_wizard_view.xml',
|
||||||
|
'views/employee_payslip_download_wizard_views.xml',
|
||||||
|
'wizards/hr_tds_calculation.xml',
|
||||||
|
'wizards/children_education_costing.xml',
|
||||||
|
'wizards/employee_life_insurance.xml',
|
||||||
|
'wizards/nsc_declaration.xml',
|
||||||
|
'wizards/self_occupied_property.xml',
|
||||||
|
'wizards/letout_house_property.xml',
|
||||||
|
'wizards/nsc_income_loss.xml',
|
||||||
|
'data/default_investment_types.xml',
|
||||||
|
# 'views/it_investment_type.xml',
|
||||||
|
# 'views/it_investment_costing.xml'
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
from . import main
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
import io
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
from odoo import _
|
||||||
|
from odoo.exceptions import AccessError, UserError
|
||||||
|
from odoo.http import Controller, content_disposition, request, route
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeePayslipDownloadController(Controller):
|
||||||
|
|
||||||
|
@route('/employee_it_declaration/my_payslips/<int:wizard_id>', type='http', auth='user')
|
||||||
|
def download_my_payslips(self, wizard_id, **kwargs):
|
||||||
|
wizard = request.env['employee.payslip.download.wizard'].browse(wizard_id)
|
||||||
|
if not wizard.exists() or wizard.create_uid != request.env.user:
|
||||||
|
return request.not_found()
|
||||||
|
|
||||||
|
try:
|
||||||
|
payslips = wizard._get_payslips_for_download()
|
||||||
|
except (AccessError, UserError):
|
||||||
|
return request.not_found()
|
||||||
|
|
||||||
|
if wizard.download_type == 'single':
|
||||||
|
payslip = payslips[:1]
|
||||||
|
report, pdf_content = wizard._get_pdf_content(payslip)
|
||||||
|
headers = [
|
||||||
|
('Content-Type', 'application/pdf'),
|
||||||
|
('Content-Length', len(pdf_content)),
|
||||||
|
('Content-Disposition', content_disposition(wizard._get_pdf_filename(payslip, report))),
|
||||||
|
]
|
||||||
|
return request.make_response(pdf_content, headers=headers)
|
||||||
|
|
||||||
|
zip_buffer = io.BytesIO()
|
||||||
|
used_filenames = set()
|
||||||
|
with zipfile.ZipFile(zip_buffer, 'w', compression=zipfile.ZIP_DEFLATED) as payslip_zip:
|
||||||
|
for payslip in payslips:
|
||||||
|
report, pdf_content = wizard._get_pdf_content(payslip)
|
||||||
|
filename = self._deduplicate_filename(wizard._get_pdf_filename(payslip, report), used_filenames)
|
||||||
|
payslip_zip.writestr(filename, pdf_content)
|
||||||
|
|
||||||
|
zip_content = zip_buffer.getvalue()
|
||||||
|
headers = [
|
||||||
|
('Content-Type', 'application/zip'),
|
||||||
|
('Content-Length', len(zip_content)),
|
||||||
|
('Content-Disposition', content_disposition(wizard._get_zip_filename())),
|
||||||
|
]
|
||||||
|
return request.make_response(zip_content, headers=headers)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _deduplicate_filename(filename, used_filenames):
|
||||||
|
if filename not in used_filenames:
|
||||||
|
used_filenames.add(filename)
|
||||||
|
return filename
|
||||||
|
|
||||||
|
stem, extension = filename.rsplit('.', 1) if '.' in filename else (filename, '')
|
||||||
|
counter = 2
|
||||||
|
while True:
|
||||||
|
candidate = _('%(stem)s (%(counter)s)', stem=stem, counter=counter)
|
||||||
|
if extension:
|
||||||
|
candidate = '%s.%s' % (candidate, extension)
|
||||||
|
if candidate not in used_filenames:
|
||||||
|
used_filenames.add(candidate)
|
||||||
|
return candidate
|
||||||
|
counter += 1
|
||||||
|
|
@ -0,0 +1,579 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo noupdate="0">
|
||||||
|
<record id="investment_type_past_employment" model="it.investment.type">
|
||||||
|
<field name="sequence">5</field>
|
||||||
|
<field name="investment_type">past_employment</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_us80c" model="it.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="investment_type">us_80c</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_us80d" model="it.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="investment_type">us_80d</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_us10" model="it.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="investment_type">us_10</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_us80g" model="it.investment.type">
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
<field name="investment_type">us_80g</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_chapter_via" model="it.investment.type">
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
<field name="investment_type">chapter_via</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_us17" model="it.investment.type">
|
||||||
|
<field name="sequence">60</field>
|
||||||
|
<field name="investment_type">us_17</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_house_rent" model="it.investment.type">
|
||||||
|
<field name="sequence">70</field>
|
||||||
|
<field name="investment_type">house_rent</field>
|
||||||
|
<field name="regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_other_income_loss" model="it.investment.type">
|
||||||
|
<field name="sequence">80</field>
|
||||||
|
<field name="investment_type">other_i_or_l</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="investment_type_other_declaration" model="it.investment.type">
|
||||||
|
<field name="sequence">90</field>
|
||||||
|
<field name="investment_type">other_declaration</field>
|
||||||
|
<field name="regime">both</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Past Employment: required for periods before 2026-2027, configurable on Payroll Periods -->
|
||||||
|
<record id="past_emp_total_income" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="name">Previous Employer Total Income (Gross Salary + Any Other Income)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PETI</field>
|
||||||
|
<field name="tax_regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_us10_lta_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="name">Previous Employer LESS: US10 LTA exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PELTA</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_us10_gratuity_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="name">Previous Employer LESS: US10 Gratuity exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEGRAT</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_us10_leave_encashment_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
<field name="name">Previous Employer LESS: US10 Leave encashment exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PELEAV</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_us10_others_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
<field name="name">Previous Employer LESS: US10 Others (HRAUniformWashingetc)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEOTH</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_prof_tax" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">60</field>
|
||||||
|
<field name="name">Previous Employer Prof.Tax</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEPROF</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_standard_deduction" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">70</field>
|
||||||
|
<field name="name">Previous Employer Standard Deduction Benefit Claimed</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PESTD</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_invest_us80c" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">80</field>
|
||||||
|
<field name="name">Previous Employer Invest US 80C</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEIUSC</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_chapter_via" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">90</field>
|
||||||
|
<field name="name">Previous Employer Chapter IVAUS80CCCothers</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PECHAP</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_net_taxable_income" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">100</field>
|
||||||
|
<field name="name">Net Taxable Income</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PENET</field>
|
||||||
|
<field name="compute_method">1</field>
|
||||||
|
<field name="compute_code">PETI - PELTA - PEGRAT - PELEAV - PEOTH - PEPROF - PESTD - PEIUSC - PECHAP</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_taxable_income_after_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">110</field>
|
||||||
|
<field name="name">Previous Employer Taxable Income After Exemption (Gross - US10 - Chapter VIA US80C) Except Prof.Tax</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEAFT</field>
|
||||||
|
<field name="compute_method">1</field>
|
||||||
|
<field name="compute_code">PETI - PELTA - PEGRAT - PELEAV - PEOTH - PEIUSC - PECHAP</field>
|
||||||
|
<field name="tax_regime">both</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_tax" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">120</field>
|
||||||
|
<field name="name">Previous Employer Tax</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PETAX</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_surcharge" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">130</field>
|
||||||
|
<field name="name">Previous Employer Surcharge</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PESUR</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_cess" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">140</field>
|
||||||
|
<field name="name">Previous Employer Cess</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PECESS</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_tds_deduction" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">150</field>
|
||||||
|
<field name="name">Previous Employer TDS Deduction</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PETDS</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_hra_exemptions" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">160</field>
|
||||||
|
<field name="name">Previous Employer House Rent Allowance Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEHRA</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_conveyance_exemptions" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">170</field>
|
||||||
|
<field name="name">Previous Employer Conveyance Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PECON</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_medical_reimbursement_exemptions" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">180</field>
|
||||||
|
<field name="name">Previous Employer Medical Reimbursement Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEMED</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_lta_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">190</field>
|
||||||
|
<field name="name">Previous Employer LTA Exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PELT2</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_children_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">200</field>
|
||||||
|
<field name="name">Previous Employer Children Exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PECHLD</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_other_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">210</field>
|
||||||
|
<field name="name">Previous Employer other Exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEOT2</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_infra_exemption" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">220</field>
|
||||||
|
<field name="name">Previous Employer Infra exemption</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEINF</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="past_emp_gratuity_received" model="past_employment.investment.type">
|
||||||
|
<field name="sequence">230</field>
|
||||||
|
<field name="name">Previous Employer Gratuity Received</field>
|
||||||
|
<field name="investment_type" ref="investment_type_past_employment"/>
|
||||||
|
<field name="investment_code">PEGR2</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- US 80C: Old regime deductions -->
|
||||||
|
<record id="us80c_nps_employee" model="us80c.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="name">US 80CCD(1) - Contribution to NPS Scheme (10% of salary)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80NPS</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_elss" model="us80c.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="name">Mutual Fund - Equity Linked Savings Scheme (ELSS)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80ELSS</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_pension_funds" model="us80c.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="name">US 80CCC - Pension Funds</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80PEN</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_children_education_fees" model="us80c.investment.type">
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
<field name="name">Children Education Fees</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80EDU</field>
|
||||||
|
<field name="require_action">1</field>
|
||||||
|
<field name="action_id" ref="action_children_education"/>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_ppf" model="us80c.investment.type">
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
<field name="name">Public Provident Fund (PPF)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80PPF</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_lic_premiums" model="us80c.investment.type">
|
||||||
|
<field name="sequence">60</field>
|
||||||
|
<field name="name">LIC - Life Insurance Premiums</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80LIC</field>
|
||||||
|
<field name="require_action">1</field>
|
||||||
|
<field name="action_id" ref="action_us80c_insurance_line"/>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_nsc" model="us80c.investment.type">
|
||||||
|
<field name="sequence">70</field>
|
||||||
|
<field name="name">National Savings Certificate (NSC)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80NSC</field>
|
||||||
|
<field name="require_action">1</field>
|
||||||
|
<field name="action_id" ref="action_nsc_declaration_line"/>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_ulip" model="us80c.investment.type">
|
||||||
|
<field name="sequence">80</field>
|
||||||
|
<field name="name">Unit linked Insurance Plan (ULIP)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80ULIP</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_five_year_bank_fd" model="us80c.investment.type">
|
||||||
|
<field name="sequence">90</field>
|
||||||
|
<field name="name">5-Yr bank fixed deposits (FDs)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80FD5</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_post_office_time_deposit" model="us80c.investment.type">
|
||||||
|
<field name="sequence">100</field>
|
||||||
|
<field name="name">5-Yr post office time deposit (POTD) scheme</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80POT</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_home_loan_principal" model="us80c.investment.type">
|
||||||
|
<field name="sequence">110</field>
|
||||||
|
<field name="name">Certificate provided for Home Loan Principal Repayment</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80HLP</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_infrastructure_fund" model="us80c.investment.type">
|
||||||
|
<field name="sequence">120</field>
|
||||||
|
<field name="name">Infrastructure Fund</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80INF</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_nabard_rural_bonds" model="us80c.investment.type">
|
||||||
|
<field name="sequence">130</field>
|
||||||
|
<field name="name">NABARD rural bonds</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80NRB</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_pf" model="us80c.investment.type">
|
||||||
|
<field name="sequence">140</field>
|
||||||
|
<field name="name">Provident Fund (PF)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80PF</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_scss" model="us80c.investment.type">
|
||||||
|
<field name="sequence">150</field>
|
||||||
|
<field name="name">Senior Citizen Savings Scheme 2004 (SCSS)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80SCS</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_stamp_duty_registration" model="us80c.investment.type">
|
||||||
|
<field name="sequence">160</field>
|
||||||
|
<field name="name">Stamp Duty and Registration Charges for a home</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80SDR</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_superannuation" model="us80c.investment.type">
|
||||||
|
<field name="sequence">170</field>
|
||||||
|
<field name="name">Superannuation</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80SUP</field>
|
||||||
|
<field name="limit">150000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_vpf" model="us80c.investment.type">
|
||||||
|
<field name="sequence">180</field>
|
||||||
|
<field name="name">Voluntary Provident Fund (VPF)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80VPF</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80c_sukanya_samriddhi" model="us80c.investment.type">
|
||||||
|
<field name="sequence">190</field>
|
||||||
|
<field name="name">Sukanya Samriddhi Scheme</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80c"/>
|
||||||
|
<field name="investment_code">C80SSS</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- US 80D: Old regime deductions -->
|
||||||
|
<record id="us80d_medical_insurance_self" model="us80d.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="name">US 80D - Medical Insurance Premium Self</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80d"/>
|
||||||
|
<field name="for_family">1</field>
|
||||||
|
<field name="limit">25000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80d_preventive_health_checkup_self" model="us80d.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="name">US 80D - Preventive Health Checkup (Self)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80d"/>
|
||||||
|
<field name="for_family">1</field>
|
||||||
|
<field name="limit">5000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80d_medical_insurance_parents_below_60" model="us80d.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="name">US 80D - Medical Insurance Premium Parents below 60 years</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80d"/>
|
||||||
|
<field name="for_parents">1</field>
|
||||||
|
<field name="limit">25000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80d_medical_insurance_senior_parents" model="us80d.investment.type">
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
<field name="name">US 80D - Medical Insurance Premium Parents (Senior Citizen)</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80d"/>
|
||||||
|
<field name="for_senior_parent">1</field>
|
||||||
|
<field name="limit">50000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us80d_medical_expenditure_very_senior" model="us80d.investment.type">
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
<field name="name">US 80D - Medical Insurance Expenditure Very Senior Citizen</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us80d"/>
|
||||||
|
<field name="for_senior_parent">1</field>
|
||||||
|
<field name="limit">50000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- US 10: Old regime exemptions -->
|
||||||
|
<record id="us10_children_education_allowance" model="us10.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="name">Children Education Allowance Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us10"/>
|
||||||
|
<field name="limit">72000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us10_retrenchment_exemptions" model="us10.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="name">Retrenchment Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us10"/>
|
||||||
|
<field name="limit">500000</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us10_leave_travel_allowance" model="us10.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="name">Leave Travel Allowance Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us10"/>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us10_medical_reimbursement" model="us10.investment.type">
|
||||||
|
<field name="sequence">40</field>
|
||||||
|
<field name="name">Medical Reimbursement Exemptions</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us10"/>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="us10_uniform_allowance" model="us10.investment.type">
|
||||||
|
<field name="sequence">50</field>
|
||||||
|
<field name="name">Uniform Allowance US10</field>
|
||||||
|
<field name="investment_type" ref="investment_type_us10"/>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- US 80G: Old regime donations -->
|
||||||
|
<record id="us80g_donations" model="us80g.investment.type"><field name="sequence">10</field><field name="name">US 80G - Donations</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_defence_fund" model="us80g.investment.type"><field name="sequence">20</field><field name="name">01-National Defence Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_pm_national_relief_fund" model="us80g.investment.type"><field name="sequence">30</field><field name="name">02-PM's National Relief Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_pm_armenia_earthquake_relief_fund" model="us80g.investment.type"><field name="sequence">40</field><field name="name">03-PM's Armenia Earthquake Relief Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_africa_public_contributions_india_fund" model="us80g.investment.type"><field name="sequence">50</field><field name="name">04-Africa (Public Contributions - India) Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_foundation_communal_harmony" model="us80g.investment.type"><field name="sequence">60</field><field name="name">05-National Foundation for Communal Harmony</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_university_national_eminence" model="us80g.investment.type"><field name="sequence">70</field><field name="name">06-University/Educational Institution of National Eminence</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_maharashtra_cm_earthquake_relief_fund" model="us80g.investment.type"><field name="sequence">80</field><field name="name">07-Maharashtra CM's Earthquake relief Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_zila_saksharta_samiti" model="us80g.investment.type"><field name="sequence">90</field><field name="name">08-Zila Saksharta Samiti</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_blood_transfusion_council" model="us80g.investment.type"><field name="sequence">100</field><field name="name">10-The National Blood Transfusion Council</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_state_medical_relief_fund" model="us80g.investment.type"><field name="sequence">110</field><field name="name">11-State Government medical relief to the poor</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_army_central_welfare_fund" model="us80g.investment.type"><field name="sequence">120</field><field name="name">12-The Army Central Welfare Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_indian_naval_benevolent_fund" model="us80g.investment.type"><field name="sequence">130</field><field name="name">13-The Indian Naval Benevolent Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_air_force_central_welfare_fund" model="us80g.investment.type"><field name="sequence">140</field><field name="name">14-The Air Force Central Welfare Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_andhra_cm_cyclone_relief_fund_1996" model="us80g.investment.type"><field name="sequence">150</field><field name="name">15-Andhra Pradesh CM's Cyclone Relief Fund 1996</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_illness_assistance_fund" model="us80g.investment.type"><field name="sequence">160</field><field name="name">16-National Illness Assistance Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_cm_relief_fund_state" model="us80g.investment.type"><field name="sequence">170</field><field name="name">17-The CM's Relief Fund of any State</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_lg_relief_fund_union_territory" model="us80g.investment.type"><field name="sequence">180</field><field name="name">18-Lt. Governor's Relief Fund - Union Territory</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_sports_fund_central" model="us80g.investment.type"><field name="sequence">190</field><field name="name">19-National sports Fund - Central Government</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_cultural_fund_central" model="us80g.investment.type"><field name="sequence">200</field><field name="name">20-National Cultural Fund - Central Government</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_fund_technology_development_central" model="us80g.investment.type"><field name="sequence">210</field><field name="name">21-Fund for Tech Devt - Central Government</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_welfare_persons_disabilities" model="us80g.investment.type"><field name="sequence">220</field><field name="name">22-Welfare of persons with Disabilities</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_ay_trust_fund" model="us80g.investment.type"><field name="sequence">230</field><field name="name">23-Any Trust, institution or Fund covered under section 80G providing relief to the victims of earthquake in Gujarat, provided such donation is made between 26/01/2001 to 30/09/2001</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_pm_drought_relief_fund" model="us80g.investment.type"><field name="sequence">240</field><field name="name">25-Prime Minister's Drought Relief Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_family_planning" model="us80g.investment.type"><field name="sequence">250</field><field name="name">29-For promoting family planning</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_olympic_association" model="us80g.investment.type"><field name="sequence">260</field><field name="name">30-To any Olympic Association or to any other association or institution established in India and notified by the Central Government for Development of infrastructure of sports and Games, or sponsorship for sports or Games</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_slum_dwellers" model="us80g.investment.type"><field name="sequence">270</field><field name="name">31-For other than promoting family planning</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_charitable_trust_approval" model="us80g.investment.type"><field name="sequence">280</field><field name="name">32-Institutions/Charitable Trust with 80G approval</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_improvement_cities_towns_villages" model="us80g.investment.type"><field name="sequence">290</field><field name="name">33-For improvement of cities, towns or villages</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_corporation_minority" model="us80g.investment.type"><field name="sequence">300</field><field name="name">34-Corporation-Promoting Interests in Minority</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_renovation_notified_places_temples" model="us80g.investment.type"><field name="sequence">310</field><field name="name">35-For renovation-Notified Places like Temples</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_national_childrens_fund" model="us80g.investment.type"><field name="sequence">320</field><field name="name">36-National Children's Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_swachh_bharat_kosh" model="us80g.investment.type"><field name="sequence">330</field><field name="name">37-Swachh Bharat Kosh</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_clean_ganga_fund" model="us80g.investment.type"><field name="sequence">340</field><field name="name">38-Clean Ganga Fund</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us80g_drug_abuse_control_fund" model="us80g.investment.type"><field name="sequence">350</field><field name="name">39-National Fund for Control of Drug Abuse</field><field name="investment_type" ref="investment_type_us80g"/><field name="tax_regime">old</field></record>
|
||||||
|
|
||||||
|
<!-- Chapter VIA: old regime deductions plus allowed new-regime employer NPS -->
|
||||||
|
<record id="chapter_via_rgess" model="chapter.via.investment.type"><field name="sequence">10</field><field name="name">US 80CCG - Investments in RGESS</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">25000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ddb_medical_treatment" model="chapter.via.investment.type"><field name="sequence">20</field><field name="name">US 80DDB - Maintenance Including medical treatment</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">125000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ddb_non_senior" model="chapter.via.investment.type"><field name="sequence">30</field><field name="name">US 80DDB - Medical treatment for non senior citizens</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">40000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ddbs_senior" model="chapter.via.investment.type"><field name="sequence">40</field><field name="name">US 80DDBS - Medical treatment for senior citizens</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">100000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80e_higher_education" model="chapter.via.investment.type"><field name="sequence">50</field><field name="name">US 80E - Higher education</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ee_home_loan_interest" model="chapter.via.investment.type"><field name="sequence">60</field><field name="name">US 80EE - Interest Paid On Home Loan</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">50000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80gg_house_rent" model="chapter.via.investment.type"><field name="sequence">70</field><field name="name">US 80GG - House Rent Exemption</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">60000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_us24_home_loan_interest" model="chapter.via.investment.type"><field name="sequence">80</field><field name="name">US 24 - Certificate provided for Interest Paid On Home Loan</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">200000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_us24_first_time_buyer" model="chapter.via.investment.type"><field name="sequence">90</field><field name="name">US 24 - Interest Paid On Home Loan (First time buyer)</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">50000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_us24_letout_property" model="chapter.via.investment.type"><field name="sequence">100</field><field name="name">US 24 - Interest Paid On Home Loan For Let Out Property</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">200000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_us24_before_april_1999" model="chapter.via.investment.type"><field name="sequence">110</field><field name="name">US 24 - Interest Paid On Loan Before 1st April 1999</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">30000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ccd_employee_contribution" model="chapter.via.investment.type"><field name="sequence">120</field><field name="name">US 80CCD - National Pension Scheme (Employee Contribution)</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">50000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ccd2_employer_contribution" model="chapter.via.investment.type"><field name="sequence">130</field><field name="name">US 80CCD (2) - National Pension Scheme (Employer Contribution)</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="chapter_via_80ccg_outside_nps_employer" model="chapter.via.investment.type"><field name="sequence">140</field><field name="name">US 80CCG Outside National Pension Scheme (Employee Contribution)</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">50000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ccf_infra_bonds" model="chapter.via.investment.type"><field name="sequence">150</field><field name="name">US 80CCF - Long term Infrastructure bonds</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80qqb_royalty_books" model="chapter.via.investment.type"><field name="sequence">160</field><field name="name">US 80QQB - Royalty on Books</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">300000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80rrb_royalty_patents" model="chapter.via.investment.type"><field name="sequence">170</field><field name="name">US 80RRB - Royalty on patents</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">300000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80tta_saving_interest" model="chapter.via.investment.type"><field name="sequence">180</field><field name="name">US 80TTA - Interest on Saving accounts</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">10000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80u_disability" model="chapter.via.investment.type"><field name="sequence">190</field><field name="name">US 80U - Disability</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">125000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80ttb_senior_interest" model="chapter.via.investment.type"><field name="sequence">200</field><field name="name">US80TTB - For Senior Citizen, exempt Interest from FDs, Post Office</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">50000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80eea_home_loan" model="chapter.via.investment.type"><field name="sequence">210</field><field name="name">US80EEA - Home Loans Taken on Self-Occupied House Property BY 31-Mar-2020</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">150000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="chapter_via_80eeb_electric_vehicle" model="chapter.via.investment.type"><field name="sequence">220</field><field name="name">US80EEB - ELECTRONIC VEHICLE EXEMPTION</field><field name="investment_type" ref="investment_type_chapter_via"/><field name="limit">150000</field><field name="tax_regime">old</field></record>
|
||||||
|
|
||||||
|
<!-- US 17: Old regime reimbursements and allowances -->
|
||||||
|
<record id="us17_academic_allowance" model="us17.investment.type"><field name="sequence">10</field><field name="name">Academic Allowance</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_helper_allowance" model="us17.investment.type"><field name="sequence">20</field><field name="name">Helper Allowance</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_petrol_allowance" model="us17.investment.type"><field name="sequence">30</field><field name="name">Petrol Allowance</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_utility_reimbursement" model="us17.investment.type"><field name="sequence">40</field><field name="name">Utility Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_books_proofs" model="us17.investment.type"><field name="sequence">50</field><field name="name">Books Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_car_maintenance_reimbursement" model="us17.investment.type"><field name="sequence">60</field><field name="name">Car Maintenance Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_car_maintenance_small_car" model="us17.investment.type"><field name="sequence">70</field><field name="name">Car Maintenance - Small Upto 1600 CC</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_cell_phone_proofs" model="us17.investment.type"><field name="sequence">80</field><field name="name">Cell Phone Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_conveyance_proofs" model="us17.investment.type"><field name="sequence">90</field><field name="name">Conveyance Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_driver_salary_reimbursement" model="us17.investment.type"><field name="sequence">100</field><field name="name">Driver Salary Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_driver_salary_reimbursement_small_car" model="us17.investment.type"><field name="sequence">110</field><field name="name">Driver Salary Reimbursement - Small Car Upto 1600 CC</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_driver_proofs" model="us17.investment.type"><field name="sequence">120</field><field name="name">Driver Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="limit">36000</field><field name="tax_regime">both</field></record>
|
||||||
|
<record id="us17_driver_petrol_car_maintenance" model="us17.investment.type"><field name="sequence">130</field><field name="name">Driver, Petrol & Car Maintenance Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="us17_petrol_car_maintenance" model="us17.investment.type"><field name="sequence">140</field><field name="name">Petrol & Car Maintenance Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="limit">84000</field><field name="tax_regime">both</field></record>
|
||||||
|
<record id="us17_entertainment_proofs" model="us17.investment.type"><field name="sequence">150</field><field name="name">Entertainment Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_fuel_reimbursement" model="us17.investment.type"><field name="sequence">160</field><field name="name">Fuel Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_general_other_reimbursement" model="us17.investment.type"><field name="sequence">170</field><field name="name">General Other Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_gift_reimbursement" model="us17.investment.type"><field name="sequence">180</field><field name="name">Gift Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_internet_reimbursement" model="us17.investment.type"><field name="sequence">190</field><field name="name">Internet Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_journal_reimbursement" model="us17.investment.type"><field name="sequence">200</field><field name="name">Journal Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_maintenance_reimbursement" model="us17.investment.type"><field name="sequence">210</field><field name="name">Maintenance Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_meal_proofs" model="us17.investment.type"><field name="sequence">220</field><field name="name">Meal Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_news_paper_proofs" model="us17.investment.type"><field name="sequence">230</field><field name="name">News Paper Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_parking_proofs" model="us17.investment.type"><field name="sequence">240</field><field name="name">Parking Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="us17_telephone_allowance" model="us17.investment.type"><field name="sequence">250</field><field name="name">Telephone Allowance</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="us17_toll_proofs" model="us17.investment.type"><field name="sequence">260</field><field name="name">Toll Proofs</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="us17_uniform_reimbursement" model="us17.investment.type"><field name="sequence">270</field><field name="name">Uniform Reimbursement</field><field name="investment_type" ref="investment_type_us17"/><field name="tax_regime">old</field></record>
|
||||||
|
|
||||||
|
<!-- Other income/loss: both-regime taxable income plus old-regime details -->
|
||||||
|
<record id="other_il_self_occupied_house_property" model="other.il.investment.type"><field name="sequence">10</field><field name="name">Self occupied house property U/S 24</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="require_action">1</field><field name="action_id" ref="action_self_occupied_property"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_bank_interest" model="other.il.investment.type"><field name="sequence">20</field><field name="name">Bank Interest</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_debenture_interest" model="other.il.investment.type"><field name="sequence">30</field><field name="name">Debenture Interest</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_external_income_others" model="other.il.investment.type"><field name="sequence">40</field><field name="name">External Income Others</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_income_let_out_house_property" model="other.il.investment.type"><field name="sequence">50</field><field name="name">Income on Let Out House Property</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="require_action">1</field><field name="action_id" ref="action_letout_house_property"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="other_il_loss_let_out_house_property" model="other.il.investment.type"><field name="sequence">60</field><field name="name">Loss on Let Out House Property</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="limit">-200000</field><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_interest_nsc_80i" model="other.il.investment.type"><field name="sequence">70</field><field name="name">Interest on NSC (80 I)</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="require_action">1</field><field name="action_id" ref="action_nsc_interest_line"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_previous_employer_tax_free_income" model="other.il.investment.type"><field name="sequence">80</field><field name="name">Previous Employer Tax free other income</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_fully_taxable_income" model="other.il.investment.type"><field name="sequence">90</field><field name="name">Fully Taxable Income</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_fully_taxable_other_income" model="other.il.investment.type"><field name="sequence">100</field><field name="name">Fully Taxable Other Income</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">both</field></record>
|
||||||
|
<record id="other_il_external_inc_var_percentage" model="other.il.investment.type"><field name="sequence">110</field><field name="name">EXTERNAL_INC_VAR_PERCENTAGE</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
<record id="other_il_previous_employer_other_income" model="other.il.investment.type"><field name="sequence">120</field><field name="name">Previous Employer Other Income</field><field name="investment_type" ref="investment_type_other_income_loss"/><field name="tax_regime">old</field></record>
|
||||||
|
|
||||||
|
<!-- Other declarations: old regime supporting values -->
|
||||||
|
<record id="other_declaration_medical_insurance_manual_input" model="other.declaration.investment.type">
|
||||||
|
<field name="sequence">10</field>
|
||||||
|
<field name="name">Medical Insurance Premium Manual Input</field>
|
||||||
|
<field name="investment_type" ref="investment_type_other_declaration"/>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="other_declaration_hostel_going_children" model="other.declaration.investment.type">
|
||||||
|
<field name="sequence">20</field>
|
||||||
|
<field name="name">Number of Hostel going children</field>
|
||||||
|
<field name="investment_type" ref="investment_type_other_declaration"/>
|
||||||
|
<field name="limit">2</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
<record id="other_declaration_school_going_children" model="other.declaration.investment.type">
|
||||||
|
<field name="sequence">30</field>
|
||||||
|
<field name="name">Number of school going children</field>
|
||||||
|
<field name="investment_type" ref="investment_type_other_declaration"/>
|
||||||
|
<field name="limit">2</field>
|
||||||
|
<field name="tax_regime">old</field>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
from . import payroll_periods
|
||||||
|
from . import investment_types
|
||||||
|
from . import emp_it_declaration
|
||||||
|
from . import investment_costings
|
||||||
|
from . import slab_master
|
||||||
|
from . import it_tax_statement
|
||||||
|
from . import it_tax_statement_wiz
|
||||||
|
from . import employee_payslip_download_wiz
|
||||||
|
|
@ -0,0 +1,569 @@
|
||||||
|
from urllib.parse import urlencode
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
|
||||||
|
class ITDeclarationSubmittedLockMixin(models.AbstractModel):
|
||||||
|
_name = 'it.declaration.submitted.lock.mixin'
|
||||||
|
_description = 'IT Declaration Submitted Lock Mixin'
|
||||||
|
|
||||||
|
def _get_related_it_declarations(self):
|
||||||
|
if 'it_declaration_id' in self._fields:
|
||||||
|
return self.mapped('it_declaration_id')
|
||||||
|
if 'child_education_id' in self._fields:
|
||||||
|
return self.mapped('child_education_id.it_declaration_id')
|
||||||
|
if 'parent_id' in self._fields:
|
||||||
|
return self.mapped('parent_id.it_declaration_id')
|
||||||
|
return self.env['emp.it.declaration']
|
||||||
|
|
||||||
|
def _check_it_declaration_is_editable(self):
|
||||||
|
if any(declaration.state == 'submitted' for declaration in self._get_related_it_declarations()):
|
||||||
|
raise UserError(_('Submitted IT declarations are read-only. Return the declaration to draft before editing it.'))
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
records = super().create(vals_list)
|
||||||
|
records._check_it_declaration_is_editable()
|
||||||
|
return records
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
self._check_it_declaration_is_editable()
|
||||||
|
return super().write(vals)
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
self._check_it_declaration_is_editable()
|
||||||
|
return super().unlink()
|
||||||
|
|
||||||
|
|
||||||
|
class EmpITDeclaration(models.Model):
|
||||||
|
_name = 'emp.it.declaration'
|
||||||
|
_rec_name = 'employee_id'
|
||||||
|
_description = "IT Declaration"
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('name_emp_tax_period', 'unique(employee_id, period_id, tax_regime)',
|
||||||
|
"Avoid creating duplicate records for the same period_id and tax_regime."),
|
||||||
|
]
|
||||||
|
|
||||||
|
# @api.depends('period_id', 'employee_id')
|
||||||
|
# def _compute_name(self):
|
||||||
|
# for sheet in self:
|
||||||
|
# # sheet.name = _('%(period_id)s, %(emp_name)s', period_id=sheet.period_id.name, emp_name=sheet.employee_id.name)
|
||||||
|
# sheet.name='hello world'
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
string="Employee",
|
||||||
|
default=lambda self: self.env.user.employee_id,
|
||||||
|
required=True
|
||||||
|
)
|
||||||
|
period_id = fields.Many2one(
|
||||||
|
'payroll.period',
|
||||||
|
string="Payroll Period",
|
||||||
|
required=True,
|
||||||
|
copy=False
|
||||||
|
)
|
||||||
|
|
||||||
|
display_period_label = fields.Char(string="Period Label", compute='_compute_display_period_label')
|
||||||
|
|
||||||
|
@api.depends('period_id.name')
|
||||||
|
def _compute_display_period_label(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.period_id:
|
||||||
|
rec.display_period_label = f"Financial Year {rec.period_id.name}"
|
||||||
|
else:
|
||||||
|
rec.display_period_label = ""
|
||||||
|
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime')
|
||||||
|
], string="Tax Regime", required=True, default='new')
|
||||||
|
state = fields.Selection([
|
||||||
|
('draft', 'Draft'),
|
||||||
|
('submitted', 'Submitted'),
|
||||||
|
], string="Status", default='draft', required=True, copy=False)
|
||||||
|
return_reason = fields.Text(string="Return Reason", copy=False)
|
||||||
|
is_payroll_manager = fields.Boolean(compute='_compute_is_payroll_manager')
|
||||||
|
|
||||||
|
total_investment = fields.Float(string='Total Investment')
|
||||||
|
|
||||||
|
costing_details_generated = fields.Boolean(default=False)
|
||||||
|
|
||||||
|
investment_costing_ids = fields.One2many('investment.costings','it_declaration_id')
|
||||||
|
visible_investment_costing_ids = fields.Many2many(
|
||||||
|
'investment.costings',
|
||||||
|
compute='_compute_visible_investment_costing_ids',
|
||||||
|
string='Visible Investment Costings',
|
||||||
|
)
|
||||||
|
house_rent_costing_id = fields.Many2one('investment.costings', compute="_compute_investment_costing")
|
||||||
|
is_section_open = fields.Boolean()
|
||||||
|
|
||||||
|
def _compute_is_payroll_manager(self):
|
||||||
|
is_manager = self.env.user.has_group('hr_payroll.group_hr_payroll_manager')
|
||||||
|
for rec in self:
|
||||||
|
rec.is_payroll_manager = is_manager
|
||||||
|
|
||||||
|
def _check_submitted_write_allowed(self, vals):
|
||||||
|
protected_vals = set(vals) - {'state', 'return_reason'}
|
||||||
|
submitted_records = self.filtered(lambda rec: rec.state == 'submitted')
|
||||||
|
if submitted_records and protected_vals:
|
||||||
|
raise UserError(_('Submitted IT declarations are read-only. Return the declaration to draft before editing it.'))
|
||||||
|
if submitted_records and set(vals) & {'state', 'return_reason'} and not self.env.user.has_group('hr_payroll.group_hr_payroll_manager'):
|
||||||
|
raise UserError(_('Only a Payroll Manager can return a submitted IT declaration.'))
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
self._check_submitted_write_allowed(vals)
|
||||||
|
return super().write(vals)
|
||||||
|
|
||||||
|
def unlink(self):
|
||||||
|
if any(rec.state == 'submitted' for rec in self):
|
||||||
|
raise UserError(_('Submitted IT declarations cannot be deleted.'))
|
||||||
|
return super().unlink()
|
||||||
|
@api.depends('costing_details_generated','investment_costing_ids')
|
||||||
|
def _compute_investment_costing(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.investment_costing_ids and rec.costing_details_generated:
|
||||||
|
rec.house_rent_costing_id = rec.investment_costing_ids.filtered(
|
||||||
|
lambda e: e.investment_type_id.investment_type == 'house_rent'
|
||||||
|
)[:1]
|
||||||
|
else:
|
||||||
|
rec.house_rent_costing_id = False
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'investment_costing_ids',
|
||||||
|
'investment_costing_ids.investment_type_id',
|
||||||
|
'investment_costing_ids.investment_type_id.active',
|
||||||
|
'period_id',
|
||||||
|
'tax_regime',
|
||||||
|
)
|
||||||
|
def _compute_visible_investment_costing_ids(self):
|
||||||
|
for rec in self:
|
||||||
|
visible_investment_type_ids = rec._get_visible_investment_types().ids
|
||||||
|
rec.visible_investment_costing_ids = rec.investment_costing_ids.filtered(
|
||||||
|
lambda costing: costing.investment_type_id
|
||||||
|
and costing.investment_type_id.id in visible_investment_type_ids
|
||||||
|
)
|
||||||
|
past_employment_costings = fields.One2many('past_employment.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
past_employment_costings_new = fields.One2many('past_employment.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us80c_costings = fields.One2many('us80c.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us80c_costings_new = fields.One2many('us80c.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us80d_selection_type = fields.Selection([('self_family','Self-family'),('self_family_parent','Self-family and parent'),('self_family_senior_parent','Self-family and senior parent')], default='self_family',required=True)
|
||||||
|
us80d_health_checkup = fields.Boolean(string='Preventive Health Checkup')
|
||||||
|
us80d_costings = fields.One2many('us80d.costing.type','it_declaration_id',domain=[('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',False),('investment_type_line_id.for_senior_parent','=',False),('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us80d_costings_new = fields.One2many('us80d.costing.type','it_declaration_id',domain=[('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',False),('investment_type_line_id.for_senior_parent','=',False),('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us80d_costings_parents = fields.One2many('us80d.costing.type','it_declaration_id',domain=['|',('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',True),('investment_type_line_id.for_senior_parent','=',False),('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us80d_costings_parents_new = fields.One2many('us80d.costing.type','it_declaration_id',domain=['|',('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',True),('investment_type_line_id.for_senior_parent','=',False),('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us80d_costings_senior_parents = fields.One2many('us80d.costing.type','it_declaration_id',domain=['|','|',('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',True),('investment_type_line_id.for_senior_parent','=',True),('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us80d_costings_senior_parents_new = fields.One2many('us80d.costing.type','it_declaration_id',domain=['|','|',('investment_type_line_id.for_family','=',True),('investment_type_line_id.for_parents','=',True),('investment_type_line_id.for_senior_parent','=',True),('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
|
||||||
|
us10_costings = fields.One2many('us10.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us10_costings_new = fields.One2many('us10.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us80g_costings = fields.One2many('us80g.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us80g_costings_new = fields.One2many('us80g.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
chapter_via_costings = fields.One2many('chapter.via.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
chapter_via_costings_new = fields.One2many('chapter.via.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
us17_costings = fields.One2many('us17.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
us17_costings_new = fields.One2many('us17.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
|
||||||
|
house_rent_costings = fields.One2many('house.rent.declaration','it_declaration_id')
|
||||||
|
|
||||||
|
other_il_costings = fields.One2many('other.il.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
other_il_costings_new = fields.One2many('other.il.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
other_declaration_costings = fields.One2many('other.declaration.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['old', 'both'])])
|
||||||
|
other_declaration_costings_new = fields.One2many('other.declaration.costing.type','it_declaration_id',domain=[('investment_type_line_id.tax_regime', 'in', ['new', 'both'])])
|
||||||
|
show_past_employment = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_us_80c = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_us_80d = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_us_10 = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_us_80g = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_chapter_via = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_us_17 = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_house_rent = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_other_i_or_l = fields.Boolean(compute="_compute_show_records")
|
||||||
|
show_other_declaration = fields.Boolean(compute="_compute_show_records")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _investment_type_line_fields(self):
|
||||||
|
return {
|
||||||
|
'past_employment': 'past_employment_ids',
|
||||||
|
'us_80c': 'us80c_ids',
|
||||||
|
'us_80d': 'us80d_ids',
|
||||||
|
'us_10': 'us10_ids',
|
||||||
|
'us_80g': 'us80g_ids',
|
||||||
|
'chapter_via': 'chapter_via_ids',
|
||||||
|
'us_17': 'us17_ids',
|
||||||
|
'other_i_or_l': 'other_il_ids',
|
||||||
|
'other_declaration': 'other_declaration_ids',
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_available_investment_types(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not self.period_id:
|
||||||
|
return self.env['it.investment.type']
|
||||||
|
return self.env['it.investment.type'].sudo().search([
|
||||||
|
('active', '=', True),
|
||||||
|
'|',
|
||||||
|
('period_ids', 'in', self.period_id.id),
|
||||||
|
('period_ids', '=', False),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _get_visible_investment_types(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return self._get_available_investment_types().filtered(
|
||||||
|
lambda investment_type: self._is_investment_type_visible(investment_type)
|
||||||
|
)
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _investment_type_generation_config(self):
|
||||||
|
return {
|
||||||
|
'past_employment': ('past_employment_ids', 'past_employment.costing.type'),
|
||||||
|
'us_80c': ('us80c_ids', 'us80c.costing.type'),
|
||||||
|
'us_80d': ('us80d_ids', 'us80d.costing.type'),
|
||||||
|
'us_10': ('us10_ids', 'us10.costing.type'),
|
||||||
|
'us_80g': ('us80g_ids', 'us80g.costing.type'),
|
||||||
|
'chapter_via': ('chapter_via_ids', 'chapter.via.costing.type'),
|
||||||
|
'us_17': ('us17_ids', 'us17.costing.type'),
|
||||||
|
'other_i_or_l': ('other_il_ids', 'other.il.costing.type'),
|
||||||
|
'other_declaration': ('other_declaration_ids', 'other.declaration.costing.type'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_regime_values(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return ['old', 'both'] if self.tax_regime == 'old' else ['new', 'both']
|
||||||
|
|
||||||
|
def _get_regime_filtered_costings(self, field_name):
|
||||||
|
self.ensure_one()
|
||||||
|
return self[field_name].filtered(
|
||||||
|
lambda line: line.investment_type_line_id
|
||||||
|
and line.investment_type_line_id.tax_regime in self._get_regime_values()
|
||||||
|
)
|
||||||
|
|
||||||
|
def _ensure_investment_costing_records(self):
|
||||||
|
for rec in self:
|
||||||
|
available_investment_types = rec._get_available_investment_types()
|
||||||
|
generation_config = rec._investment_type_generation_config()
|
||||||
|
|
||||||
|
for inv_type in available_investment_types:
|
||||||
|
investment_costing = rec.investment_costing_ids.filtered(
|
||||||
|
lambda cost: cost.investment_type_id == inv_type
|
||||||
|
)[:1]
|
||||||
|
if not investment_costing:
|
||||||
|
investment_costing = self.env['investment.costings'].sudo().create({
|
||||||
|
'investment_type_id': inv_type.id,
|
||||||
|
'it_declaration_id': rec.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
config = generation_config.get(inv_type.investment_type)
|
||||||
|
if not config:
|
||||||
|
continue
|
||||||
|
|
||||||
|
line_field, costing_model = config
|
||||||
|
active_lines = getattr(inv_type, line_field).filtered(lambda line: line.active)
|
||||||
|
existing_costings = self.env[costing_model].sudo().search([
|
||||||
|
('it_declaration_id', '=', rec.id),
|
||||||
|
('costing_type', '=', investment_costing.id),
|
||||||
|
])
|
||||||
|
existing_line_ids = set(existing_costings.mapped('investment_type_line_id').ids)
|
||||||
|
|
||||||
|
for investment_line in active_lines:
|
||||||
|
if investment_line.id in existing_line_ids:
|
||||||
|
continue
|
||||||
|
self.env[costing_model].sudo().create({
|
||||||
|
'costing_type': investment_costing.id,
|
||||||
|
'it_declaration_id': rec.id,
|
||||||
|
'investment_type_line_id': investment_line.id,
|
||||||
|
'limit': investment_line.limit,
|
||||||
|
})
|
||||||
|
|
||||||
|
def _update_investment_amounts(self):
|
||||||
|
for rec in self:
|
||||||
|
for investment_type in rec.investment_costing_ids:
|
||||||
|
if investment_type.investment_type_id.investment_type == 'past_employment':
|
||||||
|
costings = rec.past_employment_costings if rec.tax_regime == 'old' else rec.past_employment_costings_new
|
||||||
|
investment_type.amount = sum(
|
||||||
|
cost.declaration_amount
|
||||||
|
for cost in costings
|
||||||
|
if not cost.investment_type_line_id.compute_method
|
||||||
|
)
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'us_80c':
|
||||||
|
costings = rec.us80c_costings if rec.tax_regime == 'old' else rec.us80c_costings_new
|
||||||
|
investment_type.amount = sum(
|
||||||
|
cost.declaration_amount
|
||||||
|
for cost in costings
|
||||||
|
if not cost.investment_type_line_id.compute_method
|
||||||
|
)
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'us_80d':
|
||||||
|
if rec.us80d_selection_type == 'self_family':
|
||||||
|
costings = rec.us80d_costings if rec.tax_regime == 'old' else rec.us80d_costings_new
|
||||||
|
elif rec.us80d_selection_type == 'self_family_parent':
|
||||||
|
costings = rec.us80d_costings_parents if rec.tax_regime == 'old' else rec.us80d_costings_parents_new
|
||||||
|
else:
|
||||||
|
costings = rec.us80d_costings_senior_parents if rec.tax_regime == 'old' else rec.us80d_costings_senior_parents_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'us_10':
|
||||||
|
costings = rec.us10_costings if rec.tax_regime == 'old' else rec.us10_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'us_80g':
|
||||||
|
costings = rec.us80g_costings if rec.tax_regime == 'old' else rec.us80g_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'chapter_via':
|
||||||
|
costings = rec.chapter_via_costings if rec.tax_regime == 'old' else rec.chapter_via_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'us_17':
|
||||||
|
costings = rec.us17_costings if rec.tax_regime == 'old' else rec.us17_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'house_rent':
|
||||||
|
investment_type.amount = sum(rec.house_rent_costings.mapped('rent_amount') or [0]) if rec.tax_regime == 'old' else 0
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'other_i_or_l':
|
||||||
|
costings = rec.other_il_costings if rec.tax_regime == 'old' else rec.other_il_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
elif investment_type.investment_type_id.investment_type == 'other_declaration':
|
||||||
|
costings = rec.other_declaration_costings if rec.tax_regime == 'old' else rec.other_declaration_costings_new
|
||||||
|
investment_type.amount = sum(costings.mapped('declaration_amount') or [0])
|
||||||
|
|
||||||
|
def _is_investment_type_visible(self, investment_type):
|
||||||
|
self.ensure_one()
|
||||||
|
if not investment_type or not investment_type.active:
|
||||||
|
return False
|
||||||
|
valid_regimes = ['old', 'both'] if self.tax_regime == 'old' else ['new', 'both']
|
||||||
|
if investment_type.regime not in valid_regimes:
|
||||||
|
return False
|
||||||
|
line_field = self._investment_type_line_fields().get(investment_type.investment_type)
|
||||||
|
if not line_field:
|
||||||
|
return True
|
||||||
|
return bool(getattr(investment_type, line_field).filtered(
|
||||||
|
lambda line: line.active and line.tax_regime in valid_regimes
|
||||||
|
))
|
||||||
|
|
||||||
|
@api.depends('period_id', 'tax_regime')
|
||||||
|
def _compute_show_records(self):
|
||||||
|
field_mapping = {
|
||||||
|
'past_employment': 'show_past_employment',
|
||||||
|
'us_80c': 'show_us_80c',
|
||||||
|
'us_80d': 'show_us_80d',
|
||||||
|
'us_10': 'show_us_10',
|
||||||
|
'us_80g': 'show_us_80g',
|
||||||
|
'chapter_via': 'show_chapter_via',
|
||||||
|
'us_17': 'show_us_17',
|
||||||
|
'house_rent': 'show_house_rent',
|
||||||
|
'other_i_or_l': 'show_other_i_or_l',
|
||||||
|
'other_declaration': 'show_other_declaration',
|
||||||
|
}
|
||||||
|
for rec in self:
|
||||||
|
visible_investment_types = rec._get_visible_investment_types()
|
||||||
|
for field_name in field_mapping.values():
|
||||||
|
rec[field_name] = False
|
||||||
|
|
||||||
|
for investment_type_key, field_name in field_mapping.items():
|
||||||
|
rec[field_name] = bool(visible_investment_types.filtered(
|
||||||
|
lambda inv: inv.investment_type == investment_type_key
|
||||||
|
))
|
||||||
|
|
||||||
|
def toggle_section_visibility(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.is_section_open = not rec.is_section_open
|
||||||
|
if rec.is_section_open:
|
||||||
|
if rec.costing_details_generated:
|
||||||
|
rec._ensure_investment_costing_records()
|
||||||
|
rec._update_investment_amounts()
|
||||||
|
|
||||||
|
@api.onchange('tax_regime')
|
||||||
|
def _onchange_tax_regime(self):
|
||||||
|
if self.costing_details_generated:
|
||||||
|
self._ensure_investment_costing_records()
|
||||||
|
self._update_investment_amounts()
|
||||||
|
if self.tax_regime:
|
||||||
|
# res = super(empITDeclaration, self).fields_get(allfields, attributes)
|
||||||
|
# self.fields_get()
|
||||||
|
if self.tax_regime == 'new':
|
||||||
|
domain = [('investment_type_line_id.tax_regime', 'in', ['new', 'both'])]
|
||||||
|
elif self.tax_regime == 'old':
|
||||||
|
domain = [('investment_type_line_id.tax_regime', 'in', ['old', 'both'])]
|
||||||
|
else:
|
||||||
|
domain = [] # Default case, although 'tax_regime' is required
|
||||||
|
return {'domain': {'past_employment_costings': domain}}
|
||||||
|
else:
|
||||||
|
return {'domain': {'past_employment_costings': []}} # Handle potential empty state
|
||||||
|
|
||||||
|
# def fields_get(self, allfields=None, attributes=None):
|
||||||
|
# import pdb
|
||||||
|
# pdb.set_trace()
|
||||||
|
# res = super(empITDeclaration, self).fields_get(allfields, attributes)
|
||||||
|
# print(res)
|
||||||
|
#
|
||||||
|
# # Example: Modify domain of field_1 based on field_2
|
||||||
|
# if 'tax_regime' in res:
|
||||||
|
# if self.tax_regime == '':
|
||||||
|
# res['field_1']['domain'] = [('some_field', '=', 123)]
|
||||||
|
# else:
|
||||||
|
# res['field_1']['domain'] = [('some_field', '=', 456)]
|
||||||
|
#
|
||||||
|
# return res
|
||||||
|
# import pdb
|
||||||
|
# pdb.set_trace()
|
||||||
|
# if rec.tax_regime:
|
||||||
|
# return {'domain': {'past_employment_costings': [('investment_type_line_id.tax_regime', 'in', ['new','both'])]}}
|
||||||
|
# return {
|
||||||
|
# 'domain': {
|
||||||
|
# 'past_employment_costings': [('investment_type_line_id.tax_regime', 'in', ['new','both'])]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
def generate_declarations(self):
|
||||||
|
for rec in self:
|
||||||
|
rec._ensure_investment_costing_records()
|
||||||
|
rec._update_investment_amounts()
|
||||||
|
rec.costing_details_generated = True
|
||||||
|
|
||||||
|
def action_submit(self):
|
||||||
|
for rec in self:
|
||||||
|
rec._update_investment_amounts()
|
||||||
|
rec.write({
|
||||||
|
'state': 'submitted',
|
||||||
|
'return_reason': False,
|
||||||
|
})
|
||||||
|
|
||||||
|
def action_return_to_draft(self):
|
||||||
|
for rec in self:
|
||||||
|
if not self.env.user.has_group('hr_payroll.group_hr_payroll_manager'):
|
||||||
|
raise UserError(_('Only a Payroll Manager can return a submitted IT declaration.'))
|
||||||
|
if not rec.return_reason:
|
||||||
|
raise UserError(_('Please enter a return reason before returning the declaration to draft.'))
|
||||||
|
rec.state = 'draft'
|
||||||
|
|
||||||
|
def action_download_submission_pdf(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.state != 'submitted':
|
||||||
|
raise UserError(_('You can download the IT declaration PDF only after it is submitted.'))
|
||||||
|
return self.env.ref('employee_it_declaration.action_report_it_tax_statement').report_action(self)
|
||||||
|
|
||||||
|
def _get_regime_label(self):
|
||||||
|
self.ensure_one()
|
||||||
|
return dict(self._fields['tax_regime'].selection).get(self.tax_regime, self.tax_regime)
|
||||||
|
|
||||||
|
def _get_binary_link(self, record, field_name, filename_field=False):
|
||||||
|
if not record or not record[field_name]:
|
||||||
|
return False
|
||||||
|
params = {
|
||||||
|
'model': record._name,
|
||||||
|
'id': record.id,
|
||||||
|
'field': field_name,
|
||||||
|
'download': 'true',
|
||||||
|
}
|
||||||
|
if filename_field:
|
||||||
|
params['filename_field'] = filename_field
|
||||||
|
return '/web/content?%s' % urlencode(params)
|
||||||
|
|
||||||
|
def _prepare_report_line(self, line):
|
||||||
|
name = line.investment_type_line_id.name if line.investment_type_line_id else ''
|
||||||
|
return {
|
||||||
|
'name': name,
|
||||||
|
'declaration_amount': line.declaration_amount,
|
||||||
|
'proof_amount': line.proof_amount,
|
||||||
|
'limit': line.limit,
|
||||||
|
'remarks': line.remarks,
|
||||||
|
'attachment_name': line.proof_name or _('Proof'),
|
||||||
|
'attachment_url': self._get_binary_link(line, 'proof', 'proof_name'),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _get_report_sections(self):
|
||||||
|
self.ensure_one()
|
||||||
|
old_regime = self.tax_regime == 'old'
|
||||||
|
section_fields = [
|
||||||
|
(_('Past Employment'), 'past_employment_costings' if old_regime else 'past_employment_costings_new'),
|
||||||
|
(_('US 80C'), 'us80c_costings' if old_regime else 'us80c_costings_new'),
|
||||||
|
(_('US 80D'), {
|
||||||
|
'self_family': 'us80d_costings' if old_regime else 'us80d_costings_new',
|
||||||
|
'self_family_parent': 'us80d_costings_parents' if old_regime else 'us80d_costings_parents_new',
|
||||||
|
'self_family_senior_parent': 'us80d_costings_senior_parents' if old_regime else 'us80d_costings_senior_parents_new',
|
||||||
|
}[self.us80d_selection_type]),
|
||||||
|
(_('US 10'), 'us10_costings' if old_regime else 'us10_costings_new'),
|
||||||
|
(_('US 80G'), 'us80g_costings' if old_regime else 'us80g_costings_new'),
|
||||||
|
(_('Chapter VIA'), 'chapter_via_costings' if old_regime else 'chapter_via_costings_new'),
|
||||||
|
(_('US 17'), 'us17_costings' if old_regime else 'us17_costings_new'),
|
||||||
|
(_('Other Income / Loss'), 'other_il_costings' if old_regime else 'other_il_costings_new'),
|
||||||
|
(_('Other Declarations'), 'other_declaration_costings' if old_regime else 'other_declaration_costings_new'),
|
||||||
|
]
|
||||||
|
sections = []
|
||||||
|
for title, field_name in section_fields:
|
||||||
|
lines = self[field_name].filtered(lambda line: line.declaration_amount or line.proof_amount or line.remarks or line.proof)
|
||||||
|
if lines:
|
||||||
|
sections.append({
|
||||||
|
'title': title,
|
||||||
|
'lines': [self._prepare_report_line(line) for line in lines],
|
||||||
|
})
|
||||||
|
|
||||||
|
if old_regime and self.house_rent_costings:
|
||||||
|
sections.append({
|
||||||
|
'title': _('House Rent'),
|
||||||
|
'house_rent': True,
|
||||||
|
'lines': [{
|
||||||
|
'hra_exemption_type': dict(line._fields['hra_exemption_type'].selection).get(line.hra_exemption_type, ''),
|
||||||
|
'rent_amount': line.rent_amount,
|
||||||
|
'from_date': line.from_date,
|
||||||
|
'to_date': line.to_date,
|
||||||
|
'landlord_pan_status': dict(line._fields['landlord_pan_status'].selection).get(line.landlord_pan_status, ''),
|
||||||
|
'landlord_pan_no': line.landlord_pan_no,
|
||||||
|
'landlord_name_address': line.landlord_name_address,
|
||||||
|
'remarks': line.remarks,
|
||||||
|
'attachment_name': line.attachment_filename or _('Proof'),
|
||||||
|
'attachment_url': self._get_binary_link(line, 'attachment', 'attachment_filename'),
|
||||||
|
} for line in self.house_rent_costings],
|
||||||
|
})
|
||||||
|
return sections
|
||||||
|
|
||||||
|
def _get_report_extra_sections(self):
|
||||||
|
self.ensure_one()
|
||||||
|
extra_sections = []
|
||||||
|
us80c_lines = self.us80c_costings if self.tax_regime == 'old' else self.us80c_costings_new
|
||||||
|
other_il_lines = self.other_il_costings if self.tax_regime == 'old' else self.other_il_costings_new
|
||||||
|
|
||||||
|
children_records = self.env['children.education'].sudo().search([('it_declaration_id', '=', self.id), ('us80c_id', 'in', us80c_lines.ids)])
|
||||||
|
if children_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('Children Education Details'),
|
||||||
|
'headers': [_('Child'), _('Name'), _('Class / Grade'), _('School / College'), _('Tuition Fee')],
|
||||||
|
'rows': [[child.child_id, child.name, child.chile_class, child.organization, child.tuition_fee] for record in children_records for child in record.children_ids],
|
||||||
|
})
|
||||||
|
|
||||||
|
insurance_records = self.env['us80c.insurance.line'].sudo().search([('it_declaration_id', '=', self.id), ('us80c_id', 'in', us80c_lines.ids)])
|
||||||
|
if insurance_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('Life Insurance Details'),
|
||||||
|
'headers': [_('Company'), _('Insured For'), _('Insured Name'), _('Policy No'), _('Premium'), _('Payment Date'), _('Exempt Amount')],
|
||||||
|
'rows': [[line.name_of_insurance_company, line.insured_in_favour_of, line.name_of_insured, line.policy_number, line.premium_amount, line.payment_date, line.exempt_amount] for record in insurance_records for line in record.life_insurance_ids],
|
||||||
|
})
|
||||||
|
|
||||||
|
nsc_records = self.env['nsc.declaration.line'].sudo().search([('it_declaration_id', '=', self.id), ('us80c_id', 'in', us80c_lines.ids)])
|
||||||
|
if nsc_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('NSC Declaration Details'),
|
||||||
|
'headers': [_('NSC Number'), _('Amount'), _('Payment Date')],
|
||||||
|
'rows': [[line.nsc_number, line.nsc_amount, line.nsc_payment_date] for record in nsc_records for line in record.nsc_entry_ids],
|
||||||
|
})
|
||||||
|
|
||||||
|
self_occupied_records = self.env['self.occupied.property'].sudo().search([('it_declaration_id', '=', self.id), ('other_il_id', 'in', other_il_lines.ids)])
|
||||||
|
if self_occupied_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('Self Occupied Property Details'),
|
||||||
|
'headers': [_('Address'), _('Period From'), _('Period To'), _('Interest Paid'), _('Income / Loss'), _('Lender'), _('Lender PAN')],
|
||||||
|
'rows': [[record.address, record.period_from, record.period_to, record.interest_paid_to, record.income_loss, record.lender_name, record.lender_pan] for record in self_occupied_records],
|
||||||
|
})
|
||||||
|
|
||||||
|
letout_records = self.env['letout.house.property'].sudo().search([('it_declaration_id', '=', self.id), ('other_il_id', 'in', other_il_lines.ids)])
|
||||||
|
if letout_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('Let-out House Property Details'),
|
||||||
|
'headers': [_('Address'), _('Rent'), _('Property Tax'), _('Water Tax'), _('Interest Paid'), _('Income / Loss'), _('Lender'), _('Lender PAN')],
|
||||||
|
'rows': [[record.address, record.rent_received, record.property_tax, record.water_tax, record.interest_paid_to, record.income_loss, record.lender_name, record.lender_pan] for record in letout_records],
|
||||||
|
})
|
||||||
|
|
||||||
|
nsc_interest_records = self.env['nsc.interest.line'].sudo().search([('it_declaration_id', '=', self.id), ('other_il_id', 'in', other_il_lines.ids)])
|
||||||
|
if nsc_interest_records:
|
||||||
|
extra_sections.append({
|
||||||
|
'title': _('NSC Interest Details'),
|
||||||
|
'headers': [_('NSC Number'), _('Amount'), _('Payment Date'), _('Interest Amount')],
|
||||||
|
'rows': [[line.nsc_number, line.nsc_amount, line.nsc_payment_date, line.nsc_interest_amount] for record in nsc_interest_records for line in record.nsc_entry_ids],
|
||||||
|
})
|
||||||
|
return [section for section in extra_sections if section['rows']]
|
||||||
|
|
@ -0,0 +1,311 @@
|
||||||
|
import re
|
||||||
|
|
||||||
|
from odoo import api, fields, models, _
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools.safe_eval import safe_eval
|
||||||
|
from dateutil.relativedelta import relativedelta
|
||||||
|
import logging
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class EmployeePayslipDownloadWizard(models.TransientModel):
|
||||||
|
_name = 'employee.payslip.download.wizard'
|
||||||
|
_rec_name = 'employee_id'
|
||||||
|
_description = 'Employee Payslip Download Wizard'
|
||||||
|
|
||||||
|
def _default_period(self):
|
||||||
|
today = fields.Date.today()
|
||||||
|
return self.env['payroll.period'].search([
|
||||||
|
('from_date', '<=', today),
|
||||||
|
('to_date', '>=', today),
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
def _default_period_line(self):
|
||||||
|
previous_month_end = fields.Date.today().replace(day=1) - relativedelta(days=1)
|
||||||
|
|
||||||
|
_logger.info("Today: %s", fields.Date.today())
|
||||||
|
_logger.info("Previous Month End: %s", previous_month_end)
|
||||||
|
|
||||||
|
period = self._default_period()
|
||||||
|
_logger.info("Period: %s", period.name if period else "None")
|
||||||
|
|
||||||
|
lines = self.env['payroll.period.line'].search([
|
||||||
|
('period_id', '=', period.id),
|
||||||
|
], order='from_date')
|
||||||
|
|
||||||
|
for line in lines:
|
||||||
|
_logger.info(
|
||||||
|
"Month=%s From=%s To=%s",
|
||||||
|
line.name,
|
||||||
|
line.from_date,
|
||||||
|
line.to_date
|
||||||
|
)
|
||||||
|
|
||||||
|
month = self.env['payroll.period.line'].search([
|
||||||
|
('period_id', '=', period.id),
|
||||||
|
('from_date', '<=', previous_month_end),
|
||||||
|
('to_date', '>=', previous_month_end),
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
_logger.info("Selected Month: %s", month.name if month else "None")
|
||||||
|
|
||||||
|
return month
|
||||||
|
|
||||||
|
# def _default_period_line(self):
|
||||||
|
# # Last day of the previous month
|
||||||
|
# previous_month_end = fields.Date.today().replace(day=1) - relativedelta(days=1)
|
||||||
|
#
|
||||||
|
# period = self._default_period()
|
||||||
|
# if not period:
|
||||||
|
# return False
|
||||||
|
#
|
||||||
|
# return self.env['payroll.period.line'].search([
|
||||||
|
# ('period_id', '=', period.id),
|
||||||
|
# ('from_date', '<=', previous_month_end),
|
||||||
|
# ('to_date', '<=', previous_month_end),
|
||||||
|
# ], limit=1)
|
||||||
|
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
required=True,
|
||||||
|
default=lambda self: self.env.user.employee_id.id,
|
||||||
|
)
|
||||||
|
download_type = fields.Selection(
|
||||||
|
selection=[
|
||||||
|
('single', 'Single Payslip'),
|
||||||
|
('multi', 'Multiple Payslips'),
|
||||||
|
],
|
||||||
|
string='Download Option',
|
||||||
|
required=True,
|
||||||
|
default='single',
|
||||||
|
)
|
||||||
|
period_id = fields.Many2one(
|
||||||
|
'payroll.period',
|
||||||
|
string='Payroll Period',
|
||||||
|
required=True,
|
||||||
|
default=_default_period,
|
||||||
|
)
|
||||||
|
today = fields.Date(
|
||||||
|
default=fields.Date.today
|
||||||
|
)
|
||||||
|
period_line = fields.Many2one(
|
||||||
|
'payroll.period.line',
|
||||||
|
string="Month",
|
||||||
|
default=_default_period_line,
|
||||||
|
domain="[('period_id','=',period_id), ('to_date','<=', previous_month_end)]",
|
||||||
|
)
|
||||||
|
payslip_count = fields.Integer(
|
||||||
|
string='Available Payslips',
|
||||||
|
compute='_compute_payslip_count',
|
||||||
|
)
|
||||||
|
is_hr_manager = fields.Boolean(compute="_compute_is_hr_manager")
|
||||||
|
previous_month_end = fields.Date(
|
||||||
|
default=lambda self: (
|
||||||
|
fields.Date.today().replace(day=1)
|
||||||
|
- relativedelta(days=1)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
available_period_ids = fields.Many2many(
|
||||||
|
'payroll.period',
|
||||||
|
compute='_compute_available_period_ids'
|
||||||
|
)
|
||||||
|
|
||||||
|
def _compute_is_hr_manager(self):
|
||||||
|
for rec in self:
|
||||||
|
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):
|
||||||
|
for rec in self:
|
||||||
|
rec.period_line = False
|
||||||
|
|
||||||
|
@api.depends('download_type', 'period_id', 'period_line')
|
||||||
|
def _compute_payslip_count(self):
|
||||||
|
for rec in self:
|
||||||
|
if not rec.period_id or (rec.download_type == 'single' and not rec.period_line):
|
||||||
|
rec.payslip_count = 0
|
||||||
|
else:
|
||||||
|
rec.payslip_count = len(rec._get_available_payslips())
|
||||||
|
|
||||||
|
def _get_current_employee(self):
|
||||||
|
employee = self.env.user.employee_id
|
||||||
|
if not employee:
|
||||||
|
raise UserError(_('No employee is linked to your user. Please contact HR.'))
|
||||||
|
return employee
|
||||||
|
|
||||||
|
def _get_date_range(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if self.download_type == 'single':
|
||||||
|
if not self.period_line:
|
||||||
|
raise UserError(_('Please select a month to download a single payslip.'))
|
||||||
|
return self.period_line.from_date, self.period_line.to_date
|
||||||
|
return self.period_id.from_date, self.period_id.to_date
|
||||||
|
|
||||||
|
def _get_available_payslips(self):
|
||||||
|
self.ensure_one()
|
||||||
|
employee = self._get_current_employee()
|
||||||
|
date_from, date_to = self._get_date_range()
|
||||||
|
if not date_from or not date_to:
|
||||||
|
return self.env['hr.payslip']
|
||||||
|
|
||||||
|
return self.env['hr.payslip'].sudo().search([
|
||||||
|
('employee_id', '=', employee.id),
|
||||||
|
('state', 'in', ['done', 'paid']),
|
||||||
|
('date_from', '>=', date_from),
|
||||||
|
('date_to', '<=', date_to),
|
||||||
|
('company_id', 'in', self.env.companies.ids),
|
||||||
|
], order='date_from asc, date_to asc, id asc')
|
||||||
|
|
||||||
|
def _get_payslips_for_download(self):
|
||||||
|
self.ensure_one()
|
||||||
|
payslips = self._get_available_payslips()
|
||||||
|
if not payslips:
|
||||||
|
raise UserError(_('No confirmed or paid payslip is available for the selected period.'))
|
||||||
|
if self.download_type == 'single' and len(payslips) > 1:
|
||||||
|
raise UserError(_('More than one payslip is available for this month. Please contact HR.'))
|
||||||
|
return payslips
|
||||||
|
|
||||||
|
def _get_pdf_content(self, payslip):
|
||||||
|
report = payslip.struct_id.report_id or self.env.ref('hr_payroll.action_report_payslip')
|
||||||
|
pdf_content, dummy = self.env['ir.actions.report'].sudo().with_context(
|
||||||
|
lang=payslip.employee_id.lang or self.env.lang,
|
||||||
|
)._render_qweb_pdf(report, payslip.id, data={'company_id': payslip.company_id})
|
||||||
|
return report, pdf_content
|
||||||
|
|
||||||
|
def _get_pdf_filename(self, payslip, report=False):
|
||||||
|
report = report or payslip.struct_id.report_id or self.env.ref('hr_payroll.action_report_payslip')
|
||||||
|
if report.print_report_name:
|
||||||
|
filename = safe_eval(report.print_report_name, {'object': payslip})
|
||||||
|
else:
|
||||||
|
filename = _('Payslip - %(employee)s - %(period)s', employee=payslip.employee_id.name, period=payslip.name)
|
||||||
|
return self._sanitize_filename(filename, 'payslip') + '.pdf'
|
||||||
|
|
||||||
|
def _get_zip_filename(self):
|
||||||
|
self.ensure_one()
|
||||||
|
employee = self._get_current_employee()
|
||||||
|
filename = _('Payslips - %(employee)s - %(period)s', employee=employee.name, period=self.period_id.name)
|
||||||
|
return self._sanitize_filename(filename, 'payslips') + '.zip'
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _sanitize_filename(self, filename, fallback):
|
||||||
|
filename = re.sub(r'[\\/:*?"<>|]+', '-', str(filename or '')).strip(' .')
|
||||||
|
return filename or fallback
|
||||||
|
|
||||||
|
def action_download_payslips(self):
|
||||||
|
self.ensure_one()
|
||||||
|
self._get_payslips_for_download()
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_url',
|
||||||
|
'url': '/employee_it_declaration/my_payslips/%s' % self.id,
|
||||||
|
'target': 'self',
|
||||||
|
}
|
||||||
|
|
||||||
|
# @api.onchange('employee_id')
|
||||||
|
# def _onchange_employee_id(self):
|
||||||
|
#
|
||||||
|
# if not self.employee_id:
|
||||||
|
# return {}
|
||||||
|
#
|
||||||
|
# # Replace joining_date with your employee joining field
|
||||||
|
# joining_date = self.employee_id.doj
|
||||||
|
# today = fields.Date.today()
|
||||||
|
#
|
||||||
|
# if not joining_date:
|
||||||
|
# joining_date = today
|
||||||
|
# # today = fields.Date.today()
|
||||||
|
# current_period = self.env['payroll.period'].search([
|
||||||
|
# ('from_date', '<=', today),
|
||||||
|
# ('to_date', '>=', today),
|
||||||
|
# ], limit=1)
|
||||||
|
#
|
||||||
|
# if current_period:
|
||||||
|
# self.period_id = current_period
|
||||||
|
#
|
||||||
|
# return {
|
||||||
|
# 'domain': {
|
||||||
|
# 'period_id': [
|
||||||
|
# ('to_date', '>=', joining_date),
|
||||||
|
# ('from_date', '<=', today),
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
@api.depends('employee_id')
|
||||||
|
def _compute_available_period_ids(self):
|
||||||
|
today = fields.Date.today()
|
||||||
|
|
||||||
|
for rec in self:
|
||||||
|
employee = rec.employee_id or self.env.user.employee_id
|
||||||
|
if not employee:
|
||||||
|
rec.available_period_ids = False
|
||||||
|
continue
|
||||||
|
|
||||||
|
joining_date = employee.doj or today
|
||||||
|
|
||||||
|
periods = self.env['payroll.period'].search([
|
||||||
|
('to_date', '>=', joining_date),
|
||||||
|
('from_date', '<=', today),
|
||||||
|
])
|
||||||
|
|
||||||
|
rec.available_period_ids = periods
|
||||||
|
|
||||||
|
# @api.onchange('employee_id')
|
||||||
|
# def _onchange_employee_id(self):
|
||||||
|
# if not self.employee_id:
|
||||||
|
# return {}
|
||||||
|
#
|
||||||
|
# joining_date = self.employee_id.doj
|
||||||
|
# today = fields.Date.today()
|
||||||
|
#
|
||||||
|
# if not joining_date:
|
||||||
|
# joining_date = today
|
||||||
|
#
|
||||||
|
# # Current Financial Year
|
||||||
|
# current_period = self.env['payroll.period'].search([
|
||||||
|
# ('from_date', '<=', today),
|
||||||
|
# ('to_date', '>=', today),
|
||||||
|
# ], limit=1)
|
||||||
|
# if current_period:
|
||||||
|
# self.period_id = current_period
|
||||||
|
#
|
||||||
|
# return {
|
||||||
|
# 'domain': {
|
||||||
|
# 'period_id': [
|
||||||
|
# ('to_date', '>=', joining_date), # DOJ year onwards
|
||||||
|
# ('from_date', '<=', today), # No future FY
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
# }
|
||||||
|
|
||||||
|
|
||||||
|
@api.onchange('period_id')
|
||||||
|
def _onchange_period_id(self):
|
||||||
|
|
||||||
|
self.period_line = False
|
||||||
|
|
||||||
|
if not self.period_id:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
previous_month_end = (
|
||||||
|
fields.Date.today().replace(day=1)
|
||||||
|
- relativedelta(days=1)
|
||||||
|
)
|
||||||
|
|
||||||
|
month = self.env['payroll.period.line'].search([
|
||||||
|
('period_id', '=', self.period_id.id),
|
||||||
|
('from_date', '<=', previous_month_end),
|
||||||
|
('to_date', '>=', previous_month_end),
|
||||||
|
], limit=1)
|
||||||
|
|
||||||
|
if month:
|
||||||
|
self.period_line = month
|
||||||
|
|
||||||
|
return {
|
||||||
|
'domain': {
|
||||||
|
'period_line': [
|
||||||
|
('period_id', '=', self.period_id.id),
|
||||||
|
('to_date', '<=', previous_month_end),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,318 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
import calendar
|
||||||
|
import re
|
||||||
|
|
||||||
|
|
||||||
|
class investmentCostings(models.Model):
|
||||||
|
_name = 'investment.costings'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_id'
|
||||||
|
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type')
|
||||||
|
amount = fields.Integer()
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
employee_id = fields.Many2one(
|
||||||
|
'hr.employee',
|
||||||
|
string="Employee",
|
||||||
|
related='it_declaration_id.employee_id'
|
||||||
|
)
|
||||||
|
period_id = fields.Many2one(
|
||||||
|
'payroll.period',
|
||||||
|
string="Payroll Period",
|
||||||
|
related='it_declaration_id.period_id'
|
||||||
|
)
|
||||||
|
|
||||||
|
class pastEmpcostingType(models.Model):
|
||||||
|
_name = 'past_employment.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
investment_type_line_id = fields.Many2one('past_employment.investment.type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount',compute='_compute_declaration_amount',store=True,readonly=False)
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
@api.depends(
|
||||||
|
'it_declaration_id.past_employment_costings.declaration_amount',
|
||||||
|
'it_declaration_id.past_employment_costings_new.declaration_amount',
|
||||||
|
'investment_type_line_id.compute_method',
|
||||||
|
'investment_type_line_id.compute_code',
|
||||||
|
'it_declaration_id.tax_regime',
|
||||||
|
'declaration_amount'
|
||||||
|
)
|
||||||
|
def _compute_declaration_amount(self):
|
||||||
|
for rec in self:
|
||||||
|
line = rec.investment_type_line_id
|
||||||
|
if not line or not rec.it_declaration_id:
|
||||||
|
rec.declaration_amount = 0
|
||||||
|
continue
|
||||||
|
|
||||||
|
if line.compute_method and line.compute_code:
|
||||||
|
siblings = (
|
||||||
|
rec.it_declaration_id.past_employment_costings
|
||||||
|
if rec.it_declaration_id.tax_regime == 'old'
|
||||||
|
else rec.it_declaration_id.past_employment_costings_new
|
||||||
|
)
|
||||||
|
|
||||||
|
code_vars = {}
|
||||||
|
for sibling in siblings:
|
||||||
|
code = sibling.investment_type_line_id.investment_code
|
||||||
|
if code:
|
||||||
|
code_vars[code] = sibling.declaration_amount or 0
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Extract variable names from compute_code
|
||||||
|
var_names = set(re.findall(r'\b[A-Z]+\b', line.compute_code))
|
||||||
|
for var in var_names:
|
||||||
|
code_vars.setdefault(var, 0) # Ensure missing variables default to 0
|
||||||
|
|
||||||
|
rec.declaration_amount = int(eval(line.compute_code, {"__builtins__": {}}, code_vars))
|
||||||
|
except Exception as e:
|
||||||
|
raise ValidationError(f"Error in compute_code for {line.name}: {e}")
|
||||||
|
else:
|
||||||
|
# Allow manual entry
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@api.onchange('investment_type_line_id', 'declaration_amount')
|
||||||
|
def _onchange_declaration_amount_live(self):
|
||||||
|
for rec in self:
|
||||||
|
line = rec.investment_type_line_id
|
||||||
|
if not line or not rec.it_declaration_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
siblings = rec.it_declaration_id.past_employment_costings | rec.it_declaration_id.past_employment_costings_new
|
||||||
|
code_vars = {}
|
||||||
|
for sibling in siblings:
|
||||||
|
code = sibling.investment_type_line_id.investment_code
|
||||||
|
if code:
|
||||||
|
code_vars[code] = sibling.declaration_amount or 0
|
||||||
|
|
||||||
|
if line.compute_method and line.compute_code:
|
||||||
|
try:
|
||||||
|
rec.declaration_amount = int(eval(line.compute_code, {"__builtins__": {}}, code_vars))
|
||||||
|
except Exception as e:
|
||||||
|
rec.declaration_amount = 0 # fallback
|
||||||
|
|
||||||
|
|
||||||
|
class us80cCostingType(models.Model):
|
||||||
|
_name = 'us80c.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('us80c.investment.type')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
action_id = fields.Many2one('ir.actions.act_window', related='investment_type_line_id.action_id')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
def open_action_wizard(self):
|
||||||
|
self.ensure_one()
|
||||||
|
model = self.env[self.action_id.res_model]
|
||||||
|
|
||||||
|
action_model = model.sudo().search([('it_declaration_id','=',self.it_declaration_id.id),('us80c_id','=',self.id)],order='id desc',limit=1)
|
||||||
|
# it_declaration_id
|
||||||
|
if not action_model:
|
||||||
|
# Explicitly create record so children get added in create()
|
||||||
|
action_model = model.sudo().create({
|
||||||
|
'it_declaration_id': self.it_declaration_id.id,
|
||||||
|
'us80c_id': self.id,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': self.action_id.name,
|
||||||
|
'res_model': self.action_id.res_model,
|
||||||
|
'res_id': action_model.id,
|
||||||
|
'view_mode': self.action_id.view_mode,
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
class us80dCostingType(models.Model):
|
||||||
|
_name = 'us80d.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('us80d.investment.type')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class us10CostingType(models.Model):
|
||||||
|
_name = 'us10.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('us10.investment.type')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class us80gCostingType(models.Model):
|
||||||
|
_name = 'us80g.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('us80g.investment.type')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class chapterViaCostingType(models.Model):
|
||||||
|
_name = 'chapter.via.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('chapter.via.investment.type')
|
||||||
|
investment_type_id = fields.Many2one('it.investment.type',related='investment_type_line_id.investment_type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class us17CostingType(models.Model):
|
||||||
|
_name = 'us17.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('us17.investment.type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
class OtherILCostingType(models.Model):
|
||||||
|
_name = 'other.il.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('other.il.investment.type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
action_id = fields.Many2one('ir.actions.act_window', related='investment_type_line_id.action_id')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
def open_action_wizard(self):
|
||||||
|
self.ensure_one()
|
||||||
|
model = self.env[self.action_id.res_model]
|
||||||
|
|
||||||
|
action_model = model.sudo().search([('it_declaration_id','=',self.it_declaration_id.id),('other_il_id','=',self.id)],order='id desc',limit=1)
|
||||||
|
# it_declaration_id
|
||||||
|
if not action_model:
|
||||||
|
# Explicitly create record so children get added in create()
|
||||||
|
action_model = model.sudo().create({
|
||||||
|
'it_declaration_id': self.it_declaration_id.id,
|
||||||
|
'other_il_id': self.id,
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': self.action_id.name,
|
||||||
|
'res_model': self.action_id.res_model,
|
||||||
|
'res_id': action_model.id,
|
||||||
|
'view_mode': self.action_id.view_mode,
|
||||||
|
'target': 'new',
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class OtherDeclarationCostingType(models.Model):
|
||||||
|
_name = 'other.declaration.costing.type'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_rec_name = 'investment_type_line_id'
|
||||||
|
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
investment_type_line_id = fields.Many2one('other.declaration.investment.type')
|
||||||
|
declaration_amount = fields.Integer(string='Declaration Amount')
|
||||||
|
proof_amount = fields.Integer(string="Proof Amount")
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
proof = fields.Binary(string="PROOF")
|
||||||
|
proof_name = fields.Char()
|
||||||
|
limit = fields.Integer()
|
||||||
|
|
||||||
|
|
||||||
|
class HouseRentDeclaration(models.Model):
|
||||||
|
_name = 'house.rent.declaration'
|
||||||
|
_inherit = ['it.declaration.submitted.lock.mixin']
|
||||||
|
_description = 'House Rent Declaration'
|
||||||
|
|
||||||
|
|
||||||
|
it_declaration_id = fields.Many2one('emp.it.declaration')
|
||||||
|
costing_type = fields.Many2one('investment.costings')
|
||||||
|
|
||||||
|
hra_exemption_type = fields.Selection([
|
||||||
|
('u_s_10', 'U/S 10 - HRA Exemption'),
|
||||||
|
('u_s_80gg', 'U/S 80GG - HRA Exemption')
|
||||||
|
], string="HRA Exemption Type", required=True, default='u_s_10')
|
||||||
|
|
||||||
|
rent_amount = fields.Float(string="Total Rent for the Period", required=True)
|
||||||
|
from_date = fields.Date(string="From Date", required=True)
|
||||||
|
to_date = fields.Date(string="To Date", required=True)
|
||||||
|
remarks = fields.Text(string="Remarks")
|
||||||
|
|
||||||
|
landlord_pan_no = fields.Char(string="Landlord PAN No")
|
||||||
|
landlord_name_address = fields.Text(string="Landlord Name & Address")
|
||||||
|
|
||||||
|
landlord_pan_status = fields.Selection([
|
||||||
|
('has_pan', 'Landlord has PAN CARD'),
|
||||||
|
('declaration', 'Declaration By Landlord')
|
||||||
|
], string="Landlord PAN Status", required=True, default='has_pan')
|
||||||
|
|
||||||
|
attachment = fields.Binary(string="Proof Attachment")
|
||||||
|
attachment_filename = fields.Char(string="Attachment Filename")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
# Auto-link applicant_id if context is passed correctly
|
||||||
|
if self.env.context.get('default_it_declaration_id'):
|
||||||
|
costing_id = self.env['investment.costings'].sudo().search([('id','=',self.env.context.get('it_declaration_id')),('investment_type_id.investment_type','=','house_rent')],limit=1)
|
||||||
|
vals['costing_type'] = costing_id.id
|
||||||
|
return super().create(vals)
|
||||||
|
|
@ -0,0 +1,276 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
|
||||||
|
class ItInvestmentType(models.Model):
|
||||||
|
_name = 'it.investment.type'
|
||||||
|
|
||||||
|
@api.depends('investment_type')
|
||||||
|
def _compute_display_name(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.investment_type:
|
||||||
|
rec.display_name = dict(
|
||||||
|
self._fields['investment_type'].selection
|
||||||
|
).get(rec.investment_type)
|
||||||
|
else:
|
||||||
|
rec.display_name = (
|
||||||
|
rec.investment_type.replace('_', ' ').title()
|
||||||
|
if rec.investment_type else ''
|
||||||
|
)
|
||||||
|
|
||||||
|
sequence = fields.Integer()
|
||||||
|
investment_type = fields.Selection(
|
||||||
|
[('past_employment', 'PAST EMPLOYMENT'), ('us_80c', 'US 80C'), ('us_80d', 'US 80D'), ('us_10', 'US 10'),
|
||||||
|
('us_80g', 'US 80G'), ('chapter_via', 'CHAPTER VIA'), ('us_17', 'US 17'), ('house_rent', 'HOUSE RENT'),
|
||||||
|
('other_i_or_l', 'OTHER INCOME/LOSS'), ('other_declaration', 'OTHER DECLARATION')], string="Investment Type",
|
||||||
|
required=True)
|
||||||
|
regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string='Regime', required=True, default='both')
|
||||||
|
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
past_employment_ids = fields.One2many('past_employment.investment.type','investment_type')
|
||||||
|
us80c_ids = fields.One2many('us80c.investment.type', 'investment_type')
|
||||||
|
us80d_ids = fields.One2many('us80d.investment.type', 'investment_type')
|
||||||
|
us10_ids = fields.One2many('us10.investment.type', 'investment_type')
|
||||||
|
us80g_ids = fields.One2many('us80g.investment.type', 'investment_type')
|
||||||
|
chapter_via_ids = fields.One2many('chapter.via.investment.type', 'investment_type')
|
||||||
|
us17_ids = fields.One2many('us17.investment.type', 'investment_type')
|
||||||
|
other_il_ids = fields.One2many('other.il.investment.type', 'investment_type')
|
||||||
|
other_declaration_ids = fields.One2many('other.declaration.investment.type', 'investment_type')
|
||||||
|
period_ids = fields.Many2many(
|
||||||
|
'payroll.period',
|
||||||
|
'it_investment_type_payroll_period_rel',
|
||||||
|
'investment_type_id',
|
||||||
|
'period_id',
|
||||||
|
string='Periods'
|
||||||
|
)
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
self.env.cr.execute("""
|
||||||
|
INSERT INTO it_investment_type_payroll_period_rel (investment_type_id, period_id)
|
||||||
|
SELECT investment_type.id, period.id
|
||||||
|
FROM it_investment_type AS investment_type
|
||||||
|
JOIN payroll_period AS period ON TRUE
|
||||||
|
LEFT JOIN it_investment_type_payroll_period_rel AS rel
|
||||||
|
ON rel.investment_type_id = investment_type.id
|
||||||
|
WHERE investment_type.active = TRUE
|
||||||
|
AND rel.investment_type_id IS NULL
|
||||||
|
""")
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def _get_all_period_commands(self):
|
||||||
|
return [(6, 0, self.env['payroll.period'].search([]).ids)]
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
for vals in vals_list:
|
||||||
|
if 'period_ids' not in vals and vals.get('active', True):
|
||||||
|
vals['period_ids'] = self._get_all_period_commands()
|
||||||
|
return super().create(vals_list)
|
||||||
|
|
||||||
|
def write(self, vals):
|
||||||
|
res = super().write(vals)
|
||||||
|
if vals.get('active') is True:
|
||||||
|
all_period_commands = self._get_all_period_commands()
|
||||||
|
self.filtered(lambda record: record.active and not record.period_ids).write({
|
||||||
|
'period_ids': all_period_commands,
|
||||||
|
})
|
||||||
|
return res
|
||||||
|
|
||||||
|
class pastEmpInvestmentType(models.Model):
|
||||||
|
_name = 'past_employment.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
_sql_constraints = [
|
||||||
|
('investment_code_unique', 'unique(investment_code)', 'Code must be unique'),
|
||||||
|
]
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
investment_code = fields.Char()
|
||||||
|
compute_method = fields.Boolean()
|
||||||
|
compute_code = fields.Text('Python Code')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class us80cInvestmentType(models.Model):
|
||||||
|
_name = 'us80c.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
investment_code = fields.Char()
|
||||||
|
compute_method = fields.Boolean()
|
||||||
|
compute_code = fields.Text('Python Code')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
require_action = fields.Boolean()
|
||||||
|
action_id = fields.Many2one('ir.actions.act_window')
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class us80dInvestmentType(models.Model):
|
||||||
|
_name = 'us80d.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
for_family = fields.Boolean()
|
||||||
|
for_parents = fields.Boolean()
|
||||||
|
for_senior_parent = fields.Boolean()
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class us10InvestmentType(models.Model):
|
||||||
|
_name = 'us10.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class us80gInvestmentType(models.Model):
|
||||||
|
_name = 'us80g.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", required=True)
|
||||||
|
|
||||||
|
|
||||||
|
class chapterViaInvestmentType(models.Model):
|
||||||
|
_name = 'chapter.via.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class us17InvestmentType(models.Model):
|
||||||
|
_name = 'us17.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
class OtherILInvestmentType(models.Model):
|
||||||
|
_name = 'other.il.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
require_action = fields.Boolean()
|
||||||
|
action_id = fields.Many2one('ir.actions.act_window')
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class OtherDeclarationInvestmentType(models.Model):
|
||||||
|
_name = 'other.declaration.investment.type'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
name = fields.Char(string='GENERAL', required=True)
|
||||||
|
investment_type = fields.Many2one('it.investment.type')
|
||||||
|
limit = fields.Integer(string='LIMIT')
|
||||||
|
sequence = fields.Integer()
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
tax_regime = fields.Selection([
|
||||||
|
('new', 'New Regime'),
|
||||||
|
('old', 'Old Regime'),
|
||||||
|
('both', 'Both')
|
||||||
|
], string="Tax Regime", store=True, required=True)
|
||||||
|
|
||||||
|
|
||||||
|
#
|
||||||
|
# class ItInvestmentTypeLine(models.Model):
|
||||||
|
# _name = 'it.investment.type.line'
|
||||||
|
# _description = 'IT Investment Type Line'
|
||||||
|
# _rec_name = 'name'
|
||||||
|
#
|
||||||
|
# investment_type_id = fields.Many2one(
|
||||||
|
# 'it.investment.type',
|
||||||
|
# string="Investment Type",
|
||||||
|
# required=True,
|
||||||
|
# ondelete='cascade'
|
||||||
|
# )
|
||||||
|
# investment_type = fields.Selection(
|
||||||
|
# [('past_employment', 'PAST EMPLOYMENT'), ('us_80c', 'US 80C'), ('us_80d', 'US 80D'), ('us_10', 'US 10'),
|
||||||
|
# ('us_80g', 'US 80G'), ('chapter_via', 'CHAPTER VIA'), ('us_17', 'US 17'), ('house_rent', 'HOUSE RENT'),
|
||||||
|
# ('other_i_or_l', 'OTHER INCOME/LOSS'), ('other_declaration', 'OTHER DECLARATION')], string="Investment Type",
|
||||||
|
# related='investment_type_id.name')
|
||||||
|
# name = fields.Char(string="Line Name", required=True)
|
||||||
|
# need_action = fields.Boolean(string="Needs Action")
|
||||||
|
# action_id = fields.Many2one(
|
||||||
|
# 'ir.actions.actions',
|
||||||
|
# string="Action",
|
||||||
|
# help="Linked Odoo action if needed"
|
||||||
|
# )
|
||||||
|
# tax_regime = fields.Selection([
|
||||||
|
# ('new', 'New Regime'),
|
||||||
|
# ('old', 'Old Regime')
|
||||||
|
# ], string="Tax Regime", required=True)
|
||||||
|
# sequence = fields.Integer()
|
||||||
|
# active = fields.Boolean(default=True)
|
||||||
|
# limit = fields.Integer()
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
from odoo import models, fields, api, _
|
||||||
|
|
||||||
|
class ITTaxStatement(models.Model):
|
||||||
|
_name = 'it.tax.statement'
|
||||||
|
_description = 'IT Tax Statement'
|
||||||
|
_rec_name = 'employee_id'
|
||||||
|
|
||||||
|
employee_id = fields.Many2one('hr.employee', required=True)
|
||||||
|
declaration_id = fields.Many2one('emp.it.declaration', required=True)
|
||||||
|
period_id = fields.Many2one('payroll.period', related='declaration_id.period_id', store=True)
|
||||||
|
period_line = fields.Many2one('payroll.period.line')
|
||||||
|
total_declared_amount = fields.Float(compute='_compute_totals')
|
||||||
|
tax_regime = fields.Selection(related='declaration_id.tax_regime')
|
||||||
|
pdf_generated = fields.Boolean(default=False)
|
||||||
|
|
||||||
|
@api.depends('declaration_id')
|
||||||
|
def _compute_totals(self):
|
||||||
|
for rec in self:
|
||||||
|
rec.total_declared_amount = sum(rec.declaration_id.investment_costing_ids.mapped('amount'))
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,71 @@
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
import calendar
|
||||||
|
|
||||||
|
|
||||||
|
class PayrollPeriod(models.Model):
|
||||||
|
_name = 'payroll.period'
|
||||||
|
_description = 'Payroll Period'
|
||||||
|
_rec_name = 'name'
|
||||||
|
_order = 'id desc'
|
||||||
|
_sql_constraints = [
|
||||||
|
('unique_name', 'unique(name)', 'The name must be unique.')
|
||||||
|
]
|
||||||
|
|
||||||
|
from_date = fields.Date(string="From Date", required=True)
|
||||||
|
to_date = fields.Date(string="To Date", required=True)
|
||||||
|
name = fields.Char(string="Name", required=True)
|
||||||
|
period_line_ids = fields.One2many('payroll.period.line', 'period_id', string="Monthly Periods")
|
||||||
|
|
||||||
|
@api.model_create_multi
|
||||||
|
def create(self, vals_list):
|
||||||
|
periods = super().create(vals_list)
|
||||||
|
active_investment_types = self.env['it.investment.type'].search([('active', '=', True)])
|
||||||
|
if active_investment_types:
|
||||||
|
active_investment_types.write({
|
||||||
|
'period_ids': [(4, period.id) for period in periods],
|
||||||
|
})
|
||||||
|
return periods
|
||||||
|
|
||||||
|
@api.onchange('from_date', 'to_date')
|
||||||
|
def onchange_from_to_date(self):
|
||||||
|
for rec in self:
|
||||||
|
if rec.from_date and rec.to_date:
|
||||||
|
rec.name = f"{rec.from_date.year}-{rec.to_date.year}"
|
||||||
|
|
||||||
|
|
||||||
|
def action_generate_month_lines(self):
|
||||||
|
self.ensure_one()
|
||||||
|
if not (self.from_date and self.to_date):
|
||||||
|
raise ValidationError("Please set both From Date and To Date.")
|
||||||
|
|
||||||
|
lines = []
|
||||||
|
current = self.from_date.replace(day=1)
|
||||||
|
while current <= self.to_date:
|
||||||
|
end_of_month = current.replace(day=calendar.monthrange(current.year, current.month)[1])
|
||||||
|
if end_of_month > self.to_date:
|
||||||
|
end_of_month = self.to_date
|
||||||
|
|
||||||
|
lines.append((0, 0, {
|
||||||
|
'from_date': current,
|
||||||
|
'to_date': end_of_month,
|
||||||
|
'name': f"{current.strftime('%b')} - {str(current.year)[-2:]}"
|
||||||
|
}))
|
||||||
|
|
||||||
|
if current.month == 12:
|
||||||
|
current = current.replace(year=current.year + 1, month=1)
|
||||||
|
else:
|
||||||
|
current = current.replace(month=current.month + 1)
|
||||||
|
|
||||||
|
self.period_line_ids = lines
|
||||||
|
|
||||||
|
|
||||||
|
class PayrollPeriodLine(models.Model):
|
||||||
|
_name = 'payroll.period.line'
|
||||||
|
_description = 'Payroll Period Line'
|
||||||
|
_rec_name = 'name'
|
||||||
|
|
||||||
|
period_id = fields.Many2one('payroll.period', string="Payroll Period", required=True, ondelete='cascade')
|
||||||
|
from_date = fields.Date(string="From Date")
|
||||||
|
to_date = fields.Date(string="To Date")
|
||||||
|
name = fields.Char(string="Name")
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
from odoo import fields, models, api, _
|
||||||
|
from odoo.exceptions import ValidationError
|
||||||
|
|
||||||
|
class IncomeTaxSlabMaster(models.Model):
|
||||||
|
_name = 'it.slab.master'
|
||||||
|
_description = 'Income Tax Slab Master'
|
||||||
|
_rec_name = 'name'
|
||||||
|
_sql_constraints = [
|
||||||
|
(
|
||||||
|
'unique_slab',
|
||||||
|
'unique(period_id, regime, age_category, residence_type)',
|
||||||
|
'Slab must be unique for the same name, Regime, Age Category, and Residence Type!'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
name = fields.Char(string="Slab Name", required=True, copy=False, default="AY")
|
||||||
|
period_id = fields.Many2one('payroll.period', copy=False)
|
||||||
|
regime = fields.Selection([
|
||||||
|
('old', 'Old Tax Regime'),
|
||||||
|
('new', 'New Tax Regime')
|
||||||
|
], required=True, default='old')
|
||||||
|
age_category = fields.Selection([
|
||||||
|
('below_60', 'Below 60 Years'),
|
||||||
|
('60_to_80', '60-80 Years'),
|
||||||
|
('above_80', 'Above 80 Years')
|
||||||
|
], required=True, default='below_60')
|
||||||
|
residence_type = fields.Selection([
|
||||||
|
('resident', 'Resident'),
|
||||||
|
('non_resident', 'Non Resident'),
|
||||||
|
('both', 'Both')
|
||||||
|
], required=True)
|
||||||
|
standard_deduction = fields.Float(string="Standard Deduction")
|
||||||
|
active = fields.Boolean(default=True)
|
||||||
|
rules = fields.One2many('it.slab.master.rules','slab_id', string="Slab Rules")
|
||||||
|
surcharges = fields.One2many('it.sur.charge.rules','slab_id', string="Surcharges Rules")
|
||||||
|
|
||||||
|
def copy(self, default=None):
|
||||||
|
"""Override copy to duplicate slab rules and surcharge rules"""
|
||||||
|
if default is None:
|
||||||
|
default = {}
|
||||||
|
|
||||||
|
# Check if we should duplicate from context
|
||||||
|
duplicate_rules = self.env.context.get('duplicate_slab_rules', True)
|
||||||
|
|
||||||
|
default.update({
|
||||||
|
'name': _("%s (copy)") % (self.name or 'Slab'),
|
||||||
|
'rules': [],
|
||||||
|
'surcharges': [],
|
||||||
|
})
|
||||||
|
|
||||||
|
new_slab = super(IncomeTaxSlabMaster, self).copy(default)
|
||||||
|
|
||||||
|
# Only duplicate if flag is True
|
||||||
|
if duplicate_rules:
|
||||||
|
if self.rules:
|
||||||
|
for rule in self.rules:
|
||||||
|
rule.copy({
|
||||||
|
'slab_id': new_slab.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
if self.surcharges:
|
||||||
|
for surcharge in self.surcharges:
|
||||||
|
surcharge.copy({
|
||||||
|
'slab_id': new_slab.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
return new_slab
|
||||||
|
class IncomeTaxSlabMasterRules(models.Model):
|
||||||
|
_name = 'it.slab.master.rules'
|
||||||
|
_description = 'Income Tax slab rules'
|
||||||
|
_rec_name = 'slab_id'
|
||||||
|
_sql_constraints = [
|
||||||
|
(
|
||||||
|
'check_min_max_income',
|
||||||
|
'CHECK (max_income IS NULL OR max_income > min_income)',
|
||||||
|
'Max Income must be greater than Min Income!'
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
sequence = fields.Integer(
|
||||||
|
'Sequence',
|
||||||
|
help='Used to deduct the taxes based on order')
|
||||||
|
min_income = fields.Float(string="Min Income (₹)", required=True)
|
||||||
|
max_income = fields.Float(string="Max Income (₹)")
|
||||||
|
tax_rate = fields.Float(string="Tax Rate (%)", required=True)
|
||||||
|
fixed_amount = fields.Float(string="Fixed Amount (₹)")
|
||||||
|
excess_threshold = fields.Float(string="Excess Threshold (₹)")
|
||||||
|
surcharge_rate = fields.Float(string="Surcharge Rate (%)")
|
||||||
|
cess_rate = fields.Float(string="Health & Education (%)", default=4.0)
|
||||||
|
slab_id = fields.Many2one('it.slab.master')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@api.constrains('min_income', 'max_income', 'slab_id')
|
||||||
|
def _check_overlap(self):
|
||||||
|
"""Ensure no overlapping or duplicate ranges within the same slab"""
|
||||||
|
for rule in self:
|
||||||
|
domain = [
|
||||||
|
('slab_id', '=', rule.slab_id.id),
|
||||||
|
('id', '!=', rule.id)
|
||||||
|
]
|
||||||
|
others = self.search(domain)
|
||||||
|
for other in others:
|
||||||
|
if not (rule.max_income and other.min_income >= rule.max_income) and \
|
||||||
|
not (other.max_income and rule.min_income >= other.max_income):
|
||||||
|
raise ValidationError(
|
||||||
|
f"Income ranges overlap with another slab rule: {other.min_income} - {other.max_income}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class IncomeTaxSurchargeMasterRules(models.Model):
|
||||||
|
_name = 'it.sur.charge.rules'
|
||||||
|
|
||||||
|
min_income = fields.Float(string="Min Income (₹)", required=True)
|
||||||
|
max_income = fields.Float(string="Max Income (₹)")
|
||||||
|
surcharge_rate = fields.Float(string="Surcharge Rate (%)")
|
||||||
|
slab_id = fields.Many2one('it.slab.master')
|
||||||
|
|
||||||
|
@api.constrains('min_income', 'max_income', 'slab_id')
|
||||||
|
def _check_overlap(self):
|
||||||
|
"""Ensure no overlapping or duplicate ranges within the same slab"""
|
||||||
|
for rule in self:
|
||||||
|
domain = [
|
||||||
|
('slab_id', '=', rule.slab_id.id),
|
||||||
|
('id', '!=', rule.id)
|
||||||
|
]
|
||||||
|
others = self.search(domain)
|
||||||
|
for other in others:
|
||||||
|
if not (rule.max_income and other.min_income >= rule.max_income) and \
|
||||||
|
not (other.max_income and rule.min_income >= other.max_income):
|
||||||
|
raise ValidationError(
|
||||||
|
f"Income ranges overlap with another slab rule: {other.min_income} - {other.max_income}"
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,815 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
|
||||||
|
<odoo>
|
||||||
|
<template id="generate_income_tax_statement_rpt">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<div class="page">
|
||||||
|
<t t-set="data" t-value="report_data['data']"/>
|
||||||
|
<t t-set="profile" t-value="data.get('profile')"/>
|
||||||
|
<t t-set="company_name" t-value="data.get('company_name')"/>
|
||||||
|
<t t-set="regime_info" t-value="data.get('regime_info')"/>
|
||||||
|
<t t-set="salary_components" t-value="data.get('salary_components')"/>
|
||||||
|
<t t-set="deductions" t-value="data.get('deductions')"/>
|
||||||
|
<t t-set="income_details" t-value="data.get('income_details')"/>
|
||||||
|
<t t-set="taxable_income" t-value="data.get('taxable_income')"/>
|
||||||
|
<t t-set="tax_computation" t-value="data.get('tax_computation')"/>
|
||||||
|
<t t-set="regime_used" t-value="data.get('regime_used')"/>
|
||||||
|
<t t-set="comparison" t-value="data.get('comparison')"/>
|
||||||
|
<!-- Set Dynamic Variables -->
|
||||||
|
<t t-set="emp_code" t-value="data.get('emp_code')"/>
|
||||||
|
<t t-set="name" t-value="profile.get('name', '')"/>
|
||||||
|
<t t-set="pan" t-value="profile.get('pan', '')"/>
|
||||||
|
<t t-set="month_age" t-value="profile.get('month_age', '')"/>
|
||||||
|
<t t-set="gender" t-value="(profile.get('gender', '') or '').upper()"/>
|
||||||
|
<t t-set="joining_date" t-value="profile.get('doj', '')"/>
|
||||||
|
<t t-set="leaving_date" t-value=""/>
|
||||||
|
<t t-set="status" t-value="Active"/>
|
||||||
|
<t t-set="lock_regime" t-value="regime_info.get('lock_regime', '')"/>
|
||||||
|
<t t-set="net_salary"
|
||||||
|
t-value="'{:,.0f}'.format(salary_components.get('net_salary', {}).get('total', 0))"/>
|
||||||
|
<t t-set="tax_employment" t-value="'{:,.0f}'.format(deductions.get('professional_tax', 0))"/>
|
||||||
|
<t t-set="standard_deduction" t-value="'{:,.0f}'.format(deductions.get('standard_deduction', 0))"/>
|
||||||
|
<t t-set="other_income" t-value="'{:,.0f}'.format(income_details.get('other_income', 0))"/>
|
||||||
|
<t t-set="total_sec_16_deduction" t-value="'{:,.0f}'.format(deductions.get('total_sec_16_deduction', 0))"/>
|
||||||
|
<t t-set="gross_total_income" t-value="'{:,.0f}'.format(income_details.get('gross_total_income', 0))"/>
|
||||||
|
<t t-set="taxable_income" t-value="'{:,.0f}'.format(tax_computation.get('taxable_income', 0))"/>
|
||||||
|
<t t-set="roundoff_taxable_income" t-value="'{:,.0f}'.format(tax_computation.get('roundoff_taxable_income', 0))"/>
|
||||||
|
<t t-set="tax_payable" t-value="'{:,.0f}'.format(tax_computation.get('slab_tax', 0))"/>
|
||||||
|
<t t-set="rebate_87a" t-value="'{:,.0f}'.format(tax_computation.get('rebate_87a', 0))"/>
|
||||||
|
<t t-set="cess" t-value="'{:,.0f}'.format(tax_computation.get('cess_4pct', 0))"/>
|
||||||
|
<t t-set="total_tax" t-value="'{:,.0f}'.format(tax_computation.get('total_tax', 0))"/>
|
||||||
|
<t t-set="current_employer_deducted_tax" t-value="'{:,.0f}'.format(tax_computation.get('current_employer_deducted_tax', 0))"/>
|
||||||
|
<t t-set="balance_tax" t-value="'{:,.0f}'.format(tax_computation.get('balance_tax', 0))"/>
|
||||||
|
<t t-set="report_time" t-value="data.get('report_time', '')"/>
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div style="text-align: center; margin-bottom: 20px;">
|
||||||
|
<h2 style="font-weight: bold; margin-bottom: 5px;"><t t-esc="company_name"/></h2>
|
||||||
|
<h3 style="font-weight: bold; margin-top: 0;">INCOME TAX COMPUTATION STATEMENT</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Employee Info -->
|
||||||
|
<table class="table table-sm" style="width: 100%; margin-bottom: 25px;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 33%;">
|
||||||
|
<strong>Emp Code:</strong>
|
||||||
|
<t t-esc="emp_code"/>
|
||||||
|
</td>
|
||||||
|
<td style="width: 33%;">
|
||||||
|
<strong>Name:</strong>
|
||||||
|
<t t-esc="name"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
<table class="table table-sm" style="width: 100%; margin-bottom: 25px;">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Assessment Year:</strong>
|
||||||
|
<t t-esc="data.get('assessment_year', '')"/>-
|
||||||
|
<t t-esc="(data.get('assessment_year', 0) or 0) + 1"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>Month / Age:</strong>
|
||||||
|
<t t-esc="month_age"/>
|
||||||
|
</td>
|
||||||
|
<td style="width: 34%;">
|
||||||
|
<strong>PAN:</strong>
|
||||||
|
<t t-esc="pan"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Date of Joining:</strong>
|
||||||
|
<t t-esc="joining_date"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>Leaving Date:</strong>
|
||||||
|
<t t-esc="leaving_date"/>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<strong>Gender:</strong>
|
||||||
|
<t t-esc="gender"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Head of Income -->
|
||||||
|
<h4 style="font-weight: bold; border-bottom: 2px solid #ddd; padding-bottom: 5px; margin-bottom: 15px;">
|
||||||
|
Head of Income
|
||||||
|
</h4>
|
||||||
|
<table class="table table-bordered table-sm" style="width: 100%; margin-bottom: 20px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f8f9fa;">
|
||||||
|
<th style="font-weight: bold; text-align: left;">Description</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Actual</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Projected</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Basic</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('basic', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('basic', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('basic', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>House Rent</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('house_rent', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('house_rent', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('house_rent', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>LTA</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('lta', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('lta', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('lta', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Special Allowance</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('special_allowance', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('special_allowance', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('special_allowance', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Reimbursement</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('reimbursement', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('reimbursement', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('reimbursement', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr t-foreach="salary_components.get('other_components', [])" t-as="component">
|
||||||
|
<td t-esc="component.get('name')"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(component.get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(component.get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(component.get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; font-weight: bold;">
|
||||||
|
<td>Gross Salary</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('gross_salary', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('gross_salary', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('gross_salary', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr t-if="salary_components.get('advance_recovery', {}).get('total', 0)">
|
||||||
|
<td>Advance Recovery</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('advance_recovery', {}).get('actual', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('advance_recovery', {}).get('projected', 0))"/>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="'{:,.0f}'.format(salary_components.get('advance_recovery', {}).get('total', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Less: Exemption under section 10</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; font-weight: bold; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td>Net Salary</td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="net_salary"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td><strong>Less:</strong> Deduction under section 16</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td style="padding-left: 30px;">Tax on Employment: Sec 16 - Prof. Tax</td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="tax_employment"/>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td style="padding-left: 30px;">Less: Standard Deduction for Salaried Employees</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
-<span t-esc="standard_deduction"/>
|
||||||
|
</td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td style="padding-left: 30px;">Total</td>
|
||||||
|
<td colspan="2"></td>
|
||||||
|
<td style="text-align: right;"
|
||||||
|
t-esc="total_sec_16_deduction"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="border-top: 2px solid #ddd; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td><strong>Less:</strong> Marginal Relief</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="border-top: 2px solid #ddd; font-weight: bold; margin-bottom: 8px; margin-top: 10px;">
|
||||||
|
<td>Other Income</td>
|
||||||
|
<td colspan="3"></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- <div style="margin-bottom: 8px;">-->
|
||||||
|
<!-- <strong>Net Salary:</strong>-->
|
||||||
|
<!-- <span style="float: right;" t-esc="net_salary"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <div style="margin-bottom: 8px;">-->
|
||||||
|
<!-- <strong>Other Income:</strong>-->
|
||||||
|
<!-- <span style="float: right;" t-esc="other_income"/>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<div style="margin-bottom: 20px; font-weight: bold; border-top: 1px solid #ddd; padding-top: 8px;">
|
||||||
|
<strong>Gross Total Income:</strong>
|
||||||
|
<span style="float: right;" t-esc="gross_total_income"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Deductions -->
|
||||||
|
<h4 style="border-bottom: 2px #ddd; padding-bottom: 5px; margin-bottom: 15px;">
|
||||||
|
<strong>Less</strong> Deductions under Chapter VIA
|
||||||
|
<span style="float: right;">0</span>
|
||||||
|
</h4>
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="width: 100%; margin-bottom: 20px;">-->
|
||||||
|
<!-- <thead>-->
|
||||||
|
<!-- <tr style="background-color: #f8f9fa;">-->
|
||||||
|
<!-- <th style="font-weight: bold; text-align: left;">Investment</th>-->
|
||||||
|
<!-- <th style="font-weight: bold; text-align: left;">Section</th>-->
|
||||||
|
<!-- <th style="font-weight: bold; text-align: right;">Gross</th>-->
|
||||||
|
<!-- <th style="font-weight: bold; text-align: right;">Qualifying</th>-->
|
||||||
|
<!-- <th style="font-weight: bold; text-align: right;">Deductible</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>National Pension Scheme (Employer contribution)</td>-->
|
||||||
|
<!-- <td>80CCD(2)</td>-->
|
||||||
|
<!-- <td style="text-align: right;" t-esc="'{:,.0f}'.format(deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- <td style="text-align: right;" t-esc="'{:,.0f}'.format(deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- <td style="text-align: right;" t-esc="'{:,.0f}'.format(deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Others</td>-->
|
||||||
|
<!-- <td>-</td>-->
|
||||||
|
<!-- <td style="text-align: right;"-->
|
||||||
|
<!-- t-esc="'{:,.0f}'.format(deductions.get('total_deductions', 0) - deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- <td style="text-align: right;"-->
|
||||||
|
<!-- t-esc="'{:,.0f}'.format(deductions.get('total_deductions', 0) - deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- <td style="text-align: right;"-->
|
||||||
|
<!-- t-esc="'{:,.0f}'.format(deductions.get('total_deductions', 0) - deductions.get('nps_employer', 0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
|
||||||
|
<!-- Tax Computation -->
|
||||||
|
<h4 style="font-weight: bold; border-bottom: 2px solid #ddd; padding-bottom: 5px; margin-bottom: 15px;">
|
||||||
|
Tax Calculation
|
||||||
|
</h4>
|
||||||
|
<table class="table table-sm" style="width: 100%; margin-bottom: 20px;">
|
||||||
|
<tr>
|
||||||
|
<td style="width: 70%;">
|
||||||
|
<strong>Taxable Income:</strong>
|
||||||
|
</td>
|
||||||
|
<td style="width: 30%; text-align: right;">
|
||||||
|
<t t-esc="taxable_income"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="width: 70%;">
|
||||||
|
Round off to nearest 10 Rupee:
|
||||||
|
</td>
|
||||||
|
<td style="width: 30%; text-align: right; border-bottom: 1px solid #000; padding-bottom: 2px;">
|
||||||
|
<t t-esc="roundoff_taxable_income"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Tax Payable on Total Income:</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
<t t-esc="tax_payable"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Less: Rebate u/s 87A (Tax Credit)</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
<t t-esc="rebate_87a"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Add: Surcharge</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Add: Health and Education Cess:</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
<t t-esc="cess"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Less: Relief under section 89(1)</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<strong>Less: Details of Tax Collected at Source</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
0
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="border-top: 2px solid #ddd; font-weight: bold;">
|
||||||
|
<td>
|
||||||
|
<strong>Total Tax Payable:</strong>
|
||||||
|
</td>
|
||||||
|
<td style="text-align: right;">
|
||||||
|
<t t-esc="total_tax"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<!-- Tax Breakdown -->
|
||||||
|
<h4 style="font-weight: bold; border-bottom: 2px solid #ddd; padding-bottom: 5px; margin-bottom: 15px;">
|
||||||
|
Tax Deduction Details
|
||||||
|
</h4>
|
||||||
|
<table class="table table-bordered table-sm" style="width: 100%; margin-bottom: 20px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f8f9fa;">
|
||||||
|
<th style="font-weight: bold; text-align: left;">Particulars</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Taxable Income</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Income Tax</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Total annual income and tax</td>
|
||||||
|
<td style="text-align: right;" t-esc="taxable_income"/>
|
||||||
|
<td style="text-align: right;" t-esc="total_tax"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Less: Tax deducted current employer (up to previous month)</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;" t-esc="current_employer_deducted_tax"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Less: Tax deducted from previous Employer / Self Tax Paid</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td>Balance Tax for the year</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;" t-esc="balance_tax"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Less: Adhoc tax deducted in Off-Cycle in current month</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td><strong>Balance Tax</strong></td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;" t-esc="balance_tax"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Tax deducted from current month salary</strong></td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Per month Tax (balance / No. of months incl. current month)</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-weight: bold; border-top: 2px solid #ddd;">
|
||||||
|
<td>Adhoc Tax to be deducted from current month salary</td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;" t-esc="total_tax"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-weight: bold; border-top: 2px solid #ddd;">
|
||||||
|
<td><strong>Total Tax deducted from current month salary + adhoc + off-cycle</strong></td>
|
||||||
|
<td style="text-align: right;">0</td>
|
||||||
|
<td style="text-align: right;" t-esc="total_tax"/>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 30px; display: flex; justify-content: space-between;">
|
||||||
|
<div>
|
||||||
|
<strong>Report Time:</strong>
|
||||||
|
<t t-esc="report_time"/>
|
||||||
|
</div>
|
||||||
|
<div style="text-align: right;">
|
||||||
|
<strong>User:</strong>
|
||||||
|
<t t-esc="data.get('user', '')"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template id="generate_income_tax_comparison_rpt">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<div class="page">
|
||||||
|
<t t-set="data" t-value="report_data['data']"/>
|
||||||
|
<t t-set="profile" t-value="data.get('profile')"/>
|
||||||
|
<t t-set="comparison" t-value="data.get('comparison')"/>
|
||||||
|
|
||||||
|
<div style="text-align: center; margin-bottom: 24px;">
|
||||||
|
<h2 style="font-weight: bold; margin-bottom: 5px;"><t t-esc="data.get('company_name')"/></h2>
|
||||||
|
<h3 style="font-weight: bold; margin-top: 0;">TAX REGIME COMPARISON</h3>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="table table-sm" style="width: 100%; margin-bottom: 24px;">
|
||||||
|
<tr>
|
||||||
|
<td><strong>Employee:</strong> <t t-esc="profile.get('name', '')"/></td>
|
||||||
|
<td><strong>Emp Code:</strong> <t t-esc="data.get('emp_code', '')"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>Financial Year:</strong> <t t-esc="data.get('financial_year', '')"/></td>
|
||||||
|
<td><strong>Selected Regime:</strong> <t t-esc="data.get('regime_info', {}).get('tax_regime', '')"/></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<table class="table table-bordered table-sm" style="width: 100%; margin-bottom: 20px;">
|
||||||
|
<thead>
|
||||||
|
<tr style="background-color: #f8f9fa;">
|
||||||
|
<th style="font-weight: bold; text-align: left;">Particulars</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">Old Regime</th>
|
||||||
|
<th style="font-weight: bold; text-align: right;">New Regime</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Taxable Income</td>
|
||||||
|
<td style="text-align: right;" t-esc="'{:,.0f}'.format(comparison.get('old_taxable_income', 0))"/>
|
||||||
|
<td style="text-align: right;" t-esc="'{:,.0f}'.format(comparison.get('new_taxable_income', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Tax Payable</td>
|
||||||
|
<td style="text-align: right;" t-esc="'{:,.0f}'.format(comparison.get('old_regime_tax', 0))"/>
|
||||||
|
<td style="text-align: right;" t-esc="'{:,.0f}'.format(comparison.get('new_regime_tax', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td>Tax Difference</td>
|
||||||
|
<td colspan="2" style="text-align: right;" t-esc="'{:,.0f}'.format(comparison.get('tax_savings', 0))"/>
|
||||||
|
</tr>
|
||||||
|
<tr style="font-weight: bold;">
|
||||||
|
<td>Beneficial Regime</td>
|
||||||
|
<td colspan="2" style="text-align: right;">
|
||||||
|
<t t-esc="(comparison.get('beneficial_regime') or '').upper()"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<div style="margin-top: 24px;">
|
||||||
|
<strong>Report Time:</strong>
|
||||||
|
<t t-esc="data.get('report_time', '')"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
<!-- <template id="generate_income_tax_statement_rpt">-->
|
||||||
|
<!-- <t t-call="web.basic_layout">-->
|
||||||
|
<!-- <main class="page">-->
|
||||||
|
<!-- <t t-set="data" t-value="report_data['data']"/>-->
|
||||||
|
<!-- <t t-set="profile" t-value="data.get('profile')"/>-->
|
||||||
|
<!-- <t t-set="regime_info" t-value="data.get('regime_info')"/>-->
|
||||||
|
<!-- <t t-set="salary_components" t-value="data.get('salary_components')"/>-->
|
||||||
|
<!-- <t t-set="deductions" t-value="data.get('deductions')"/>-->
|
||||||
|
<!-- <t t-set="income_details" t-value="data.get('income_details')"/>-->
|
||||||
|
<!-- <t t-set="taxable_income" t-value="data.get('taxable_income')"/>-->
|
||||||
|
<!-- <t t-set="tax_computation" t-value="data.get('tax_computation')"/>-->
|
||||||
|
<!-- <t t-set="regime_used" t-value="data.get('regime_used')"/>-->
|
||||||
|
<!-- <t t-set="comparison" t-value="data.get('comparison')"/>-->
|
||||||
|
|
||||||
|
<!-- <!– Report Title –>-->
|
||||||
|
<!-- <div class="text-center" style="margin-bottom: 25px;">-->
|
||||||
|
<!-- <h3 style="font-size:20px; font-weight:bold; text-decoration: underline;">INCOME TAX COMPUTATION STATEMENT</h3>-->
|
||||||
|
<!-- <p style="font-size:14px; margin-top: 5px;">-->
|
||||||
|
<!-- Assessment Year:-->
|
||||||
|
<!-- <t t-esc="data.get('assessment_year')"/>-->
|
||||||
|
<!-- |-->
|
||||||
|
<!-- Financial Year:-->
|
||||||
|
<!-- <t t-esc="data.get('financial_year')"/>-->
|
||||||
|
<!-- |-->
|
||||||
|
<!-- <t t-esc="regime_info.get('tax_regime')"/>-->
|
||||||
|
<!-- </p>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Employee Info –>-->
|
||||||
|
<!-- <table class="table table-sm" style="margin-bottom: 25px; font-size: 13px;">-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td><strong>Employee Name</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="profile.get('name')"/></td>-->
|
||||||
|
<!-- <td><strong>PAN</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="profile.get('pan', '')"/></td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td><strong>Age</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="profile.get('age')"/></td>-->
|
||||||
|
<!-- <td><strong>Residential Status</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="profile.get('residential_status')"/></td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td><strong>Parent's Age</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="profile.get('parent_age')"/></td>-->
|
||||||
|
<!-- <td><strong>User</strong></td>-->
|
||||||
|
<!-- <td><t t-esc="data.get('user')"/></td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Regime Lock Status</td>-->
|
||||||
|
<!-- <td>-->
|
||||||
|
<!-- <t t-esc="regime_info.get('lock_regime')"/>-->
|
||||||
|
<!-- </td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Current Tax Regime</td>-->
|
||||||
|
<!-- <td>-->
|
||||||
|
<!-- <t t-esc="regime_info.get('tax_regime')"/>-->
|
||||||
|
<!-- </td>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
|
||||||
|
<!-- <!– Head of Income –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #007bff;">Head of Income</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <thead style="background:#e9ecef;">-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th>Particulars</th>-->
|
||||||
|
<!-- <th class="text-right">Actual</th>-->
|
||||||
|
<!-- <th class="text-right">Projected</th>-->
|
||||||
|
<!-- <th class="text-right">Total</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Basic</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('basic', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('basic', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('basic', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>House Rent</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('house_rent', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('house_rent', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('house_rent', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>LTA</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('lta', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('lta', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('lta', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Special Allowance</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('special_allowance', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('special_allowance', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('special_allowance', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Perquisites</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('perquisites', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('perquisites', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('perquisites', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Reimbursement</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('reimbursement', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('reimbursement', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('reimbursement', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Gross Salary</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('gross_salary', {}).get('actual',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('gross_salary', {}).get('projected',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(salary_components.get('gross_salary', {}).get('total',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Deductions –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #007bff;">Deductions</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <thead style="background:#e9ecef;">-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th>Particulars</th>-->
|
||||||
|
<!-- <th class="text-right">Amount (₹)</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Professional Tax</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('professional_tax',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Standard Deduction</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('standard_deduction',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>NPS Employer Contribution</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('nps_employer',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>HRA Exemption</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('hra_exemption',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Interest on Home Loan</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('interest_home_loan',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Deduction under 80C</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_80C',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Deduction under 80CCD(1B)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_80CCD1B',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Deduction under 80D (Self)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_80D_self',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Deduction under 80D (Parents)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_80D_parents',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Deduction under 80G</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_80G',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Other Deductions</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('ded_other',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Total Deductions</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(deductions.get('total_deductions',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Income Details –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #007bff;">Income Details</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <thead style="background:#e9ecef;">-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th>Particulars</th>-->
|
||||||
|
<!-- <th class="text-right">Amount (₹)</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Gross Salary</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(income_details.get('gross_salary',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Other Income</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(income_details.get('other_income',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>House Property Income</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(income_details.get('house_property_income',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Total Income</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(income_details.get('total_income',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Taxable Income –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #007bff;">Taxable Income</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Taxable Income (Old Regime)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(taxable_income.get('old_regime',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Taxable Income (New Regime)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(taxable_income.get('new_regime',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Taxable Income (Current Regime)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(taxable_income.get('current_regime',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Tax Regime Comparison –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #6f42c1;">Tax Regime Comparison</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <thead style="background:#e9ecef;">-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <th>Particulars</th>-->
|
||||||
|
<!-- <th class="text-right">Old Regime (₹)</th>-->
|
||||||
|
<!-- <th class="text-right">New Regime (₹)</th>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </thead>-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Total Tax Payable</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(comparison.get('old_regime_tax',0))"/>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(comparison.get('new_regime_tax',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Tax Savings</td>-->
|
||||||
|
<!-- <td class="text-right" colspan="2" t-esc="'{:,.2f}'.format(comparison.get('tax_savings',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Beneficial Regime</td>-->
|
||||||
|
<!-- <td class="text-center" colspan="2" t-esc="comparison.get('beneficial_regime','').capitalize() + ' Regime'"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
|
||||||
|
<!-- <!– Tax Computation –>-->
|
||||||
|
<!-- <div style="page-break-inside: avoid; margin-bottom:20px;">-->
|
||||||
|
<!-- <h4 style="background:#f2f2f2; padding:5px; border-left:4px solid #dc3545;">Tax Computation</h4>-->
|
||||||
|
<!-- <table class="table table-bordered table-sm" style="font-size: 13px;">-->
|
||||||
|
<!-- <tbody>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Taxable Income</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('taxable_income',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Slab Tax</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('slab_tax',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Rebate under Section 87A</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('rebate_87a',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Surcharge</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('surcharge',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Marginal Relief</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('marginal_relief',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr>-->
|
||||||
|
<!-- <td>Health & Education Cess (4%)</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('cess_4pct',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- <tr style="font-weight:bold; background:#f8f9fa;">-->
|
||||||
|
<!-- <td>Total Tax Payable</td>-->
|
||||||
|
<!-- <td class="text-right" t-esc="'{:,.2f}'.format(tax_computation.get('total_tax',0))"/>-->
|
||||||
|
<!-- </tr>-->
|
||||||
|
<!-- </tbody>-->
|
||||||
|
<!-- </table>-->
|
||||||
|
<!-- </div>-->
|
||||||
|
<!-- </main>-->
|
||||||
|
<!-- </t>-->
|
||||||
|
<!-- </template>-->
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="action_report_it_tax_statement" model="ir.actions.report">
|
||||||
|
<field name="name">IT Declaration Submission</field>
|
||||||
|
<field name="model">emp.it.declaration</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">employee_it_declaration.report_it_tax_statement</field>
|
||||||
|
<field name="report_file">employee_it_declaration.report_it_tax_statement</field>
|
||||||
|
<field name="print_report_name">'IT Declaration - %s - %s' % (object.employee_id.name or '', object.period_id.name or '')</field>
|
||||||
|
<field name="binding_model_id" eval="False"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||||
|
|
||||||
|
|
||||||
|
access_payroll_period_user,payroll.period,model_payroll_period,,1,1,1,1
|
||||||
|
access_payroll_period_line_user,payroll.period.line,model_payroll_period_line,,1,1,1,1
|
||||||
|
|
||||||
|
access_it_investment_type,it.investment.type,model_it_investment_type,,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_past_employment_investment_type,past_employment.investment.type,model_past_employment_investment_type,,1,1,1,1
|
||||||
|
access_us80c_investment_type,us80c.investment.type,model_us80c_investment_type,,1,1,1,1
|
||||||
|
access_us80d_investment_type,us80d.investment.type,model_us80d_investment_type,,1,1,1,1
|
||||||
|
access_us10_investment_type,us10.investment.type,model_us10_investment_type,,1,1,1,1
|
||||||
|
access_us80g_investment_type,us80g.investment.type,model_us80g_investment_type,,1,1,1,1
|
||||||
|
access_chapter_via_investment_type,chapter.via.investment.type,model_chapter_via_investment_type,,1,1,1,1
|
||||||
|
access_us17_investment_type,us17.investment.type,model_us17_investment_type,,1,1,1,1
|
||||||
|
access_other_il_investment_type,other.il.investment.type,model_other_il_investment_type,,1,1,1,1
|
||||||
|
access_other_declaration_investment_type,other.declaration.investment.type,model_other_declaration_investment_type,,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_investment_costings,investment.costings,model_investment_costings,,1,1,1,1
|
||||||
|
|
||||||
|
access_past_employment_costing_type,past_employment.costing.type,model_past_employment_costing_type,,1,1,1,1
|
||||||
|
access_us80c_costing_type,us80c.costing.type,model_us80c_costing_type,,1,1,1,1
|
||||||
|
access_us80d_costing_type,us80d.costing.type,model_us80d_costing_type,,1,1,1,1
|
||||||
|
access_us10_costing_type,us10.costing.type,model_us10_costing_type,,1,1,1,1
|
||||||
|
access_us80g_costing_type,us80g.costing.type,model_us80g_costing_type,,1,1,1,1
|
||||||
|
access_chapter_via_costing_type,chapter.via.costing.type,model_chapter_via_costing_type,,1,1,1,1
|
||||||
|
access_us17_costing_type,us17.costing.type,model_us17_costing_type,,1,1,1,1
|
||||||
|
access_other_il_costing_type,other.il.costing.type,model_other_il_costing_type,,1,1,1,1
|
||||||
|
access_other_declaration_costing_type,other.declaration.costing.type,model_other_declaration_costing_type,,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_emp_it_declaration_user,emp.it.declarations,model_emp_it_declaration,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_children_education,access.children.education,model_children_education,base.group_user,1,1,1,1
|
||||||
|
access_children_education_costing,access.children.education.costing,model_children_education_costing,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
access_us80c_insurance_line,access.us80c.insurance.line,model_us80c_insurance_line,,1,1,1,1
|
||||||
|
access_employee_life_insurance,access.employee.life.insurance,model_employee_life_insurance,,1,1,1,1
|
||||||
|
|
||||||
|
access_nsc_declaration_line_user,nsc.declaration.line,model_nsc_declaration_line,base.group_user,1,1,1,1
|
||||||
|
access_nsc_entry_user,nsc.entry,model_nsc_entry,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_self_occupied_property_user,self.occupied.property,model_self_occupied_property,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
access_letout_house_property_user,access.letout.house.property.user,model_letout_house_property,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
|
||||||
|
access_nsc_interest_line_user,nsc.interest.line,model_nsc_interest_line,base.group_user,1,1,1,1
|
||||||
|
access_nsc_interest_entry_user,nsc.interest.entry,model_nsc_interest_entry,base.group_user,1,1,1,1
|
||||||
|
|
||||||
|
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,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
|
||||||
|
|
|
@ -0,0 +1,487 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="emp_it_declaration_list" model="ir.ui.view">
|
||||||
|
<field name="name">emp.it.declaration.list</field>
|
||||||
|
<field name="model">emp.it.declaration</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="period_id"/>
|
||||||
|
<field name="total_investment"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="state"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_emp_it_declaration_form" model="ir.ui.view">
|
||||||
|
<field name="name">emp.it.declarations.form</field>
|
||||||
|
<field name="model">emp.it.declaration</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="IT Declaration">
|
||||||
|
<header>
|
||||||
|
<button name="action_submit"
|
||||||
|
string="Submit"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
invisible="state != 'draft' or not costing_details_generated"/>
|
||||||
|
<button name="action_download_submission_pdf"
|
||||||
|
string="Download PDF"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
icon="fa-download"
|
||||||
|
invisible="state != 'submitted'"/>
|
||||||
|
<button name="action_return_to_draft"
|
||||||
|
string="Return to Draft"
|
||||||
|
type="object"
|
||||||
|
class="btn-secondary"
|
||||||
|
invisible="state != 'submitted'"
|
||||||
|
groups="hr_payroll.group_hr_payroll_manager"/>
|
||||||
|
<field name="state" widget="statusbar" statusbar_visible="draft,submitted"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<div class="oe_title mb24">
|
||||||
|
<div class="o_row">
|
||||||
|
<field name="employee_id" widget="res_partner_many2one" placeholder="Employee Name..." readonly="costing_details_generated or state == 'submitted'"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="period_id" readonly="costing_details_generated or state == 'submitted'"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="total_investment" readonly="state == 'submitted'"/>
|
||||||
|
<field name="return_reason" placeholder="Reason for returning the declaration to draft..." readonly="state != 'submitted' or not is_payroll_manager"/>
|
||||||
|
<field name="is_payroll_manager" invisible="1"/>
|
||||||
|
<field name="costing_details_generated" invisible="1" force_save="1"/>
|
||||||
|
<field name="house_rent_costing_id"/>
|
||||||
|
<field name="show_past_employment" invisible="1"/>
|
||||||
|
<field name="show_us_80c" invisible="1"/>
|
||||||
|
<field name="show_us_80d" invisible="1"/>
|
||||||
|
<field name="show_us_10" invisible="1"/>
|
||||||
|
<field name="show_us_80g" invisible="1"/>
|
||||||
|
<field name="show_chapter_via" invisible="1"/>
|
||||||
|
<field name="show_us_17" invisible="1"/>
|
||||||
|
<field name="show_house_rent" invisible="1"/>
|
||||||
|
<field name="show_other_i_or_l" invisible="1"/>
|
||||||
|
<field name="show_other_declaration" invisible="1"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<br/>
|
||||||
|
<br/>
|
||||||
|
<field name="tax_regime" nolabel="1" widget="radio" options="{'horizontal': true}" readonly="state == 'submitted'"/>
|
||||||
|
<br/>
|
||||||
|
<button name="generate_declarations" type="object" class="btn-primary" string="Generate" confirm="Upon Confirming you won't be able to change the Period & Employee" help="Generate Data to upload the declaration Costing" invisible="costing_details_generated or state == 'submitted'"/>
|
||||||
|
<field name="is_section_open" invisible="1"/> <!-- Store toggle state -->
|
||||||
|
|
||||||
|
<group invisible="not costing_details_generated">
|
||||||
|
<!-- Styled full-width button -->
|
||||||
|
<div style="background-color: #1167A8; padding: 10px; color: white; font-weight: bold; display: flex; justify-content: space-between; align-items: center; cursor: pointer;">
|
||||||
|
<span><field name="display_period_label" readonly="1" nolabel="1" style="color: white; font-weight: bold;"/></span>
|
||||||
|
<button name="toggle_section_visibility"
|
||||||
|
type="object"
|
||||||
|
style="background: none; border: none; color: white; font-size: 16px;"
|
||||||
|
icon="fa-chevron-up"/>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<!-- Collapsible section -->
|
||||||
|
<div invisible="not is_section_open">
|
||||||
|
<notebook>
|
||||||
|
<page string="Total Investment Costing">
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="visible_investment_costing_ids" nolabel="1" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0" edit="0">
|
||||||
|
<field name="investment_type_id"/>
|
||||||
|
<field name="amount"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</group>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</div>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
|
||||||
|
<notebook invisible="not costing_details_generated">
|
||||||
|
<!-- <page name="investment_costings" string="Total Investment">-->
|
||||||
|
<!-- <field name="investment_costing_ids" readonly="1" force_save="1">-->
|
||||||
|
<!-- <list editable="bottom" create="0" delete="0" edit="0">-->
|
||||||
|
<!-- <field name="investment_type_id" width="50%"/>-->
|
||||||
|
<!-- <field name="amount" width="50%" class="text-start"/>-->
|
||||||
|
<!-- </list>-->
|
||||||
|
<!-- </field>-->
|
||||||
|
<!-- </page>-->
|
||||||
|
<page name="past_employment_costings" string="PAST EMPLOYMENT" invisible="not show_past_employment">
|
||||||
|
|
||||||
|
<field name="past_employment_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="past_employment_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page name="us_80c_costings" string="US 80C" invisible="not show_us_80c">
|
||||||
|
|
||||||
|
<field name="us80c_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="action_id" column_invisible="1"/>
|
||||||
|
<button name="open_action_wizard"
|
||||||
|
string="Action"
|
||||||
|
type="object"
|
||||||
|
icon="fa-external-link"
|
||||||
|
invisible="not action_id" width="60px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="200px"/>
|
||||||
|
<field name="proof" width="100px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us80c_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="action_id" column_invisible="1"/>
|
||||||
|
<button name="open_action_wizard"
|
||||||
|
string="Action"
|
||||||
|
type="object"
|
||||||
|
icon="fa-external-link"
|
||||||
|
invisible="not action_id" width="60px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="200px"/>
|
||||||
|
<field name="proof" width="100px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
<page name="us_80d_costings" string="US 80D" invisible="not show_us_80d">
|
||||||
|
<group>
|
||||||
|
<field name="us80d_selection_type" widget="radio" options="{'horizontal': true}" required="tax_regime == 'old' and costing_details_generated" readonly="state == 'submitted'"/>
|
||||||
|
<field name="us80d_health_checkup" readonly="state == 'submitted'"/>
|
||||||
|
</group>
|
||||||
|
<field name="us80d_costings" invisible="tax_regime != 'old' or us80d_selection_type != 'self_family'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us80d_costings_new" invisible="tax_regime != 'new' or us80d_selection_type != 'self_family'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us80d_costings_parents" invisible="tax_regime != 'old' or us80d_selection_type != 'self_family_parent'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
<field name="us80d_costings_parents_new" invisible="tax_regime != 'new' or us80d_selection_type != 'self_family_parent'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
<field name="us80d_costings_senior_parents" invisible="tax_regime != 'old' or us80d_selection_type != 'self_family_senior_parent'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
<field name="us80d_costings_senior_parents_new" invisible="tax_regime != 'new' or us80d_selection_type != 'self_family_senior_parent'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="us_10_costing" string="US 10" invisible="not show_us_10">
|
||||||
|
<field name="us10_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us10_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="us_80g_costing" string="US 80G" invisible="not show_us_80g">
|
||||||
|
<field name="us80g_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us80g_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page name="chapter_via_costings" string="CHAPTER VIA" invisible="not show_chapter_via">
|
||||||
|
<field name="chapter_via_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="chapter_via_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</page>
|
||||||
|
<page name="us_17_costings" string="US 17" invisible="not show_us_17">
|
||||||
|
<field name="us17_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="us17_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="house_rent_costings" string="HOUSE RENT" invisible="not show_house_rent">
|
||||||
|
<!-- <field name="house_rent_costing_line_ids"/>-->
|
||||||
|
<field name="house_rent_costings" readonly="state == 'submitted'" context="{
|
||||||
|
'default_costing_type': house_rent_costing_id
|
||||||
|
}">
|
||||||
|
<list string="House Rent Declarations">
|
||||||
|
<field name="hra_exemption_type"/>
|
||||||
|
<field name="rent_amount"/>
|
||||||
|
<field name="from_date"/>
|
||||||
|
<field name="to_date"/>
|
||||||
|
<field name="landlord_pan_status"/>
|
||||||
|
</list>
|
||||||
|
<form string="House Rent Declaration" class="o_form">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="it_declaration_id" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="costing_type" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="hra_exemption_type" widget="radio"/>
|
||||||
|
<field name="landlord_pan_status" widget="radio"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
|
||||||
|
<notebook>
|
||||||
|
<page string="Rent Details">
|
||||||
|
<group col="2">
|
||||||
|
<field name="rent_amount"/>
|
||||||
|
<field name="from_date"/>
|
||||||
|
<field name="to_date"/>
|
||||||
|
</group>
|
||||||
|
<field name="remarks" placeholder="Any additional information..."/>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Landlord Information">
|
||||||
|
<group col="2">
|
||||||
|
<field name="landlord_pan_no" invisible="landlord_pan_status == 'declaration'" required="landlord_pan_status == 'has_pan'"/>
|
||||||
|
<field name="landlord_name_address"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
|
||||||
|
<page string="Proof Attachment">
|
||||||
|
<group>
|
||||||
|
<field name="attachment" filename="attachment_filename"/>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="other_i_or_l_costings" string="OTHER INCOME/LOSS" invisible="not show_other_i_or_l">
|
||||||
|
<field name="other_il_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="action_id" column_invisible="1" force_save="1"/>
|
||||||
|
<button name="open_action_wizard"
|
||||||
|
string="Action"
|
||||||
|
type="object"
|
||||||
|
icon="fa-external-link"
|
||||||
|
invisible="not action_id" width="60px"/>
|
||||||
|
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="200px"/>
|
||||||
|
<field name="proof" width="100px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="other_il_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="action_id" column_invisible="1" force_save="1"/>
|
||||||
|
<button name="open_action_wizard"
|
||||||
|
string="Action"
|
||||||
|
type="object"
|
||||||
|
icon="fa-external-link"
|
||||||
|
invisible="not action_id" width="60px"/>
|
||||||
|
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="200px"/>
|
||||||
|
<field name="proof" width="100px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page name="other_declaration_costings" string="Other Declarations" invisible="not show_other_declaration">
|
||||||
|
<field name="other_declaration_costings" invisible="tax_regime != 'old'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
<field name="other_declaration_costings_new" invisible="tax_regime != 'new'" readonly="state == 'submitted'">
|
||||||
|
<list editable="bottom" create="0" delete="0">
|
||||||
|
<field name="investment_type_line_id" width="600px" readonly="1" force_save="1" options="{'no_open': True, 'no_quick_create': True}"/>
|
||||||
|
<field name="declaration_amount" width="130px"/>
|
||||||
|
<field name="proof_amount" width="100px" readonly="1" force_save="1"/>
|
||||||
|
<field name="remarks" width="250px"/>
|
||||||
|
<field name="proof" width="120px"/>
|
||||||
|
<field name="limit" readonly="1" force_save="1"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="action_emp_it_declaration" model="ir.actions.act_window">
|
||||||
|
<field name="name">IT Declarations</field>
|
||||||
|
<field name="path">income-tax-declaration</field>
|
||||||
|
<field name="res_model">emp.it.declaration</field>
|
||||||
|
<field name="domain">[('employee_id.user_id', '=', uid)]</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
<record id="action_manager_it_declaration" model="ir.actions.act_window">
|
||||||
|
<field name="name">IT Declarations</field>
|
||||||
|
<field name="path">employees-tax-declarations</field>
|
||||||
|
<field name="res_model">emp.it.declaration</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_hr_payroll_emp_root" name="Payroll" sequence="190" web_icon="hr_payroll,static/description/icon.png" groups="base.group_user"/>
|
||||||
|
|
||||||
|
<menuitem id="menu_it_declarations" name="IT Declarations"
|
||||||
|
parent="hr_payroll.menu_hr_payroll_root"
|
||||||
|
action="action_manager_it_declaration" sequence="99"/>
|
||||||
|
<menuitem id="menu_it_declarations_emp" name="IT Declarations"
|
||||||
|
parent="menu_hr_payroll_emp_root"
|
||||||
|
action="action_emp_it_declaration" sequence="1"/>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<record id="view_employee_payslip_download_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">employee.payslip.download.wizard.form</field>
|
||||||
|
<field name="model">employee.payslip.download.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Download Payslips">
|
||||||
|
<header>
|
||||||
|
<button name="action_download_payslips"
|
||||||
|
string="Download Payslip"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-download"
|
||||||
|
invisible="download_type != 'single'"/>
|
||||||
|
<button name="action_download_payslips"
|
||||||
|
string="Download ZIP"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-file-archive-o"
|
||||||
|
invisible="download_type != 'multi'"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="is_hr_manager" invisible="1"/>
|
||||||
|
<field name="employee_id" readonly="not is_hr_manager" options="{'no_edit': True, 'no_create': True}"/>
|
||||||
|
<field name="download_type" widget="radio" options="{'horizontal': true}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="period_id" domain="[('id', 'in', available_period_ids)]" options="{'no_edit': True, 'no_create': True, 'no_open': True}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="period_line"
|
||||||
|
force_save="1"
|
||||||
|
required="download_type == 'single'"
|
||||||
|
invisible="download_type != 'single'"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="payslip_count" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button string="Close"
|
||||||
|
special="cancel"
|
||||||
|
class="btn-secondary"/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_employee_payslip_download_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">Download Payslips</field>
|
||||||
|
<field name="res_model">employee.payslip.download.wizard</field>
|
||||||
|
<field name="path">download-payslips</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">current</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_employee_payslip_download_wizard"
|
||||||
|
name="Salary Payslip"
|
||||||
|
parent="employee_it_declaration.menu_hr_payroll_emp_root"
|
||||||
|
action="action_employee_payslip_download_wizard"
|
||||||
|
sequence="2"
|
||||||
|
groups="base.group_user"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="view_it_investment_type_list" model="ir.ui.view">
|
||||||
|
<field name="name">it.investment.type.list</field>
|
||||||
|
<field name="model">it.investment.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="investment_type"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_it_investment_type_form" model="ir.ui.view">
|
||||||
|
<field name="name">it.investment.type.form</field>
|
||||||
|
<field name="model">it.investment.type</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Investment Type">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="sequence" invisible="1"/>
|
||||||
|
<field name="investment_type"/>
|
||||||
|
<field name="regime"/>
|
||||||
|
<field name="period_ids" widget="many2many_tags"/>
|
||||||
|
<field name="active"/>
|
||||||
|
</group>
|
||||||
|
<notebook>
|
||||||
|
<page string="Past Employment" invisible="investment_type != 'past_employment'">
|
||||||
|
<field name="past_employment_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="investment_code"/>
|
||||||
|
<field name="compute_method"/>
|
||||||
|
<field name="compute_code"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="US 80C" invisible="investment_type != 'us_80c'">
|
||||||
|
<field name="us80c_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="investment_code"/>
|
||||||
|
<field name="compute_method"/>
|
||||||
|
<field name="compute_code"/>
|
||||||
|
<field name="require_action"/>
|
||||||
|
<field name="action_id" readonly="not require_action" required="require_action"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="US 80D" invisible="investment_type != 'us_80d'">
|
||||||
|
<field name="us80d_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="for_family" string="Family"/>
|
||||||
|
<field name="for_parents" string="Parents"/>
|
||||||
|
<field name="for_senior_parent" string="Senior Parents"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="US 10" invisible="investment_type != 'us_10'">
|
||||||
|
<field name="us10_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="US 80G" invisible="investment_type != 'us_80g'">
|
||||||
|
<field name="us80g_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Chapter VIA" invisible="investment_type != 'chapter_via'">
|
||||||
|
<field name="chapter_via_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="US 17" invisible="investment_type != 'us_17'">
|
||||||
|
<field name="us17_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Other Income or Loss" invisible="investment_type != 'other_i_or_l'">
|
||||||
|
<field name="other_il_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="require_action"/>
|
||||||
|
<field name="action_id" readonly="not require_action" required="require_action"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
<page string="Other Declaration" invisible="investment_type != 'other_declaration'">
|
||||||
|
<field name="other_declaration_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="sequence" widget="handle"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
<field name="limit"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</notebook>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- Action -->
|
||||||
|
<record id="action_it_investment_type" model="ir.actions.act_window">
|
||||||
|
<field name="name">Investment Types</field>
|
||||||
|
<field name="res_model">it.investment.type</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create the different Investment Types here.
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_it_investment_type" name="Investment Types"
|
||||||
|
parent="menu_it_payroll_declarations"
|
||||||
|
action="action_it_investment_type" sequence="20"/>
|
||||||
|
|
||||||
|
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<!-- <menuitem id="menu_it_tax_statement_root" name="IT Tax Statement" parent="hr.menu_hr_root" sequence="100"/>-->
|
||||||
|
<record id="view_it_tax_statement_wizard_list" model="ir.ui.view">
|
||||||
|
<field name="name">it.tax.statement.wizard.list</field>
|
||||||
|
<field name="model">it.tax.statement.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="employee_id"/>
|
||||||
|
<field name="period_id"/>
|
||||||
|
<field name="period_line" force_save="1"/>
|
||||||
|
<field name="tax_regime"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="view_it_tax_statement_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">it.tax.statement.wizard.form</field>
|
||||||
|
<field name="model">it.tax.statement.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form>
|
||||||
|
<header>
|
||||||
|
<button name="action_generate_report"
|
||||||
|
string="Generate Tax Statement"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-file-text"/>
|
||||||
|
<button name="action_check_regime_comparison"
|
||||||
|
string="Check Comparison"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-calculator"/>
|
||||||
|
<button name="action_generate_comparison_report"
|
||||||
|
string="Download Comparison"
|
||||||
|
type="object"
|
||||||
|
class="oe_stat_button"
|
||||||
|
icon="fa-balance-scale"
|
||||||
|
invisible="not comparison_available"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<field name="comparison_available" invisible="1"/>
|
||||||
|
<field name="currency_id" invisible="1"/>
|
||||||
|
<field name="is_general_tax_statement" invisible="1"/>
|
||||||
|
<group>
|
||||||
|
<field name="is_hr_manager" invisible="1" force_save="1"/>
|
||||||
|
<field name="employee_id" readonly="not is_hr_manager" options="{'no_edit': True, 'no_create': True}"/>
|
||||||
|
<field name="contract_id" readonly="1" force_save="1" invisible="0"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="taxpayer_age"/>
|
||||||
|
<field name="residential_status"/>
|
||||||
|
<field name="parent_age"/>
|
||||||
|
<field name="emp_doj" force_save="1" invisible="0" readonly="1"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<group>
|
||||||
|
<field name="period_id" domain="[('id', 'in', available_period_ids)]"
|
||||||
|
options="{'no_edit': True, 'no_create': True, 'no_open': True}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="period_line" force_save="1" domain="[('period_id', '=', period_id),('to_date','<',(context_today() + datetime.timedelta(days=30)).strftime('%Y-%m-%d')),('to_date','>',emp_doj)]"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="tax_regime" nolabel="1" widget="radio" options="{'horizontal': true}" on_change="1"/>
|
||||||
|
</group>
|
||||||
|
<group string="Tax Regime Comparison" invisible="not comparison_available">
|
||||||
|
<group string="Old Regime">
|
||||||
|
<field name="old_regime_taxable_income" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||||
|
<field name="old_regime_tax_payable" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||||
|
</group>
|
||||||
|
<group string="New Regime">
|
||||||
|
<field name="new_regime_taxable_income" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||||
|
<field name="new_regime_tax_payable" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||||
|
</group>
|
||||||
|
<group>
|
||||||
|
<field name="beneficial_regime" readonly="1"/>
|
||||||
|
<field name="tax_difference" readonly="1" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<record id="action_it_tax_statement_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">Generate Tax Statement</field>
|
||||||
|
<field name="res_model">it.tax.statement.wizard</field>
|
||||||
|
<field name="path">tax-statement</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="domain">[("activity_ids.active", "in", [True, False])]</field>
|
||||||
|
<field name="help" type="html">
|
||||||
|
<p class="o_view_nocontent_smiling_face">
|
||||||
|
Create a new employment type
|
||||||
|
</p>
|
||||||
|
</field>
|
||||||
|
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<menuitem id="menu_it_tax_statement_root" name="IT Tax Statement"
|
||||||
|
parent="employee_it_declaration.menu_hr_payroll_emp_root"
|
||||||
|
groups="base.group_user"
|
||||||
|
action="action_it_tax_statement_wizard" sequence="3"/>
|
||||||
|
|
||||||
|
<record id="it_statement_paper_format" model="report.paperformat">
|
||||||
|
<field name="name">A4 - statement</field>
|
||||||
|
<field name="default" eval="True"/>
|
||||||
|
<field name="format">A4</field>
|
||||||
|
<field name="page_height">0</field>
|
||||||
|
<field name="page_width">0</field>
|
||||||
|
<field name="orientation">Portrait</field>
|
||||||
|
<field name="margin_top">10</field>
|
||||||
|
<field name="margin_bottom">10</field>
|
||||||
|
<field name="margin_left">7</field>
|
||||||
|
<field name="margin_right">7</field>
|
||||||
|
<field name="header_line" eval="False"/>
|
||||||
|
<field name="header_spacing">10</field>
|
||||||
|
<field name="dpi">90</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="income_tax_statement_action_report" model="ir.actions.report">
|
||||||
|
<field name="name">Download Income Tax</field>
|
||||||
|
<field name="model">it.tax.statement.wizard</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">employee_it_declaration.generate_income_tax_statement_rpt</field>
|
||||||
|
<field name="report_file">employee_it_declaration.generate_income_tax_statement_rpt</field>
|
||||||
|
<field name="binding_model_id" ref="employee_it_declaration.model_it_tax_statement_wizard"/>
|
||||||
|
<field name="print_report_name">'%s - %s' % (object.employee_id.name or '', object.period_line.name or '')</field>
|
||||||
|
<field name="paperformat_id" ref="it_statement_paper_format"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="income_tax_comparison_action_report" model="ir.actions.report">
|
||||||
|
<field name="name">Download Tax Regime Comparison</field>
|
||||||
|
<field name="model">it.tax.statement.wizard</field>
|
||||||
|
<field name="report_type">qweb-pdf</field>
|
||||||
|
<field name="report_name">employee_it_declaration.generate_income_tax_comparison_rpt</field>
|
||||||
|
<field name="report_file">employee_it_declaration.generate_income_tax_comparison_rpt</field>
|
||||||
|
<field name="binding_model_id" ref="employee_it_declaration.model_it_tax_statement_wizard"/>
|
||||||
|
<field name="print_report_name">'%s - Tax Regime Comparison' % (object.employee_id.name or '')</field>
|
||||||
|
<field name="paperformat_id" ref="it_statement_paper_format"/>
|
||||||
|
<field name="binding_type">report</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
<odoo>
|
||||||
|
<record id="view_payroll_period_form" model="ir.ui.view">
|
||||||
|
<field name="name">payroll.period.form</field>
|
||||||
|
<field name="model">payroll.period</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="Payroll Period">
|
||||||
|
<header>
|
||||||
|
<button name="action_generate_month_lines" type="object" string="Generate Periods" class="btn-primary"/>
|
||||||
|
</header>
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="from_date"/>
|
||||||
|
<field name="to_date"/>
|
||||||
|
<field name="name" readonly="1" force_save="1"/>
|
||||||
|
</group>
|
||||||
|
<field name="period_line_ids">
|
||||||
|
<list editable="bottom">
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="from_date"/>
|
||||||
|
<field name="to_date"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</sheet>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="view_payroll_period_tree" model="ir.ui.view">
|
||||||
|
<field name="name">payroll.period.tree</field>
|
||||||
|
<field name="model">payroll.period</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<list>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="from_date"/>
|
||||||
|
<field name="to_date"/>
|
||||||
|
</list>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<record id="action_payroll_period" model="ir.actions.act_window">
|
||||||
|
<field name="name">Payroll Periods</field>
|
||||||
|
<field name="res_model">payroll.period</field>
|
||||||
|
<field name="view_mode">list,form</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
|
||||||
|
<menuitem id="menu_it_payroll_declarations" name="Payroll" parent="hr_payroll.menu_hr_payroll_configuration"/>
|
||||||
|
<menuitem id="menu_payroll_period" name="Payroll Periods" parent="menu_it_payroll_declarations" action="action_payroll_period"/>
|
||||||
|
</odoo>
|
||||||
|
|
@ -0,0 +1,158 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<template id="report_it_tax_statement">
|
||||||
|
<t t-call="web.html_container">
|
||||||
|
<t t-foreach="docs" t-as="doc">
|
||||||
|
<t t-call="web.external_layout">
|
||||||
|
<div class="page">
|
||||||
|
<style>
|
||||||
|
.it-title { text-align: center; margin-bottom: 18px; }
|
||||||
|
.it-title h2 { margin: 0; font-size: 21px; font-weight: 700; }
|
||||||
|
.it-title p { margin: 4px 0 0; color: #555; }
|
||||||
|
.it-section { margin-top: 18px; }
|
||||||
|
.it-section h4 { font-size: 14px; font-weight: 700; border-bottom: 1px solid #999; padding-bottom: 4px; margin-bottom: 8px; }
|
||||||
|
.it-table { width: 100%; border-collapse: collapse; font-size: 11px; }
|
||||||
|
.it-table th { background: #f2f2f2; font-weight: 700; }
|
||||||
|
.it-table th, .it-table td { border: 1px solid #ddd; padding: 5px; vertical-align: top; }
|
||||||
|
.it-right { text-align: right; }
|
||||||
|
.it-muted { color: #777; }
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<div class="it-title">
|
||||||
|
<h2>IT Declaration Submission</h2>
|
||||||
|
<p>
|
||||||
|
<span t-esc="doc.employee_id.company_id.name"/>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<table class="it-table">
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<th>Employee</th>
|
||||||
|
<td><span t-esc="doc.employee_id.name"/></td>
|
||||||
|
<th>Payroll Period</th>
|
||||||
|
<td><span t-esc="doc.period_id.name"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Tax Regime</th>
|
||||||
|
<td><span t-esc="doc._get_regime_label()"/></td>
|
||||||
|
<th>Status</th>
|
||||||
|
<td><span t-esc="dict(doc._fields['state'].selection).get(doc.state)"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<th>Submitted On</th>
|
||||||
|
<td><span t-esc="doc.write_date"/></td>
|
||||||
|
<th>Total Investment</th>
|
||||||
|
<td class="it-right"><span t-esc="'{:,.2f}'.format(doc.total_investment or 0.0)"/></td>
|
||||||
|
</tr>
|
||||||
|
<tr t-if="doc.return_reason">
|
||||||
|
<th>Return Reason</th>
|
||||||
|
<td colspan="3"><span t-esc="doc.return_reason"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<t t-set="sections" t-value="doc._get_report_sections()"/>
|
||||||
|
<t t-if="not sections">
|
||||||
|
<div class="it-section">
|
||||||
|
<p class="it-muted">No declaration lines are available for the selected regime.</p>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-foreach="sections" t-as="section">
|
||||||
|
<div class="it-section">
|
||||||
|
<h4><span t-esc="section.get('title')"/></h4>
|
||||||
|
<t t-if="section.get('house_rent')">
|
||||||
|
<table class="it-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Rent Amount</th>
|
||||||
|
<th>From</th>
|
||||||
|
<th>To</th>
|
||||||
|
<th>Landlord PAN</th>
|
||||||
|
<th>Remarks</th>
|
||||||
|
<th>Proof</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="section.get('lines')" t-as="line">
|
||||||
|
<td><span t-esc="line.get('hra_exemption_type')"/></td>
|
||||||
|
<td class="it-right"><span t-esc="'{:,.2f}'.format(line.get('rent_amount') or 0.0)"/></td>
|
||||||
|
<td><span t-esc="line.get('from_date')"/></td>
|
||||||
|
<td><span t-esc="line.get('to_date')"/></td>
|
||||||
|
<td>
|
||||||
|
<span t-esc="line.get('landlord_pan_status')"/>
|
||||||
|
<br t-if="line.get('landlord_pan_no')"/>
|
||||||
|
<span t-if="line.get('landlord_pan_no')" t-esc="line.get('landlord_pan_no')"/>
|
||||||
|
</td>
|
||||||
|
<td><span t-esc="line.get('remarks')"/></td>
|
||||||
|
<td>
|
||||||
|
<a t-if="line.get('attachment_url')" t-att-href="line.get('attachment_url')">
|
||||||
|
<span t-esc="line.get('attachment_name')"/>
|
||||||
|
</a>
|
||||||
|
<span t-if="not line.get('attachment_url')" class="it-muted">No proof</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</t>
|
||||||
|
<t t-else="">
|
||||||
|
<table class="it-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Description</th>
|
||||||
|
<th>Declaration Amount</th>
|
||||||
|
<th>Proof Amount</th>
|
||||||
|
<th>Limit</th>
|
||||||
|
<th>Remarks</th>
|
||||||
|
<th>Proof</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="section.get('lines')" t-as="line">
|
||||||
|
<td><span t-esc="line.get('name')"/></td>
|
||||||
|
<td class="it-right"><span t-esc="'{:,.2f}'.format(line.get('declaration_amount') or 0.0)"/></td>
|
||||||
|
<td class="it-right"><span t-esc="'{:,.2f}'.format(line.get('proof_amount') or 0.0)"/></td>
|
||||||
|
<td class="it-right"><span t-esc="'{:,.2f}'.format(line.get('limit') or 0.0)"/></td>
|
||||||
|
<td><span t-esc="line.get('remarks')"/></td>
|
||||||
|
<td>
|
||||||
|
<a t-if="line.get('attachment_url')" t-att-href="line.get('attachment_url')">
|
||||||
|
<span t-esc="line.get('attachment_name')"/>
|
||||||
|
</a>
|
||||||
|
<span t-if="not line.get('attachment_url')" class="it-muted">No proof</span>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
|
||||||
|
<t t-foreach="doc._get_report_extra_sections()" t-as="extra_section">
|
||||||
|
<div class="it-section">
|
||||||
|
<h4><span t-esc="extra_section.get('title')"/></h4>
|
||||||
|
<table class="it-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th t-foreach="extra_section.get('headers')" t-as="header">
|
||||||
|
<span t-esc="header"/>
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr t-foreach="extra_section.get('rows')" t-as="row">
|
||||||
|
<td t-foreach="row" t-as="cell">
|
||||||
|
<span t-esc="cell"/>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</div>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</t>
|
||||||
|
</template>
|
||||||
|
</odoo>
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue