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,5 @@
from . import sale_order
from . import product
from . import closed_doc
from . import account_move

View File

@@ -0,0 +1,8 @@
from odoo import fields, models
class AccountMove(models.Model):
_inherit = 'account.move'
closed_doc_id = fields.Many2one('doc.closeddoc', string='Закрывающий документ')

View File

@@ -0,0 +1,21 @@
from odoo import fields, models
class ClosedDoc(models.Model):
_name = 'doc.closeddoc'
_description = 'Закрывающий документ по счёту'
name = fields.Char(string='Номер', required=True)
date_doc = fields.Date(string='Дата получения документа', required=True)
invoice_id = fields.Many2one('account.move', string='Инвойс', required=True)
channel = fields.Selection(
[
('post', 'Почта'),
('edi', 'ЭДО'),
('original', 'Поступил в офис'),
('scan', 'Скан-копия'),
],
string='Канал получения', default='post', required=True,
)
file_inv = fields.Binary(string='Файл: скан или XML')

View File

@@ -0,0 +1,34 @@
from odoo import api, models
class ProductProduct(models.Model):
_inherit = 'product.product'
@api.model_create_multi
def create(self, vals_list):
for vals in vals_list:
if not vals.get('default_code'):
sku = self.env['ir.sequence'].next_by_code('SKU')
if sku:
vals['default_code'] = sku
return super().create(vals_list)
def set_sku(self):
for rec in self:
rec.default_code = self.env['ir.sequence'].next_by_code('SKU')
def get_fob_price(self):
"""Цена FOB из спецификации (BoM), иначе прайсовая цена.
Спецификации доступны только при установленном ``mrp``; без него
возвращаем прайсовую цену.
"""
self.ensure_one()
bom_ids = self['bom_ids'] if 'bom_ids' in self._fields else False
if bom_ids:
fob = bom_ids[0].bom_line_ids.filtered(
lambda ln: ln.product_id.name and 'FOB' in ln.product_id.name
)
return fob[0].product_id.lst_price if fob else self.lst_price
return self.lst_price

View File

@@ -0,0 +1,111 @@
from datetime import date
from odoo import api, fields, models
class SaleOrder(models.Model):
_inherit = 'sale.order'
price_alarm = fields.Boolean(
string='Цены требуют проверки',
compute='_compute_price_alarm', store=True,
)
type_validation = fields.Selection(
[
('balance', 'По балансу'),
('overdraft', 'По балансу + овердрафт'),
('normal', 'По подтверждению'),
],
string='Тип контроля отгрузки', default='normal',
)
state = fields.Selection(
selection_add=[('wait', 'Ожидание оплаты')],
ondelete={'wait': 'set default'},
)
@api.depends('partner_id', 'order_line', 'order_line.price_unit', 'state')
def _compute_price_alarm(self):
SaleLine = self.env['sale.order.line']
for order in self:
if order.state != 'draft':
order.price_alarm = False
continue
alarm = False
partner = order.partner_id.parent_id or order.partner_id
for line in order.order_line:
if not line.product_id:
continue
previous_prices = SaleLine.search([
('order_id.partner_id', '=', partner.id),
('order_id.state', '=', 'sale'),
('product_id', '=', line.product_id.id),
]).sorted(
key=lambda ln: ln.order_id.date_order or fields.Datetime.now(),
reverse=True,
).mapped('price_unit')
# Сравниваем только с последней отгрузкой этому клиенту.
if previous_prices and line.price_unit < previous_prices[0]:
alarm = True
order.price_alarm = alarm
def _get_shipment_credit(self):
"""Долг и лимит кредита партнёра (с учётом головного контрагента)."""
self.ensure_one()
partner = self.partner_id.parent_id or self.partner_id
return partner.credit, partner.credit_limit
def _is_shipment_allowed(self):
"""Проверка финансовых условий отгрузки по выбранному типу контроля."""
self.ensure_one()
# Расчёт долга ведём в контексте компании заказа (без мутации env).
order = self.with_company(self.company_id)
credit, credit_limit = order._get_shipment_credit()
if self.type_validation == 'normal':
return True
if self.type_validation == 'balance':
return credit * (-1) >= self.amount_total
if self.type_validation == 'overdraft':
return credit * (-1) + credit_limit >= self.amount_total
return False
def action_confirm(self):
confirmable = self.env['sale.order']
for order in self:
if order._is_shipment_allowed():
order._log_shipment_activity()
# Заказ в ожидании оплаты вернём в черновик, чтобы штатный
# action_confirm принял его к подтверждению.
if order.state == 'wait':
order.state = 'draft'
confirmable |= order
elif order.state != 'wait':
order.message_post(
body='Проверка фин. условий оставила заказ в ожидании оплаты.'
)
order.state = 'wait'
if confirmable:
return super(SaleOrder, confirmable).action_confirm()
return True
def _log_shipment_activity(self):
self.ensure_one()
activity_type = self.env.ref(
'ruodoo_sale_control.mail_activity_shipment_allowed',
raise_if_not_found=False,
)
if not activity_type:
return
self.env['mail.activity'].create({
'res_model_id': self.env.ref('sale.model_sale_order').id,
'res_id': self.id,
'user_id': self.user_id.id,
'activity_type_id': activity_type.id,
'date_deadline': date.today(),
})
@api.model
def validate_all(self):
"""Повторно проверить все заказы в ожидании оплаты (cron/действие)."""
for order in self.search([('state', '=', 'wait')]):
order.action_confirm()