diff --git a/mrp_workorder/models/mrp_workorder.py b/mrp_workorder/models/mrp_workorder.py index 81be5f2d..01363bb5 100644 --- a/mrp_workorder/models/mrp_workorder.py +++ b/mrp_workorder/models/mrp_workorder.py @@ -27,7 +27,7 @@ class MrpWorkcenter(models.Model): class MrpProductionWorkcenterLine(models.Model): _name = 'mrp.workorder' - _inherit = ['mrp.workorder', 'barcodes.barcode_events_mixin'] + _inherit = ['mrp.workorder', 'barcodes.barcode_events_mixin', 'mail.thread', 'mail.activity.mixin'] quality_point_ids = fields.Many2many('quality.point', compute='_compute_quality_point_ids', store=True) quality_point_count = fields.Integer('Steps', compute='_compute_quality_point_count') @@ -47,14 +47,17 @@ class MrpProductionWorkcenterLine(models.Model): is_last_lot = fields.Boolean('Is Last lot', compute='_compute_is_last_lot') is_first_started_wo = fields.Boolean('Is The first Work Order', compute='_compute_is_last_unfinished_wo') - is_last_unfinished_wo = fields.Boolean('Is Last Work Order To Process', compute='_compute_is_last_unfinished_wo', store=False) + is_last_unfinished_wo = fields.Boolean('Is Last Work Order To Process', compute='_compute_is_last_unfinished_wo', + store=False) lot_id = fields.Many2one(related='current_quality_check_id.lot_id', readonly=False) move_id = fields.Many2one(related='current_quality_check_id.move_id', readonly=False) move_line_id = fields.Many2one(related='current_quality_check_id.move_line_id', readonly=False) move_line_ids = fields.One2many(related='move_id.move_line_ids') - quality_state = fields.Selection(related='current_quality_check_id.quality_state', string="Quality State", readonly=False) + quality_state = fields.Selection(related='current_quality_check_id.quality_state', string="Quality State", + readonly=False) qty_done = fields.Float(related='current_quality_check_id.qty_done', readonly=False) - test_type_id = fields.Many2one('quality.point.test_type', 'Test Type', related='current_quality_check_id.test_type_id') + test_type_id = fields.Many2one('quality.point.test_type', 'Test Type', + related='current_quality_check_id.test_type_id') test_type = fields.Char(related='test_type_id.technical_name') user_id = fields.Many2one(related='current_quality_check_id.user_id', readonly=False) worksheet_page = fields.Integer('Worksheet page') @@ -65,7 +68,8 @@ class MrpProductionWorkcenterLine(models.Model): def _compute_quality_point_ids(self): for workorder in self: quality_points = workorder.operation_id.quality_point_ids - quality_points = quality_points.filtered(lambda qp: not qp.product_ids or workorder.production_id.product_id in qp.product_ids) + quality_points = quality_points.filtered( + lambda qp: not qp.product_ids or workorder.production_id.product_id in qp.product_ids) workorder.quality_point_ids = quality_points @api.depends('operation_id') @@ -91,7 +95,8 @@ class MrpProductionWorkcenterLine(models.Model): @api.depends('check_ids') def _compute_finished_product_check_ids(self): for wo in self: - wo.finished_product_check_ids = wo.check_ids.filtered(lambda c: c.finished_product_sequence == wo.qty_produced) + wo.finished_product_check_ids = wo.check_ids.filtered( + lambda c: c.finished_product_sequence == wo.qty_produced) def write(self, values): res = super().write(values) @@ -138,7 +143,8 @@ class MrpProductionWorkcenterLine(models.Model): self.finished_lot_id = self.env['stock.lot'].create({ 'product_id': self.product_id.id, 'company_id': self.company_id.id, - 'name': self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env['ir.sequence'].next_by_code('stock.lot.serial'), + 'name': self.env['stock.lot']._get_next_serial(self.company_id, self.product_id) or self.env[ + 'ir.sequence'].next_by_code('stock.lot.serial'), }) def _create_subsequent_checks(self): @@ -152,7 +158,7 @@ class MrpProductionWorkcenterLine(models.Model): """ # Create another quality check if necessary next_check = self.current_quality_check_id.next_check_id - if next_check.component_id != self.current_quality_check_id.product_id or\ + if next_check.component_id != self.current_quality_check_id.product_id or \ next_check.point_id != self.current_quality_check_id.point_id: # TODO: manage reservation here @@ -279,7 +285,8 @@ class MrpProductionWorkcenterLine(models.Model): if self.current_quality_check_id: team = self.current_quality_check_id.team_id else: - team = self.env['quality.alert.team'].search(['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1) + team = self.env['quality.alert.team'].search( + ['|', ('company_id', '=', self.company_id.id), ('company_id', '=', False)], limit=1) return { 'type': 'ir.actions.act_window', 'res_model': 'quality.check', @@ -320,7 +327,8 @@ class MrpProductionWorkcenterLine(models.Model): production = wo.production_id move_raw_ids = wo.move_raw_ids.filtered(lambda m: m.state not in ('done', 'cancel')) - move_finished_ids = wo.move_finished_ids.filtered(lambda m: m.state not in ('done', 'cancel') and m.product_id != wo.production_id.product_id) + move_finished_ids = wo.move_finished_ids.filtered( + lambda m: m.state not in ('done', 'cancel') and m.product_id != wo.production_id.product_id) previous_check = self.env['quality.check'] for point in wo.quality_point_ids: # Check if we need a quality control for this point @@ -342,11 +350,13 @@ class MrpProductionWorkcenterLine(models.Model): if point.test_type == 'register_byproducts': moves = move_finished_ids.filtered(lambda m: m.product_id == point.component_id) if not moves: - moves = production.move_finished_ids.filtered(lambda m: not m.operation_id and m.product_id == point.component_id) + moves = production.move_finished_ids.filtered( + lambda m: not m.operation_id and m.product_id == point.component_id) elif point.test_type == 'register_consumed_materials': moves = move_raw_ids.filtered(lambda m: m.product_id == point.component_id) if not moves: - moves = production.move_raw_ids.filtered(lambda m: not m.operation_id and m.product_id == point.component_id) + moves = production.move_raw_ids.filtered( + lambda m: not m.operation_id and m.product_id == point.component_id) else: check = self.env['quality.check'].create(values) previous_check.next_check_id = check @@ -363,8 +373,10 @@ class MrpProductionWorkcenterLine(models.Model): processed_move |= moves # Generate quality checks associated with unreferenced components - moves_without_check = ((move_raw_ids | move_finished_ids) - processed_move).filtered(lambda move: (move.has_tracking != 'none' and not move.raw_material_production_id.use_auto_consume_components_lots) or move.operation_id) - quality_team_id = self.env['quality.alert.team'].search(['|', ('company_id', '=', wo.company_id.id), ('company_id', '=', False)], limit=1).id + moves_without_check = ((move_raw_ids | move_finished_ids) - processed_move).filtered(lambda move: ( + move.has_tracking != 'none' and not move.raw_material_production_id.use_auto_consume_components_lots) or move.operation_id) + quality_team_id = self.env['quality.alert.team'].search( + ['|', ('company_id', '=', wo.company_id.id), ('company_id', '=', False)], limit=1).id for move in moves_without_check: values = { 'production_id': production.id, @@ -412,7 +424,8 @@ class MrpProductionWorkcenterLine(models.Model): backorder = False # Trigger the backorder process if we produce less than expected - if float_compare(self.qty_producing, self.qty_remaining, precision_rounding=self.product_uom_id.rounding) == -1 and self.is_first_started_wo: + if float_compare(self.qty_producing, self.qty_remaining, + precision_rounding=self.product_uom_id.rounding) == -1 and self.is_first_started_wo: backorder = self.production_id._split_productions()[1:] for workorder in backorder.workorder_ids: if workorder.product_tracking == 'serial': @@ -423,7 +436,8 @@ class MrpProductionWorkcenterLine(models.Model): else: if self.operation_id: backorder = (self.production_id.procurement_group_id.mrp_production_ids - self.production_id).filtered( - lambda p: p.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).state not in ('cancel', 'done') + lambda p: p.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).state not in ( + 'cancel', 'done') )[:1] else: index = list(self.production_id.workorder_ids).index(self) @@ -442,7 +456,8 @@ class MrpProductionWorkcenterLine(models.Model): wo.current_quality_check_id._update_component_quantity() if not self.env.context.get('no_start_next'): if self.operation_id: - return backorder.workorder_ids.filtered(lambda wo: wo.operation_id == self.operation_id).open_tablet_view() + return backorder.workorder_ids.filtered( + lambda wo: wo.operation_id == self.operation_id).open_tablet_view() else: index = list(self.production_id.workorder_ids).index(self) return backorder.workorder_ids[index].open_tablet_view() @@ -466,7 +481,8 @@ class MrpProductionWorkcenterLine(models.Model): def open_tablet_view(self): self.ensure_one() - if not self.is_user_working and self.working_state != 'blocked' and self.state in ('ready', 'waiting', 'progress', 'pending'): + if not self.is_user_working and self.working_state != 'blocked' and self.state in ( + 'ready', 'waiting', 'progress', 'pending'): self.button_start() action = self.env["ir.actions.actions"]._for_xml_id("mrp_workorder.tablet_client_action") action['target'] = 'fullscreen' @@ -521,7 +537,8 @@ class MrpProductionWorkcenterLine(models.Model): data = { 'mrp.workorder': self.read(self._get_fields_for_tablet(), load=False)[0], 'quality.check': self.check_ids._get_fields_for_tablet(sorted_check_list), - 'operation': self.operation_id.read(self.operation_id._get_fields_for_tablet())[0] if self.operation_id else {}, + 'operation': self.operation_id.read(self.operation_id._get_fields_for_tablet())[ + 0] if self.operation_id else {}, 'working_state': self.workcenter_id.working_state, 'views': { 'workorder': self.env.ref('mrp_workorder.mrp_workorder_view_form_tablet').id, @@ -553,7 +570,8 @@ class MrpProductionWorkcenterLine(models.Model): return { 'duration': self.duration, - 'position': bisect_left(last30op, self.duration), # which position regarded other workorders ranked by duration + 'position': bisect_left(last30op, self.duration), + # which position regarded other workorders ranked by duration 'quality_score': score, 'show_rainbow': show_rainbow, } diff --git a/mrp_workorder/views/mrp_workorder_views.xml b/mrp_workorder/views/mrp_workorder_views.xml index 37702c00..8c208f19 100644 --- a/mrp_workorder/views/mrp_workorder_views.xml +++ b/mrp_workorder/views/mrp_workorder_views.xml @@ -27,6 +27,13 @@ + +
+ + + +
+
diff --git a/sf_manufacturing/models/agv_setting.py b/sf_manufacturing/models/agv_setting.py index 64af1d90..06f9edba 100644 --- a/sf_manufacturing/models/agv_setting.py +++ b/sf_manufacturing/models/agv_setting.py @@ -2,7 +2,8 @@ import requests import logging import time -from odoo import fields, models +from odoo import fields, models, api +from odoo.exceptions import UserError class AgvSetting(models.Model): @@ -59,11 +60,17 @@ class AgvTaskRoute(models.Model): ('F01', '搬运'), ], string='任务类型', default="F01") route_type = fields.Selection([ ('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型') - start_site_id = fields.Many2one('sf.agv.site', '起点接驳站位置编号') - end_site_id = fields.Many2one('sf.agv.site', '终点接驳站位置编号') + start_site_id = fields.Many2one('sf.agv.site', '起点接驳站') + end_site_id = fields.Many2one('sf.agv.site', '终点接驳站') destination_production_line_id = fields.Many2one('sf.production.line', '目的生产线') active = fields.Boolean('有效', default=True) + @api.constrains('end_site_id') + def _check_end_site_id(self): + if self.end_site_id: + if self.end_site_id == self.start_site_id: + raise UserError("您选择的终点接驳站与起点接驳站重复,请重新选择") + class Center_controlInterfaceLog(models.Model): _name = 'center_control.interface.log' diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 3e155f78..4b9de955 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -53,13 +53,13 @@ class MrpProduction(models.Model): active = fields.Boolean(string='已归档', default=True) programming_no = fields.Char('编程单号') work_state = fields.Char('业务状态') - programming_state = fields.Char('编程状态') + programming_state = fields.Char('编程状态', tracking=True) glb_file = fields.Binary("glb模型文件") - production_line_id = fields.Many2one('sf.production.line', string='生产线') + production_line_id = fields.Many2one('sf.production.line', string='生产线', tracking=True) plan_start_processing_time = fields.Datetime('计划开始加工时间') production_line_state = fields.Selection( [('待上产线', '待上产线'), ('已上产线', '已上产线'), ('已下产线', '已下产线')], - string='上/下产线', default='待上产线') + string='上/下产线', default='待上产线', tracking=True) # 工序状态 # Todo 研究下用法 process_state = fields.Selection([ @@ -338,8 +338,8 @@ class MrpProduction(models.Model): limit=1, order='id asc') if not production_programming.programming_no or (is_fetchcnc is True and scrap_production): # 制造订单报废/返工也需重新编程 - if (is_fetchcnc is True and scrap_production) or ( - is_fetchcnc is False and scrap_production): + if (is_fetchcnc is True and scrap_production is False) or ( + is_fetchcnc is False and scrap_production is False): production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id]), scrap_production) else: diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index b5566f82..7eea98da 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -102,7 +102,7 @@ class ResMrpWorkOrder(models.Model): Z10_axis = fields.Float(default=0) X_deviation_angle = fields.Integer(string="X轴偏差度", default=0) test_results = fields.Selection([("合格", "合格"), ("返工", "返工"), ("报废", "报废")], default='合格', - string="检测结果") + string="检测结果", tracking=True) cnc_ids = fields.One2many("sf.cnc.processing", 'workorder_id', string="CNC加工程序") cmm_ids = fields.One2many("sf.cmm.program", 'workorder_id', string="CMM程序") tray_code = fields.Char(string="托盘编码") @@ -133,7 +133,7 @@ class ResMrpWorkOrder(models.Model): return action supplier_id = fields.Many2one('res.partner', string='外协供应商') - equipment_id = fields.Many2one('maintenance.equipment', string='加工设备') + equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True) is_ok = fields.Boolean(string='是否合格') # 加工人 processing_user_id = fields.Many2one('res.users', string='加工人') @@ -195,15 +195,15 @@ class ResMrpWorkOrder(models.Model): rfid_code_old = fields.Char('RFID码(已解除)') production_line_id = fields.Many2one('sf.production.line', related='production_id.production_line_id', - string='生产线', store=True) + string='生产线', store=True, tracking=True) production_line_state = fields.Selection(related='production_id.production_line_state', - string='上/下产线', store=True) + string='上/下产线', store=True, tracking=True) detection_report = fields.Binary('检测报告', readonly=True) is_remanufacture = fields.Boolean(string='重新生成制造订单', default=False) is_fetchcnc = fields.Boolean(string='重新获取NC程序', default=False) reason = fields.Selection( [("programming", "编程"), ("clamping", "返工"), ("cutter", "刀具"), ("operate computer", "操机"), - ("technology", "工艺"), ("customer redrawing", "客户改图"), ("other", "其他"), ], string="原因") + ("technology", "工艺"), ("customer redrawing", "客户改图"), ("other", "其他"), ], string="原因", tracking=True) detailed_reason = fields.Text('详细原因') @api.onchange('rfid_code') @@ -1318,17 +1318,18 @@ class SfWorkOrderBarcodes(models.Model): class WorkPieceDelivery(models.Model): _name = "sf.workpiece.delivery" + _inherit = ['mail.thread', 'mail.activity.mixin'] _description = '工件配送' - name = fields.Char('单据编号') + name = fields.Char('单据编码') workorder_id = fields.Many2one('mrp.workorder', string='工单', readonly=True) workorder_state = fields.Selection(related='workorder_id.state', string='工单状态') rfid_code = fields.Char(related='workorder_id.rfid_code', string='rfid码', store=True) production_id = fields.Many2one('mrp.production', string='制造订单号', readonly=True) - production_line_id = fields.Many2one('sf.production.line', string='目的生产线') + production_line_id = fields.Many2one('sf.production.line', string='目的生产线', tracking=True) plan_start_processing_time = fields.Datetime('计划开始加工时间', readonly=True) - route_id = fields.Many2one('sf.agv.task.route', '任务路线') + route_id = fields.Many2one('sf.agv.task.route', '任务路线', tracking=True) feeder_station_start_id = fields.Many2one('sf.agv.site', '起点接驳站') feeder_station_destination_id = fields.Many2one('sf.agv.site', '目的接驳站') task_delivery_time = fields.Datetime('任务下发时间') @@ -1337,26 +1338,41 @@ class WorkPieceDelivery(models.Model): [('上产线', '上产线'), ('下产线', '下产线'), ('运送空料架', '运送空料架')], string='类型') delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration') status = fields.Selection( - [('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送')], string='状态', default='待下发') - is_cnc_program_down = fields.Boolean('程序是否下发', default=False) + [('待下发', '待下发'), ('待配送', '待配送'), ('已配送', '已配送')], string='状态', default='待下发', + tracking=True) + is_cnc_program_down = fields.Boolean('程序是否下发', default=False, tracking=True) is_manual_work = fields.Boolean('人工操作', default=False) active = fields.Boolean(string="有效", default=True) @api.model def create(self, vals): - if vals.get('name', '/') == '/' or vals.get('name', '/') is False: - vals['name'] = self.env['ir.sequence'].next_by_code('sf.workpiece.delivery') or '/' - else: + if vals['route_id'] and vals.get('type') is None: vals['type'] = '运送空料架' + else: + if vals.get('name', '/') == '/' or vals.get('name', '/') is False: + vals['name'] = self.env['ir.sequence'].next_by_code('sf.workpiece.delivery') or '/' obj = super(WorkPieceDelivery, self).create(vals) + if obj.type == '运送空料架': + if obj.name is False: + obj.name = "运送空料架路线:%s-%s" % ( + obj.feeder_station_start_id.name, obj.feeder_station_destination_id.name) return obj - @api.constrains('name') - def _check_name(self): + @api.constrains('route_id') + def _check_route_id(self): if self.type == '运送空料架': - wd = self.sudo().search([('name', '=', self.name), ('id', '!=', self.id)]) - if wd: - raise UserError("该名称已存在") + if self.route_id and self.name is False: + route = self.sudo().search( + [('route_id', '=', self.route_id.id), ('id', '!=', self.id), ('name', 'ilike', '运送空料架路线')]) + if route: + raise UserError("该任务路线已存在,请重新选择") + + # @api.constrains('name') + # def _check_name(self): + # if self.type == '运送空料架': + # wd = self.sudo().search([('name', '=', self.name), ('id', '!=', self.id)]) + # if wd: + # raise UserError("该名称已存在") def action_delivery_history(self): return { diff --git a/sf_manufacturing/views/agv_setting_views.xml b/sf_manufacturing/views/agv_setting_views.xml index 9a4bf25e..377bee74 100644 --- a/sf_manufacturing/views/agv_setting_views.xml +++ b/sf_manufacturing/views/agv_setting_views.xml @@ -7,9 +7,9 @@ sf.agv.site - - - + + + @@ -34,12 +34,14 @@ sf.agv.task.route - + - - + + - + @@ -73,7 +75,8 @@ center_control.interface.log - + + diff --git a/sf_manufacturing/views/mrp_workorder_view.xml b/sf_manufacturing/views/mrp_workorder_view.xml index cef2d5d2..083eca14 100644 --- a/sf_manufacturing/views/mrp_workorder_view.xml +++ b/sf_manufacturing/views/mrp_workorder_view.xml @@ -609,7 +609,7 @@ 工件配送 sf.workpiece.delivery - +
@@ -617,6 +617,7 @@ decoration-success="status == '已配送'" decoration-warning="status == '待下发'" decoration-danger="status == '待配送'"/> + @@ -633,13 +634,58 @@ + + 工件配送 + sf.workpiece.delivery + +
+
+ +
+ +
+

+ +

+
+ + + + + + + + + + + + + + + + + +
+
+ + + +
+
+
+
+ 工件配送 sf.workpiece.delivery - - + + + @@ -661,8 +707,10 @@ 工件配送 sf.workpiece.delivery - {'search_default_on_up':1} - tree,search + {'search_default_filter_to_be_issued': 1, + 'search_default_filter_waiting_delivery': 1} + + tree,form [('type','in',['上产线','下产线']),('workorder_state','=','done'),('is_manual_work','=',false)] @@ -675,23 +723,39 @@ sf.workpiece.delivery -
-
- + + + + + +