Theme Files Added
This commit is contained in:
commit
78d5dc53ea
1
addons/accounting_community/__init__.py
Normal file
1
addons/accounting_community/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
# from . import models
|
||||
27
addons/accounting_community/__manifest__.py
Normal file
27
addons/accounting_community/__manifest__.py
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
'name': 'Accounting Community',
|
||||
'version': '17.0.1.0.0',
|
||||
'summary': 'Full Accounting Features for Community Edition',
|
||||
'description': """
|
||||
Accounting Community
|
||||
====================
|
||||
This module unlocks the Full Accounting features in Odoo Community Edition.
|
||||
|
||||
Features:
|
||||
- Full Accounting Menu
|
||||
- Journal Entries
|
||||
- Journal Items
|
||||
- General Ledger (Basic View)
|
||||
- Partner Ledger (Basic View)
|
||||
""",
|
||||
'category': 'Accounting/Accounting',
|
||||
'author': 'Antigravity',
|
||||
'depends': ['account'],
|
||||
'data': [
|
||||
'views/account_menus.xml',
|
||||
'views/account_move_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
BIN
addons/accounting_community/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
addons/accounting_community/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
36
addons/accounting_community/views/account_menus.xml
Normal file
36
addons/accounting_community/views/account_menus.xml
Normal file
@ -0,0 +1,36 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Rename "Invoicing" to "Accounting" -->
|
||||
<record id="account.menu_finance" model="ir.ui.menu">
|
||||
<field name="name">Accounting</field>
|
||||
<field name="groups_id" eval="[(6, 0, [])]"/> <!-- Ensure it is visible -->
|
||||
</record>
|
||||
|
||||
<!-- Top-level "Accounting" menu (Controller) - usually hidden in Community -->
|
||||
<menuitem id="account.menu_finance_entries"
|
||||
name="Accounting"
|
||||
parent="account.menu_finance"
|
||||
sequence="4"
|
||||
groups="account.group_account_user,account.group_account_manager"/>
|
||||
|
||||
<!-- Journal Entries -->
|
||||
<menuitem id="menu_action_move_journal_line_form"
|
||||
action="account.action_move_journal_line"
|
||||
parent="account.menu_finance_entries"
|
||||
sequence="1"/>
|
||||
|
||||
<!-- Journal Items -->
|
||||
<menuitem id="menu_action_account_moves_all_a"
|
||||
action="account.action_account_moves_all_a"
|
||||
parent="account.menu_finance_entries"
|
||||
sequence="10"/>
|
||||
|
||||
<!-- Reporting: General Ledger -->
|
||||
<!-- Note: Standard reports in Community are limited. We enable what we can or point to list views -->
|
||||
<menuitem id="menu_action_account_moves_all_a_gl"
|
||||
name="General Ledger (Items)"
|
||||
action="account.action_account_moves_all_a"
|
||||
parent="account.menu_finance_reports"
|
||||
sequence="100"/>
|
||||
|
||||
</odoo>
|
||||
20
addons/accounting_community/views/account_move_views.xml
Normal file
20
addons/accounting_community/views/account_move_views.xml
Normal file
@ -0,0 +1,20 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Ensure the actions are available -->
|
||||
<record id="account.action_move_journal_line" model="ir.actions.act_window">
|
||||
<field name="name">Journal Entries</field>
|
||||
<field name="res_model">account.move</field>
|
||||
<field name="view_mode">tree,kanban,form</field>
|
||||
<field name="view_id" ref="account.view_move_tree"/>
|
||||
<field name="search_view_id" ref="account.view_account_move_filter"/>
|
||||
<field name="context">{'default_move_type': 'entry', 'search_default_misc_filter':1, 'view_no_maturity': True}</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create a journal entry
|
||||
</p><p>
|
||||
A journal entry consists of several journal items, each of
|
||||
which is either a debit or a credit.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
4
addons/accounting_community/views/account_views.xml
Normal file
4
addons/accounting_community/views/account_views.xml
Normal file
@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- Placeholder for generic views if needed -->
|
||||
</odoo>
|
||||
3
addons/c2c_payroll/__init__.py
Normal file
3
addons/c2c_payroll/__init__.py
Normal file
@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
from . import wizard
|
||||
38
addons/c2c_payroll/__manifest__.py
Normal file
38
addons/c2c_payroll/__manifest__.py
Normal file
@ -0,0 +1,38 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'C2C Payroll',
|
||||
'version': '17.0.1.0.0',
|
||||
'category': 'Human Resources/Payroll',
|
||||
'summary': 'Enterprise-like Payroll for Odoo Community',
|
||||
'description': """
|
||||
Complete payroll solution for Odoo 17 Community Edition.
|
||||
Features:
|
||||
- Salary Structures (Basic, HRA, Allowances)
|
||||
- Automatic PF, ESI, Professional Tax deductions
|
||||
- Payslip generation with auto-computed net salary
|
||||
- Accounting journal entry on payslip confirmation
|
||||
- QWeb PDF payslip report
|
||||
- Multi-company support
|
||||
""",
|
||||
'author': 'C2C',
|
||||
'website': '',
|
||||
'license': 'LGPL-3',
|
||||
'depends': [
|
||||
'hr',
|
||||
'hr_contract',
|
||||
'account',
|
||||
],
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/salary_structure_views.xml',
|
||||
'views/payslip_views.xml',
|
||||
'views/contract_views.xml',
|
||||
'views/menu.xml',
|
||||
'reports/payslip_report.xml',
|
||||
'reports/payslip_report_template.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'auto_install': False,
|
||||
}
|
||||
BIN
addons/c2c_payroll/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
addons/c2c_payroll/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
4
addons/c2c_payroll/models/__init__.py
Normal file
4
addons/c2c_payroll/models/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import salary_structure
|
||||
from . import contract_extension
|
||||
from . import payslip
|
||||
BIN
addons/c2c_payroll/models/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
addons/c2c_payroll/models/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
BIN
addons/c2c_payroll/models/__pycache__/payslip.cpython-310.pyc
Normal file
BIN
addons/c2c_payroll/models/__pycache__/payslip.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
23
addons/c2c_payroll/models/contract_extension.py
Normal file
23
addons/c2c_payroll/models/contract_extension.py
Normal file
@ -0,0 +1,23 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class ContractExtension(models.Model):
|
||||
_inherit = 'hr.contract'
|
||||
|
||||
salary_structure_id = fields.Many2one(
|
||||
'c2c.salary.structure', string='Salary Structure',
|
||||
help='Salary structure used for payslip computation.',
|
||||
)
|
||||
gross_salary = fields.Float(
|
||||
string='Gross Salary',
|
||||
help='Total gross salary (CTC) before deductions.',
|
||||
)
|
||||
pf_applicable = fields.Boolean(
|
||||
string='PF Applicable', default=True,
|
||||
help='Whether Provident Fund deduction applies to this contract.',
|
||||
)
|
||||
esi_applicable = fields.Boolean(
|
||||
string='ESI Applicable', default=False,
|
||||
help='Whether ESI deduction applies to this contract.',
|
||||
)
|
||||
299
addons/c2c_payroll/models/payslip.py
Normal file
299
addons/c2c_payroll/models/payslip.py
Normal file
@ -0,0 +1,299 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class Payslip(models.Model):
|
||||
_name = 'c2c.payslip'
|
||||
_description = 'Employee Payslip'
|
||||
_inherit = ['mail.thread', 'mail.activity.mixin']
|
||||
_order = 'date_from desc, id desc'
|
||||
_rec_name = 'display_name'
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Core fields
|
||||
# ------------------------------------------------------------------
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', string='Employee', required=True,
|
||||
tracking=True,
|
||||
)
|
||||
contract_id = fields.Many2one(
|
||||
'hr.contract', string='Contract', required=True,
|
||||
tracking=True,
|
||||
domain="[('employee_id', '=', employee_id), ('state', '=', 'open')]",
|
||||
)
|
||||
date_from = fields.Date(
|
||||
string='Period From', required=True,
|
||||
)
|
||||
date_to = fields.Date(
|
||||
string='Period To', required=True,
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Earnings (computed & stored)
|
||||
# ------------------------------------------------------------------
|
||||
gross_salary = fields.Float(
|
||||
string='Gross Salary', compute='_compute_salary', store=True,
|
||||
)
|
||||
basic = fields.Float(
|
||||
string='Basic', compute='_compute_salary', store=True,
|
||||
)
|
||||
hra = fields.Float(
|
||||
string='HRA', compute='_compute_salary', store=True,
|
||||
)
|
||||
allowances = fields.Float(
|
||||
string='Allowances', compute='_compute_salary', store=True,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Deductions (computed & stored)
|
||||
# ------------------------------------------------------------------
|
||||
pf_deduction = fields.Float(
|
||||
string='PF Deduction', compute='_compute_salary', store=True,
|
||||
)
|
||||
esi_deduction = fields.Float(
|
||||
string='ESI Deduction', compute='_compute_salary', store=True,
|
||||
)
|
||||
professional_tax = fields.Float(
|
||||
string='Professional Tax', compute='_compute_salary', store=True,
|
||||
)
|
||||
total_deductions = fields.Float(
|
||||
string='Total Deductions', compute='_compute_salary', store=True,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Net
|
||||
# ------------------------------------------------------------------
|
||||
net_salary = fields.Float(
|
||||
string='Net Salary', compute='_compute_salary', store=True,
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# State & Accounting
|
||||
# ------------------------------------------------------------------
|
||||
state = fields.Selection([
|
||||
('draft', 'Draft'),
|
||||
('confirmed', 'Confirmed'),
|
||||
('paid', 'Paid'),
|
||||
], string='Status', default='draft', tracking=True, copy=False)
|
||||
|
||||
journal_entry_id = fields.Many2one(
|
||||
'account.move', string='Journal Entry', readonly=True, copy=False,
|
||||
)
|
||||
journal_entry_count = fields.Integer(
|
||||
compute='_compute_journal_entry_count',
|
||||
)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Display name
|
||||
# ------------------------------------------------------------------
|
||||
display_name = fields.Char(compute='_compute_display_name', store=True)
|
||||
|
||||
@api.depends('employee_id', 'date_from', 'date_to')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
emp_name = rec.employee_id.name or 'New'
|
||||
date_from = rec.date_from or ''
|
||||
date_to = rec.date_to or ''
|
||||
rec.display_name = '%s (%s - %s)' % (emp_name, date_from, date_to)
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Computed salary
|
||||
# ------------------------------------------------------------------
|
||||
@api.depends(
|
||||
'contract_id',
|
||||
'contract_id.gross_salary',
|
||||
'contract_id.salary_structure_id',
|
||||
'contract_id.salary_structure_id.basic_percentage',
|
||||
'contract_id.salary_structure_id.hra_percentage',
|
||||
'contract_id.salary_structure_id.allowance_percentage',
|
||||
'contract_id.salary_structure_id.pf_percentage',
|
||||
'contract_id.salary_structure_id.esi_percentage',
|
||||
'contract_id.salary_structure_id.professional_tax_fixed',
|
||||
'contract_id.pf_applicable',
|
||||
'contract_id.esi_applicable',
|
||||
)
|
||||
def _compute_salary(self):
|
||||
for rec in self:
|
||||
contract = rec.contract_id
|
||||
structure = contract.salary_structure_id if contract else False
|
||||
|
||||
if not contract or not structure:
|
||||
rec.gross_salary = 0.0
|
||||
rec.basic = 0.0
|
||||
rec.hra = 0.0
|
||||
rec.allowances = 0.0
|
||||
rec.pf_deduction = 0.0
|
||||
rec.esi_deduction = 0.0
|
||||
rec.professional_tax = 0.0
|
||||
rec.total_deductions = 0.0
|
||||
rec.net_salary = 0.0
|
||||
continue
|
||||
|
||||
gross = contract.gross_salary
|
||||
rec.gross_salary = gross
|
||||
|
||||
# Earnings
|
||||
rec.basic = gross * structure.basic_percentage / 100.0
|
||||
rec.hra = gross * structure.hra_percentage / 100.0
|
||||
rec.allowances = gross * structure.allowance_percentage / 100.0
|
||||
|
||||
# Deductions
|
||||
pf = (rec.basic * structure.pf_percentage / 100.0) if contract.pf_applicable else 0.0
|
||||
esi = (gross * structure.esi_percentage / 100.0) if contract.esi_applicable else 0.0
|
||||
pt = structure.professional_tax_fixed
|
||||
|
||||
rec.pf_deduction = pf
|
||||
rec.esi_deduction = esi
|
||||
rec.professional_tax = pt
|
||||
rec.total_deductions = pf + esi + pt
|
||||
rec.net_salary = gross - rec.total_deductions
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Journal entry count
|
||||
# ------------------------------------------------------------------
|
||||
@api.depends('journal_entry_id')
|
||||
def _compute_journal_entry_count(self):
|
||||
for rec in self:
|
||||
rec.journal_entry_count = 1 if rec.journal_entry_id else 0
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Onchange helpers
|
||||
# ------------------------------------------------------------------
|
||||
@api.onchange('employee_id')
|
||||
def _onchange_employee_id(self):
|
||||
"""Auto-fill contract when employee changes."""
|
||||
if self.employee_id:
|
||||
contract = self.env['hr.contract'].search([
|
||||
('employee_id', '=', self.employee_id.id),
|
||||
('state', '=', 'open'),
|
||||
('company_id', '=', self.env.company.id),
|
||||
], limit=1)
|
||||
self.contract_id = contract
|
||||
else:
|
||||
self.contract_id = False
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Constraints
|
||||
# ------------------------------------------------------------------
|
||||
@api.constrains('date_from', 'date_to')
|
||||
def _check_dates(self):
|
||||
for rec in self:
|
||||
if rec.date_from and rec.date_to and rec.date_from > rec.date_to:
|
||||
raise ValidationError('Period From cannot be after Period To.')
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
# Actions
|
||||
# ------------------------------------------------------------------
|
||||
def action_confirm(self):
|
||||
"""Confirm the payslip and create an accounting journal entry."""
|
||||
for rec in self:
|
||||
if rec.state != 'draft':
|
||||
raise UserError('Only draft payslips can be confirmed.')
|
||||
if not rec.contract_id or not rec.contract_id.salary_structure_id:
|
||||
raise UserError(
|
||||
'Please set a salary structure on the contract before confirming.'
|
||||
)
|
||||
if rec.net_salary <= 0:
|
||||
raise UserError('Net salary must be greater than zero to confirm.')
|
||||
|
||||
# Get accounts from system parameters (company-specific)
|
||||
ICP = self.env['ir.config_parameter'].sudo()
|
||||
expense_account_id = int(
|
||||
ICP.get_param('c2c_payroll.salary_expense_account_id', default=0)
|
||||
)
|
||||
payable_account_id = int(
|
||||
ICP.get_param('c2c_payroll.salary_payable_account_id', default=0)
|
||||
)
|
||||
|
||||
if not expense_account_id or not payable_account_id:
|
||||
raise UserError(
|
||||
'Please configure Salary Expense and Payable accounts in '
|
||||
'Settings → Technical → Parameters → System Parameters.\n\n'
|
||||
'Keys:\n'
|
||||
' • c2c_payroll.salary_expense_account_id\n'
|
||||
' • c2c_payroll.salary_payable_account_id'
|
||||
)
|
||||
|
||||
# Validate accounts exist
|
||||
expense_account = self.env['account.account'].browse(expense_account_id)
|
||||
payable_account = self.env['account.account'].browse(payable_account_id)
|
||||
if not expense_account.exists() or not payable_account.exists():
|
||||
raise UserError(
|
||||
'Configured salary accounts do not exist. '
|
||||
'Please verify the system parameter values.'
|
||||
)
|
||||
|
||||
journal = self.env['account.journal'].search([
|
||||
('type', '=', 'general'),
|
||||
('company_id', '=', rec.company_id.id),
|
||||
], limit=1)
|
||||
if not journal:
|
||||
raise UserError(
|
||||
'No general journal found for company %s.' % rec.company_id.name
|
||||
)
|
||||
|
||||
move_vals = {
|
||||
'journal_id': journal.id,
|
||||
'date': rec.date_to,
|
||||
'ref': 'Payslip: %s' % rec.display_name,
|
||||
'company_id': rec.company_id.id,
|
||||
'line_ids': [
|
||||
(0, 0, {
|
||||
'name': 'Salary Expense - %s' % rec.employee_id.name,
|
||||
'account_id': expense_account_id,
|
||||
'debit': rec.net_salary,
|
||||
'credit': 0.0,
|
||||
}),
|
||||
(0, 0, {
|
||||
'name': 'Employee Payable - %s' % rec.employee_id.name,
|
||||
'account_id': payable_account_id,
|
||||
'debit': 0.0,
|
||||
'credit': rec.net_salary,
|
||||
}),
|
||||
],
|
||||
}
|
||||
move = self.env['account.move'].create(move_vals)
|
||||
rec.write({
|
||||
'state': 'confirmed',
|
||||
'journal_entry_id': move.id,
|
||||
})
|
||||
|
||||
def action_mark_paid(self):
|
||||
"""Mark confirming payslip as paid."""
|
||||
for rec in self:
|
||||
if rec.state != 'confirmed':
|
||||
raise UserError('Only confirmed payslips can be marked as paid.')
|
||||
rec.write({'state': 'paid'})
|
||||
|
||||
def action_reset_to_draft(self):
|
||||
"""Reset to draft (deletes journal entry if unposted)."""
|
||||
for rec in self:
|
||||
if rec.state == 'paid':
|
||||
raise UserError('Paid payslips cannot be reset to draft.')
|
||||
if rec.journal_entry_id:
|
||||
if rec.journal_entry_id.state == 'posted':
|
||||
raise UserError(
|
||||
'Cannot reset: the journal entry is already posted. '
|
||||
'Please cancel it first.'
|
||||
)
|
||||
rec.journal_entry_id.unlink()
|
||||
rec.write({'state': 'draft', 'journal_entry_id': False})
|
||||
|
||||
def action_open_journal_entry(self):
|
||||
"""Smart button to open the linked journal entry."""
|
||||
self.ensure_one()
|
||||
if not self.journal_entry_id:
|
||||
raise UserError('No journal entry linked to this payslip.')
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Journal Entry',
|
||||
'res_model': 'account.move',
|
||||
'res_id': self.journal_entry_id.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'current',
|
||||
}
|
||||
49
addons/c2c_payroll/models/salary_structure.py
Normal file
49
addons/c2c_payroll/models/salary_structure.py
Normal file
@ -0,0 +1,49 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class SalaryStructure(models.Model):
|
||||
_name = 'c2c.salary.structure'
|
||||
_description = 'Salary Structure'
|
||||
_rec_name = 'name'
|
||||
|
||||
name = fields.Char(string='Name', required=True)
|
||||
basic_percentage = fields.Float(
|
||||
string='Basic (%)', required=True, default=50.0,
|
||||
help='Percentage of gross salary allocated as Basic.',
|
||||
)
|
||||
hra_percentage = fields.Float(
|
||||
string='HRA (%)', required=True, default=20.0,
|
||||
help='Percentage of gross salary allocated as HRA.',
|
||||
)
|
||||
allowance_percentage = fields.Float(
|
||||
string='Allowances (%)', required=True, default=30.0,
|
||||
help='Percentage of gross salary allocated as Allowances.',
|
||||
)
|
||||
pf_percentage = fields.Float(
|
||||
string='PF (%)', required=True, default=12.0,
|
||||
help='Provident Fund deduction percentage on Basic salary.',
|
||||
)
|
||||
esi_percentage = fields.Float(
|
||||
string='ESI (%)', required=True, default=1.75,
|
||||
help='ESI deduction percentage on Gross salary.',
|
||||
)
|
||||
professional_tax_fixed = fields.Float(
|
||||
string='Professional Tax (Fixed)', default=200.0,
|
||||
help='Fixed professional tax amount deducted per month.',
|
||||
)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
active = fields.Boolean(default=True)
|
||||
|
||||
@api.constrains('basic_percentage', 'hra_percentage', 'allowance_percentage')
|
||||
def _check_percentages(self):
|
||||
for rec in self:
|
||||
total = rec.basic_percentage + rec.hra_percentage + rec.allowance_percentage
|
||||
if abs(total - 100.0) > 0.01:
|
||||
raise models.ValidationError(
|
||||
'Basic + HRA + Allowances percentages must equal 100%%. '
|
||||
'Current total: %.2f%%' % total
|
||||
)
|
||||
15
addons/c2c_payroll/reports/payslip_report.xml
Normal file
15
addons/c2c_payroll/reports/payslip_report.xml
Normal file
@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Report Action: Payslip PDF -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="action_report_payslip" model="ir.actions.report">
|
||||
<field name="name">Payslip</field>
|
||||
<field name="model">c2c.payslip</field>
|
||||
<field name="report_type">qweb-pdf</field>
|
||||
<field name="report_name">c2c_payroll.report_payslip_document</field>
|
||||
<field name="report_file">c2c_payroll.report_payslip_document</field>
|
||||
<field name="binding_model_id" ref="model_c2c_payslip"/>
|
||||
<field name="binding_type">report</field>
|
||||
</record>
|
||||
</odoo>
|
||||
184
addons/c2c_payroll/reports/payslip_report_template.xml
Normal file
184
addons/c2c_payroll/reports/payslip_report_template.xml
Normal file
@ -0,0 +1,184 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<template id="report_payslip_document">
|
||||
<t t-call="web.html_container">
|
||||
<t t-foreach="docs" t-as="o">
|
||||
<t t-call="web.external_layout">
|
||||
<div class="page" style="font-family: Arial, sans-serif;">
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Header -->
|
||||
<!-- ============================================ -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12 text-center">
|
||||
<h2 style="color: #2c3e50; border-bottom: 3px solid #3498db; padding-bottom: 10px;">
|
||||
<strong>PAYSLIP</strong>
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Employee & Period Details -->
|
||||
<!-- ============================================ -->
|
||||
<div class="row mb-4" style="background-color: #f8f9fa; padding: 15px; border-radius: 5px;">
|
||||
<div class="col-6">
|
||||
<table class="table table-borderless table-sm mb-0">
|
||||
<tr>
|
||||
<td><strong>Employee:</strong></td>
|
||||
<td><span t-field="o.employee_id.name"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Department:</strong></td>
|
||||
<td><span t-field="o.employee_id.department_id.name"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Job Position:</strong></td>
|
||||
<td><span t-field="o.employee_id.job_id.name"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<table class="table table-borderless table-sm mb-0">
|
||||
<tr>
|
||||
<td><strong>Period:</strong></td>
|
||||
<td>
|
||||
<span t-field="o.date_from"/> —
|
||||
<span t-field="o.date_to"/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Company:</strong></td>
|
||||
<td><span t-field="o.company_id.name"/></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>Status:</strong></td>
|
||||
<td><span t-field="o.state"/></td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Earnings Table -->
|
||||
<!-- ============================================ -->
|
||||
<h4 style="color: #27ae60; margin-top: 20px;">
|
||||
<i class="fa fa-plus-circle"/> Earnings
|
||||
</h4>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead style="background-color: #27ae60; color: white;">
|
||||
<tr>
|
||||
<th>Component</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Basic Salary</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.basic"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>House Rent Allowance (HRA)</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.hra"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Other Allowances</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.allowances"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-weight: bold; background-color: #eafaf1;">
|
||||
<td>Gross Salary</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.gross_salary"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Deductions Table -->
|
||||
<!-- ============================================ -->
|
||||
<h4 style="color: #e74c3c; margin-top: 20px;">
|
||||
<i class="fa fa-minus-circle"/> Deductions
|
||||
</h4>
|
||||
<table class="table table-bordered table-sm">
|
||||
<thead style="background-color: #e74c3c; color: white;">
|
||||
<tr>
|
||||
<th>Component</th>
|
||||
<th class="text-end">Amount</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr t-if="o.pf_deduction > 0">
|
||||
<td>Provident Fund (PF)</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.pf_deduction"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-if="o.esi_deduction > 0">
|
||||
<td>Employee State Insurance (ESI)</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.esi_deduction"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr t-if="o.professional_tax > 0">
|
||||
<td>Professional Tax</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.professional_tax"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
<tr style="font-weight: bold; background-color: #fdedec;">
|
||||
<td>Total Deductions</td>
|
||||
<td class="text-end">
|
||||
<span t-field="o.total_deductions"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Net Salary — Highlighted -->
|
||||
<!-- ============================================ -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<table class="table table-bordered">
|
||||
<tr style="background-color: #2c3e50; color: white; font-size: 18px;">
|
||||
<td class="py-3"><strong>NET SALARY PAYABLE</strong></td>
|
||||
<td class="text-end py-3">
|
||||
<strong>
|
||||
<span t-field="o.net_salary"
|
||||
t-options='{"widget": "monetary", "display_currency": o.company_id.currency_id}'/>
|
||||
</strong>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ============================================ -->
|
||||
<!-- Footer note -->
|
||||
<!-- ============================================ -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 text-center text-muted" style="font-size: 11px;">
|
||||
<p>This is a computer-generated payslip and does not require a signature.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
</t>
|
||||
</template>
|
||||
</odoo>
|
||||
7
addons/c2c_payroll/security/ir.model.access.csv
Normal file
7
addons/c2c_payroll/security/ir.model.access.csv
Normal file
@ -0,0 +1,7 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_salary_structure_user,c2c.salary.structure.user,model_c2c_salary_structure,c2c_payroll.group_payroll_user,1,0,0,0
|
||||
access_salary_structure_manager,c2c.salary.structure.manager,model_c2c_salary_structure,c2c_payroll.group_payroll_manager,1,1,1,1
|
||||
access_payslip_user,c2c.payslip.user,model_c2c_payslip,c2c_payroll.group_payroll_user,1,1,1,0
|
||||
access_payslip_manager,c2c.payslip.manager,model_c2c_payslip,c2c_payroll.group_payroll_manager,1,1,1,1
|
||||
access_payslip_wizard_user,c2c.payslip.generate.wizard.user,model_c2c_payslip_generate_wizard,c2c_payroll.group_payroll_user,1,1,1,1
|
||||
access_payslip_wizard_manager,c2c.payslip.generate.wizard.manager,model_c2c_payslip_generate_wizard,c2c_payroll.group_payroll_manager,1,1,1,1
|
||||
|
43
addons/c2c_payroll/security/security.xml
Normal file
43
addons/c2c_payroll/security/security.xml
Normal file
@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Module Category -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="module_category_c2c_payroll" model="ir.module.category">
|
||||
<field name="name">C2C Payroll</field>
|
||||
<field name="description">Access rights for C2C Payroll module.</field>
|
||||
<field name="sequence">30</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Security Groups -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="group_payroll_user" model="res.groups">
|
||||
<field name="name">Payroll User</field>
|
||||
<field name="category_id" ref="module_category_c2c_payroll"/>
|
||||
<field name="implied_ids" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="group_payroll_manager" model="res.groups">
|
||||
<field name="name">Payroll Manager</field>
|
||||
<field name="category_id" ref="module_category_c2c_payroll"/>
|
||||
<field name="implied_ids" eval="[(4, ref('c2c_payroll.group_payroll_user'))]"/>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Record Rules — multi-company -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="rule_payslip_company" model="ir.rule">
|
||||
<field name="name">Payslip: multi-company</field>
|
||||
<field name="model_id" ref="model_c2c_payslip"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
|
||||
<record id="rule_salary_structure_company" model="ir.rule">
|
||||
<field name="name">Salary Structure: multi-company</field>
|
||||
<field name="model_id" ref="model_c2c_salary_structure"/>
|
||||
<field name="domain_force">[('company_id', 'in', company_ids)]</field>
|
||||
<field name="groups" eval="[(4, ref('base.group_user'))]"/>
|
||||
</record>
|
||||
</odoo>
|
||||
27
addons/c2c_payroll/views/contract_views.xml
Normal file
27
addons/c2c_payroll/views/contract_views.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Extend hr.contract Form — add Payroll tab -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_hr_contract_form_inherit_payroll" model="ir.ui.view">
|
||||
<field name="name">hr.contract.form.inherit.c2c.payroll</field>
|
||||
<field name="model">hr.contract</field>
|
||||
<field name="inherit_id" ref="hr_contract.hr_contract_view_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Payroll" name="payroll_tab">
|
||||
<group>
|
||||
<group string="Salary">
|
||||
<field name="salary_structure_id"/>
|
||||
<field name="gross_salary"/>
|
||||
</group>
|
||||
<group string="Deduction Applicability">
|
||||
<field name="pf_applicable"/>
|
||||
<field name="esi_applicable"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
35
addons/c2c_payroll/views/menu.xml
Normal file
35
addons/c2c_payroll/views/menu.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Top-level Menu: Payroll under HR -->
|
||||
<!-- ============================================================ -->
|
||||
<menuitem id="menu_payroll_root"
|
||||
name="Payroll"
|
||||
parent="hr.menu_hr_root"
|
||||
sequence="90"
|
||||
groups="c2c_payroll.group_payroll_user"/>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Sub-menus -->
|
||||
<!-- ============================================================ -->
|
||||
<menuitem id="menu_salary_structure"
|
||||
name="Salary Structures"
|
||||
parent="menu_payroll_root"
|
||||
action="action_salary_structure"
|
||||
sequence="10"
|
||||
groups="c2c_payroll.group_payroll_user"/>
|
||||
|
||||
<menuitem id="menu_payslip"
|
||||
name="Payslips"
|
||||
parent="menu_payroll_root"
|
||||
action="action_payslip"
|
||||
sequence="20"
|
||||
groups="c2c_payroll.group_payroll_user"/>
|
||||
|
||||
<menuitem id="menu_payslip_generate"
|
||||
name="Generate Payslips"
|
||||
parent="menu_payroll_root"
|
||||
action="action_payslip_generate_wizard"
|
||||
sequence="30"
|
||||
groups="c2c_payroll.group_payroll_manager"/>
|
||||
</odoo>
|
||||
191
addons/c2c_payroll/views/payslip_views.xml
Normal file
191
addons/c2c_payroll/views/payslip_views.xml
Normal file
@ -0,0 +1,191 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Payslip — Tree View -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_payslip_tree" model="ir.ui.view">
|
||||
<field name="name">c2c.payslip.tree</field>
|
||||
<field name="model">c2c.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Payslips" decoration-info="state == 'draft'"
|
||||
decoration-success="state == 'confirmed'"
|
||||
decoration-muted="state == 'paid'">
|
||||
<field name="employee_id"/>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="gross_salary"/>
|
||||
<field name="total_deductions"/>
|
||||
<field name="net_salary"/>
|
||||
<field name="state" widget="badge"
|
||||
decoration-info="state == 'draft'"
|
||||
decoration-success="state == 'confirmed'"
|
||||
decoration-warning="state == 'paid'"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Payslip — Form View -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_payslip_form" model="ir.ui.view">
|
||||
<field name="name">c2c.payslip.form</field>
|
||||
<field name="model">c2c.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Payslip">
|
||||
<header>
|
||||
<button name="action_confirm" string="Confirm"
|
||||
type="object" class="oe_highlight"
|
||||
invisible="state != 'draft'"
|
||||
confirm="Are you sure you want to confirm this payslip and create a journal entry?"/>
|
||||
<button name="action_mark_paid" string="Mark as Paid"
|
||||
type="object" class="oe_highlight"
|
||||
invisible="state != 'confirmed'"/>
|
||||
<button name="action_reset_to_draft" string="Reset to Draft"
|
||||
type="object"
|
||||
invisible="state != 'confirmed'"/>
|
||||
<field name="state" widget="statusbar"
|
||||
statusbar_visible="draft,confirmed,paid"/>
|
||||
</header>
|
||||
<sheet>
|
||||
<div class="oe_button_box" name="button_box">
|
||||
<button name="action_open_journal_entry"
|
||||
type="object"
|
||||
class="oe_stat_button"
|
||||
icon="fa-book"
|
||||
invisible="journal_entry_count == 0">
|
||||
<field name="journal_entry_count" widget="statinfo"
|
||||
string="Journal Entry"/>
|
||||
</button>
|
||||
</div>
|
||||
<div class="oe_title">
|
||||
<h1>
|
||||
<field name="display_name" readonly="1"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Employee Details">
|
||||
<field name="employee_id"/>
|
||||
<field name="contract_id"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<group string="Period">
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
</group>
|
||||
</group>
|
||||
<notebook>
|
||||
<page string="Earnings" name="earnings">
|
||||
<group>
|
||||
<group>
|
||||
<field name="gross_salary"/>
|
||||
<field name="basic"/>
|
||||
<field name="hra"/>
|
||||
<field name="allowances"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Deductions" name="deductions">
|
||||
<group>
|
||||
<group>
|
||||
<field name="pf_deduction"/>
|
||||
<field name="esi_deduction"/>
|
||||
<field name="professional_tax"/>
|
||||
<separator/>
|
||||
<field name="total_deductions"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
<page string="Summary" name="summary">
|
||||
<group>
|
||||
<group>
|
||||
<field name="gross_salary" string="Gross Salary"/>
|
||||
<field name="total_deductions" string="Total Deductions"/>
|
||||
<separator/>
|
||||
<field name="net_salary" class="oe_subtotal_footer_separator"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</notebook>
|
||||
</sheet>
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="activity_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Payslip — Search View -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_payslip_search" model="ir.ui.view">
|
||||
<field name="name">c2c.payslip.search</field>
|
||||
<field name="model">c2c.payslip</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Payslips">
|
||||
<field name="employee_id"/>
|
||||
<field name="state"/>
|
||||
<filter name="filter_draft" string="Draft"
|
||||
domain="[('state', '=', 'draft')]"/>
|
||||
<filter name="filter_confirmed" string="Confirmed"
|
||||
domain="[('state', '=', 'confirmed')]"/>
|
||||
<filter name="filter_paid" string="Paid"
|
||||
domain="[('state', '=', 'paid')]"/>
|
||||
<separator/>
|
||||
<filter name="group_employee" string="Employee"
|
||||
context="{'group_by': 'employee_id'}"/>
|
||||
<filter name="group_state" string="Status"
|
||||
context="{'group_by': 'state'}"/>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Payslip — Action -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="action_payslip" model="ir.actions.act_window">
|
||||
<field name="name">Payslips</field>
|
||||
<field name="res_model">c2c.payslip</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
No payslips yet
|
||||
</p>
|
||||
<p>
|
||||
Create payslips individually or use the "Generate Payslips" wizard
|
||||
to create them in bulk for all employees with active contracts.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Generate Payslip Wizard — Form + Action -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_payslip_generate_wizard_form" model="ir.ui.view">
|
||||
<field name="name">c2c.payslip.generate.wizard.form</field>
|
||||
<field name="model">c2c.payslip.generate.wizard</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Generate Payslips">
|
||||
<group>
|
||||
<field name="date_from"/>
|
||||
<field name="date_to"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</group>
|
||||
<footer>
|
||||
<button name="action_generate_payslips" string="Generate"
|
||||
type="object" class="oe_highlight"/>
|
||||
<button string="Cancel" class="btn-secondary" special="cancel"/>
|
||||
</footer>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="action_payslip_generate_wizard" model="ir.actions.act_window">
|
||||
<field name="name">Generate Payslips</field>
|
||||
<field name="res_model">c2c.payslip.generate.wizard</field>
|
||||
<field name="view_mode">form</field>
|
||||
<field name="target">new</field>
|
||||
</record>
|
||||
</odoo>
|
||||
76
addons/c2c_payroll/views/salary_structure_views.xml
Normal file
76
addons/c2c_payroll/views/salary_structure_views.xml
Normal file
@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<!-- ============================================================ -->
|
||||
<!-- Salary Structure — Tree View -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_salary_structure_tree" model="ir.ui.view">
|
||||
<field name="name">c2c.salary.structure.tree</field>
|
||||
<field name="model">c2c.salary.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Salary Structures">
|
||||
<field name="name"/>
|
||||
<field name="basic_percentage"/>
|
||||
<field name="hra_percentage"/>
|
||||
<field name="allowance_percentage"/>
|
||||
<field name="pf_percentage"/>
|
||||
<field name="esi_percentage"/>
|
||||
<field name="professional_tax_fixed"/>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Salary Structure — Form View -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="view_salary_structure_form" model="ir.ui.view">
|
||||
<field name="name">c2c.salary.structure.form</field>
|
||||
<field name="model">c2c.salary.structure</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Salary Structure">
|
||||
<sheet>
|
||||
<div class="oe_title">
|
||||
<label for="name"/>
|
||||
<h1>
|
||||
<field name="name" placeholder="e.g. Standard Structure"/>
|
||||
</h1>
|
||||
</div>
|
||||
<group>
|
||||
<group string="Earnings (%)">
|
||||
<field name="basic_percentage"/>
|
||||
<field name="hra_percentage"/>
|
||||
<field name="allowance_percentage"/>
|
||||
</group>
|
||||
<group string="Deductions">
|
||||
<field name="pf_percentage"/>
|
||||
<field name="esi_percentage"/>
|
||||
<field name="professional_tax_fixed"/>
|
||||
</group>
|
||||
</group>
|
||||
<group>
|
||||
<field name="company_id" groups="base.group_multi_company"/>
|
||||
<field name="active" invisible="1"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- ============================================================ -->
|
||||
<!-- Salary Structure — Action -->
|
||||
<!-- ============================================================ -->
|
||||
<record id="action_salary_structure" model="ir.actions.act_window">
|
||||
<field name="name">Salary Structures</field>
|
||||
<field name="res_model">c2c.salary.structure</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first Salary Structure
|
||||
</p>
|
||||
<p>
|
||||
Define how gross salary is split into Basic, HRA, and Allowances,
|
||||
and configure PF, ESI, and Professional Tax deduction rules.
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
2
addons/c2c_payroll/wizard/__init__.py
Normal file
2
addons/c2c_payroll/wizard/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import payslip_generate_wizard
|
||||
BIN
addons/c2c_payroll/wizard/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
addons/c2c_payroll/wizard/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
72
addons/c2c_payroll/wizard/payslip_generate_wizard.py
Normal file
72
addons/c2c_payroll/wizard/payslip_generate_wizard.py
Normal file
@ -0,0 +1,72 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import api, fields, models
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
|
||||
class PayslipGenerateWizard(models.TransientModel):
|
||||
_name = 'c2c.payslip.generate.wizard'
|
||||
_description = 'Generate Payslips Wizard'
|
||||
|
||||
date_from = fields.Date(string='Period From', required=True)
|
||||
date_to = fields.Date(string='Period To', required=True)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Company', required=True,
|
||||
default=lambda self: self.env.company,
|
||||
)
|
||||
|
||||
def action_generate_payslips(self):
|
||||
"""Generate payslips for all employees with active contracts."""
|
||||
self.ensure_one()
|
||||
|
||||
if self.date_from > self.date_to:
|
||||
raise UserError('Period From cannot be after Period To.')
|
||||
|
||||
contracts = self.env['hr.contract'].search([
|
||||
('state', '=', 'open'),
|
||||
('company_id', '=', self.company_id.id),
|
||||
('salary_structure_id', '!=', False),
|
||||
('gross_salary', '>', 0),
|
||||
])
|
||||
|
||||
if not contracts:
|
||||
raise UserError(
|
||||
'No active contracts found with a salary structure and gross salary '
|
||||
'for company %s.' % self.company_id.name
|
||||
)
|
||||
|
||||
Payslip = self.env['c2c.payslip']
|
||||
created_payslips = Payslip
|
||||
|
||||
for contract in contracts:
|
||||
# Skip if payslip already exists for this employee and period
|
||||
existing = Payslip.search([
|
||||
('employee_id', '=', contract.employee_id.id),
|
||||
('date_from', '=', self.date_from),
|
||||
('date_to', '=', self.date_to),
|
||||
('company_id', '=', self.company_id.id),
|
||||
], limit=1)
|
||||
if existing:
|
||||
continue
|
||||
|
||||
payslip = Payslip.create({
|
||||
'employee_id': contract.employee_id.id,
|
||||
'contract_id': contract.id,
|
||||
'date_from': self.date_from,
|
||||
'date_to': self.date_to,
|
||||
'company_id': self.company_id.id,
|
||||
})
|
||||
created_payslips |= payslip
|
||||
|
||||
if not created_payslips:
|
||||
raise UserError(
|
||||
'All eligible employees already have payslips for the selected period.'
|
||||
)
|
||||
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'name': 'Generated Payslips',
|
||||
'res_model': 'c2c.payslip',
|
||||
'view_mode': 'tree,form',
|
||||
'domain': [('id', 'in', created_payslips.ids)],
|
||||
'target': 'current',
|
||||
}
|
||||
1
addons/employee_documents/__init__.py
Normal file
1
addons/employee_documents/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import models
|
||||
22
addons/employee_documents/__manifest__.py
Normal file
22
addons/employee_documents/__manifest__.py
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': 'Employee Documents',
|
||||
'version': '1.0',
|
||||
'category': 'Human Resources',
|
||||
'summary': 'Manage Employee Documents',
|
||||
'description': """
|
||||
This module allows you to manage employee documents such as Offer Letters, Contracts, ID Proofs, etc.
|
||||
It adds a documents tab in the Employee form view.
|
||||
""",
|
||||
'author': 'Antigravity',
|
||||
'depends': ['hr'],
|
||||
'data': [
|
||||
'security/security.xml',
|
||||
'security/ir.model.access.csv',
|
||||
'views/employee_document_views.xml',
|
||||
'views/hr_employee_views.xml',
|
||||
],
|
||||
'installable': True,
|
||||
'application': True,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
BIN
addons/employee_documents/__pycache__/__init__.cpython-310.pyc
Normal file
BIN
addons/employee_documents/__pycache__/__init__.cpython-310.pyc
Normal file
Binary file not shown.
2
addons/employee_documents/models/__init__.py
Normal file
2
addons/employee_documents/models/__init__.py
Normal file
@ -0,0 +1,2 @@
|
||||
from . import employee_document
|
||||
from . import hr_employee
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
19
addons/employee_documents/models/employee_document.py
Normal file
19
addons/employee_documents/models/employee_document.py
Normal file
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api
|
||||
from datetime import datetime
|
||||
|
||||
class EmployeeDocument(models.Model):
|
||||
_name = 'employee.document'
|
||||
_description = 'Employee Document'
|
||||
|
||||
name = fields.Char(string='Document Reference', required=True)
|
||||
employee_id = fields.Many2one('hr.employee', string='Employee', required=True)
|
||||
document_type = fields.Selection([
|
||||
('offer_letter', 'Offer Letter'),
|
||||
('contract', 'Contract'),
|
||||
('id_proof', 'ID Proof'),
|
||||
('other', 'Other'),
|
||||
], string='Document Type', default='other', required=True)
|
||||
file = fields.Binary(string='File', required=True)
|
||||
file_name = fields.Char(string='File Name')
|
||||
upload_date = fields.Datetime(string='Upload Date', default=fields.Datetime.now)
|
||||
7
addons/employee_documents/models/hr_employee.py
Normal file
7
addons/employee_documents/models/hr_employee.py
Normal file
@ -0,0 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields
|
||||
|
||||
class HrEmployee(models.Model):
|
||||
_inherit = 'hr.employee'
|
||||
|
||||
document_ids = fields.One2many('employee.document', 'employee_id', string='Documents')
|
||||
3
addons/employee_documents/security/ir.model.access.csv
Normal file
3
addons/employee_documents/security/ir.model.access.csv
Normal file
@ -0,0 +1,3 @@
|
||||
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
|
||||
access_employee_document_user,employee.document.user,model_employee_document,base.group_user,1,0,0,0
|
||||
access_employee_document_manager,employee.document.manager,model_employee_document,employee_documents.group_hr_documents,1,1,1,1
|
||||
|
10
addons/employee_documents/security/security.xml
Normal file
10
addons/employee_documents/security/security.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<data noupdate="1">
|
||||
<record id="group_hr_documents" model="res.groups">
|
||||
<field name="name">HR Documents</field>
|
||||
<field name="category_id" ref="base.module_category_human_resources_employees"/>
|
||||
<field name="users" eval="[(4, ref('base.user_root')), (4, ref('base.user_admin'))]"/>
|
||||
</record>
|
||||
</data>
|
||||
</odoo>
|
||||
78
addons/employee_documents/views/employee_document_views.xml
Normal file
78
addons/employee_documents/views/employee_document_views.xml
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
|
||||
<!-- Tree View -->
|
||||
<record id="view_employee_document_tree" model="ir.ui.view">
|
||||
<field name="name">employee.document.tree</field>
|
||||
<field name="model">employee.document</field>
|
||||
<field name="arch" type="xml">
|
||||
<tree string="Employee Documents">
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="document_type"/>
|
||||
<field name="upload_date"/>
|
||||
</tree>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Form View -->
|
||||
<record id="view_employee_document_form" model="ir.ui.view">
|
||||
<field name="name">employee.document.form</field>
|
||||
<field name="model">employee.document</field>
|
||||
<field name="arch" type="xml">
|
||||
<form string="Employee Document">
|
||||
<sheet>
|
||||
<group>
|
||||
<group>
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="document_type"/>
|
||||
</group>
|
||||
<group>
|
||||
<field name="upload_date"/>
|
||||
<field name="file" filename="file_name"/>
|
||||
<field name="file_name" invisible="1"/>
|
||||
</group>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Search View -->
|
||||
<record id="view_employee_document_search" model="ir.ui.view">
|
||||
<field name="name">employee.document.search</field>
|
||||
<field name="model">employee.document</field>
|
||||
<field name="arch" type="xml">
|
||||
<search string="Search Employee Documents">
|
||||
<field name="name"/>
|
||||
<field name="employee_id"/>
|
||||
<field name="document_type"/>
|
||||
<group expand="0" string="Group By">
|
||||
<filter string="Employee" name="group_by_employee" context="{'group_by':'employee_id'}"/>
|
||||
<filter string="Document Type" name="group_by_type" context="{'group_by':'document_type'}"/>
|
||||
</group>
|
||||
</search>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Haction -->
|
||||
<record id="action_employee_document" model="ir.actions.act_window">
|
||||
<field name="name">Documents</field>
|
||||
<field name="res_model">employee.document</field>
|
||||
<field name="view_mode">tree,form</field>
|
||||
<field name="help" type="html">
|
||||
<p class="o_view_nocontent_smiling_face">
|
||||
Create your first employee document
|
||||
</p>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- Menu Item -->
|
||||
<menuitem id="menu_employee_document"
|
||||
name="Documents"
|
||||
parent="hr.menu_hr_root"
|
||||
action="action_employee_document"
|
||||
sequence="5"/>
|
||||
|
||||
</odoo>
|
||||
23
addons/employee_documents/views/hr_employee_views.xml
Normal file
23
addons/employee_documents/views/hr_employee_views.xml
Normal file
@ -0,0 +1,23 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<odoo>
|
||||
<record id="view_employee_form_inherit_document" model="ir.ui.view">
|
||||
<field name="name">hr.employee.form.inherit.document</field>
|
||||
<field name="model">hr.employee</field>
|
||||
<field name="inherit_id" ref="hr.view_employee_form"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr="//notebook" position="inside">
|
||||
<page string="Documents" name="documents">
|
||||
<field name="document_ids">
|
||||
<tree editable="bottom">
|
||||
<field name="name"/>
|
||||
<field name="document_type"/>
|
||||
<field name="upload_date"/>
|
||||
<field name="file" filename="file_name"/>
|
||||
<field name="file_name" invisible="1"/>
|
||||
</tree>
|
||||
</field>
|
||||
</page>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
</odoo>
|
||||
33
docker-compose.yml
Normal file
33
docker-compose.yml
Normal file
@ -0,0 +1,33 @@
|
||||
version: "3.8"
|
||||
|
||||
services:
|
||||
db:
|
||||
image: postgres:15
|
||||
container_name: odoo_metatroncube_db
|
||||
environment:
|
||||
POSTGRES_DB: postgres
|
||||
POSTGRES_USER: odoo
|
||||
POSTGRES_PASSWORD: odoo
|
||||
volumes:
|
||||
- client1_pgdata:/var/lib/postgresql/data
|
||||
restart: always
|
||||
|
||||
odoo:
|
||||
image: odoo:17.0
|
||||
container_name: odoo_metatroncube
|
||||
depends_on:
|
||||
- db
|
||||
ports:
|
||||
- "10101:8069"
|
||||
environment:
|
||||
HOST: db
|
||||
USER: odoo
|
||||
PASSWORD: odoo
|
||||
volumes:
|
||||
- client1_odoo_data:/var/lib/odoo
|
||||
- ./addons:/mnt/extra-addons
|
||||
restart: always
|
||||
|
||||
volumes:
|
||||
client1_pgdata:
|
||||
client1_odoo_data:
|
||||
Loading…
x
Reference in New Issue
Block a user