diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py index 03597980..0a7e1d0e 100644 --- a/sf_manufacturing/models/mrp_workcenter.py +++ b/sf_manufacturing/models/mrp_workcenter.py @@ -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' diff --git a/sf_manufacturing/views/mrp_workcenter_views.xml b/sf_manufacturing/views/mrp_workcenter_views.xml index d0b4bee6..37ff8af5 100644 --- a/sf_manufacturing/views/mrp_workcenter_views.xml +++ b/sf_manufacturing/views/mrp_workcenter_views.xml @@ -6,9 +6,9 @@ mrp.production - - - + + +