161 lines
7.3 KiB
Python
161 lines
7.3 KiB
Python
from odoo import api, fields, models, _
|
||
from odoo.exceptions import UserError
|
||
|
||
|
||
class HrPayslipRun(models.Model):
|
||
"""Реестр зарплаты (зарплатный проект банка).
|
||
|
||
Расширяет штатный «Пакет расчётных листков» до реестра на выплату:
|
||
банк одним платежом зачисляет общую сумму сотрудникам (в выписке одна
|
||
строка от банка), а реестр раскладывает её на выплаты по каждому
|
||
сотруднику (Дт 70 — Кт 51) одной кнопкой.
|
||
"""
|
||
_inherit = 'hr.payslip.run'
|
||
|
||
payment_bank_partner_id = fields.Many2one(
|
||
'res.partner', string='Банк (зарплатный проект)',
|
||
help='Контрагент-банк зарплатного проекта (напр. Т-Банк), от которого '
|
||
'приходит единый платёж по реестру.')
|
||
payment_date = fields.Date(
|
||
string='Дата выплаты',
|
||
default=fields.Date.context_today)
|
||
bank_fee_amount = fields.Monetary(
|
||
string='Комиссия банка', currency_field='currency_id', default=0.0)
|
||
currency_id = fields.Many2one(
|
||
'res.currency', related='company_id.currency_id', readonly=True)
|
||
total_net = fields.Monetary(
|
||
string='Итого к перечислению', currency_field='currency_id',
|
||
compute='_compute_total_net', store=False,
|
||
help='Сумма «к выплате» (правило NET) по проведённым расчётным '
|
||
'листкам реестра.')
|
||
rsbu_payout_move_id = fields.Many2one(
|
||
'rsbu.move', string='РСБУ-проводка выплаты', copy=False, readonly=True)
|
||
payout_state = fields.Selection(
|
||
[('to_pay', 'К выплате'), ('paid', 'Выплачено')],
|
||
string='Статус выплаты', default='to_pay', copy=False, readonly=True)
|
||
|
||
@api.depends('slip_ids.line_ids.total', 'slip_ids.state')
|
||
def _compute_total_net(self):
|
||
for run in self:
|
||
total = 0.0
|
||
for slip in run.slip_ids.filtered(lambda s: s.state == 'done'):
|
||
total += run._slip_net(slip)
|
||
run.total_net = total
|
||
|
||
@staticmethod
|
||
def _slip_net(slip):
|
||
"""Сумма «к выплате» по расчётному листку (правило NET)."""
|
||
net_lines = slip.line_ids.filtered(lambda l: l.code == 'NET')
|
||
return sum(net_lines.mapped('total'))
|
||
|
||
# ------------------------------------------------------------------
|
||
# Проводки выплаты
|
||
# ------------------------------------------------------------------
|
||
def _rsbu_account(self, code):
|
||
"""Найти счёт РСБУ по коду в разрезе компании реестра."""
|
||
self.ensure_one()
|
||
account = self.env['rsbu.account'].search([
|
||
('code', '=', code),
|
||
('company_id', '=', self.company_id.id),
|
||
], limit=1)
|
||
if not account:
|
||
raise UserError(_(
|
||
'В плане счетов РСБУ компании «%(company)s» не найден счёт %(code)s.',
|
||
company=self.company_id.display_name, code=code))
|
||
return account
|
||
|
||
def action_rsbu_pay(self):
|
||
"""Сформировать РСБУ-проводку выплаты по реестру (Дт 70 — Кт 51)."""
|
||
for run in self:
|
||
if run.rsbu_payout_move_id:
|
||
# Идемпотентность: повторное нажатие не дублирует.
|
||
continue
|
||
done_slips = run.slip_ids.filtered(lambda s: s.state == 'done')
|
||
if not done_slips:
|
||
raise UserError(_(
|
||
'В реестре «%s» нет проведённых расчётных листков.')
|
||
% run.display_name)
|
||
|
||
acc_70 = run._rsbu_account('70')
|
||
acc_51 = run._rsbu_account('51')
|
||
|
||
lines = []
|
||
total_pay = 0.0
|
||
for slip in done_slips:
|
||
net = run._slip_net(slip)
|
||
if not net:
|
||
continue
|
||
if not slip.employee_id:
|
||
raise UserError(_(
|
||
'В расчётном листке %s не указан сотрудник.') % slip.number)
|
||
# Дт 70 — закрытие задолженности перед сотрудником.
|
||
lines.append({
|
||
'account_id': acc_70.id,
|
||
'name': _('Выплата зарплаты: %s') % slip.employee_id.name,
|
||
'debit': net,
|
||
'employee_id': slip.employee_id.id,
|
||
})
|
||
total_pay += net
|
||
|
||
if not lines:
|
||
raise UserError(_(
|
||
'Нет сумм к выплате по реестру «%s».') % run.display_name)
|
||
|
||
fee = run.bank_fee_amount or 0.0
|
||
# Кт 51 — единый платёж банка (сумма выплат + комиссия).
|
||
lines.append({
|
||
'account_id': acc_51.id,
|
||
'name': _('Перечисление зарплаты (реестр %s)') % run.name,
|
||
'credit': total_pay + fee,
|
||
})
|
||
if fee:
|
||
acc_fee = run._rsbu_account('91')
|
||
lines.append({
|
||
'account_id': acc_fee.id,
|
||
'name': _('Комиссия банка по реестру %s') % run.name,
|
||
'debit': fee,
|
||
})
|
||
|
||
usn_recognition = (
|
||
'expense'
|
||
if run.company_id.rsbu_usn_mode == 'usn_income_expense'
|
||
else 'none')
|
||
|
||
move = self.env['rsbu.move'].sudo().create({
|
||
'date': run.payment_date or fields.Date.context_today(run),
|
||
'company_id': run.company_id.id,
|
||
'ref': _('Выплата зарплаты по реестру %s') % run.name,
|
||
'source_model': run._name,
|
||
'source_res_id': run.id,
|
||
'usn_recognition': usn_recognition,
|
||
'line_ids': [(0, 0, line) for line in lines],
|
||
})
|
||
move.action_post()
|
||
run.write({
|
||
'rsbu_payout_move_id': move.id,
|
||
'payout_state': 'paid',
|
||
})
|
||
return True
|
||
|
||
def action_rsbu_pay_cancel(self):
|
||
"""Отменить проводку выплаты (сторно/удаление)."""
|
||
for run in self:
|
||
move = run.rsbu_payout_move_id.sudo()
|
||
if move:
|
||
run.rsbu_payout_move_id = False
|
||
run.payout_state = 'to_pay'
|
||
if move.state == 'posted':
|
||
move.action_draft()
|
||
move.unlink()
|
||
return True
|
||
|
||
def action_view_rsbu_payout_move(self):
|
||
self.ensure_one()
|
||
return {
|
||
'type': 'ir.actions.act_window',
|
||
'name': _('РСБУ-проводка выплаты'),
|
||
'res_model': 'rsbu.move',
|
||
'view_mode': 'form',
|
||
'res_id': self.rsbu_payout_move_id.id,
|
||
}
|