Accept Merge Request #1285: (feature/update_production_line -> develop)
Merge Request: 计划排程优化 Created By: @胡嘉莹 Reviewed By: @胡尧 Approved By: @胡尧 Accepted By: @胡嘉莹 URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1285?initial=true
This commit is contained in:
@@ -1,7 +1,9 @@
|
||||
import datetime
|
||||
from datetime import timedelta, time
|
||||
from collections import defaultdict
|
||||
from odoo import fields, models
|
||||
from odoo import fields, models, api
|
||||
from odoo.addons.resource.models.resource import Intervals
|
||||
from odoo.exceptions import UserError, ValidationError
|
||||
|
||||
|
||||
class ResWorkcenter(models.Model):
|
||||
@@ -41,14 +43,16 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
oee_target = fields.Float(
|
||||
string='OEE Target', help="Overall Effective Efficiency Target in percentage", default=90, tracking=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month', store=True)
|
||||
oee = fields.Float(compute='_compute_oee', help='Overall Equipment Effectiveness, based on the last month',
|
||||
store=True)
|
||||
|
||||
time_start = fields.Float('Setup Time', tracking=True)
|
||||
time_stop = fields.Float('Cleanup Time', tracking=True)
|
||||
costs_hour = fields.Float(string='Cost per hour', help='Hourly processing cost.', default=0.0, tracking=True)
|
||||
|
||||
equipment_status = fields.Selection(
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"), ("封存(报废)", "封存(报废)")],
|
||||
[("正常", "正常"), ("故障停机", "故障停机"), ("计划维保", "计划维保"), ("空闲", "空闲"),
|
||||
("封存(报废)", "封存(报废)")],
|
||||
string="设备状态", related='equipment_id.state')
|
||||
|
||||
# @api.depends('equipment_id')
|
||||
@@ -127,6 +131,102 @@ class ResWorkcenter(models.Model):
|
||||
|
||||
# AGV是否可配送
|
||||
is_agv_scheduling = fields.Boolean(string="AGV所属区域", tracking=True)
|
||||
# 生产线优化
|
||||
available_machine_number = fields.Integer(string="可用机台数量")
|
||||
single_machine_capacity = fields.Float(string="单台小时产能")
|
||||
production_line_hour_capacity = fields.Float(string="生产线小时产能", readonly=True,
|
||||
compute='_compute_production_line_hour_capacity')
|
||||
effective_working_hours_day = fields.Float(string="日有效工作时长", default=0, readonly=True,
|
||||
compute='_compute_effective_working_hours_day')
|
||||
default_capacity = fields.Float(
|
||||
string='生产线日产能', compute='_compute_production_line_day_capacity', readonly=True)
|
||||
|
||||
# 计算生产线日产能
|
||||
@api.depends('production_line_hour_capacity', 'effective_working_hours_day')
|
||||
def _compute_production_line_day_capacity(self):
|
||||
for record in self:
|
||||
record.default_capacity = round(
|
||||
record.production_line_hour_capacity * record.effective_working_hours_day, 2)
|
||||
|
||||
# 计算日有效工作时长
|
||||
@api.depends('resource_calendar_id', 'resource_calendar_id.attendance_ids',
|
||||
'resource_calendar_id.attendance_ids.hour_to', 'resource_calendar_id.attendance_ids.hour_from')
|
||||
def _compute_effective_working_hours_day(self):
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == self.get_current_day_of_week(datetime.datetime.now())]
|
||||
if attendance_ids:
|
||||
for attendance_id in attendance_ids:
|
||||
if attendance_id.hour_from and attendance_id.hour_to:
|
||||
record.effective_working_hours_day += attendance_id.hour_to - attendance_id.hour_from
|
||||
else:
|
||||
record.effective_working_hours_day = 0
|
||||
|
||||
# 获取传入时间是星期几
|
||||
def get_current_day_of_week(self, datetime):
|
||||
day_num = datetime.weekday()
|
||||
return str(day_num)
|
||||
|
||||
# 计算生产线小时产能
|
||||
@api.depends('single_machine_capacity', 'available_machine_number')
|
||||
def _compute_production_line_hour_capacity(self):
|
||||
for record in self:
|
||||
record.production_line_hour_capacity = round(
|
||||
record.single_machine_capacity * record.available_machine_number, 2)
|
||||
|
||||
# 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
def deal_with_workcenter_calendar(self, start_date):
|
||||
start_date = start_date + timedelta(hours=8) # 转换为北京时间
|
||||
for record in self:
|
||||
attendance_ids = [p for p in record.resource_calendar_id.attendance_ids if
|
||||
p.dayofweek == record.get_current_day_of_week(start_date) and self.is_between_times(
|
||||
p.hour_from, p.hour_to, start_date)]
|
||||
return False if not attendance_ids else True
|
||||
|
||||
# 判断传入时间是否在配置的工作中心的工作日历内
|
||||
def is_between_times(self, hour_from, hour_to, start_date):
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_from)
|
||||
start_time = time(integer_part, decimal_part)
|
||||
integer_part, decimal_part = self.get_integer_and_decimal_parts(hour_to)
|
||||
end_time = time(integer_part, decimal_part)
|
||||
return start_time <= start_date.time() <= end_time
|
||||
|
||||
# 获取整数部分和小数部分
|
||||
def get_integer_and_decimal_parts(self, value):
|
||||
integer_part = int(value)
|
||||
decimal_part = value - integer_part
|
||||
return int(integer_part), int(decimal_part)
|
||||
|
||||
# 处理排程是否超过日产能
|
||||
def deal_available_default_capacity(self, date_planned):
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d')
|
||||
date_planned_end = date_planned + timedelta(days=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end), ('state', '!=', 'draft')])
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.default_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
# 处理排程是否超过小时产能
|
||||
def deal_available_single_machine_capacity(self, date_planned):
|
||||
|
||||
date_planned_start = date_planned.strftime('%Y-%m-%d %H:00:00')
|
||||
date_planned_end = date_planned + timedelta(hours=1)
|
||||
date_planned_end = date_planned_end.strftime('%Y-%m-%d %H:00:00')
|
||||
plan_ids = self.env['sf.production.plan'].sudo().search([('date_planned_start', '>=', date_planned_start),
|
||||
('date_planned_start', '<',
|
||||
date_planned_end), ('state', '!=', 'draft')])
|
||||
|
||||
if plan_ids:
|
||||
sum_qty = sum([p.product_qty for p in plan_ids])
|
||||
if sum_qty >= self.single_machine_capacity:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
class ResWorkcenterProductivity(models.Model):
|
||||
_inherit = 'mrp.workcenter.productivity'
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
@@ -25,38 +25,81 @@
|
||||
</record>
|
||||
|
||||
<record id="custom_model_form_view_inherit" model="ir.ui.view">
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<field name="name">custom.model.form.view.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<xpath expr='//form//sheet' position="after">
|
||||
<div class="oe_chatter">
|
||||
<field name="message_follower_ids"/>
|
||||
<field name="message_ids"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="attributes">
|
||||
<attribute name="invisible">1</attribute>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="after">
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" />
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status" />
|
||||
<field name="equipment_image" widget="image" />
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<label for="default_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="default_capacity" string="产线日产能"/>
|
||||
台
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//field[@name='default_capacity'][last()]" position="before">
|
||||
<label for="available_machine_number"/>
|
||||
<div class="o_row">
|
||||
<field name="available_machine_number"/>
|
||||
台
|
||||
</div>
|
||||
<label for="single_machine_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="single_machine_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="production_line_hour_capacity"/>
|
||||
<div class="o_row">
|
||||
<field name="production_line_hour_capacity"/>
|
||||
件/小时
|
||||
</div>
|
||||
<label for="effective_working_hours_day"/>
|
||||
<div class="o_row">
|
||||
<field name="effective_working_hours_day"/>
|
||||
小时
|
||||
</div>
|
||||
</xpath>
|
||||
<!-- <xpath expr='//group[@name="capacity"]//field[@name="default_capacity"])' position="after">-->
|
||||
<!-- <group>-->
|
||||
<!-- <field name="available_machine_number"/>-->
|
||||
<!-- <field name="single_machine_capacity"/>-->
|
||||
<!-- <field name="production_line_hour_capacity"/>-->
|
||||
<!-- <field name="effective_working_hours_day"/>-->
|
||||
|
||||
<!-- </group>-->
|
||||
<!-- </xpath>-->
|
||||
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_workcenter_kanban"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- Desktop view -->
|
||||
<xpath expr='(//field[@name="name"])[1]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//field[@name="name"])[2]' position="after">
|
||||
<field name="equipment_status"/>
|
||||
<field name="equipment_image" widget="image"/>
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<record id="mrp_workcenter_view_kanban_inherit_workorder" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.view.kanban.inherit.mrp.workorder</field>
|
||||
@@ -73,11 +116,11 @@
|
||||
|
||||
</xpath>
|
||||
<xpath expr='(//a[@name="unblock"])' position="after">
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
<!-- <div class="czyg">绿色:正常,红色:故障,黄色:下线/暂停</div>-->
|
||||
</xpath>
|
||||
</field>
|
||||
</record>
|
||||
<!-------------------->
|
||||
<!-------------------->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -99,7 +142,7 @@
|
||||
</field>
|
||||
</record>
|
||||
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<!-- 继承原有的看板视图 -->
|
||||
<record id="mrp_workcenter_kanban_inherit1" model="ir.ui.view">
|
||||
<field name="name">mrp.workcenter.kanban.inherit</field>
|
||||
<field name="model">mrp.workcenter</field>
|
||||
@@ -391,9 +434,9 @@
|
||||
<field name="model">mrp.production</field>
|
||||
<field name="inherit_id" ref="mrp.mrp_production_form_view"/>
|
||||
<field name="arch" type="xml">
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<!-- <button name="action_cancel" position="before"> -->
|
||||
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
|
||||
<!-- </button> -->
|
||||
<div name="button_box" position="inside">
|
||||
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
|
||||
attrs="{'invisible': [('maintenance_count', '=', 0)]}"
|
||||
|
||||
@@ -191,7 +191,7 @@ class sf_production_plan(models.Model):
|
||||
|
||||
return num
|
||||
|
||||
def do_production_schedule(self):
|
||||
def do_production_schedule(self, date_planned_start):
|
||||
"""
|
||||
排程方法
|
||||
"""
|
||||
@@ -199,6 +199,10 @@ class sf_production_plan(models.Model):
|
||||
if not record.production_line_id:
|
||||
raise ValidationError("未选择生产线")
|
||||
else:
|
||||
|
||||
is_schedule = self.deal_processing_schedule(date_planned_start)
|
||||
if not is_schedule:
|
||||
raise ValidationError("排程失败")
|
||||
workorder_id_list = record.production_id.workorder_ids.ids
|
||||
if record.production_id:
|
||||
if record.production_id.workorder_ids:
|
||||
@@ -249,6 +253,26 @@ class sf_production_plan(models.Model):
|
||||
'target': 'current', # 跳转的目标窗口,可以是'current'或'new'
|
||||
}
|
||||
|
||||
# 处理是否可排程
|
||||
def deal_processing_schedule(self, date_planned_start):
|
||||
for record in self:
|
||||
workcenter_ids = record.production_line_id.mrp_workcenter_ids
|
||||
if not workcenter_ids:
|
||||
raise UserError('生产线没有配置工作中心')
|
||||
production_lines = workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
|
||||
if not production_lines: # 判断是否配置了自动生产线
|
||||
raise UserError('生产线没有配置自动生产线')
|
||||
if date_planned_start < datetime.now(): # 判断计划开始时间是否小于当前时间
|
||||
raise UserError('计划开始时间不能小于当前时间')
|
||||
if all(not production_line.deal_with_workcenter_calendar(date_planned_start) for production_line in
|
||||
production_lines): # 判断计划开始时间是否在配置的工作中心的工作日历内
|
||||
raise UserError('当前计划开始时间不能预约排程,请在工作时间内排程')
|
||||
if not production_lines.deal_available_default_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线今日没有可排程的资源')
|
||||
if not production_lines.deal_available_single_machine_capacity(date_planned_start): # 判断生产线是否可排程
|
||||
raise UserError('当前计划开始时间不能预约排程,生产线该时间段没有可排程的资源')
|
||||
return True
|
||||
|
||||
def calculate_plan_time_before(self, item, workorder_id_list):
|
||||
"""
|
||||
根据CNC工单的时间去计算之前的其他工单的开始结束时间
|
||||
|
||||
@@ -36,7 +36,7 @@ class Action_Plan_All_Wizard(models.TransientModel):
|
||||
plan_obj = self.env['sf.production.plan'].browse(plan.id)
|
||||
plan_obj.production_line_id = self.production_line_id.id
|
||||
plan.date_planned_start = self.date_planned_start
|
||||
plan_obj.do_production_schedule()
|
||||
plan_obj.do_production_schedule(self.date_planned_start)
|
||||
# plan_obj.state = 'done'
|
||||
print('处理计划:', plan.id, '完成')
|
||||
|
||||
|
||||
Reference in New Issue
Block a user