Odoo18-Base/addons/stock/wizard/stock_replenishment_info.py
2025-01-06 10:57:38 +07:00

165 lines
8.5 KiB
Python

# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
import babel.dates
from json import dumps
from datetime import datetime, time
from dateutil.relativedelta import relativedelta
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.osv.expression import AND
from odoo.tools.date_utils import get_month, subtract
from odoo.tools.float_utils import float_compare
from odoo.tools.misc import get_lang, format_date
class StockReplenishmentInfo(models.TransientModel):
_name = 'stock.replenishment.info'
_description = 'Stock supplier replenishment information'
_rec_name = 'orderpoint_id'
orderpoint_id = fields.Many2one('stock.warehouse.orderpoint')
product_id = fields.Many2one('product.product', related='orderpoint_id.product_id')
qty_to_order = fields.Float(related='orderpoint_id.qty_to_order')
json_lead_days = fields.Char(compute='_compute_json_lead_days')
json_replenishment_history = fields.Char(compute='_compute_json_replenishment_history')
warehouseinfo_ids = fields.One2many(related='orderpoint_id.warehouse_id.resupply_route_ids')
wh_replenishment_option_ids = fields.One2many('stock.replenishment.option', 'replenishment_info_id', compute='_compute_wh_replenishment_options')
@api.depends('orderpoint_id')
def _compute_wh_replenishment_options(self):
for replenishment_info in self:
replenishment_info.wh_replenishment_option_ids = self.env['stock.replenishment.option'].create([
{'product_id': replenishment_info.product_id.id, 'route_id': route_id.id, 'replenishment_info_id': replenishment_info.id}
for route_id in replenishment_info.warehouseinfo_ids
]).sorted(lambda o: o.free_qty, reverse=True)
@api.depends('orderpoint_id')
def _compute_json_lead_days(self):
self.json_lead_days = False
for replenishment_report in self:
if not replenishment_report.orderpoint_id.product_id or not replenishment_report.orderpoint_id.location_id:
continue
orderpoint = replenishment_report.orderpoint_id
orderpoints_values = orderpoint._get_lead_days_values()
dummy, lead_days_description = orderpoint.rule_ids._get_lead_days(
orderpoint.product_id, **orderpoints_values)
replenishment_report.json_lead_days = dumps({
'lead_days_date': format_date(self.env, replenishment_report.orderpoint_id.lead_days_date),
'lead_days_description': lead_days_description,
'today': format_date(self.env, fields.Date.today()),
'trigger': orderpoint.trigger,
'qty_forecast': self.env['ir.qweb.field.float'].value_to_html(orderpoint.qty_forecast, {'decimal_precision': 'Product Unit of Measure'}),
'qty_to_order': self.env['ir.qweb.field.float'].value_to_html(orderpoint.qty_to_order, {'decimal_precision': 'Product Unit of Measure'}),
'product_min_qty': self.env['ir.qweb.field.float'].value_to_html(orderpoint.product_min_qty, {'decimal_precision': 'Product Unit of Measure'}),
'product_max_qty': self.env['ir.qweb.field.float'].value_to_html(orderpoint.product_max_qty, {'decimal_precision': 'Product Unit of Measure'}),
'product_uom_name': orderpoint.product_uom_name,
'virtual': orderpoint.trigger == 'manual' and orderpoint.create_uid.id == SUPERUSER_ID,
'visibility_days': orderpoint.visibility_days if float_compare(orderpoint.qty_forecast, orderpoint.product_min_qty, precision_rounding=orderpoint.product_uom.rounding) < 0 else 0,
'visibility_days_date': format_date(self.env, replenishment_report.orderpoint_id.lead_days_date + relativedelta(days=orderpoint.visibility_days))
})
@api.depends('orderpoint_id')
def _compute_json_replenishment_history(self):
for replenishment_report in self:
replenishment_history = []
today = fields.Datetime.now()
first_month = subtract(today, months=2)
date_from, dummy = get_month(first_month)
dummy, date_to = get_month(today)
domain = [
('product_id', '=', replenishment_report.product_id.id),
('date', '>=', date_from),
('date', '<=', datetime.combine(date_to, time.max)),
('state', '=', 'done'),
('company_id', '=', replenishment_report.orderpoint_id.company_id.id)
]
quantity_by_month_out = self.env['stock.move']._read_group(
AND([domain, [('location_dest_id.usage', '=', 'customer')]]),
['date:month'], ['product_qty:sum'])
quantity_by_month_returned = dict(self.env['stock.move']._read_group(
AND([domain, [('location_id.usage', '=', 'customer')]]),
['date:month'], ['product_qty:sum']))
locale = get_lang(self.env).code
fmt = models.READ_GROUP_DISPLAY_FORMAT['month']
for month, product_qty_sum in quantity_by_month_out:
replenishment_history.append({
'name': babel.dates.format_datetime(month, format=fmt, locale=locale),
'quantity': product_qty_sum - quantity_by_month_returned.get(month, 0),
'uom_name': replenishment_report.product_id.uom_id.display_name,
})
replenishment_report.json_replenishment_history = dumps({
'template': 'stock.replenishmentHistory',
'replenishment_history': replenishment_history
})
class StockReplenishmentOption(models.TransientModel):
_name = 'stock.replenishment.option'
_description = 'Stock warehouse replenishment option'
route_id = fields.Many2one('stock.route')
product_id = fields.Many2one('product.product')
replenishment_info_id = fields.Many2one('stock.replenishment.info')
location_id = fields.Many2one('stock.location', related='warehouse_id.lot_stock_id')
warehouse_id = fields.Many2one('stock.warehouse', related='route_id.supplier_wh_id')
uom = fields.Char(related='product_id.uom_name')
qty_to_order = fields.Float(related='replenishment_info_id.qty_to_order')
free_qty = fields.Float(compute='_compute_free_qty')
lead_time = fields.Char(compute='_compute_lead_time')
warning_message = fields.Char(compute='_compute_warning_message')
@api.depends('product_id', 'route_id')
def _compute_free_qty(self):
for record in self:
record.free_qty = record.product_id.with_context(location=record.location_id.id).free_qty
@api.depends('replenishment_info_id')
def _compute_lead_time(self):
for record in self:
rule = self.env['procurement.group']._get_rule(record.product_id, record.location_id, {
'route_ids': record.route_id,
'warehouse_id': record.warehouse_id,
})
delay = rule._get_lead_days(record.product_id)[0]['total_delay'] if rule else 0
record.lead_time = _("%s days", delay)
@api.depends('warehouse_id', 'free_qty', 'uom', 'qty_to_order')
def _compute_warning_message(self):
self.warning_message = ''
for record in self:
if record.free_qty < record.qty_to_order:
record.warning_message = _(
'%(warehouse)s can only provide %(free_qty)s %(uom)s, while the quantity to order is %(qty_to_order)s %(uom)s.',
warehouse=record.warehouse_id.name,
free_qty=record.free_qty,
uom=record.uom,
qty_to_order=record.qty_to_order
)
def select_route(self):
if self.free_qty < self.qty_to_order:
return {
"type": "ir.actions.act_window",
"res_model": "stock.replenishment.option",
"res_id": self.id,
"views": [[self.env.ref('stock.replenishment_option_warning_view').id, "form"]],
"target": "new",
"name": _("Quantity available too low")
}
return self.order_all()
def order_avbl(self):
self.replenishment_info_id.orderpoint_id.route_id = self.route_id
self.replenishment_info_id.orderpoint_id.qty_to_order = self.free_qty
return {'type': 'ir.actions.act_window_close'}
def order_all(self):
self.replenishment_info_id.orderpoint_id.route_id = self.route_id
return {'type': 'ir.actions.act_window_close'}