Files
public/mklab_forecast_mrp/models/forecast.py

237 lines
9.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
from odoo import models, fields, api, exceptions
from datetime import timedelta, datetime
from collections import defaultdict
import logging
_logger = logging.getLogger(__name__)
class MklabForecast(models.Model):
_name = 'mklab.forecast'
name = fields.Char(string='Название', compute='_compute_name')
start_date = fields.Date(string='Дата прогноза', required=True)
forecast_line_ids = fields.One2many(
comodel_name='mklab.forecastline',
inverse_name='forecast_id',
string='Строки',
)
partner_ids = fields.Many2many(
comodel_name='res.partner',
string='Ограничить контрагентами',
)
expected_growth = fields.Float(
string='Ожидаемый рост продаж при акции, %',
required=True,
default=20,
)
is_promo = fields.Boolean(string='Запланирована акция', compute='_compute_is_promo')
is_ready_to_calc = fields.Boolean(string='Отправить на расчет')
total_amount = fields.Float(string='Итого', compute='_compute_total_amount')
state = fields.Selection(
selection=[('draft', 'Черновик'), ('active', 'Утвержден')],
string='Статус',
default='draft',
)
def _compute_name(self):
for s in self:
s.name = str(s.start_date) if s.start_date else ''
def _compute_total_amount(self):
for s in self:
s.total_amount = sum(s.forecast_line_ids.mapped(lambda r: r.total_amount))
def _compute_is_promo(self):
for s in self:
promos = self.env['mklab.marketaction'].search([
('start_date', '<=', s.start_date),
('end_date', '>=', s.start_date),
])
s.is_promo = bool(promos)
@api.constrains('is_ready_to_calc')
def _reset_calculated(self):
for s in self:
s.forecast_line_ids.write({'is_calculated': False})
def action_validate(self):
self.state = 'active'
def action_cancel(self):
self.state = 'draft'
def fill_lines(self):
for s in self:
if s.forecast_line_ids:
raise exceptions.UserError(
'Для автоматического заполнения нужно сначала очистить все строки')
line_obj = self.env['mklab.forecastline']
products = self.env['product.product'].search([('is_forecast_ok', '=', True)])
for prod in products:
line_obj.create({
'forecast_id': s.id,
'product_id': prod.id,
'price': prod.list_price,
})
def create_mrp(self):
for s in self:
lines = s.forecast_line_ids.filtered(
lambda r: r.forecast_value > 0 and not r.mrp_order_id)
for line in lines:
bom = line.bom_id
if not bom:
raise exceptions.UserError(
'Для продукта ' + line.product_id.name + ' не найдена ведомость материалов')
new_order = self.env['mrp.production'].create({
'product_id': line.product_id.id,
'product_qty': line.forecast_value,
'bom_id': bom.id,
'product_uom_id': line.product_id.uom_id.id,
'origin': s.name,
'company_id': self.env.user.company_id.id,
'date_start': s.start_date,
})
line.mrp_order_id = new_order
class MklabForecastLine(models.Model):
_name = 'mklab.forecastline'
product_id = fields.Many2one(
comodel_name='product.product',
string='Товар',
required=True,
)
forecast_value = fields.Float(string='Прогноз/план')
order_value = fields.Float(string='В заказах факт')
invoice_value = fields.Float(string='Отгружено факт')
forecast_id = fields.Many2one(
comodel_name='mklab.forecast',
string='Прогноз',
)
is_calculated = fields.Boolean(string='Пересчитано автоматом')
price = fields.Float(string='Цена')
total_amount = fields.Float(string='Итого', compute='_compute_total_amount')
mrp_order_id = fields.Many2one(
comodel_name='mrp.production',
string='Заказ на производство',
)
notes = fields.Text(string='Комментарий')
bom_id = fields.Many2one(
comodel_name='mrp.bom',
string='Ведомость материалов',
)
def _compute_total_amount(self):
for s in self:
s.total_amount = s.price * s.forecast_value
def calc_bom(self):
for s in self:
s.bom_id, s.notes = s.product_id.start_planning(s.forecast_value)
def get_fact_values(self):
for s in self:
for_start = datetime.combine(s.forecast_id.start_date, datetime.min.time())
for_end = for_start + timedelta(hours=23)
if s.forecast_id.partner_ids:
orders = self.env['sale.order'].search([
('date_order', '<=', for_end),
('date_order', '>=', for_start),
('partner_id', 'in', s.forecast_id.partner_ids.ids),
])
else:
orders = self.env['sale.order'].search([
('date_order', '<=', for_end),
('date_order', '>=', for_start),
])
ord_value = 0
inv_value = 0
for order in orders:
for line in order.order_line.filtered(lambda r: r.product_id == s.product_id):
ord_value += line.product_uom_qty
for inv in order.invoice_ids:
for line in inv.invoice_line_ids.filtered(lambda r: r.product_id == s.product_id):
inv_value += line.quantity
s.invoice_value = inv_value
s.order_value = ord_value
def get_forecast_values(self):
for s in self:
start_date_forforecast = s.forecast_id.start_date - timedelta(days=84)
weekday = s.forecast_id.start_date.weekday()
if s.forecast_id.partner_ids:
orders = self.env['sale.order'].search([
('date_order', '<', s.forecast_id.start_date),
('date_order', '>=', start_date_forforecast),
('dayofweek', '=', weekday),
('partner_id', 'in', s.forecast_id.partner_ids.ids),
])
else:
orders = self.env['sale.order'].search([
('date_order', '<', s.forecast_id.start_date),
('date_order', '>=', start_date_forforecast),
('dayofweek', '=', weekday),
])
f_value = 0
promos_forecast = self.env['mklab.marketaction'].search([
('start_date', '<=', s.forecast_id.start_date),
('end_date', '>=', s.forecast_id.start_date),
])
for order in orders:
promos = self.env['mklab.marketaction'].search([
('start_date', '<=', order.date_order),
('end_date', '>=', order.date_order),
])
koeff = 0
if promos:
mark_lines = self.env['mklab.productline'].search([
('action_id', '=', promos[0].id),
('product_id', '=', s.product_id.id),
])
pids = self.env['mklab.partnerline'].search([
('action_id', '=', promos[0].id),
('partner_id', '=', order.partner_id.id),
])
if mark_lines and (pids or not promos[0].partner_line_ids):
koeff = mark_lines[0].expected_growth
is_promo = False
if promos_forecast:
mark_lines_f = self.env['mklab.productline'].search([
('action_id', '=', promos_forecast[0].id),
('product_id', '=', s.product_id.id),
])
pids_f = self.env['mklab.partnerline'].search([
('action_id', '=', promos_forecast[0].id),
('partner_id', '=', order.partner_id.id),
])
if mark_lines_f and (pids_f or not promos_forecast[0].partner_line_ids):
is_promo = True
order_qty = 0
for line in order.order_line.filtered(lambda r: r.product_id == s.product_id):
line_qty = (line.product_uom_qty * (1 + koeff / 100) * (1 + s.forecast_id.expected_growth / 100)
if is_promo else line.product_uom_qty * (1 + koeff / 100))
f_value += line_qty
order_qty += line_qty
s.forecast_value = round(f_value / 12, 0)
@api.model
def recalc_all(self):
"""Cron: пересчитывает по 5 строк за раз."""
lines = self.env['mklab.forecastline'].search(
[('forecast_id.is_ready_to_calc', '=', True), ('is_calculated', '=', False)],
limit=5,
)
for line in lines:
line.get_forecast_values()
line.get_fact_values()
line.is_calculated = True