Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
145
rsbu_payroll/models/rsbu_ndfl_summary.py
Normal file
145
rsbu_payroll/models/rsbu_ndfl_summary.py
Normal file
@@ -0,0 +1,145 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Регистр помесячных итогов НДФЛ по сотрудникам (для 6-НДФЛ).
|
||||
|
||||
Аналог rsbu.rsv.summary: агрегирует проведённые расчётные листки по
|
||||
(сотрудник × месяц начисления × ставка), вычисляя начисленный доход,
|
||||
вычеты, базу, исчисленный и удержанный НДФЛ. Дополнительно фиксирует
|
||||
дату удержания (= дату выплаты) и номер срока перечисления внутри
|
||||
квартала (1..6), что требуется для раздела 1 формы 6-НДФЛ (КНД 1151100,
|
||||
формат 5.05): НДФЛ разбивается по шести срокам квартала (каждый месяц —
|
||||
два периода: дни 1–22 и 23–конец).
|
||||
"""
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class RsbuNdflSummary(models.Model):
|
||||
_name = 'rsbu.ndfl.summary'
|
||||
_description = 'РСБУ — Итоги НДФЛ по сотрудникам (6-НДФЛ)'
|
||||
_order = 'year desc, month, employee_id'
|
||||
_check_company_auto = True
|
||||
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Компания', required=True, index=True,
|
||||
default=lambda self: self.env.company)
|
||||
employee_id = fields.Many2one(
|
||||
'hr.employee', string='Сотрудник', required=True, index=True)
|
||||
year = fields.Integer(string='Год', required=True, index=True)
|
||||
month = fields.Integer(string='Месяц начисления', required=True, index=True)
|
||||
rate = fields.Integer(string='Ставка НДФЛ, %', required=True, index=True)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', string='Валюта',
|
||||
compute='_compute_currency_id', store=True)
|
||||
|
||||
accrued = fields.Monetary(string='Начислено (доход)',
|
||||
currency_field='currency_id')
|
||||
deduction = fields.Monetary(string='Вычеты',
|
||||
currency_field='currency_id')
|
||||
base = fields.Monetary(string='Налоговая база',
|
||||
currency_field='currency_id')
|
||||
computed_tax = fields.Monetary(string='Исчислено НДФЛ',
|
||||
currency_field='currency_id')
|
||||
withheld_tax = fields.Monetary(string='Удержано НДФЛ',
|
||||
currency_field='currency_id')
|
||||
|
||||
# Удержание: дата удержания (= дата выплаты) и срок квартала (1..6).
|
||||
withholding_date = fields.Date(string='Дата удержания')
|
||||
withholding_month = fields.Integer(
|
||||
string='Месяц удержания',
|
||||
help='Календарный месяц даты удержания (для разбивки по срокам).')
|
||||
deadline_term = fields.Integer(
|
||||
string='Срок квартала (1..6)',
|
||||
help='Номер срока перечисления внутри квартала удержания: по два '
|
||||
'на месяц (дни 1–22 → нечётный, 23–конец → чётный).')
|
||||
|
||||
@api.depends('company_id')
|
||||
def _compute_currency_id(self):
|
||||
for rec in self:
|
||||
rec.currency_id = rec.company_id.currency_id
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def _deadline_term(wh_date):
|
||||
"""Номер срока перечисления внутри КВАРТАЛА (1..6) по дате удержания.
|
||||
|
||||
Квартал делится на 6 сроков: по два на каждый месяц квартала —
|
||||
дни 1–22 (первый срок месяца) и 23–конец (второй срок месяца).
|
||||
Для месяца-в-квартале k (1..3): срок = (k-1)*2 + (1 если день<=22
|
||||
иначе 2).
|
||||
"""
|
||||
month_in_quarter = (wh_date.month - 1) % 3 + 1
|
||||
half = 1 if wh_date.day <= 22 else 2
|
||||
return (month_in_quarter - 1) * 2 + half
|
||||
|
||||
@api.model
|
||||
def recompute_period(self, date_from, date_to, company):
|
||||
"""Пересобрать помесячные итоги НДФЛ за период из листков.
|
||||
|
||||
Идемпотентно: удаляет итоги месяцев периода и заново агрегирует
|
||||
проведённые листки (state='done') по сотруднику, месяцу начисления
|
||||
и ставке. Срок удержания берётся по дате удержания листка.
|
||||
"""
|
||||
self.search([
|
||||
('company_id', '=', company.id),
|
||||
('year', '>=', date_from.year),
|
||||
('year', '<=', date_to.year),
|
||||
]).filtered(lambda r: date_from <= fields.Date.to_date(
|
||||
'%04d-%02d-01' % (r.year, r.month)) <= date_to).unlink()
|
||||
|
||||
slips = self.env['hr.payslip'].sudo().search([
|
||||
('company_id', '=', company.id),
|
||||
('state', '=', 'done'),
|
||||
('date_from', '>=', date_from),
|
||||
('date_from', '<=', date_to),
|
||||
])
|
||||
acc = {}
|
||||
for slip in slips:
|
||||
v = slip._ndfl_report_values()
|
||||
if not (v['gross'] or v['withheld']):
|
||||
continue
|
||||
key = (slip.employee_id.id, slip.date_from.year,
|
||||
slip.date_from.month, v['rate'])
|
||||
wh = v['withholding_date']
|
||||
data = acc.setdefault(key, {
|
||||
'accrued': 0.0, 'deduction': 0.0, 'base': 0.0,
|
||||
'computed': 0.0, 'withheld': 0.0,
|
||||
'wh_date': wh, 'wh_month': wh.month if wh else 0,
|
||||
'term': self._deadline_term(wh) if wh else 0,
|
||||
})
|
||||
data['accrued'] += v['gross']
|
||||
data['deduction'] += v['deduction']
|
||||
data['base'] += v['base']
|
||||
data['computed'] += v['computed']
|
||||
data['withheld'] += v['withheld']
|
||||
|
||||
vals = []
|
||||
for (emp, y, m, rate), d in acc.items():
|
||||
vals.append({
|
||||
'company_id': company.id, 'employee_id': emp,
|
||||
'year': y, 'month': m, 'rate': rate,
|
||||
'accrued': round(d['accrued'], 2),
|
||||
'deduction': round(d['deduction'], 2),
|
||||
'base': round(d['base'], 2),
|
||||
'computed_tax': round(d['computed'], 2),
|
||||
'withheld_tax': round(d['withheld'], 2),
|
||||
'withholding_date': d['wh_date'],
|
||||
'withholding_month': d['wh_month'],
|
||||
'deadline_term': d['term'],
|
||||
})
|
||||
return self.create(vals) if vals else self.browse()
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
@staticmethod
|
||||
def _quarter_months(quarter):
|
||||
start = (quarter - 1) * 3 + 1
|
||||
return (start, start + 1, start + 2)
|
||||
|
||||
@api.model
|
||||
def ytd_records(self, company, year, up_to_quarter):
|
||||
"""Записи нарастающим итогом с начала года по конец квартала."""
|
||||
_, _, last = self._quarter_months(up_to_quarter)
|
||||
return self.search([
|
||||
('company_id', '=', company.id),
|
||||
('year', '=', year),
|
||||
('month', '>=', 1),
|
||||
('month', '<=', last),
|
||||
])
|
||||
Reference in New Issue
Block a user