85 lines
4.6 KiB
Python
85 lines
4.6 KiB
Python
"""Дети сотрудника для стандартного вычета по НДФЛ (ст. 218 НК РФ).
|
||
|
||
Размер вычета зависит от очерёдности рождения и возраста ребёнка;
|
||
вычет на ребёнка-инвалида суммируется с вычетом по очерёдности. Право
|
||
на вычет прекращается по достижении возраста (18 лет, либо 24 при очной
|
||
форме обучения) и при превышении дохода 450 000 ₽ нарастающим итогом
|
||
(проверяется при расчёте НДФЛ, не здесь).
|
||
"""
|
||
from dateutil.relativedelta import relativedelta
|
||
|
||
from odoo import _, api, fields, models
|
||
|
||
# Размеры детских вычетов по очерёдности (₽/мес), с 2025 (пп.4 п.1 ст.218 НК).
|
||
CHILD_DEDUCTION_BY_ORDER = {1: 1400.0, 2: 2800.0, 3: 6000.0}
|
||
# Вычет на ребёнка-инвалида (₽/мес): родитель/усыновитель — 12000,
|
||
# опекун/попечитель/приёмный — 6000.
|
||
DISABLED_DEDUCTION_PARENT = 12000.0
|
||
DISABLED_DEDUCTION_GUARDIAN = 6000.0
|
||
|
||
|
||
class RsbuEmployeeChild(models.Model):
|
||
_name = 'rsbu.employee.child'
|
||
_description = 'РСБУ — Ребёнок сотрудника (вычет НДФЛ)'
|
||
_order = 'birthdate, id'
|
||
|
||
employee_id = fields.Many2one(
|
||
'hr.employee', string='Сотрудник', required=True,
|
||
ondelete='cascade', index=True)
|
||
name = fields.Char(string='ФИО ребёнка')
|
||
birthdate = fields.Date(string='Дата рождения', required=True)
|
||
order = fields.Integer(
|
||
string='Очерёдность', required=True, default=1,
|
||
help='Порядковый номер ребёнка по рождению (1-й, 2-й, 3-й…). '
|
||
'Определяет размер вычета: 1400 / 2800 / 6000 ₽. Учитываются '
|
||
'все дети, включая совершеннолетних, при определении очерёдности.')
|
||
is_disabled = fields.Boolean(
|
||
string='Ребёнок-инвалид',
|
||
help='Добавляет вычет 12 000 ₽ (родителю/усыновителю) или 6 000 ₽ '
|
||
'(опекуну), суммируется с вычетом по очерёдности.')
|
||
is_student = fields.Boolean(
|
||
string='Очная форма обучения',
|
||
help='Продлевает право на вычет до 24 лет (иначе — до 18 лет).')
|
||
is_guardian = fields.Boolean(
|
||
string='Опекун/попечитель',
|
||
help='Отмечено — применяется вычет на ребёнка-инвалида 6 000 ₽ '
|
||
'(опекун), иначе 12 000 ₽ (родитель/усыновитель).')
|
||
single_parent = fields.Boolean(
|
||
string='Единственный родитель',
|
||
help='Двойной вычет единственному родителю (пп.4 п.1 ст.218 НК).')
|
||
|
||
def _age_on(self, date):
|
||
"""Полных лет ребёнку на указанную дату."""
|
||
self.ensure_one()
|
||
if not self.birthdate:
|
||
return 0
|
||
return relativedelta(date, self.birthdate).years
|
||
|
||
def _is_eligible_on(self, date):
|
||
"""Даёт ли ребёнок право на вычет на указанную дату (по возрасту)."""
|
||
self.ensure_one()
|
||
age = self._age_on(date)
|
||
limit = 24 if self.is_student else 18
|
||
return age < limit
|
||
|
||
def _deduction_amount(self, date):
|
||
"""Сумма месячного вычета по ребёнку на дату (по очерёдности +
|
||
инвалидность), с учётом двойного вычета единственному родителю."""
|
||
self.ensure_one()
|
||
if not self._is_eligible_on(date):
|
||
return 0.0
|
||
amount = CHILD_DEDUCTION_BY_ORDER.get(
|
||
self.order, CHILD_DEDUCTION_BY_ORDER[3]) if self.order else 0.0
|
||
if self.is_disabled:
|
||
amount += (DISABLED_DEDUCTION_GUARDIAN if self.is_guardian
|
||
else DISABLED_DEDUCTION_PARENT)
|
||
if self.single_parent:
|
||
amount *= 2.0
|
||
return amount
|
||
|
||
@api.depends('name', 'order', 'birthdate')
|
||
def _compute_display_name(self):
|
||
for rec in self:
|
||
rec.display_name = '%s (%s, №%s)' % (
|
||
rec.name or _('Ребёнок'), rec.birthdate or '?', rec.order or 0)
|