Files
public/ruodoo_commservice/models/contract.py

306 lines
13 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.
import calendar
from datetime import date, datetime
from odoo import api, fields, models
QUARTER_NAMES = {1: 'первый', 4: 'второй', 7: 'третий', 10: 'четвертый'}
class CommContract(models.Model):
_name = 'comm.contract'
_inherit = ['mail.thread', 'mail.activity.mixin']
_description = 'Договор аренды'
_order = 'name'
name = fields.Char(string='Номер договора', tracking=True)
date_start = fields.Date(string='Дата начала действия')
date_end = fields.Date(string='Дата окончания действия')
building_id = fields.Many2one('comm.building', string='Объект')
state = fields.Selection(
[
('draft', 'Черновик'),
('open', 'Действует'),
('closed', 'Не действует'),
],
string='Статус', default='draft', tracking=True,
)
partner_id = fields.Many2one('res.partner', string='Заказчик', required=True)
commercial_partner_id = fields.Many2one(
'res.partner', string='Коммерческий партнёр',
store=True, compute='_compute_commercial_partner_id',
)
line_ids = fields.One2many('comm.contractline', 'contract_id', string='Услуги')
building = fields.Char(string='Название объекта')
credit = fields.Float(string='Долг', compute='_compute_credit')
inv_count = fields.Integer(string='Количество счетов', compute='_compute_inv_count')
company_id = fields.Many2one(
'res.company', string='Компания', required=True,
default=lambda self: self.env.company,
)
contract_id = fields.Many2one(
'partner.contract.customer', string='Связанный фин. договор',
)
day_inv = fields.Integer(string='День счёта, 0 если автомат', default=0)
day_upd = fields.Integer(string='День УПД, 0 если автомат', default=0)
@api.depends('partner_id', 'partner_id.parent_id')
def _compute_commercial_partner_id(self):
for rec in self:
rec.commercial_partner_id = (
rec.partner_id.parent_id or rec.partner_id
)
def _compute_credit(self):
for rec in self:
rec.credit = rec.partner_id.credit if rec.partner_id else 0.0
def _compute_inv_count(self):
Move = self.env['account.move']
for rec in self:
rec.inv_count = Move.search_count([
('commercial_partner_id', '=', rec.commercial_partner_id.id),
('year', '>', 0),
('state', 'not in', ['cancel']),
('comm_contract_id', '=', rec.id),
])
def connect_counters(self):
"""Привязать счётчики строк договора к самому договору."""
for contract in self.env['comm.contract'].search([]):
for line in contract.line_ids:
if line.counter_id:
line.counter_id.contract_id = contract
# ------------------------------------------------------------------
# Формирование счетов
# ------------------------------------------------------------------
def _is_period_due(self, line, month):
"""Наступил ли период начисления для строки в данном месяце."""
if line.period == 'month':
return True
if line.period == 'quarter':
return month in (1, 4, 7, 10)
if line.period == 'year':
return month == 1
return False
def _period_line_name(self, line, year, month):
"""Название строки счёта с учётом периодичности."""
if line.period == 'year' and month == 1:
return '%s, ежегодное начисление за %sг.' % (line.name, year)
if line.period == 'quarter' and month in QUARTER_NAMES:
return '%s, ежеквартальное начисление за %s кв. %sг.' % (
line.name, QUARTER_NAMES[month], year,
)
return '%s %s-%s' % (line.name, year, month)
def _prepare_invoice_vals(self, line, year, month):
self.ensure_one()
sale_journal = self.env['account.journal'].search([
('type', '=', 'sale'),
('company_id', '=', self.company_id.id),
], limit=1)
origin = 'Начисление по договору аренды за %s %s-%s' % (
line.name, year, month,
)
return {
'ref': origin,
'invoice_origin': origin,
'move_type': 'out_invoice',
'journal_id': sale_journal.id or False,
'partner_id': self.commercial_partner_id.id,
'partner_shipping_id': self.partner_id.id,
'fiscal_position_id': self.partner_id.property_account_position_id.id,
'team_id': False,
'invoice_date_due': (
date(year, month, 1) if self.day_inv == 0
else date(year, month, self.day_inv)
),
'invoice_date': (
date(year, month, calendar.monthrange(year, month)[1])
if self.day_upd == 0 else date(year, month, self.day_upd)
),
'month': month,
'year': year,
'company_id': self.company_id.id,
'mt_contract_id': self.contract_id.id if self.contract_id else False,
'comm_contract_id': self.id,
'kladov': self.company_id.chief_id.id if self.company_id.chief_id else False,
}
def _income_account_id(self, line):
return line.product_id.categ_id.property_account_income_categ_id.id or False
def create_invoice(self, year=False, month=False):
"""Создать счета за период по всем строкам договоров self.
Возвращает текстовый отчёт об операции (сообщения по строкам).
"""
result = ''
Move = self.env['account.move']
for contract in self:
if not year or not month:
year = datetime.today().year
month = datetime.today().month
for line in contract.line_ids:
if not contract._is_period_due(line, month):
continue
origin = 'Начисление по договору аренды за %s %s-%s' % (
line.name, year, month,
)
existing = Move.search([
('commercial_partner_id', '=', contract.commercial_partner_id.id),
('state', '!=', 'cancel'),
('invoice_origin', '=', origin),
('comm_contract_id', '=', contract.id),
], limit=1)
if existing:
result += (
'Счёт за указанный период за данную услугу уже есть в базе. '
'Например, %s. Пожалуйста, отмените старый счёт, прежде чем '
'создавать новый.' % existing.name
)
continue
invoice_vals = contract._prepare_invoice_vals(line, year, month)
account_id = contract._income_account_id(line)
if line.counter_id:
line_vals, message = contract._prepare_counter_line(
line, year, month, account_id,
)
result += message
if not line_vals:
continue
invoice_vals['invoice_line_ids'] = line_vals
else:
invoice_vals['invoice_line_ids'] = contract._prepare_flat_line(
line, year, month, account_id,
)
result += (
'Создано начисление за период: %s, услуга %s, цена %s; '
% (line.period, line.product_id.name, line.price)
)
new_inv = Move.create(invoice_vals)
new_inv.set_ons()
return result
def _prepare_counter_line(self, line, year, month, account_id):
"""Строка счёта по показаниям счётчика (расход за период)."""
CounterVal = self.env['comm.counterval']
current = CounterVal.search([
('counter_id', '=', line.counter_id.id),
('year', '=', year),
('month', '=', month),
], limit=1)
previous = CounterVal.search([
('counter_id', '=', line.counter_id.id),
('id', '!=', current.id if current else 0),
], order='year desc, month desc', limit=1)
if not (current and previous) or (current.name - previous.name) <= 0:
return [], (
'для услуги %s не удалось вычислить потребление; '
% line.product_id.name
)
consumption = current.name - previous.name
message = '%s, цена %s потребление: %s' % (
line.product_id.name, line.price, consumption,
)
vals = {
'product_id': line.product_id.id,
'product_uom_id': line.counter_id.type.uom.id,
'name': self._period_line_name(line, year, month),
'quantity': consumption,
'tax_ids': [(6, 0, [line.tax_id.id])] if line.tax_id else False,
'price_unit': line.price,
'partner_id': self.partner_id.parent_id.id or self.partner_id.id,
}
# account_id задаём только если найден счёт дохода — иначе Odoo
# подставит счёт по умолчанию (из журнала/категории продукта).
if account_id:
vals['account_id'] = account_id
return [(0, 0, vals)], message
def _prepare_flat_line(self, line, year, month, account_id):
"""Строка счёта без счётчика (фиксированная сумма за период)."""
if line.period == 'year' and month == 1:
name = '%s, ежегодное начисление за %sг.' % (line.name, year)
elif line.period == 'quarter' and month in QUARTER_NAMES:
name = '%s, ежеквартальное начисление за %s кв. %sг.' % (
line.name, QUARTER_NAMES[month], year,
)
else:
last_day = calendar.monthrange(year, month)[1]
name = '%s %s , период 01.%s.%s-%s.%s.%s' % (
line.product_id.name, self.building_id.name,
month, year, last_day, month, year,
)
vals = {
'product_id': line.product_id.id,
'name': name,
'quantity': 1,
'price_unit': line.price,
'tax_ids': [(6, 0, [line.tax_id.id])] if line.tax_id else False,
'product_uom_id': line.product_id.uom_id.id,
}
if account_id:
vals['account_id'] = account_id
return [(0, 0, vals)]
# ------------------------------------------------------------------
# Действия
# ------------------------------------------------------------------
def action_view_invoice(self):
self.ensure_one()
action = self.env['ir.actions.act_window']._for_xml_id(
'account.action_move_out_invoice_type'
)
action.update({
'context': dict(
self.env.context,
default_partner_id=self.partner_id.id,
group_by=False,
),
'domain': [
('commercial_partner_id', '=', self.commercial_partner_id.id),
('comm_contract_id', '=', self.id),
],
})
return action
def create_fincontract(self):
"""Создать связанный российский договор (partner.contract.customer)."""
Contract = self.env['partner.contract.customer']
profile = self.env['contract.profile'].search([], limit=1)
for rec in self:
partner_type = 'company' if rec.partner_id.is_company else 'person'
vals = {
'date_start': rec.date_start or fields.Date.today(),
'date_end': rec.date_end or fields.Date.today(),
'type': 'customer',
'partner_id': rec.partner_id.id,
'partner_type': partner_type,
'company_id': rec.company_id.id,
}
if profile:
vals['profile_id'] = profile.id
rec.contract_id = Contract.create(vals)
@api.constrains('contract_id')
def _sync_fincontract(self):
for rec in self:
if rec.contract_id:
rec.contract_id.write({
'date_start': rec.date_start,
'date_end': rec.date_end,
'partner_id': rec.partner_id.id,
'company_id': rec.company_id.id,
})