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:
胡嘉莹
2024-09-09 14:16:31 +08:00
committed by Coding
4 changed files with 212 additions and 45 deletions

View File

@@ -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'

View File

@@ -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>
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<!--&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;&#45;-->
<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)]}"

View File

@@ -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工单的时间去计算之前的其他工单的开始结束时间

View File

@@ -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, '完成')