diff --git a/jikimo_hide_options/models/models.py b/jikimo_hide_options/models/models.py index 84aee828..8f30543c 100644 --- a/jikimo_hide_options/models/models.py +++ b/jikimo_hide_options/models/models.py @@ -324,4 +324,4 @@ def unlink(self): BaseModel._create = _create -BaseModel.unlink = unlink \ No newline at end of file +# BaseModel.unlink = unlink \ No newline at end of file diff --git a/sf_base/models/common.py b/sf_base/models/common.py index 65dfe13d..c2858b95 100644 --- a/sf_base/models/common.py +++ b/sf_base/models/common.py @@ -88,6 +88,7 @@ class MrsProductionProcess(models.Model): code = fields.Char("编码") name = fields.Char('名称') remark = fields.Text("备注") + sequence = fields.Integer('排序') # processing_order_ids = fields.One2many('sf.processing.order', 'production_process_id', string='工序') partner_process_ids = fields.Many2many('res.partner', 'process_ids', '加工工厂') active = fields.Boolean('有效', default=True) @@ -96,7 +97,7 @@ class MrsProductionProcess(models.Model): # workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_process', required=True) processing_day = fields.Float('加工天数/d') travel_day = fields.Float('路途天数/d') - + sequence = fields.Integer('排序') # class MrsProcessingTechnology(models.Model): # _name = 'sf.processing.technology' @@ -148,6 +149,7 @@ class MrsProductionProcessParameter(models.Model): processing_day = fields.Float('加工天数/d') travel_day = fields.Float('路途天数/d') active = fields.Boolean('有效', default=True) + processing_mm = fields.Char('加工厚度/mm') def name_get(self): result = [] diff --git a/sf_base/models/tool_base_new.py.rej b/sf_base/models/tool_base_new.py.rej deleted file mode 100644 index 6db1f28a..00000000 --- a/sf_base/models/tool_base_new.py.rej +++ /dev/null @@ -1,10 +0,0 @@ -diff a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py (rejected hunks) -@@ -108,6 +108,4 @@ - cutting_speed_ids = fields.One2many('sf.cutting.speed', 'standard_library_id', string='切削速度Vc') -- feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz', -- domain=[('cutting_speed', '!=', False)]) -- feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz', -- domain=[('cutting_speed', '!=', False)]) -+ feed_per_tooth_ids = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz') -+ feed_per_tooth_ids_3 = fields.One2many('sf.feed.per.tooth', 'standard_library_id', '每齿走刀量fz') - diff --git a/sf_base/views/common_view.xml b/sf_base/views/common_view.xml index 533a3e04..334eaa32 100644 --- a/sf_base/views/common_view.xml +++ b/sf_base/views/common_view.xml @@ -16,7 +16,7 @@ sf.production.process.parameter -
+

@@ -33,11 +33,12 @@ + - + @@ -52,7 +53,7 @@ - + @@ -140,7 +141,7 @@ sf.production.process.category - + @@ -163,7 +164,8 @@ sf.production.process - + + @@ -174,7 +176,7 @@ sf.production.process - +

