Public release from ruodoo-project: 19.0 - 2026-11-01 22:52:25 UTC
This commit is contained in:
5
ruodoo_sale_control/models/__init__.py
Normal file
5
ruodoo_sale_control/models/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
|
||||
from . import sale_order
|
||||
from . import product
|
||||
from . import closed_doc
|
||||
from . import account_move
|
||||
8
ruodoo_sale_control/models/account_move.py
Normal file
8
ruodoo_sale_control/models/account_move.py
Normal 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='Закрывающий документ')
|
||||
21
ruodoo_sale_control/models/closed_doc.py
Normal file
21
ruodoo_sale_control/models/closed_doc.py
Normal 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')
|
||||
34
ruodoo_sale_control/models/product.py
Normal file
34
ruodoo_sale_control/models/product.py
Normal 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
|
||||
111
ruodoo_sale_control/models/sale_order.py
Normal file
111
ruodoo_sale_control/models/sale_order.py
Normal 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()
|
||||
Reference in New Issue
Block a user