Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
205
rsbu_fns_reporting/models/rsbu_enp_notification.py
Normal file
205
rsbu_fns_reporting/models/rsbu_enp_notification.py
Normal file
@@ -0,0 +1,205 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""Уведомление об исчисленных суммах налогов, авансов, сборов, взносов.
|
||||
|
||||
Форма ФНС КНД 1110355, формат UT_UVISCHSUMNAL версии 5.03 (приказ ФНС
|
||||
от 16.01.2024 № ЕД-7-8/20@). Подаётся в составе механизма ЕНП в месяцы,
|
||||
когда налог/взносы исчислены, но срок декларации/расчёта ещё не наступил.
|
||||
|
||||
Автозаполнение строк за отчётный месяц из данных учёта:
|
||||
* страховые взносы — помесячные итоги rsbu.rsv.summary (ОПС+ОМС+ВНиМ);
|
||||
* НДФЛ — удержанный налог из проведённых расчётных листков (строки NDFL);
|
||||
* аванс УСН — из закрытия периода (rsbu.period.close) за квартал.
|
||||
|
||||
Каждая строка несёт КБК/ОКТМО/период/сумму. Один документ может содержать
|
||||
несколько строк по разным налогам (НДФЛ + взносы + аванс УСН).
|
||||
"""
|
||||
from odoo import api, fields, models
|
||||
|
||||
# Код квартала (Период) по номеру квартала.
|
||||
ENP_PERIOD_CODE = {1: '21', 2: '31', 3: '33', 4: '34'}
|
||||
|
||||
|
||||
class RsbuEnpNotification(models.Model):
|
||||
_name = 'rsbu.enp.notification'
|
||||
_description = 'РСБУ — Уведомление об исчисленных суммах (ЕНП)'
|
||||
_order = 'year desc, month desc, id desc'
|
||||
_check_company_auto = True
|
||||
|
||||
name = fields.Char(string='Наименование', compute='_compute_name',
|
||||
store=True)
|
||||
company_id = fields.Many2one(
|
||||
'res.company', string='Компания', required=True, index=True,
|
||||
default=lambda self: self.env.company)
|
||||
year = fields.Integer(string='Год', required=True,
|
||||
default=lambda self: fields.Date.today().year)
|
||||
month = fields.Integer(
|
||||
string='Отчётный месяц', required=True,
|
||||
default=lambda self: fields.Date.today().month,
|
||||
help='Календарный месяц, за который исчислены суммы. Определяет '
|
||||
'код периода (квартал) и номер месяца в квартале.')
|
||||
date_doc = fields.Date(
|
||||
string='Дата документа', default=fields.Date.context_today)
|
||||
state = fields.Selection(
|
||||
selection=[('draft', 'Черновик'), ('computed', 'Рассчитано')],
|
||||
default='draft', string='Состояние', copy=False)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='company_id.currency_id')
|
||||
line_ids = fields.One2many(
|
||||
'rsbu.enp.notification.line', 'notification_id', string='Строки')
|
||||
|
||||
@api.depends('year', 'month')
|
||||
def _compute_name(self):
|
||||
names = ['', 'январь', 'февраль', 'март', 'апрель', 'май', 'июнь',
|
||||
'июль', 'август', 'сентябрь', 'октябрь', 'ноябрь', 'декабрь']
|
||||
for rec in self:
|
||||
m = names[rec.month] if rec.month and 1 <= rec.month <= 12 else '?'
|
||||
rec.name = 'Уведомление ЕНП: %s %s' % (m, rec.year or '?')
|
||||
|
||||
def _quarter(self):
|
||||
self.ensure_one()
|
||||
return (self.month - 1) // 3 + 1 if self.month else 1
|
||||
|
||||
def _month_in_quarter(self):
|
||||
"""Порядковый номер месяца в квартале (01..03)."""
|
||||
self.ensure_one()
|
||||
return '%02d' % ((self.month - 1) % 3 + 1)
|
||||
|
||||
def _month_bounds(self):
|
||||
import calendar
|
||||
self.ensure_one()
|
||||
last = calendar.monthrange(self.year, self.month)[1]
|
||||
return (fields.Date.to_date('%04d-%02d-01' % (self.year, self.month)),
|
||||
fields.Date.to_date('%04d-%02d-%02d' % (
|
||||
self.year, self.month, last)))
|
||||
|
||||
# ------------------------------------------------------------------
|
||||
def _insurance_amount(self):
|
||||
"""Начисленные страховые взносы (ОПС+ОМС+ВНиМ) за отчётный месяц."""
|
||||
self.ensure_one()
|
||||
Summary = self.env['rsbu.rsv.summary'].sudo()
|
||||
recs = Summary.search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('year', '=', self.year),
|
||||
('month', '=', self.month),
|
||||
])
|
||||
return round(sum(recs.mapped('ops')) + sum(recs.mapped('oms'))
|
||||
+ sum(recs.mapped('vnim')), 2)
|
||||
|
||||
def _ndfl_amount(self):
|
||||
"""Удержанный НДФЛ за отчётный месяц (строки NDFL в листках)."""
|
||||
self.ensure_one()
|
||||
date_from, date_to = self._month_bounds()
|
||||
slips = self.env['hr.payslip'].sudo().search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('state', '=', 'done'),
|
||||
('date_from', '>=', date_from),
|
||||
('date_from', '<=', date_to),
|
||||
])
|
||||
total = 0.0
|
||||
for slip in slips:
|
||||
total += abs(sum(slip.line_ids.filtered(
|
||||
lambda l: l.code == 'NDFL').mapped('total')))
|
||||
return round(total, 2)
|
||||
|
||||
def _usn_advance_amount(self):
|
||||
"""Аванс УСН за квартал (если месяц — месяц уплаты аванса).
|
||||
|
||||
Авансы УСН уплачиваются по итогам квартала в следующем месяце
|
||||
(апрель/июль/октябрь). Берём из закрытия периода последнего месяца
|
||||
соответствующего квартала.
|
||||
"""
|
||||
self.ensure_one()
|
||||
# Уведомление по УСН-авансу подаётся в апреле/июле/октябре за
|
||||
# предыдущий квартал (1/2/3). За 4 квартал — годовая декларация.
|
||||
usn_quarter = {4: 1, 7: 2, 10: 3}.get(self.month)
|
||||
if not usn_quarter:
|
||||
return 0.0
|
||||
import calendar
|
||||
q_last_month = usn_quarter * 3
|
||||
last = calendar.monthrange(self.year, q_last_month)[1]
|
||||
close = self.env['rsbu.period.close'].sudo().search([
|
||||
('company_id', '=', self.company_id.id),
|
||||
('date_to', '=', fields.Date.to_date('%04d-%02d-%02d' % (
|
||||
self.year, q_last_month, last))),
|
||||
], limit=1)
|
||||
return round(close.usn_tax_amount, 2) if close else 0.0
|
||||
|
||||
def action_compute(self):
|
||||
"""Пересобрать итоги взносов за месяц и автозаполнить строки."""
|
||||
for rec in self:
|
||||
df, dt = rec._month_bounds()
|
||||
self.env['rsbu.rsv.summary'].sudo().recompute_period(
|
||||
df, dt, rec.company_id)
|
||||
return self.fill_lines()
|
||||
|
||||
def fill_lines(self):
|
||||
"""Автозаполнить строки из готовых итогов учёта за месяц.
|
||||
|
||||
Отделено от action_compute, чтобы заполнять по существующим данным
|
||||
без принудительного пересбора итогов (тесты, повторный расчёт).
|
||||
"""
|
||||
for rec in self:
|
||||
rec.line_ids.unlink()
|
||||
company = rec.company_id
|
||||
oktmo = company.rsbu_oktmo or ''
|
||||
period = ENP_PERIOD_CODE[rec._quarter()]
|
||||
mq = rec._month_in_quarter()
|
||||
Line = self.env['rsbu.enp.notification.line']
|
||||
vals = []
|
||||
|
||||
insurance = rec._insurance_amount()
|
||||
if insurance > 0:
|
||||
vals.append({
|
||||
'notification_id': rec.id, 'kind': 'insurance',
|
||||
'kbk': company.rsbu_kbk_insurance or '', 'oktmo': oktmo,
|
||||
'amount': insurance, 'period_code': period,
|
||||
'month_in_quarter': mq, 'report_year': rec.year,
|
||||
})
|
||||
ndfl = rec._ndfl_amount()
|
||||
if ndfl > 0:
|
||||
vals.append({
|
||||
'notification_id': rec.id, 'kind': 'ndfl',
|
||||
'kbk': company.rsbu_kbk_ndfl or '', 'oktmo': oktmo,
|
||||
'amount': ndfl, 'period_code': period,
|
||||
'month_in_quarter': mq, 'report_year': rec.year,
|
||||
})
|
||||
usn = rec._usn_advance_amount()
|
||||
if usn > 0:
|
||||
# Аванс УСН: период — квартал, за который платится аванс.
|
||||
usn_q = {4: 1, 7: 2, 10: 3}[rec.month]
|
||||
vals.append({
|
||||
'notification_id': rec.id, 'kind': 'usn',
|
||||
'kbk': company.rsbu_kbk_usn or '', 'oktmo': oktmo,
|
||||
'amount': usn, 'period_code': ENP_PERIOD_CODE[usn_q],
|
||||
'month_in_quarter': '03', 'report_year': rec.year,
|
||||
})
|
||||
if vals:
|
||||
Line.create(vals)
|
||||
rec.state = 'computed'
|
||||
return True
|
||||
|
||||
|
||||
class RsbuEnpNotificationLine(models.Model):
|
||||
_name = 'rsbu.enp.notification.line'
|
||||
_description = 'РСБУ — Строка уведомления ЕНП'
|
||||
_order = 'notification_id, kind'
|
||||
|
||||
notification_id = fields.Many2one(
|
||||
'rsbu.enp.notification', string='Уведомление', required=True,
|
||||
ondelete='cascade', index=True)
|
||||
currency_id = fields.Many2one(
|
||||
'res.currency', related='notification_id.currency_id')
|
||||
kind = fields.Selection(
|
||||
selection=[('insurance', 'Страховые взносы'), ('ndfl', 'НДФЛ'),
|
||||
('usn', 'Аванс УСН')],
|
||||
string='Вид платежа', required=True)
|
||||
kpp_decl = fields.Char(string='КПП (деклар.)')
|
||||
oktmo = fields.Char(string='ОКТМО', required=True)
|
||||
kbk = fields.Char(string='КБК', required=True)
|
||||
amount = fields.Monetary(string='Сумма', currency_field='currency_id')
|
||||
period_code = fields.Char(string='Период', required=True,
|
||||
help='Код периода: 21/31/33/34.')
|
||||
month_in_quarter = fields.Char(
|
||||
string='Номер месяца/квартала', required=True,
|
||||
help='Номер месяца в квартале (01-03) либо квартальный код (11-13).')
|
||||
report_year = fields.Integer(string='Год', required=True)
|
||||
Reference in New Issue
Block a user