Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC

This commit is contained in:
CI Publish Bot
2026-11-01 22:54:30 +00:00
commit 26250aa580
2492 changed files with 664277 additions and 0 deletions

View 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

View 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='Кладовщик')

View 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)]

View 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,
})

View 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

View 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

View 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='Результат')

View 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(
'Текущее значение не может быть меньше предыдущего.'
)