# -*- 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