Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
8
ruodoo_commservice/models/__init__.py
Normal file
8
ruodoo_commservice/models/__init__.py
Normal file
@@ -0,0 +1,8 @@
|
||||
|
||||
from . import building
|
||||
from . import counter
|
||||
from . import contract
|
||||
from . import contract_line
|
||||
from . import account_move
|
||||
from . import create_invoice_wizard
|
||||
from . import save_values_wizard
|
||||
11
ruodoo_commservice/models/account_move.py
Normal file
11
ruodoo_commservice/models/account_move.py
Normal file
@@ -0,0 +1,11 @@
|
||||
|
||||
from odoo import fields, models
|
||||
|
||||
|
||||
class AccountMove(models.Model):
|
||||
_inherit = 'account.move'
|
||||
|
||||
month = fields.Integer(string='Месяц')
|
||||
year = fields.Integer(string='Год')
|
||||
comm_contract_id = fields.Many2one('comm.contract', string='Договор аренды')
|
||||
kladov = fields.Many2one('res.users', string='Кладовщик')
|
||||
91
ruodoo_commservice/models/building.py
Normal file
91
ruodoo_commservice/models/building.py
Normal file
@@ -0,0 +1,91 @@
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class CommBuildingType(models.Model):
|
||||
_name = 'comm.btype'
|
||||
_description = 'Тип объекта аренды'
|
||||
|
||||
name = fields.Char(string='Название типа объекта', required=True)
|
||||
|
||||
|
||||
class CommBuilding(models.Model):
|
||||
_name = 'comm.building'
|
||||
_description = 'Объект аренды'
|
||||
|
||||
name = fields.Char(string='Номер офиса или машиноместа', required=True)
|
||||
type = fields.Many2one('comm.btype', string='Тип')
|
||||
busy_until = fields.Date(string='Занято до', compute='_compute_busy_until')
|
||||
is_free = fields.Boolean(
|
||||
string='Сейчас свободно',
|
||||
compute='_compute_is_free',
|
||||
search='_search_is_free',
|
||||
)
|
||||
price_analysis = fields.Html(string='История цен', compute='_compute_price_analysis')
|
||||
|
||||
@api.depends('name', 'type')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
if rec.type and rec.name:
|
||||
rec.display_name = '%s %s' % (rec.type.name, rec.name)
|
||||
else:
|
||||
rec.display_name = rec.name or ''
|
||||
|
||||
def _compute_price_analysis(self):
|
||||
ContractLine = self.env['comm.contractline']
|
||||
for rec in self:
|
||||
lines = ContractLine.search([
|
||||
('counter_id', '=', False),
|
||||
('contract_id.building_id', '=', rec.id),
|
||||
])
|
||||
if lines:
|
||||
sorted_lines = lines.sorted(
|
||||
key=lambda line: line.contract_id.date_end or fields.Date.today(),
|
||||
reverse=True,
|
||||
)
|
||||
rows = sorted_lines.mapped(
|
||||
lambda line: '%s %s %sр. ' % (
|
||||
line.contract_id.date_end or '',
|
||||
line.product_id.name or '',
|
||||
line.price,
|
||||
)
|
||||
)
|
||||
rec.price_analysis = '<p>%s</p>' % '<br/>'.join(rows)
|
||||
else:
|
||||
rec.price_analysis = ''
|
||||
|
||||
def _compute_busy_until(self):
|
||||
Contract = self.env['comm.contract']
|
||||
for rec in self:
|
||||
contracts = Contract.search([
|
||||
('state', '=', 'open'),
|
||||
('building_id', '=', rec.id),
|
||||
])
|
||||
if contracts:
|
||||
rec.busy_until = contracts.sorted(
|
||||
key=lambda c: c.date_end or fields.Date.today(),
|
||||
reverse=True,
|
||||
)[0].date_end
|
||||
else:
|
||||
rec.busy_until = False
|
||||
|
||||
@api.depends('busy_until')
|
||||
def _compute_is_free(self):
|
||||
for rec in self:
|
||||
rec.is_free = not rec.busy_until
|
||||
|
||||
def _search_is_free(self, operator, value):
|
||||
busy_building_ids = self.env['comm.contract'].search([
|
||||
('state', '=', 'open'),
|
||||
]).mapped('building_id').ids
|
||||
# Нормализуем леф вида (is_free, op, value) к «ищем свободные?».
|
||||
if operator in ('=', '=='):
|
||||
searching_free = bool(value)
|
||||
elif operator == '!=':
|
||||
searching_free = not bool(value)
|
||||
else:
|
||||
searching_free = bool(value)
|
||||
# Свободные объекты — те, что не заняты действующим договором.
|
||||
if searching_free:
|
||||
return [('id', 'not in', busy_building_ids)]
|
||||
return [('id', 'in', busy_building_ids)]
|
||||
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,
|
||||
})
|
||||
57
ruodoo_commservice/models/contract_line.py
Normal file
57
ruodoo_commservice/models/contract_line.py
Normal file
@@ -0,0 +1,57 @@
|
||||
|
||||
from odoo import api, fields, models
|
||||
|
||||
|
||||
class CommContractLine(models.Model):
|
||||
_name = 'comm.contractline'
|
||||
_description = 'Услуга договора аренды'
|
||||
_order = 'period,product_name'
|
||||
|
||||
name = fields.Char(string='Название услуги', compute='_compute_name', store=True)
|
||||
product_id = fields.Many2one('product.product', string='Услуга')
|
||||
product_name = fields.Char(
|
||||
related='product_id.name', store=True, readonly=True,
|
||||
)
|
||||
counter_id = fields.Many2one('comm.counter', string='Счётчик')
|
||||
price = fields.Float(string='Цена', digits='Product Price')
|
||||
tax_id = fields.Many2one('account.tax', string='НДС')
|
||||
period = fields.Selection(
|
||||
[
|
||||
('month', 'Месяц'),
|
||||
('quarter', 'Квартал'),
|
||||
('year', 'Год'),
|
||||
],
|
||||
string='Период', default='month',
|
||||
)
|
||||
contract_id = fields.Many2one('comm.contract', string='Номер договора')
|
||||
|
||||
@api.depends('product_id', 'product_id.name')
|
||||
def _compute_name(self):
|
||||
for rec in self:
|
||||
rec.name = rec.product_id.name or ''
|
||||
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
for rec in self:
|
||||
rec.tax_id = rec.product_id.taxes_id[:1]
|
||||
rec.price = rec.product_id.lst_price
|
||||
|
||||
def write(self, values):
|
||||
if self.counter_id and self.contract_id:
|
||||
self.counter_id.contract_id = self.contract_id
|
||||
return super().write(values)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
for record in records:
|
||||
if record.contract_id and record.counter_id:
|
||||
record.counter_id.contract_id = record.contract_id
|
||||
return records
|
||||
|
||||
@api.model
|
||||
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
|
||||
195
ruodoo_commservice/models/counter.py
Normal file
195
ruodoo_commservice/models/counter.py
Normal file
@@ -0,0 +1,195 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, exceptions
|
||||
|
||||
|
||||
class CommCounterType(models.Model):
|
||||
_name = 'comm.countertype'
|
||||
_description = 'Тип счётчика'
|
||||
|
||||
name = fields.Char(string='Название типа счётчика', required=True)
|
||||
uom = fields.Many2one('uom.uom', string='Единица измерения')
|
||||
|
||||
|
||||
class CommCounter(models.Model):
|
||||
_name = 'comm.counter'
|
||||
_description = 'Прибор учёта (счётчик)'
|
||||
_order = 'contract_name,type,name'
|
||||
|
||||
name = fields.Char(string='Номер счётчика', required=True)
|
||||
type = fields.Many2one('comm.countertype', string='Тип счётчика')
|
||||
contract_id = fields.Many2one('comm.contract', string='Договор')
|
||||
contract_name = fields.Char(
|
||||
string='Номер договора',
|
||||
related='contract_id.name',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
price = fields.Float(
|
||||
string='Цена',
|
||||
compute='_compute_price',
|
||||
store=True,
|
||||
readonly=True,
|
||||
)
|
||||
last_value_val = fields.Float(
|
||||
string='Последнее показание',
|
||||
compute='_compute_last_value',
|
||||
store=True,
|
||||
)
|
||||
last_period = fields.Char(
|
||||
string='Последний период',
|
||||
compute='_compute_last_period',
|
||||
store=True,
|
||||
)
|
||||
values_ids = fields.Many2many('comm.counterval', string='Показания')
|
||||
|
||||
@api.depends('name', 'type', 'last_period', 'last_value_val')
|
||||
def _compute_display_name(self):
|
||||
for rec in self:
|
||||
if rec.last_period:
|
||||
suffix = ' (%s: %s, %s)' % (
|
||||
rec.last_period, rec.last_value_val, rec.type.name,
|
||||
)
|
||||
elif rec.type:
|
||||
suffix = ' (%s)' % rec.type.name
|
||||
else:
|
||||
suffix = ''
|
||||
rec.display_name = '%s%s' % (rec.name or '', suffix)
|
||||
|
||||
@api.depends('values_ids', 'contract_id')
|
||||
def _compute_last_value(self):
|
||||
for rec in self:
|
||||
rec.last_value_val = rec._last_value()
|
||||
|
||||
def _last_value(self):
|
||||
self.ensure_one()
|
||||
value = self.env['comm.counterval'].search(
|
||||
[('counter_id', '=', self.id)],
|
||||
order='year desc, month desc',
|
||||
limit=1,
|
||||
)
|
||||
return value.name if value else 0.0
|
||||
|
||||
@api.depends('contract_id', 'contract_id.line_ids.price')
|
||||
def _compute_price(self):
|
||||
for rec in self:
|
||||
line = rec.contract_id.line_ids.filtered(
|
||||
lambda ln: ln.counter_id.id == rec.id
|
||||
)
|
||||
rec.price = line[:1].price
|
||||
|
||||
@api.depends('values_ids', 'contract_id')
|
||||
def _compute_last_period(self):
|
||||
for rec in self:
|
||||
value = self.env['comm.counterval'].search(
|
||||
[('counter_id', '=', rec.id)],
|
||||
order='year desc, month desc',
|
||||
limit=1,
|
||||
)
|
||||
rec.last_period = (
|
||||
'%s-%s' % (value.year, value.month) if value else ''
|
||||
)
|
||||
|
||||
@api.onchange('contract_id')
|
||||
def _onchange_contract_id(self):
|
||||
for rec in self:
|
||||
if not rec.contract_id:
|
||||
continue
|
||||
existing = rec.contract_id.line_ids.mapped('counter_id').ids
|
||||
if rec.id and rec.id not in existing:
|
||||
rec.contract_id.line_ids.create({
|
||||
'counter_id': rec.id,
|
||||
'contract_id': rec.contract_id.id,
|
||||
})
|
||||
|
||||
def action_show_last_value(self):
|
||||
self.ensure_one()
|
||||
raise exceptions.UserError(
|
||||
'На сегодняшнее число последнее показание этого счётчика, '
|
||||
'зарегистрированное в базе: %s' % self._last_value()
|
||||
)
|
||||
|
||||
def action_add_value(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'comm.counterval',
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
'context': {'default_counter_id': self.id},
|
||||
}
|
||||
|
||||
|
||||
class CommCounterValue(models.Model):
|
||||
_name = 'comm.counterval'
|
||||
_description = 'Показание счётчика'
|
||||
_order = 'year desc, month desc'
|
||||
|
||||
name = fields.Float(string='Показание', required=True)
|
||||
year = fields.Integer(
|
||||
string='Год', required=True,
|
||||
default=lambda self: datetime.today().year,
|
||||
)
|
||||
month = fields.Integer(
|
||||
string='Месяц', required=True,
|
||||
default=lambda self: datetime.today().month,
|
||||
)
|
||||
counter_id = fields.Many2one('comm.counter', string='Счётчик', required=True)
|
||||
contract_id = fields.Many2one(
|
||||
'comm.contract', string='Договор',
|
||||
related='counter_id.contract_id', store=True,
|
||||
)
|
||||
type = fields.Many2one(
|
||||
'comm.countertype', string='Тип',
|
||||
related='counter_id.type', store=True,
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Партнёр',
|
||||
related='counter_id.contract_id.partner_id', store=True,
|
||||
)
|
||||
|
||||
@api.constrains('name', 'month', 'year', 'counter_id')
|
||||
def _check_value(self):
|
||||
for rec in self:
|
||||
if rec.month > 12 or rec.month < 1:
|
||||
raise exceptions.ValidationError(
|
||||
'Счётчик: %s. Месяц должен быть от 1 до 12!' % rec.counter_id.name
|
||||
)
|
||||
if rec.year < 2010 or rec.year > 2050:
|
||||
raise exceptions.ValidationError(
|
||||
'Счётчик: %s. Некорректно указан год!' % rec.counter_id.name
|
||||
)
|
||||
if rec.name == 0:
|
||||
raise exceptions.ValidationError(
|
||||
'Счётчик: %s. Значение не может быть равно 0.' % rec.counter_id.name
|
||||
)
|
||||
later = self.search([
|
||||
('year', '>=', rec.year),
|
||||
('month', '>=', rec.month),
|
||||
('counter_id', '=', rec.counter_id.id),
|
||||
('id', '!=', rec.id),
|
||||
])
|
||||
if later:
|
||||
raise exceptions.ValidationError(
|
||||
'Счётчик: %s. Уже существует показание за указанный или '
|
||||
'более поздний период.' % rec.counter_id.name
|
||||
)
|
||||
bigger = self.search([
|
||||
('name', '>', rec.name),
|
||||
('counter_id', '=', rec.counter_id.id),
|
||||
('id', '!=', rec.id),
|
||||
])
|
||||
if bigger:
|
||||
raise exceptions.ValidationError(
|
||||
'Счётчик: %s. Уже существует большее показание для этого '
|
||||
'счётчика. Например: %s.' % (rec.counter_id.name, bigger[0].name)
|
||||
)
|
||||
|
||||
@api.model_create_multi
|
||||
def create(self, vals_list):
|
||||
records = super().create(vals_list)
|
||||
for record in records:
|
||||
if record.counter_id:
|
||||
record.counter_id.values_ids |= record
|
||||
return records
|
||||
73
ruodoo_commservice/models/create_invoice_wizard.py
Normal file
73
ruodoo_commservice/models/create_invoice_wizard.py
Normal file
@@ -0,0 +1,73 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, exceptions
|
||||
|
||||
|
||||
class CommCreateInvoiceWizard(models.TransientModel):
|
||||
_name = 'comm.createinv'
|
||||
_description = 'Мастер создания счетов за период'
|
||||
|
||||
contract_ids = fields.Many2many('comm.contract', string='Договоры', required=True)
|
||||
line_ids = fields.Many2many('comm.wizardline', string='Результат работы')
|
||||
year = fields.Integer(
|
||||
string='Год', required=True,
|
||||
default=lambda self: datetime.today().year,
|
||||
)
|
||||
month = fields.Integer(
|
||||
string='Месяц', required=True,
|
||||
default=lambda self: datetime.today().month,
|
||||
)
|
||||
invoice_created = fields.Boolean(string='Счета уже создавались')
|
||||
message = fields.Char(string='Сообщение')
|
||||
|
||||
@api.constrains('year', 'month')
|
||||
def _check_period(self):
|
||||
for rec in self:
|
||||
if rec.year < 2018 or rec.month < 1 or rec.month > 12:
|
||||
raise exceptions.ValidationError(
|
||||
'Введите корректные данные для периода!'
|
||||
)
|
||||
|
||||
def fill_contracts(self):
|
||||
self.ensure_one()
|
||||
contracts = self.env['comm.contract'].search([('state', '=', 'open')])
|
||||
self.contract_ids = [(6, 0, contracts.ids)]
|
||||
return self._reopen_form()
|
||||
|
||||
def do_creation(self):
|
||||
self.ensure_one()
|
||||
WizardLine = self.env['comm.wizardline']
|
||||
for contract in self.contract_ids:
|
||||
new_line = WizardLine.create({'contract_id': contract.id})
|
||||
self.line_ids |= new_line
|
||||
new_line.result = contract.create_invoice(
|
||||
year=self.year, month=self.month,
|
||||
)
|
||||
if self.line_ids:
|
||||
self.invoice_created = True
|
||||
self.message = (
|
||||
'Обработка завершена. Если имеются сообщения, что не удалось '
|
||||
'вычислить потребление, это означает, что не были внесены данные '
|
||||
'по счётчикам. Как только данные подадут, начисление автоматически '
|
||||
'учтёт весь расход.'
|
||||
)
|
||||
return self._reopen_form()
|
||||
|
||||
def _reopen_form(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
|
||||
class CommCreateInvoiceWizardLine(models.TransientModel):
|
||||
_name = 'comm.wizardline'
|
||||
_description = 'Строка результата мастера создания счетов'
|
||||
|
||||
contract_id = fields.Many2one('comm.contract', string='Договор')
|
||||
result = fields.Char(string='Результат')
|
||||
110
ruodoo_commservice/models/save_values_wizard.py
Normal file
110
ruodoo_commservice/models/save_values_wizard.py
Normal file
@@ -0,0 +1,110 @@
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from odoo import api, fields, models, exceptions
|
||||
|
||||
|
||||
class CommSaveValuesWizard(models.TransientModel):
|
||||
_name = 'comm.savevalues'
|
||||
_description = 'Мастер ввода показаний счётчиков'
|
||||
|
||||
year = fields.Integer(
|
||||
string='Год', required=True,
|
||||
default=lambda self: datetime.today().year,
|
||||
)
|
||||
month = fields.Integer(
|
||||
string='Месяц', required=True,
|
||||
default=lambda self: datetime.today().month,
|
||||
)
|
||||
line_ids = fields.Many2many('comm.save_line', string='Счётчики и показания')
|
||||
message = fields.Char(string='Сообщение')
|
||||
type_ids = fields.Many2many('comm.countertype', string='Типы счётчиков')
|
||||
|
||||
def _active_counters(self):
|
||||
return self.env['comm.contractline'].search([
|
||||
('contract_id.state', '=', 'open'),
|
||||
('counter_id', '!=', False),
|
||||
]).mapped('counter_id')
|
||||
|
||||
def _build_lines(self, counters):
|
||||
self.line_ids = False
|
||||
SaveLine = self.env['comm.save_line']
|
||||
for counter in counters.sorted(
|
||||
key=lambda c: (c.type.id, c.contract_id.id, c.id)
|
||||
):
|
||||
if counter.type in self.type_ids or not self.type_ids:
|
||||
self.line_ids |= SaveLine.create({'counter_id': counter.id})
|
||||
return self._reopen_form()
|
||||
|
||||
def fill_values(self):
|
||||
"""Счётчики без показаний за выбранный период."""
|
||||
self.ensure_one()
|
||||
active = self._active_counters()
|
||||
with_values = self.env['comm.counterval'].search([
|
||||
('year', '=', self.year),
|
||||
('month', '=', self.month),
|
||||
]).mapped('counter_id')
|
||||
return self._build_lines(active - with_values)
|
||||
|
||||
def fill_all_values(self):
|
||||
"""Все счётчики по действующим договорам."""
|
||||
self.ensure_one()
|
||||
return self._build_lines(self._active_counters())
|
||||
|
||||
def do_save(self):
|
||||
self.ensure_one()
|
||||
CounterVal = self.env['comm.counterval']
|
||||
for line in self.line_ids:
|
||||
CounterVal.create({
|
||||
'name': line.current_value,
|
||||
'year': self.year,
|
||||
'month': self.month,
|
||||
'counter_id': line.counter_id.id,
|
||||
})
|
||||
self.message = 'Показания записаны'
|
||||
self.fill_values()
|
||||
return self._reopen_form()
|
||||
|
||||
def _reopen_form(self):
|
||||
self.ensure_one()
|
||||
return {
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': self._name,
|
||||
'res_id': self.id,
|
||||
'view_mode': 'form',
|
||||
'target': 'new',
|
||||
}
|
||||
|
||||
|
||||
class CommSaveValuesWizardLine(models.TransientModel):
|
||||
_name = 'comm.save_line'
|
||||
_description = 'Строка мастера ввода показаний'
|
||||
|
||||
counter_id = fields.Many2one('comm.counter', string='Счётчик')
|
||||
type = fields.Many2one(
|
||||
'comm.countertype', string='Тип', related='counter_id.type',
|
||||
)
|
||||
contract_id = fields.Many2one(
|
||||
'comm.contract', string='Договор', related='counter_id.contract_id',
|
||||
)
|
||||
partner_id = fields.Many2one(
|
||||
'res.partner', string='Партнёр',
|
||||
related='counter_id.contract_id.partner_id',
|
||||
)
|
||||
last_value = fields.Float(
|
||||
string='Последнее показание', related='counter_id.last_value_val',
|
||||
)
|
||||
current_value = fields.Float(string='Текущее показание')
|
||||
values_ids = fields.Many2many(
|
||||
'comm.counterval', string='Показания', related='counter_id.values_ids',
|
||||
)
|
||||
last_period = fields.Char(
|
||||
string='Последний период', related='counter_id.last_period',
|
||||
)
|
||||
|
||||
@api.onchange('current_value')
|
||||
def _onchange_current_value(self):
|
||||
if self.current_value and self.current_value <= self.last_value:
|
||||
raise exceptions.ValidationError(
|
||||
'Текущее значение не может быть меньше предыдущего.'
|
||||
)
|
||||
Reference in New Issue
Block a user