Files
public/rsbu_fns_reporting/models/rsbu_enp_notification.py

206 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# -*- 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)