removed unwanted addons
This commit is contained in:
parent
2e1af13715
commit
206d6bba68
72
CLAUDE.md
Normal file
72
CLAUDE.md
Normal file
@ -0,0 +1,72 @@
|
||||
# Odoo Metatroncube — Project Context
|
||||
|
||||
## Live Instance
|
||||
|
||||
| Field | Value |
|
||||
|----------|--------------------------------------------|
|
||||
| URL | https://workplace.metatroncube.in/ |
|
||||
| Database | `metatroncube_db` |
|
||||
| User | `info@metatroncubesolutions.com` |
|
||||
| Version | Odoo 17.0 (Community) |
|
||||
|
||||
## MCP Connection
|
||||
|
||||
The `.mcp.json` in this folder connects Claude to the live Odoo instance via `odoo-mcp`.
|
||||
To connect at the start of a session run:
|
||||
|
||||
```
|
||||
connect to metatroncube Odoo — url https://workplace.metatroncube.in/, db metatroncube_db, user info@metatroncubesolutions.com
|
||||
```
|
||||
|
||||
Or use the `mcp__odoo-mcp__connect_database` tool with `instance_name: metatroncube`.
|
||||
|
||||
## Local Docker Environment
|
||||
|
||||
File: `docker-compose.yml`
|
||||
|
||||
- Odoo 17.0 container → port `10001` (http://localhost:10001)
|
||||
- PostgreSQL 15
|
||||
- Addons mounted at `./addons:/mnt/extra-addons`
|
||||
- External volumes:
|
||||
- `odoo-testing-addons_metatroncube_pgdata`
|
||||
- `odoo-testing-addons_metatroncube_odoo_data`
|
||||
|
||||
Start locally:
|
||||
```bash
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
## Custom Addons
|
||||
|
||||
All addons live in `./addons/`. Author: **Metatroncube Software Solutions**.
|
||||
|
||||
| Module | Version | Purpose | Depends On |
|
||||
|------------------------------|---------------|----------------------------------------------------------|-----------------------|
|
||||
| `accounting_community` | 17.0.1.0.0 | Unlocks full accounting menus on Community Edition | `account` |
|
||||
| `employee_documents` | — | Document management for employees | — |
|
||||
| `mcs_appointment_booking` | 17.0.1.0.0 | Website appointment booking with calendar integration | `calendar`, `website` |
|
||||
| `mcs_invoice_currency_display` | — | Currency display on invoices | — |
|
||||
| `mcs_payroll_localization` | 17.0.1.0.0 | India & Canada payroll structures (OCA Payroll) | `payroll_account` |
|
||||
| `payroll` | — | OCA Payroll base module | — |
|
||||
| `payroll_account` | — | OCA Payroll + Accounting integration | `payroll` |
|
||||
|
||||
## Key Custom Modules Detail
|
||||
|
||||
### `mcs_appointment_booking`
|
||||
- Full module: models, views, controllers, security
|
||||
- Website-facing booking forms + backend calendar views
|
||||
- License: AGPL-3
|
||||
|
||||
### `mcs_payroll_localization`
|
||||
- India and Canada payroll structures
|
||||
- Has a `post_init_hook` in `hooks.py`
|
||||
- License: LGPL-3
|
||||
|
||||
### `accounting_community`
|
||||
- By Antigravity (third-party, included in repo)
|
||||
- Adds Journal Entries, Items, General Ledger, Partner Ledger to Community
|
||||
- License: LGPL-3
|
||||
|
||||
## Snapshots
|
||||
|
||||
Odoo MCP snapshots are stored in `./snapshots/` — used for rollback via `mcp__odoo-mcp__rollback_snapshot`.
|
||||
@ -1,3 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import models
|
||||
from . import wizard
|
||||
@ -1,38 +0,0 @@
|
||||
# -*- 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,
|
||||
}
|
||||
Binary file not shown.
@ -1,4 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import salary_structure
|
||||
from . import contract_extension
|
||||
from . import payslip
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1,23 +0,0 @@
|
||||
# -*- 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.',
|
||||
)
|
||||
@ -1,299 +0,0 @@
|
||||
# -*- 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',
|
||||
}
|
||||
@ -1,49 +0,0 @@
|
||||
# -*- 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
|
||||
)
|
||||
@ -1,15 +0,0 @@
|
||||
<?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>
|
||||
@ -1,184 +0,0 @@
|
||||
<?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>
|
||||
@ -1,7 +0,0 @@
|
||||
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
|
||||
|
@ -1,43 +0,0 @@
|
||||
<?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>
|
||||
@ -1,27 +0,0 @@
|
||||
<?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>
|
||||
@ -1,35 +0,0 @@
|
||||
<?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>
|
||||
@ -1,191 +0,0 @@
|
||||
<?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>
|
||||
@ -1,76 +0,0 @@
|
||||
<?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>
|
||||
@ -1,2 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import payslip_generate_wizard
|
||||
Binary file not shown.
Binary file not shown.
@ -1,72 +0,0 @@
|
||||
# -*- 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',
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user