@@ -192,11 +194,11 @@ - - - + + + - + diff --git a/sf_bf_connect/models/process_status.py b/sf_bf_connect/models/process_status.py index 9c939edd..d0657a83 100644 --- a/sf_bf_connect/models/process_status.py +++ b/sf_bf_connect/models/process_status.py @@ -31,7 +31,7 @@ class StatusChange(models.Model): res = super(StatusChange, self).action_confirm() # 原有方法执行后,进行额外的操作(如调用外部API) - process_start_time = str(datetime.now()) + process_start_time = datetime.now().strftime('%Y-%m-%d %H:%M:%S') config = self.env['res.config.settings'].get_values() json1 = { 'params': { diff --git a/sf_hr/__init__.py b/sf_hr/__init__.py new file mode 100644 index 00000000..cde864ba --- /dev/null +++ b/sf_hr/__init__.py @@ -0,0 +1,3 @@ +# -*- coding: utf-8 -*- + +from . import models diff --git a/sf_hr/__manifest__.py b/sf_hr/__manifest__.py new file mode 100644 index 00000000..aaf9cfc7 --- /dev/null +++ b/sf_hr/__manifest__.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. +{ + 'name': '机企猫智能工厂 员工管理', + 'version': '1.0', + 'summary': '智能工厂员工模块', + 'sequence': 1, + 'category': 'sf', + 'website': 'https://www.sf.jikimo.com', + 'depends': ['hr'], + 'data': [ + 'views/hr_employee.xml', + ], + 'demo': [ + ], + 'qweb': [ + ], + 'license': 'LGPL-3', + 'installable': True, + 'application': False, + 'auto_install': False, +} diff --git a/sf_hr/models/__init__.py b/sf_hr/models/__init__.py new file mode 100644 index 00000000..633f8661 --- /dev/null +++ b/sf_hr/models/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- + diff --git a/sf_hr/security/ir.model.access.csv b/sf_hr/security/ir.model.access.csv new file mode 100644 index 00000000..e69de29b diff --git a/sf_hr/views/hr_employee.xml b/sf_hr/views/hr_employee.xml new file mode 100644 index 00000000..a3db9076 --- /dev/null +++ b/sf_hr/views/hr_employee.xml @@ -0,0 +1,15 @@ + + + + + employee_form + hr.employee + + + + 1 + + + + + \ No newline at end of file diff --git a/sf_machine_connect/controllers/controllers.py b/sf_machine_connect/controllers/controllers.py index 946bb78c..96708dd8 100644 --- a/sf_machine_connect/controllers/controllers.py +++ b/sf_machine_connect/controllers/controllers.py @@ -163,6 +163,7 @@ class Sf_Dashboard_Connect(http.Controller): # 停机时间:关机时间 - 运行时间 # 停机时长:关机时间 - 初次上线时间 'img': f'data:image/png;base64,{machine_data.machine_tool_picture.decode("utf-8")}', + 'equipment_type': machine_data.category_id.name, }) return json.dumps(res) @@ -172,49 +173,6 @@ class Sf_Dashboard_Connect(http.Controller): res['message'] = '前端请求机床数据失败,原因:%s' % e return json.JSONEncoder().encode(res) - # @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, - # cors="*") - # def logs_list(self, **kw): - # """ - # 拿到日志数据返回给大屏展示 - # :param kw: - # :return: - # """ - # res = {'status': 1, 'message': '成功', 'data': []} - # logging.info('前端请求日志数据的参数为:%s' % kw) - # - # try: - # # 获取请求的日志数据 - # logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo() - # # 获取请求的机床数据 - # machine_list = ast.literal_eval(kw['machine_list']) - # begin_time_str = kw['begin_time'].strip('"') - # end_time_str = kw['end_time'].strip('"') - # - # begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') - # end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') - # - # print('begin_time: %s' % begin_time) - # for item in machine_list: - # log_datas = logs_obj.search( - # [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)]) - # print('log_datas: %s' % log_datas) - # for log_data in log_datas: - # res['data'].append({ - # 'equipment_code': log_data.equipment_code, - # 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'), - # 'state': log_data.state - # - # }) - # - # return json.JSONEncoder().encode(res) - # - # except Exception as e: - # logging.info('前端请求日志数据失败,原因:%s' % e) - # res['status'] = -1 - # res['message'] = '前端请求日志数据失败,原因:%s' % e - # return json.JSONEncoder().encode(res) - @http.route('/api/logs/list', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") def logs_list(self, **kw): """ @@ -226,9 +184,9 @@ class Sf_Dashboard_Connect(http.Controller): logging.info('前端请求日志数据的参数为:%s' % kw) try: - # 获取请求的日志数据 - logs_obj = request.env['maintenance.equipment.oee.log.detail'].sudo() - # 获取请求的机床数据 + # 连接数据库 + conn = psycopg2.connect(**db_config) + cur = conn.cursor() machine_list = ast.literal_eval(kw['machine_list']) begin_time_str = kw['begin_time'].strip('"') end_time_str = kw['end_time'].strip('"') @@ -239,19 +197,25 @@ class Sf_Dashboard_Connect(http.Controller): print('begin_time: %s' % begin_time) for item in machine_list: - log_datas = logs_obj.search( - [('equipment_code', '=', item), ('time', '>=', begin_time), ('time', '<=', end_time)]) - print('log_datas: %s' % log_datas) + sql = ''' + SELECT time, device_state, program_name + FROM device_data + WHERE device_name = %s AND time >= %s AND time <= %s + ORDER BY time ASC; + ''' + # 执行SQL命令,使用参数绑定 + cur.execute(sql, (item, begin_time, end_time)) + results = cur.fetchall() # 将数据按照 equipment_code 进行分组 if item not in res['data']: res['data'][item] = [] - for log_data in log_datas: + for result in results: res['data'][item].append({ - 'time': log_data.time.strftime('%Y-%m-%d %H:%M:%S'), - 'state': log_data.state, - 'production_name': log_data.production_name, + 'time': result[0].strftime('%Y-%m-%d %H:%M:%S'), + 'state': result[1], + 'production_name': result[2], }) return json.dumps(res) # 注意使用 json.dumps 而不是直接用 json.JSONEncoder().encode() @@ -623,7 +587,7 @@ class Sf_Dashboard_Connect(http.Controller): # 查询pg库来获得待机次数 @http.route('/api/IdleAlarmCount', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") - def idle_count(self, **kw): + def idle_alarm_count(self, **kw): """ 查询设备的待机次数 """ @@ -636,69 +600,50 @@ class Sf_Dashboard_Connect(http.Controller): try: # 获取请求的机床数据 machine_list = ast.literal_eval(kw['machine_list']) - idle_times = [] - idle_dict = {} - + total_alarm_time = 0 + alarm_count_num = 0 for item in machine_list: sql = ''' - SELECT idle_start_time,alarm_time,alarm_repair_time FROM device_data WHERE device_name = %s; + SELECT COUNT(*) + FROM ( + SELECT DISTINCT ON (idle_start_time) idle_start_time + FROM device_data + WHERE device_name = %s AND idle_start_time IS NOT NULL + ORDER BY idle_start_time, time + ) subquery; + ''' + + sql2 = ''' + SELECT DISTINCT ON (alarm_time) alarm_time, alarm_repair_time + FROM device_data + WHERE device_name = %s AND alarm_time IS NOT NULL + ORDER BY alarm_time, time; + ''' # 执行SQL命令 cur.execute(sql, (item,)) result = cur.fetchall() - # # print('result', result) - # - # # 将查询结果添加到idle_times列表中 - # idle_times = [row[0] for row in result if row[0] is not None] - # - # # 对结果去重 - # unique_idle_times = set(idle_times) - # # print('unique_idle_times', unique_idle_times) - # - # # 统计去重后的数量 - # idle_count = len(unique_idle_times) - # # idle_dict[item] = idle_count - # - # res['data'][item] = idle_count - - total_alarm_time = 0 - alarm_count = 0 - alarm_time_list = [] - idle_times = [] - alarm_times = [] + print('result========', result) + cur.execute(sql2, (item,)) + result2 = cur.fetchall() + print('result2========', result2) + # for row in result: - idle_start_time = row[0] - alarm_time = row[1] - alarm_repair_time = row[2] - - alarm_time_list.append(alarm_time) # 将时长累加,以秒为单位 - idle_times.append(idle_start_time) - # if alarm_repair_time is not None: - # alarm_times.append(alarm_repair_time) - alarm_times.append(alarm_repair_time) - - # 对结果去重 - unique_total_alarm_time = set(alarm_time_list) - unique_idle_times = set(idle_times) - unique_alarm_times = set(alarm_times) - - # 统计去重后的数量 - idle_count = len(unique_idle_times) - - for alarm_time in unique_total_alarm_time: - if alarm_time is not None: - total_alarm_time += abs(float(alarm_time)) - - alarm_count = len(unique_alarm_times) if unique_alarm_times else 0 - alarm_count = alarm_count if total_alarm_time else 0 - - # 存储待机次数和总待机时长(单位:秒) - res['data'][item] = { - 'idle_count': idle_count, - 'total_alarm_time': total_alarm_time / 3600, # 以秒为单位 - 'alarm_count': alarm_count - } + res['data'][item] = {'idle_count': row[0]} + alarm_count = [] + for row in result2: + alarm_count.append(row[0]) + total_alarm_time += abs(float(row[0])) + if len(list(set(alarm_count))) == 1: + if list(set(alarm_count))[0] is None: + alarm_count_num = 0 + else: + alarm_count_num = 1 + else: + alarm_count_num = len(list(set(alarm_count))) + res['data'][item]['total_alarm_time'] = total_alarm_time / 3600 + res['data'][item]['alarm_count_num'] = alarm_count_num # 返回统计结果 return json.dumps(res) @@ -711,7 +656,7 @@ class Sf_Dashboard_Connect(http.Controller): # 查询pg库来获得异常情况 @http.route('/api/alarm/logs', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") - def idle_count(self, **kw): + def alarm_logs(self, **kw): """ 查询设备的异常情况 """ @@ -763,3 +708,110 @@ class Sf_Dashboard_Connect(http.Controller): finally: cur.close() conn.close() + + # 设备oee + @http.route('/api/OEE', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + def OEE(self, **kw): + """ + 获取产线等oee + """ + res = {'status': 1, 'message': '成功', 'data': {}} + logging.info('前端请求oee数据的参数为:%s' % kw) + + try: + count_oee = 1 + workcenter_obj = request.env['mrp.workcenter'].sudo() + workcenter_list = ast.literal_eval(kw['workcenter_list']) + print('workcenter_list: %s' % workcenter_list) + for line in workcenter_list: + res['data'][line] = workcenter_obj.search([('name', '=', line)]).oee + count_oee *= workcenter_obj.search([('name', '=', line)]).oee + res['data']['综合oee'] = count_oee / 1000000 + except Exception as e: + print(f"An error occurred: {e}") + + return json.dumps(res) + + # # 查询某段时间的设备oee + # @http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + # def OEEByTime(self, **kw): + # """ + # 获取某段时间的oee + # """ + # res = {'status': 1, 'message': '成功', 'data': {}} + # logging.info('前端请求获取某段时间的oee的参数为:%s' % kw) + # workcenter_list = ast.literal_eval(kw['workcenter_list']) + # begin_time_str = kw['begin_time'].strip('"') + # end_time_str = kw['end_time'].strip('"') + # begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') + # end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') + # print('workcenter_list: %s' % workcenter_list) + # # 连接数据库 + # conn = psycopg2.connect(**db_config) + # cur = conn.cursor() + # # 查询并计算OEE平均值 + # oee_data = {} + # for workcenter in workcenter_list: + # cur.execute(""" + # SELECT AVG(oee) as avg_oee + # FROM oee_data + # WHERE workcenter_name = %s + # AND time BETWEEN %s AND %s + # """, (workcenter, begin_time, end_time)) + # + # result = cur.fetchone() + # avg_oee = result[0] if result else 0.0 + # oee_data[workcenter] = avg_oee + # + # # 返回数据 + # res['data'] = oee_data + # return json.dumps(res) + + @http.route('/api/OEEByTime', type='http', auth='public', methods=['GET', 'POST'], csrf=False, cors="*") + def OEEByTime(self, **kw): + """ + 获取某段时间的oee,根据用户指定的时间单位(day或hour)返回对应的平均值 + """ + res = {'status': 1, 'message': '成功', 'data': {}} + logging.info('前端请求获取某段时间的oee的参数为:%s' % kw) + + # 获取并解析参数 + workcenter_list = ast.literal_eval(kw['workcenter_list']) + begin_time_str = kw['begin_time'].strip('"') + end_time_str = kw['end_time'].strip('"') + time_unit = kw.get('time_unit', 'day') # 默认单位为天 + begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') + end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') + + # 连接数据库 + conn = psycopg2.connect(**db_config) + cur = conn.cursor() + + # 根据时间单位选择不同的时间格式 + if time_unit == 'hour': + time_format = 'YYYY-MM-DD HH24:00:00' + else: # 默认为'day' + time_format = 'YYYY-MM-DD' + + # 查询并计算OEE平均值 + oee_data = {} + for workcenter in workcenter_list: + cur.execute(f""" + SELECT to_char(time, '{time_format}') as time_unit, AVG(oee) as avg_oee + FROM oee_data + WHERE workcenter_name = %s + AND time BETWEEN %s AND %s + GROUP BY time_unit + ORDER BY time_unit + """, (workcenter, begin_time, end_time)) + + results = cur.fetchall() + oee_data[workcenter] = {row[0]: row[1] for row in results} + + # 关闭数据库连接 + cur.close() + conn.close() + + # 返回数据 + res['data'] = oee_data + return json.dumps(res) diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py index e84b34cc..35501367 100644 --- a/sf_manufacturing/__manifest__.py +++ b/sf_manufacturing/__manifest__.py @@ -32,6 +32,7 @@ 'views/model_type_view.xml', 'views/agv_setting_views.xml', 'views/sf_maintenance_equipment.xml', + 'views/res_config_settings_views.xml', ], 'assets': { diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index b93d613e..7d6aa8ae 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -10,3 +10,4 @@ from . import res_user from . import production_line_base from . import agv_setting from . import agv_scheduling +from . import res_config_setting diff --git a/sf_manufacturing/models/agv_scheduling.py b/sf_manufacturing/models/agv_scheduling.py index 237038ba..d232c643 100644 --- a/sf_manufacturing/models/agv_scheduling.py +++ b/sf_manufacturing/models/agv_scheduling.py @@ -231,7 +231,9 @@ class AgvScheduling(models.Model): rec.site_state = '空闲' rec.end_site_id = agv_task_route.end_site_id.id rec.agv_route_id = agv_task_route.id - # rec._delivery_avg() + is_agv_task_dispatch = self.env['ir.config_parameter'].sudo().get_param('is_agv_task_dispatch') + if is_agv_task_dispatch: + rec._delivery_avg() # 更新接驳站状态 rec.env['sf.agv.site'].update_site_state({rec.end_site_id.name: '占用'}, False) diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index cf4a6dce..c67fd105 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -123,9 +123,39 @@ class MrpProduction(models.Model): # 上传零件图纸 part_drawing = fields.Binary('零件图纸') - manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) + @api.depends('product_id.manual_quotation') + def _compute_manual_quotation(self): + for item in self: + item.manual_quotation = item.product_id.manual_quotation + + manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True) is_scrap = fields.Boolean('是否报废', default=False) is_remanufacture = fields.Boolean('是否重新制造', default=False) + remanufacture_count = fields.Integer("重新制造订单数量", compute='_compute_remanufacture_production_ids') + remanufacture_production_id = fields.Many2one('mrp.production', string='') + + @api.depends('remanufacture_production_id') + def _compute_remanufacture_production_ids(self): + for production in self: + if production.remanufacture_production_id: + remanufacture_production = self.env['mrp.production'].search( + [('id', '=', production.remanufacture_production_id.id)]) + if remanufacture_production: + production.remanufacture_count = len(remanufacture_production) + else: + production.remanufacture_count = 0 + + def action_view_remanufacture_productions(self): + self.ensure_one() + mrp_production = self.env['mrp.production'].search( + [('id', '=', self.remanufacture_production_id.id)]) + action = { + 'res_model': 'mrp.production', + 'type': 'ir.actions.act_window', + 'view_mode': 'form', + 'res_id': mrp_production.id, + } + return action @api.depends( 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', 'tool_state', @@ -446,44 +476,45 @@ class MrpProduction(models.Model): # self.env['mrp.workorder'].json_workorder_str(k, production, route)) # 表面工艺工序 # 获取表面工艺id - if production.product_id.model_process_parameters_ids: - logging.info('model_process_parameters_ids:%s' % production.product_id.model_process_parameters_ids) - surface_technics_arr = [] - # 工序id - route_workcenter_arr = [] - for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: - surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id) - route_workcenter_arr.append(item.route_workcenter_id.id) - if surface_technics_arr: - production_process_category = self.env['sf.production.process.category'].search( - [('production_process_ids.id', 'in', surface_technics_arr)], - order='sequence asc' - ) - # 用filter刷选表面工艺id'是否存在工艺类别对象里 - if production_process_category: - for p in production_process_category: - logging.info('production_process_category:%s' % p.name) - production_process = p.production_process_ids.filtered( - lambda pp: pp.id in surface_technics_arr) - if production_process: - process_parameter = production.product_id.model_process_parameters_ids.filtered( - lambda pm: pm.process_id.id == production_process.id) - if process_parameter: - # 产品为表面工艺服务的供应商 - product_production_process = self.env['product.template'].search( - [('server_product_process_parameters_id', '=', process_parameter.id)]) - if product_production_process: - route_production_process = self.env[ - 'mrp.routing.workcenter'].search( - [('surface_technics_id', '=', production_process.id), - ('id', 'in', route_workcenter_arr)]) - if route_production_process: - workorders_values.append( - self.env[ - 'mrp.workorder']._json_workorder_surface_process_str( - production, route_production_process, - process_parameter, - product_production_process.seller_ids[0].partner_id.id)) + # 工序id + surface_technics_arr = [] + route_workcenter_arr = [] + for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: + if item.route_workcenter_id.surface_technics_id.id: + for process_param in production.product_id.model_process_parameters_ids: + logging.info('process_param:%s%s' % (process_param.id, process_param.name)) + if item.route_workcenter_id.surface_technics_id == process_param.process_id: + logging.info( + 'surface_technics_id:%s%s' % (item.route_workcenter_id.surface_technics_id.id, + item.route_workcenter_id.surface_technics_id.name)) + surface_technics_arr.append(item.route_workcenter_id.surface_technics_id.id) + route_workcenter_arr.append(item.route_workcenter_id.id) + if surface_technics_arr: + production_process = self.env['sf.production.process'].search( + [('id', 'in', surface_technics_arr)], + order='sequence asc' + ) + for p in production_process: + logging.info('production_process:%s' % p.name) + # if production_process: + process_parameter = production.product_id.model_process_parameters_ids.filtered( + lambda pm: pm.process_id.id == p.id) + if process_parameter: + # 产品为表面工艺服务的供应商 + product_production_process = self.env['product.template'].search( + [('server_product_process_parameters_id', '=', process_parameter.id)]) + if product_production_process: + route_production_process = self.env[ + 'mrp.routing.workcenter'].search( + [('surface_technics_id', '=', p.id), + ('id', 'in', route_workcenter_arr)]) + if route_production_process: + workorders_values.append( + self.env[ + 'mrp.workorder']._json_workorder_surface_process_str( + production, route_production_process, + process_parameter, + product_production_process.seller_ids[0].partner_id.id)) elif production.product_id.categ_id.type == '坯料': embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( [('embryo_model_type_id', '=', production.product_id.embryo_model_type_id.id)], @@ -850,8 +881,8 @@ class MrpProduction(models.Model): 'target': 'new', 'context': { 'default_production_id': self.id, - 'default_programming_state': '编程中' if cloud_programming[ - 'programming_state'] != '已下发' else '已下发', + 'default_reprogramming_num': cloud_programming['reprogramming_num'], + 'default_programming_states': cloud_programming['programming_state'], 'default_is_reprogramming': True if cloud_programming['programming_state'] in ['已下发'] else False } } @@ -998,32 +1029,19 @@ class MrpProduction(models.Model): move_values = {'product_description_variants': '', 'date_planned': datetime.now(), 'date_deadline': datetime.now(), - # 'move_dest_ids': self.env['stock.move'].search([('id', '=', move.id)]), 'move_dest_ids': move, - 'group_id': False, + 'group_id': move.group_id, 'route_ids': [], 'warehouse_id': self.warehouse_id, 'priority': 0, 'orderpoint_id': False, 'product_packaging_id': False} - rule = self.env['stock.rule'].search( - [('action', '=', 'pull'), ('procure_method', '=', 'mts_else_mto'), ( - 'location_dest_id', '=', self.env['stock.location'].search([('parent_path', '=', '2/5/')]).id), - ('location_src_id', '=', self.env['stock.location'].search( - [('barcode', '=', 'CP')]).id)]) - # origin = move._prepare_procurement_origin() procurement_requests.append(self.env['procurement.group'].Procurement( move.product_id, 1.0, move.product_uom, - self.env['stock.location'].search([('barcode', '=', 'CP')]), - rule and rule.name or "/", + move.location_id, move.rule_id and move.rule_id.name or "/", sale_order.name, move.company_id, move_values)) self.env['procurement.group'].run(procurement_requests, raise_user_error=not self.env.context.get('from_orderpoint')) - # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) - # productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ - # ( - # p.move_dest_ids.procure_method != 'make_to_order' and - # not p.move_raw_ids and not p.workorder_ids)).action_confirm() productions = self.env['mrp.production'].sudo().search( [('origin', '=', self.origin)], order='id desc', limit=1) move = self.env['stock.move'].search([('origin', '=', productions.name)], order='id desc') @@ -1051,8 +1069,29 @@ class MrpProduction(models.Model): 'picking_id': sfp_move.picking_id.id, 'picking_type_id': sfp_move.picking_type_id.id, 'production_id': False}) productions.write({'programming_no': self.programming_no, 'is_remanufacture': True}) - productions.procurement_group_id.mrp_production_ids.move_dest_ids.write( - {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])}) + # productions.procurement_group_id.mrp_production_ids.move_dest_ids.write( + # {'group_id': self.env['procurement.group'].search([('name', '=', sale_order.name)])}) + stock_picking = None + pc_picking = self.env['stock.picking'].search( + [('origin', '=', productions.name), ('name', 'ilike', 'WH/PC/')]) + stock_picking = pc_picking + int_picking = self.env['stock.picking'].search( + [('origin', '=', productions.name), ('name', 'ilike', 'WH/INT/')]) + stock_picking |= int_picking + for pick in stock_picking: + if pick.move_ids: + product_type_id = pick.move_ids[0].product_id.categ_id + if product_type_id.name == '坯料': + location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) + if not location_id: + logging.info(f'没有搜索到【坯料存货区】: {location_id}') + break + if pick.picking_type_id.name == '内部调拨': + if pick.location_dest_id.product_type != product_type_id: + pick.location_dest_id = location_id.id + elif pick.picking_type_id.name == '生产发料': + if pick.location_id.product_type != product_type_id: + pick.location_id = location_id.id scarp_process_parameter_workorder = self.env['mrp.workorder'].search( [('surface_technics_parameters_id', '!=', False), ('production_id', '=', self.id), ('is_subcontract', '=', True)]) diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py index 461ef6ad..03597980 100644 --- a/sf_manufacturing/models/mrp_workcenter.py +++ b/sf_manufacturing/models/mrp_workcenter.py @@ -41,6 +41,7 @@ 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) time_start = fields.Float('Setup Time', tracking=True) time_stop = fields.Float('Cleanup Time', tracking=True) diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index 0c8dad5f..8bce60d6 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -60,7 +60,12 @@ class ResMrpWorkOrder(models.Model): default='pending', copy=False, readonly=True, recursive=True, index=True, tracking=True) # state = fields.Selection(selection_add=[('to be detected', "待检测"), ('rework', '返工')], tracking=True) - manual_quotation = fields.Boolean('人工编程', default=False, readonly=True) + @api.depends('production_id.manual_quotation') + def _compute_manual_quotation(self): + for item in self: + item.manual_quotation = item.production_id.manual_quotation + + manual_quotation = fields.Boolean('人工编程', default=False, compute=_compute_manual_quotation, store=True) def _compute_working_users(self): super()._compute_working_users() @@ -1162,10 +1167,10 @@ class ResMrpWorkOrder(models.Model): def button_finish(self): for record in self: if record.routing_type == '装夹预调': - if not record.material_center_point and record.X_deviation_angle > 0: - raise UserError("请对前置三元检测定位参数进行计算定位") if not record.rfid_code and record.is_rework is False: raise UserError("请扫RFID码进行绑定") + if not record.material_center_point or record.X_deviation_angle <= 0: + raise UserError("请对前置三元检测定位参数进行计算定位") record.process_state = '待加工' # record.write({'process_state': '待加工'}) record.production_id.process_state = '待加工' @@ -1265,15 +1270,14 @@ class ResMrpWorkOrder(models.Model): # 解绑托盘 def unbind_tray(self): - self.write({ + self.production_id.workorder_ids.write({ 'rfid_code': False, 'tray_serial_number': False, 'tray_product_id': False, 'tray_brand_id': False, 'tray_type_id': False, 'tray_model_id': False, - 'is_trayed': False - }) + 'is_trayed': False}) # 将FTP的检测报告文件下载到临时目录 def download_reportfile_tmp(self, workorder, reportpath): diff --git a/sf_manufacturing/models/res_config_setting.py b/sf_manufacturing/models/res_config_setting.py new file mode 100644 index 00000000..d6b029c6 --- /dev/null +++ b/sf_manufacturing/models/res_config_setting.py @@ -0,0 +1,22 @@ +from odoo import models, fields, api + + +class ResConfigSettings(models.TransientModel): + _inherit = 'res.config.settings' + + is_agv_task_dispatch = fields.Boolean('是否下发AGV任务', default=False) + + @api.model + def get_values(self): + values = super(ResConfigSettings, self).get_values() + config = self.env['ir.config_parameter'].sudo() + is_agv_task_dispatch = config.get_param('is_agv_task_dispatch') + values.update( + is_agv_task_dispatch=is_agv_task_dispatch, + ) + return values + + def set_values(self): + super(ResConfigSettings, self).set_values() + config = self.env['ir.config_parameter'].sudo() + config.set_param("is_agv_task_dispatch", self.is_agv_task_dispatch or False) diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index f53d7056..27ce925a 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -169,7 +169,6 @@ class StockRule(models.Model): else: forecasted_qties_by_loc[rule.location_src_id][procurement.product_id.id] -= qty_needed procure_method = 'make_to_stock' - move_values = rule._get_stock_move_values(*procurement) move_values['procure_method'] = procure_method moves_values_by_company[procurement.company_id.id].append(move_values) @@ -177,13 +176,10 @@ class StockRule(models.Model): for company_id, moves_values in moves_values_by_company.items(): # create the move as SUPERUSER because the current user may not have the rights to do it (mto product # launched by a sale for example) - logging.info(moves_values) moves = self.env['stock.move'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( moves_values) - logging.info(moves) # Since action_confirm launch following procurement_group we should activate it. moves._action_confirm() - return True @api.model diff --git a/sf_manufacturing/static/src/js/customRFID.js b/sf_manufacturing/static/src/js/customRFID.js index eaa07d8a..cb58b3ea 100644 --- a/sf_manufacturing/static/src/js/customRFID.js +++ b/sf_manufacturing/static/src/js/customRFID.js @@ -3,13 +3,34 @@ $(document).off('keydown') $(document).on('keydown', 'body.o_web_client', function (e) { setTimeout(() => { RFID = '' - }, 200) + }, 200) if(e.key == 'Enter' && e.keyCode == 13 || e.key == 'Tab' && e.keyCode == 9){ + + let fieldValue1 = $('[name="routing_type"]'); + console.log('字段值:', fieldValue1.text()); console.log(RFID) - if(!RFID || RFID.length <= 3) return; - $('[name="button_start"]').trigger('click') - RFID = '' + let fieldValue2 = $('[name="rfid_code"]'); + console.log('字段值2:', fieldValue2.text()); + // if(!RFID || RFID.length <= 3) return; + // $('[name="button_start"]').trigger('click') + // setTimeout(() => { + // $('.o_dialog .modal-footer .btn-primary').trigger('click') + // }, 50) + // RFID = '' + // return; + + // fieldValue2.val() === '') + // 检查字段值是否等于“装夹预调” + if (fieldValue1.text() === '装夹预调') { + if (!RFID || RFID.length <= 3) return; + $('[name="button_start"]').trigger('click'); + setTimeout(() => { + $('.o_dialog .modal-footer .btn-primary').trigger('click'); + }, 100); + } + + RFID = ''; return; } RFID += e.key diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 99d27576..6fa14a6f 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -127,7 +127,7 @@ confirm="是否确认更新程序" attrs="{'invisible': ['|',('state', '!=', 'rework'),('programming_state', '!=', '已编程未下发')]}"/> + +

