Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
305
ruodoo_commservice/models/contract.py
Normal file
305
ruodoo_commservice/models/contract.py
Normal file
@@ -0,0 +1,305 @@
|
||||
|
||||
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,
|
||||
})
|
||||
Reference in New Issue
Block a user