AGV参数配置

-
+
diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py index 348d6c79..8e92040b 100644 --- a/sf_sale/models/sale_order.py +++ b/sf_sale/models/sale_order.py @@ -60,7 +60,7 @@ class ReSaleOrder(models.Model): deadline_of_delivery, payments_way, pay_way): now_time = datetime.datetime.now() partner = self.get_customer() - data ={ + data = { 'company_id': company_id.id, 'date_order': now_time, 'name': self.env['ir.sequence'].next_by_code('sale.order', sequence_date=now_time), @@ -79,7 +79,7 @@ class ReSaleOrder(models.Model): if not isinstance(deadline_of_delivery, str): data.update({'deadline_of_delivery': deadline_of_delivery}) else: - if deadline_of_delivery!="False": + if deadline_of_delivery != "False": data.update({'deadline_of_delivery': deadline_of_delivery}) order_id = self.env['sale.order'].sudo().create(data) @@ -258,7 +258,7 @@ class RePurchaseOrder(models.Model): if is_exist is False: purchase_order = self.env['purchase.order'].search( [('state', '=', 'draft'), ('origin', '=', ','.join(production_process))]) - if not purchase_order: + if not purchase_order or len(purchase_order) >= 1: self.env['purchase.order'].sudo().create({ 'partner_id': server_product.seller_ids.partner_id.id, 'origin': ','.join(production_process), diff --git a/sf_sale/views/sale_order_view.xml b/sf_sale/views/sale_order_view.xml index d5af8ebd..534d42e4 100644 --- a/sf_sale/views/sale_order_view.xml +++ b/sf_sale/views/sale_order_view.xml @@ -50,6 +50,7 @@ ('check_status', '!=', 'approved'),('state', 'in', ['draft','cancel']),'&','&',('check_status', '=', 'approved'),('state', 'in', ['sale','cancel']),('delivery_status', '!=', False)]} + 拒绝接单 1 @@ -129,6 +130,9 @@ {'readonly': [('state', 'in', ['cancel','sale'])]} + + 拒绝接单 + diff --git a/sf_tool_management/views/tool_base_views.xml b/sf_tool_management/views/tool_base_views.xml index 2fc9d5ba..390fcb15 100644 --- a/sf_tool_management/views/tool_base_views.xml +++ b/sf_tool_management/views/tool_base_views.xml @@ -842,21 +842,21 @@ - - - - - - -