diff --git a/jikimo_frontend/static/src/scss/custom_style.scss b/jikimo_frontend/static/src/scss/custom_style.scss index 7f11b5f5..0b6fb8bb 100644 --- a/jikimo_frontend/static/src/scss/custom_style.scss +++ b/jikimo_frontend/static/src/scss/custom_style.scss @@ -526,4 +526,8 @@ div:has(.o_required_modifier) > label::before { // 设置表单页面label文本不换行 .o_form_view .o_group .o_wrap_label .o_form_label { white-space: nowrap; +} +// 修复表格内容覆盖表头bug +.o_list_renderer .o_list_table tbody th { + position: unset; } \ No newline at end of file diff --git a/jikimo_hide_options/models/models.py b/jikimo_hide_options/models/models.py index 72046dca..84aee828 100644 --- a/jikimo_hide_options/models/models.py +++ b/jikimo_hide_options/models/models.py @@ -301,53 +301,27 @@ def unlink(self): # This is used to restrict the access right to unlink a record current_model_id = self.env['ir.model'].sudo().search( [('model', '=', self._name)]).id - # access_right_rec = self.env['access.right'].sudo().search_read( - # [('model_id', '=', current_model_id)], ['model_id', 'is_delete', - # 'groups_id']) - # if access_right_rec and not self.env.is_admin(): - # for rec in access_right_rec: - # group_name = self.env['ir.model.data'].sudo().search([ - # ('model', '=', 'res.groups'), - # ('res_id', '=', rec['groups_id'][0]) - # ]).name - # module_name = self.env['ir.model.data'].sudo().search([ - # ('model', '=', 'res.groups'), - # ('res_id', '=', rec['groups_id'][0]) - # ]).module - # group = module_name + "." + group_name - # if self.env.user.has_group(group): - # if rec['is_delete']: - # raise UserError(_('You are restricted from performing this' - # ' operation. Please contact the' - # ' administrator.')) - # 检查 'access.right' 模型是否存在于环境中 - if 'access.right' in self.env: - # current_model_id = self.env['ir.model'].sudo().search([('model', '=', self._name)]).id - access_right_rec = self.env['access.right'].sudo().search_read( - [('model_id', '=', current_model_id)], ['model_id', 'is_delete', 'groups_id'] - ) - - if access_right_rec and not self.env.is_admin(): - for rec in access_right_rec: - group_data = self.env['ir.model.data'].sudo().search_read( - [('model', '=', 'res.groups'), ('res_id', '=', rec['groups_id'][0])], - ['name', 'module'] - ) - - if group_data: - group_name = group_data[0]['name'] - module_name = group_data[0]['module'] - group_xml_id = f"{module_name}.{group_name}" - - if self.env.user.has_group(group_xml_id) and rec['is_delete']: - raise UserError( - _('You are restricted from performing this operation. Please contact the administrator.')) - else: - # 如果 'access.right' 模型不存在,可以在这里定义备选逻辑 - pass - + access_right_rec = self.env['access.right'].sudo().search_read( + [('model_id', '=', current_model_id)], ['model_id', 'is_delete', + 'groups_id']) + if access_right_rec and not self.env.is_admin(): + for rec in access_right_rec: + group_name = self.env['ir.model.data'].sudo().search([ + ('model', '=', 'res.groups'), + ('res_id', '=', rec['groups_id'][0]) + ]).name + module_name = self.env['ir.model.data'].sudo().search([ + ('model', '=', 'res.groups'), + ('res_id', '=', rec['groups_id'][0]) + ]).module + group = module_name + "." + group_name + if self.env.user.has_group(group): + if rec['is_delete']: + raise UserError(_('You are restricted from performing this' + ' operation. Please contact the' + ' administrator.')) return True BaseModel._create = _create -BaseModel.unlink = unlink +BaseModel.unlink = unlink \ No newline at end of file diff --git a/sf_base/models/tool_base_new.py b/sf_base/models/tool_base_new.py index 7977ed80..98eea36c 100644 --- a/sf_base/models/tool_base_new.py +++ b/sf_base/models/tool_base_new.py @@ -331,7 +331,7 @@ class ToolInventory(models.Model): work_material = fields.Selection([('钢', '钢'), ('铝', '铝')], string='加工材料') life_span = fields.Float('寿命(min)') - tool_groups_id = fields.Many2one('sf.tool.groups', string='刀具组') + tool_groups_id = fields.Many2one('sf.tool.groups', string='刀具组', required=True) active = fields.Boolean('已归档', default=True) diff --git a/sf_base/security/ir.model.access.csv b/sf_base/security/ir.model.access.csv index 09b96baa..ecc572c7 100644 --- a/sf_base/security/ir.model.access.csv +++ b/sf_base/security/ir.model.access.csv @@ -11,8 +11,8 @@ access_sf_machine_control_system,sf_machine_control_system,model_sf_machine_cont access_sf_machine_control_system_admin,sf_machine_control_system_admin,model_sf_machine_control_system,base.group_system,1,1,1,0 access_sf_production_process_group_sale_director,sf_production_process_group_sale_director,model_sf_production_process,sf_base.group_sale_director,1,0,0,0 access_sf_production_process_group_sale_salemanager,sf_production_process_group_sale_salemanager,model_sf_production_process,sf_base.group_sale_salemanager,1,0,0,0 -access_res_partner_category_group_sale_salemanager,res_partner_category_group_sale_salemanager,base.model_res_partner_category,sf_base.group_sale_salemanager,1,0,1,0 -access_res_partner_category_group_sale_director,res_partner_category_group_sale_director,base.model_res_partner_category,sf_base.group_sale_director,1,0,1,0 +access_res_partner_category_group_sale_salemanager,res_partner_category_group_sale_salemanager,base.model_res_partner_category,sf_base.group_sale_salemanager,1,1,1,0 +access_res_partner_category_group_sale_director,res_partner_category_group_sale_director,base.model_res_partner_category,sf_base.group_sale_director,1,1,1,0 access_sf_production_process,sf_production_process,model_sf_production_process,base.group_user,1,1,1,0 access_sf_production_process_admin,sf_production_process_admin,model_sf_production_process,base.group_system,1,1,1,0 access_sf_production_materials,sf_production_materials,model_sf_production_materials,base.group_user,1,1,1,0 diff --git a/sf_bf_connect/models/http.py b/sf_bf_connect/models/http.py index aac8ba38..8b10aa21 100644 --- a/sf_bf_connect/models/http.py +++ b/sf_bf_connect/models/http.py @@ -21,6 +21,7 @@ class Http(models.AbstractModel): def _auth_method_sf_token(cls): # 从headers.environ中获取对方传过来的token,timestamp,加密的校验字符串 datas = request.httprequest.headers.environ + _logger.info('datas:%s' % datas) if 'HTTP_TOKEN' in datas: _logger.info('token:%s' % datas['HTTP_TOKEN']) # 查询密钥 diff --git a/sf_bf_connect/models/jd_eclp.py b/sf_bf_connect/models/jd_eclp.py index e381242a..7879938e 100644 --- a/sf_bf_connect/models/jd_eclp.py +++ b/sf_bf_connect/models/jd_eclp.py @@ -5,7 +5,7 @@ import requests import cpca # from odoo.exceptions import UserError # from odoo.exceptions import ValidationError -from odoo import api, fields, models +from odoo import api, fields, models, _ from odoo.exceptions import ValidationError _logger = logging.getLogger(__name__) @@ -14,7 +14,7 @@ _logger = logging.getLogger(__name__) class JdEclp(models.Model): _inherit = 'stock.picking' - senderNickName = fields.Char(string='寄件工厂简称', required=True, default='MW') + senderNickName = fields.Char(string='寄件工厂简称', required=True, default='XT') # receiverName = fields.Char(string='收件人姓名') # receiverMobile = fields.Char(string='收件人电话') @@ -161,15 +161,25 @@ class JdEclp(models.Model): url2 = config['bfm_url'] + '/api/get/jd/no' response = requests.post(url2, json=json2, data=None) # _logger.info('调用成功2', response.json()['result']['wbNo']) - self.carrier_tracking_ref = response.json()['result']['wbNo'] + self.carrier_tracking_ref = response.json()['result'].get('wbNo') + if not self.carrier_tracking_ref: + raise ValidationError('物流下单未成功,请联系管理员') self.is_bill = True self.logistics_status = '1' - # # 京东物流下单后,销售订单状态改为待收货 - # self.env['sale.order'].search([('name', '=', self.origin)]).write({'scheduled_status': 'to receive'}) + notification = { + 'type': 'ir.actions.client', + 'tag': 'display_notification', + 'params': { + 'title': _('成功'), + 'type': 'success', + 'message': '物流下单成功', + 'sticky': False, + 'next': {'type': 'ir.actions.client', 'tag': 'reload'} + } + } - # else: - # raise UserError("选择京东物流才能下单呦") + return notification def get_bill(self): """ diff --git a/sf_bf_connect/views/view.xml b/sf_bf_connect/views/view.xml index 6db5cb1d..184d0e71 100644 --- a/sf_bf_connect/views/view.xml +++ b/sf_bf_connect/views/view.xml @@ -32,7 +32,7 @@ - @@ -45,42 +45,50 @@ stock.picking - + + - - - - - - - - - - - - - - - - - - + + tracking only + stock.picking + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - \ No newline at end of file diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index accc23a0..2cdfd1a3 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -88,7 +88,7 @@ - + + diff --git a/sf_machine_connect/views/compensation.xml b/sf_machine_connect/views/compensation.xml index f79c539e..7496df0f 100644 --- a/sf_machine_connect/views/compensation.xml +++ b/sf_machine_connect/views/compensation.xml @@ -5,39 +5,46 @@ mrp.workorder - - - - + + + + + + - - - - - - - - - - - - - - - - + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/sf_machine_connect/views/default_delivery.xml b/sf_machine_connect/views/default_delivery.xml index b0890981..0238c736 100644 --- a/sf_machine_connect/views/default_delivery.xml +++ b/sf_machine_connect/views/default_delivery.xml @@ -9,7 +9,6 @@ - diff --git a/sf_machine_connect/views/ftp_button.xml b/sf_machine_connect/views/ftp_button.xml index f102dc00..2a273553 100644 --- a/sf_machine_connect/views/ftp_button.xml +++ b/sf_machine_connect/views/ftp_button.xml @@ -19,7 +19,7 @@ - context="{'default_sf_cnc_processing_id': id}" attrs='{"invisible": ["|",("state","!=","progress"), ("user_permissions","!=",True)]}'/> diff --git a/sf_machine_connect/wizard/up_select.xml b/sf_machine_connect/wizard/up_select.xml index cbdac5b3..6ffbbfc5 100644 --- a/sf_machine_connect/wizard/up_select.xml +++ b/sf_machine_connect/wizard/up_select.xml @@ -14,7 +14,7 @@ diff --git a/sf_manufacturing/controllers/controllers.py b/sf_manufacturing/controllers/controllers.py index 75badcc8..dd97531f 100644 --- a/sf_manufacturing/controllers/controllers.py +++ b/sf_manufacturing/controllers/controllers.py @@ -162,7 +162,8 @@ class Manufacturing_Connect(http.Controller): routing_type = ret['CraftId'] equipment_id = ret["DeviceId"] workorder = request.env['mrp.workorder'].sudo().search( - [('production_id', '=', production_id), ('routing_type', '=', routing_type)], limit=1) + [('production_id', '=', production_id), ('routing_type', '=', routing_type), + ('rfid_code', '!=', False)], limit=1) if not workorder: res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单不存在'} return json.JSONEncoder().encode(res) @@ -203,12 +204,14 @@ class Manufacturing_Connect(http.Controller): res = {'Succeed': True, 'Datas': ['工单已结束']} datas = request.httprequest.data ret = json.loads(datas) + logging.info('button_Work_End:%s' % ret) request.env['center_control.interface.log'].sudo().create( {'content': ret, 'name': 'AutoDeviceApi/FeedBackEnd'}) production_id = ret['BillId'] routing_type = ret['CraftId'] workorder = request.env['mrp.workorder'].sudo().search( - [('production_id', '=', production_id), ('routing_type', '=', routing_type)], limit=1) + [('production_id', '=', production_id), ('routing_type', '=', routing_type), + ('rfid_code', '!=', False)], limit=1) if not workorder: res = {'Succeed': False, 'ErrorCode': 202, 'Error': '该工单不存在'} return json.JSONEncoder().encode(res) @@ -222,14 +225,14 @@ class Manufacturing_Connect(http.Controller): # workorder.sudo().production_id.process_state = '待解除装夹' # 根据工单的实际结束时间修改排程单的结束时间、状态,同时修改销售订单的状态 - if workorder.date_finished: - request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write( - {'actual_end_time': workorder.date_finished, - 'state': 'finished'}) - production_obj = request.env['mrp.production'].sudo().search([('name', '=', production_id)]) - if production_obj: - production_obj.sudo().work_order_state = '已完成' - production_obj.write({'state': 'completed'}) + # if workorder.date_finished: + # request.env['sf.production.plan'].sudo().search([('production_id', '=', production_id)]).write( + # {'actual_end_time': workorder.date_finished, + # 'state': 'finished'}) + # production_obj = request.env['mrp.production'].sudo().search([('name', '=', production_id)]) + # if production_obj: + # production_obj.sudo().work_order_state = '已完成' + # production_obj.write({'state': 'done'}) # request.env['sale.order'].sudo().search( # [('name', '=', production_obj.origin)]).write({'schedule_status': 'to deliver'}) @@ -317,15 +320,15 @@ class Manufacturing_Connect(http.Controller): [('rfid_code', '=', ret['RfidCode']), ('routing_type', '=', 'CNC加工')]) if workorder: for item in workorder.cmm_ids: - if item.program_date is not False: - program_date = item.program_date.strftime("%Y-%m-%d %H:%M:%S") - program_date_str = request.env['sf.sync.common'].sudo().get_add_time(program_date) + if item.program_create_date is not False: + program_create_date = item.program_create_date.strftime("%Y-%m-%d %H:%M:%S") + program_date_str = request.env['sf.sync.common'].sudo().get_add_time(program_create_date) res['Datas'].append({ 'CraftId': workorder.id, 'CraftKey': workorder.name, - 'ProgramDate': '' if not item.program_date else program_date_str, + 'ProgramDate': '' if not item.program_create_date else program_date_str, 'ProgramPath': item.program_path, - 'PostProcessing': item.post_processing_name, + 'PostProcessing': item.program_name, }) else: res = {'Succeed': False, 'ErrorCode': 203, 'Error': '暂无工单及对应的CNC程序数据'} diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index 32124b28..95242b54 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -30,13 +30,10 @@ class MrpProduction(models.Model): # ('completed', '已完工') # ]) state = fields.Selection([ - ('draft', 'Draft'), - ('confirmed', 'Confirmed'), - ('progress', '待排程'), - ('pending_cam', '待装夹'), - ('pending_processing', '待加工'), - ('pending_era_cam', '待解除装夹'), - ('completed', '已完工'), + ('draft', '草稿'), + ('confirmed', '待排程'), + ('pending_cam', '待加工'), + ('progress', '加工中'), ('to_close', 'To Close'), ('done', 'Done'), ('cancel', 'Cancelled')], string='State', @@ -53,7 +50,8 @@ class MrpProduction(models.Model): active = fields.Boolean(string='已归档', default=True) programming_no = fields.Char('编程单号') work_state = fields.Char('业务状态') - programming_state = fields.Char('编程状态', tracking=True) + programming_state = fields.Selection( + [('编程中', '编程中'), ('已编程', '已编程')], string='编程状态', tracking=True) glb_file = fields.Binary("glb模型文件") production_line_id = fields.Many2one('sf.production.line', string='生产线', tracking=True) plan_start_processing_time = fields.Datetime('计划开始加工时间') @@ -81,7 +79,7 @@ class MrpProduction(models.Model): @api.depends( 'move_raw_ids.state', 'move_raw_ids.quantity_done', 'move_finished_ids.state', - 'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state', 'process_state') + 'workorder_ids.state', 'product_qty', 'qty_producing', 'schedule_state') def _compute_state(self): for production in self: if not production.state or not production.product_uom_id: @@ -109,22 +107,19 @@ class MrpProduction(models.Model): production.state = 'progress' elif any(not float_is_zero(move.quantity_done, precision_rounding=move.product_uom.rounding or move.product_id.uom_id.rounding) - for move in production.move_raw_ids): + for move in production.move_raw_ids if move.product_id): production.state = 'progress' - # 新添加的状态逻辑 - if production.state == 'progress' and production.schedule_state == '已排' and production.process_state == '待装夹': - # production.state = 'pending_processing' + # # 新添加的状态逻辑 + if ( + production.state == 'to_close' or production.state == 'progress') and production.schedule_state == '未排': + production.state = 'confirmed' + elif production.state == 'to_close' and production.schedule_state == '已排': production.state = 'pending_cam' - if production.state == 'progress' and production.schedule_state == '已排' and production.process_state == '待加工': - # if production.state == 'pending_cam' and production.process_state == '待加工': - production.state = 'pending_processing' - elif production.state == 'progress' and production.process_state == '待解除装夹': - production.state = 'pending_era_cam' - elif production.state == 'progress' and production.process_state == '已完工': - production.state = 'completed' - elif production.state == 'progress' and production.work_order_state == '已完成': - production.state = 'completed' + + if production.state == 'progress': + if all(wo_state not in ('progress', 'done') for wo_state in production.workorder_ids.mapped('state')): + production.state = 'pending_cam' def action_check(self): """ @@ -292,15 +287,7 @@ class MrpProduction(models.Model): # 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制; # 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心; - def _create_workorder3(self): - # 根据product_id对self进行分组 - grouped_product_ids = {k: list(g) for k, g in groupby(self, key=lambda x: x.product_id.id)} - # 初始化一个字典来存储每个product_id对应的生产订单名称列表 - product_id_to_production_names = {} - # 对于每个product_id,获取其所有生产订单的名称 - for product_id, productions in grouped_product_ids.items(): - # 为同一个product_id创建一个生产订单名称列表 - product_id_to_production_names[product_id] = [production.name for production in productions] + def _create_workorder3(self, item): for production in self: if not production.bom_id or not production.product_id: continue @@ -328,19 +315,6 @@ class MrpProduction(models.Model): 'state': 'pending', }] if production.product_id.categ_id.type == '成品': - if production.product_id.id in product_id_to_production_names: - # # 同一个产品多个制造订单对应一个编程单和模型库 - # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 - if not production.programming_no: - production_programming = self.search( - [('product_id.id', '=', production.product_id.id), ('origin', '=', production.origin)], - limit=1, order='id asc') - if not production_programming.programming_no: - production.fetchCNC(', '.join(product_id_to_production_names[production.product_id.id])) - else: - production.write({'programming_no': production_programming.programming_no, - 'programming_state': '编程中'}) - # # 根据加工面板的面数及对应的工序模板生成工单 i = 0 processing_panel_len = len(production.product_id.model_processing_panel.split(',')) @@ -353,13 +327,14 @@ class MrpProduction(models.Model): for route in product_routing_workcenter: if route.is_repeat is True: workorders_values.append( - self.env['mrp.workorder'].json_workorder_str(k, production, route)) - if i == processing_panel_len and route.routing_type == '解除装夹': - workorders_values.append( - self.env['mrp.workorder'].json_workorder_str(k, production, route)) + self.env['mrp.workorder'].json_workorder_str(k, production, route, item)) + # if i == processing_panel_len and route.routing_type == '解除装夹': + # workorders_values.append( + # 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 = [] @@ -374,6 +349,7 @@ class MrpProduction(models.Model): # 用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: @@ -404,6 +380,52 @@ class MrpProduction(models.Model): workorders_values.append( self.env['mrp.workorder'].json_workorder_str('', production, route)) production.workorder_ids = workorders_values + # for production_item in productions: + process_parameter_workorder = self.env['mrp.workorder'].search( + [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id), + ('is_subcontract', '=', True)]) + if process_parameter_workorder: + is_pick = False + consecutive_workorders = [] + m = 0 + sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id) + for i in range(len(sorted_workorders) - 1): + if m == 0: + is_pick = False + if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \ + sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \ + sorted_workorders[i].id == sorted_workorders[i + 1].id - 1: + if sorted_workorders[i] not in consecutive_workorders: + consecutive_workorders.append(sorted_workorders[i]) + consecutive_workorders.append(sorted_workorders[i + 1]) + m += 1 + continue + else: + if m == len(consecutive_workorders) - 1 and m != 0: + self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, + production) + if sorted_workorders[i] in consecutive_workorders: + is_pick = True + consecutive_workorders = [] + m = 0 + # 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单 + if is_pick is False: + self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], + production) + if m == len(consecutive_workorders) - 1 and m != 0: + self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, + production) + if sorted_workorders[i] in consecutive_workorders: + is_pick = True + consecutive_workorders = [] + m = 0 + if m == len(consecutive_workorders) - 1 and m != 0: + self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production) + if is_pick is False and m == 0: + if len(sorted_workorders) == 1: + self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production) + else: + self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production) for workorder in production.workorder_ids: workorder.duration_expected = workorder._get_duration_expected() @@ -498,46 +520,63 @@ class MrpProduction(models.Model): def _reset_work_order_sequence(self): for rec in self: sequence_list = {} + # 产品模型类型 model_type_id = rec.product_id.product_model_type_id + # 产品加工面板 + model_processing_panel = rec.product_id.model_processing_panel if model_type_id: - tmpl_num = 1 - # 成品工序 - product_routing_tmpl_ids = model_type_id.product_routing_tmpl_ids - if product_routing_tmpl_ids: - for tmpl_id in product_routing_tmpl_ids: - sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) - tmpl_num += 1 - # 表面工艺工序 - # 模型类型的表面工艺工序模版 - surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids - # 产品选择的表面工艺 - model_process_parameters_ids = rec.product_id.model_process_parameters_ids - process_dict = {} - if model_process_parameters_ids: - for process_parameters_id in model_process_parameters_ids: - process_id = process_parameters_id.process_id - for surface_tmpl_id in surface_tmpl_ids: - if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id: - surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name - process_dict.update({int(process_id.category_id.code): '%s-%s' % ( - surface_tmpl_name, process_parameters_id.name)}) - process_list = sorted(process_dict.keys()) - for process_num in process_list: - sequence_list.update({process_dict.get(process_num): tmpl_num}) - tmpl_num += 1 - # 坯料工序 - tmpl_num = 1 - embryo_routing_tmpl_ids = model_type_id.embryo_routing_tmpl_ids - if embryo_routing_tmpl_ids: - for tmpl_id in embryo_routing_tmpl_ids: - sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + if model_processing_panel: + tmpl_num = 1 + panel_list = model_processing_panel.split(',') + for panel in panel_list: + panel_sequence_list = {} + # 成品工序 + product_routing_tmpl_ids = model_type_id.product_routing_tmpl_ids + if product_routing_tmpl_ids: + for tmpl_id in product_routing_tmpl_ids: + panel_sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + tmpl_num += 1 + sequence_list.update({panel: panel_sequence_list}) + # 表面工艺工序 + # 模型类型的表面工艺工序模版 + surface_tmpl_ids = model_type_id.surface_technics_routing_tmpl_ids + # 产品选择的表面工艺 + model_process_parameters_ids = rec.product_id.model_process_parameters_ids + process_dict = {} + if model_process_parameters_ids: + for process_parameters_id in model_process_parameters_ids: + process_id = process_parameters_id.process_id + for surface_tmpl_id in surface_tmpl_ids: + if process_id == surface_tmpl_id.route_workcenter_id.surface_technics_id: + surface_tmpl_name = surface_tmpl_id.route_workcenter_id.name + process_dict.update({int(process_id.category_id.code): '%s-%s' % ( + surface_tmpl_name, process_parameters_id.name)}) + process_list = sorted(process_dict.keys()) + for process_num in process_list: + sequence_list.update({process_dict.get(process_num): tmpl_num}) tmpl_num += 1 + # 坯料工序 + tmpl_num = 1 + embryo_routing_tmpl_ids = model_type_id.embryo_routing_tmpl_ids + if embryo_routing_tmpl_ids: + for tmpl_id in embryo_routing_tmpl_ids: + sequence_list.update({tmpl_id.route_workcenter_id.name: tmpl_num}) + tmpl_num += 1 + else: + raise ValidationError('该产品【加工面板】为空!') + else: raise ValidationError('该产品没有选择【模版类型】!') for work in rec.workorder_ids: if sequence_list.get(work.name): work.sequence = sequence_list[work.name] + elif sequence_list.get(work.processing_panel): + processing_panel = sequence_list.get(work.processing_panel) + if processing_panel.get(work.name): + work.sequence = processing_panel[work.name] + else: + raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name) else: raise ValidationError('工序【%s】在产品选择的模版类型中不存在!' % work.name) # if work.name == '获取CNC加工程序': @@ -546,8 +585,8 @@ class MrpProduction(models.Model): # work.button_finish() # 创建工单并进行排序 - def _create_workorder(self): - self._create_workorder3() + def _create_workorder(self, item): + self._create_workorder3(item) self._reset_work_order_sequence() return True @@ -591,9 +630,10 @@ class MrpProduction(models.Model): }) for production in self: + logging.info('qty_produced:%s' % production.qty_produced) production.write({ 'date_finished': fields.Datetime.now(), - 'product_qty': production.qty_produced, + 'product_qty': production.product_qty if production.qty_produced < 1.0 else production.qty_produced, 'priority': '0', 'is_locked': True, 'state': 'done', @@ -619,6 +659,7 @@ class MrpProduction(models.Model): if any(mo.show_allocation for mo in self): action = self.action_view_reception_report() return action + logging.info('last-product_qty:%s' % production.product_qty) return True context = self.env.context.copy() context = {k: v for k, v in context.items() if not k.startswith('default_')} diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index ba7bc2c5..09bde519 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -1,5 +1,5 @@ import re - +import json import logging import base64 import urllib.parse @@ -19,7 +19,7 @@ from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController class ResMrpWorkOrder(models.Model): _inherit = 'mrp.workorder' - _order = 'sequence asc,create_date desc' + _order = 'id' product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name') @@ -136,11 +136,6 @@ class ResMrpWorkOrder(models.Model): supplier_id = fields.Many2one('res.partner', string='外协供应商') equipment_id = fields.Many2one('maintenance.equipment', string='加工设备', tracking=True) - is_ok = fields.Boolean(string='是否合格') - # 加工人 - processing_user_id = fields.Many2one('res.users', string='加工人') - # 检测人 - inspection_user_id = fields.Many2one('res.users', string='检测人') # 保存名称 save_name = fields.Char(string='检测文件保存名称', compute='_compute_save_name') # 获取数据状态 @@ -208,6 +203,8 @@ class ResMrpWorkOrder(models.Model): ("technology", "工艺"), ("customer redrawing", "客户改图"), ("other", "其他"), ], string="原因", tracking=True) detailed_reason = fields.Text('详细原因') + # is_send_program_again = fields.Boolean(string='是否重新下发NC程序', default=False) + @api.onchange('rfid_code') def _onchange(self): if self.rfid_code and self.state == 'progress': @@ -233,16 +230,6 @@ class ResMrpWorkOrder(models.Model): ids = [t[0] for t in self.env.cr.fetchall()] return [('id', 'in', ids)] - @api.onchange('is_ok') - def _onchange_inspection_user_id(self): - """ - 检测is_ok(是否合格)被修改的话,就将当前用户赋值给inspection_user_id - """ - if not self.inspection_user_id: - self.inspection_user_id = self.env.user.id - else: - self.inspection_user_id = False - @api.onchange('functional_fixture_id') def _onchange_functional_fixture_id(self): if self.functional_fixture_id: @@ -473,7 +460,7 @@ class ResMrpWorkOrder(models.Model): raise UserError(_("该工单暂未完成,无法进行工件配送")) # 拼接工单对象属性值 - def json_workorder_str(self, k, production, route): + def json_workorder_str(self, k, production, route, item): # 计算预计时长duration_expected if route.routing_type == '切割': duration_expected = self.env['mrp.routing.workcenter'].sudo().search( @@ -506,7 +493,7 @@ class ResMrpWorkOrder(models.Model): 'processing_panel': k, 'quality_point_ids': route.route_workcenter_id.quality_point_ids, 'routing_type': route.routing_type, - 'work_state': '待发起', + # 'work_state': '待发起', 'workcenter_id': self.env['mrp.routing.workcenter'].get_workcenter(route.workcenter_ids.ids, route.routing_type, production.product_id), @@ -515,6 +502,10 @@ class ResMrpWorkOrder(models.Model): 'date_planned_finished': datetime.now() + timedelta(days=1), 'duration_expected': duration_expected, 'duration': 0, + 'cnc_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cnc.processing']._json_cnc_processing( + k, item), + 'cmm_ids': False if route.routing_type != 'CNC加工' else self.env['sf.cmm.program']._json_cmm_program(k, + item), 'workpiece_delivery_ids': False if not route.routing_type == '装夹预调' else self._json_workpiece_delivery_list( production) }] @@ -871,10 +862,9 @@ class ResMrpWorkOrder(models.Model): ('location_dest_id', '=', self.env['stock.location'].search( [('barcode', 'ilike', 'VL-SPOC')]).id), ('origin', '=', self.production_id.name)]) - purchase = self.env['purchase.order'].search([('origin', '=', self.production_id.name)]) - if purchase and move_out: + if move_out: move_out.write({'state': 'assigned'}) - self.env['stock.move.line'].create(move_out.get_move_line(purchase, self)) + self.env['stock.move.line'].create(move_out.get_move_line(self.production_id, self)) # move_out._action_assign() if self.state == 'waiting' or self.state == 'ready' or self.state == 'progress': @@ -1016,22 +1006,24 @@ class ResMrpWorkOrder(models.Model): if workorder.processing_panel == record.processing_panel: rfid_code = workorder.rfid_code workorder.write({'rfid_code_old': rfid_code, - 'rfid_code': ''}) + 'rfid_code': False}) workorder.rfid_code_old = rfid_code - workorder.rfid_code = '' + workorder.rfid_code = False if is_production_id is True and record.routing_type in ['解除装夹', '表面工艺']: + logging.info('product_qty:%s' % record.production_id.product_qty) for move_raw_id in record.production_id.move_raw_ids: move_raw_id.quantity_done = move_raw_id.product_uom_qty record.process_state = '已完工' record.production_id.process_state = '已完工' - if record.routing_type in ['表面工艺']: + if record.routing_type in ['解除装夹', '表面工艺']: raw_move = self.env['stock.move'].sudo().search( - [('origin', '=', record.production_id.name), ('procure_method', '=', 'make_to_order'), + [('origin', '=', record.production_id.name), + ('procure_method', 'in', ['make_to_order', 'make_to_stock']), ('state', '!=', 'done')]) if raw_move: raw_move.write({'state': 'done'}) record.production_id.button_mark_done1() - # self.production_id.state = 'done' + # record.production_id.state = 'done' # 将FTP的检测报告文件下载到临时目录 def download_reportfile_tmp(self, workorder, reportpath): @@ -1073,6 +1065,32 @@ class ResMrpWorkOrder(models.Model): workorder.detection_report = base64.b64encode(open(report_file_path, 'rb').read()) return True + # 重新下发nc程序 + # def button_send_program_again(self): + # try: + # res = {'programming_no': self.production_id.programming_no} + # configsettings = self.env['res.config.settings'].get_values() + # config_header = Common.get_headers(self, configsettings['token'], configsettings['sf_secret_key']) + # url = '/api/intelligent_programming/reset_state_again' + # config_url = configsettings['sf_url'] + url + # r = requests.post(config_url, json=res, data=None, headers=config_header) + # r = r.json() + # result = json.loads(r['result']) + # if result['status'] == 1: + # productions = self.env['mrp.production'].search( + # [('programming_no', '=', self.production_id.programming_no), ('programming_state', '=', '已编程')]) + # if productions: + # workorder = productions.workorder_ids.filtered( + # lambda ap: ap.routing_type in ['装夹预调', 'CNC加工'] and ap.state not in ['done', 'cancel', + # 'progress']) + # if workorder: + # productions.write({'work_state': '编程中', 'programming_state': '编程中'}) + # else: + # raise UserError(result['message']) + # except Exception as e: + # logging.info('button_send_program_again error:%s' % e) + # raise UserError("重新下发nc程序失败,请联系管理员") + class CNCprocessing(models.Model): _name = 'sf.cnc.processing' @@ -1098,6 +1116,7 @@ class CNCprocessing(models.Model): production_id = fields.Many2one('mrp.production', string="制造订单") button_state = fields.Boolean(string='是否已经下发') program_path = fields.Char('程序文件路径') + program_create_date = fields.Datetime('程序创建日期') # mrs下发编程单创建CNC加工 def cnc_processing_create(self, cnc_workorder, ret, program_path, program_path_tmp): @@ -1130,24 +1149,27 @@ class CNCprocessing(models.Model): cnc_workorder.write({'programming_state': '已编程', 'work_state': '已编程'}) return cnc_processing - def _json_cnc_processing(self, obj): - cnc_processing_str = (0, 0, { - 'sequence_number': obj['sequence_number'], - 'program_name': obj['program_name'], - 'cutting_tool_name': obj['cutting_tool_name'], - 'cutting_tool_no': obj['cutting_tool_no'], - 'processing_type': obj['processing_type'], - 'margin_x_y': obj['margin_x_y'], - 'margin_z': obj['margin_z'], - 'depth_of_processing_z': obj['depth_of_processing_z'], - 'cutting_tool_extension_length': obj['cutting_tool_extension_length'], - 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], - 'estimated_processing_time': obj['estimated_processing_time'], - 'program_path': obj['program_path'], - 'cnc_id': obj['cnc_id'].id, - 'remark': obj['remark'] - }) - return cnc_processing_str + def _json_cnc_processing(self, panel, ret): + cnc_processing = [] + for item in ret['programming_list']: + if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') == -1: + cnc_processing.append((0, 0, { + 'sequence_number': item['sequence_number'], + 'program_name': item['program_name'], + 'cutting_tool_name': item['cutting_tool_name'], + 'cutting_tool_no': item['cutting_tool_no'], + 'processing_type': item['processing_type'], + 'margin_x_y': item['margin_x_y'], + 'margin_z': item['margin_z'], + 'depth_of_processing_z': item['depth_of_processing_z'], + 'cutting_tool_extension_length': item['cutting_tool_extension_length'], + 'cutting_tool_handle_type': item['cutting_tool_handle_type'], + 'estimated_processing_time': item['estimated_processing_time'], + 'program_path': item['ftp_path'], + 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), + 'remark': item['remark'] + })) + return cnc_processing # 根据程序名和加工面匹配到ftp里对应的Nc程序名,可优化为根据cnc_processing.program_path进行匹配 def get_cnc_processing_file(self, serverdir, cnc_processing, program_path): @@ -1177,14 +1199,16 @@ class CNCprocessing(models.Model): }) return attachment - # 将FTP的nc文件下载到临时目录 + # 将FTP的多面的程序单文件下载到临时目录 def download_file_tmp(self, production_no, processing_panel): - remotepath = os.path.join('/NC', production_no, 'return', processing_panel) + remotepath = os.path.join('/home/ftp/ftp_root/NC', production_no, 'return', processing_panel) serverdir = os.path.join('/tmp', production_no, 'return', processing_panel) ftp_resconfig = self.env['res.config.settings'].get_values() ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp_resconfig['ftp_user'], ftp_resconfig['ftp_password']) - download_state = ftp.download_file_tree(remotepath, serverdir) + if not ftp.file_exists_1(remotepath): + logging.info('目录不存在:%s' % remotepath) + download_state = ftp.download_program_file(remotepath, serverdir) logging.info('download_state:%s' % download_state) return download_state @@ -1229,6 +1253,7 @@ class SfWorkOrderBarcodes(models.Model): _inherit = ["mrp.workorder", "barcodes.barcode_events_mixin"] def on_barcode_scanned(self, barcode): + logging.info('Rfid:%s' % barcode) workorder = self.env['mrp.workorder'].browse(self.ids) # workorder_preset = self.env['mrp.workorder'].search( # [('routing_type', '=', '装夹预调'), ('rfid_code', '=', barcode)]) @@ -1272,7 +1297,7 @@ class SfWorkOrderBarcodes(models.Model): self.process_state = '待检测' self.date_start = datetime.now() else: - raise UserError('该托盘信息不存在!!!') + raise UserError('没有找到Rfid为【%s】的托盘信息!!!' % barcode) # stock_move_line = self.env['stock.move.line'].search([('lot_name', '=', barcode)]) # if stock_move_line.product_id.categ_type == '夹具': # workorder.write({ @@ -1589,80 +1614,22 @@ class CMMprogram(models.Model): _name = 'sf.cmm.program' _description = "CMM程序" - cmm_id = fields.Many2one('ir.attachment') sequence_number = fields.Integer('序号') program_name = fields.Char('程序名') - cutting_tool_name = fields.Char('刀具名称') - cutting_tool_no = fields.Char('刀号') - processing_type = fields.Char('加工类型') - margin_x_y = fields.Char('余量_X/Y') - margin_z = fields.Char('余量_Z') - depth_of_processing_z = fields.Char('加工深度(Z)') - cutting_tool_extension_length = fields.Char('刀具伸出长度') - cutting_tool_handle_type = fields.Char('刀柄型号') - estimated_processing_time = fields.Char('预计加工时间') remark = fields.Text('备注') workorder_id = fields.Many2one('mrp.workorder', string="工单") production_id = fields.Many2one('mrp.production', string="制造订单") program_path = fields.Char('程序文件路径') + program_create_date = fields.Datetime('程序创建日期') - def cmm_program_create(self, ret, program_path, program_path_tmp): - cmm_program = None - for obj in ret['programming_list']: - workorder = self.env['mrp.workorder'].search( - [('production_id.name', '=', ret['production_order_no'].split(',')[0]), - ('processing_panel', '=', obj['processing_panel']), - ('routing_type', '=', 'CNC加工')]) - if obj['program_name'] in program_path: - logging.info('obj:%s' % obj['program_name']) - cmm_program = self.sudo().create({ - 'workorder_id': workorder.id, - 'sequence_number': obj['sequence_number'], - 'program_name': obj['program_name'], - 'cutting_tool_name': obj['cutting_tool_name'], - 'cutting_tool_no': obj['cutting_tool_no'], - 'processing_type': obj['processing_type'], - 'margin_x_y': obj['margin_x_y'], - 'margin_z': obj['margin_z'], - 'depth_of_processing_z': obj['depth_of_processing_z'], - 'cutting_tool_extension_length': obj['cutting_tool_extension_length'], - 'cutting_tool_handle_type': obj['cutting_tool_handle_type'], - 'estimated_processing_time': obj['estimated_processing_time'], - 'remark': obj['remark'], - 'program_path': program_path.replace('/tmp', '') - }) - cmm_program.get_cmm_program_file(program_path_tmp, cmm_program, program_path) + def _json_cmm_program(self, panel, ret): + cmm_program = [] + for item in ret['programming_list']: + if item['processing_panel'] == panel and item['ftp_path'].find('.dmi') != -1: + cmm_program.append((0, 0, { + 'sequence_number': 1, + 'program_name': item['program_name'], + 'program_path': item['ftp_path'], + 'program_create_date': datetime.strptime(item['program_create_date'], '%Y-%m-%d %H:%M:%S'), + })) return cmm_program - - # 根据程序名和加工面匹配到ftp里对应的cmm程序名 - def get_cmm_program_file(self, serverdir, cmm_program, program_path): - logging.info('cmm-serverdir:%s' % serverdir) - for root, dirs, files in os.walk(serverdir): - for f in files: - if f in program_path: - cmm_program_file_path = os.path.join(serverdir, root, f) - self.write_file_cmm(cmm_program_file_path, cmm_program) - - # 创建附件(nc文件) - def attachment_create(self, name, data): - attachment = self.env['ir.attachment'].create({ - 'datas': base64.b64encode(data), - 'type': 'binary', - 'public': True, - 'description': '程序文件', - 'name': name - }) - return attachment - - # 将cmm文件存到attach的datas里 - def write_file_cmm(self, cmm_file_path, cnc): - cmm_file_name = cmm_file_path.split('/') - logging.info('cmm_file_name:%s' % cmm_file_name[-1]) - if os.path.exists(cmm_file_path): - with open(cmm_file_path, 'rb') as file: - data_bytes = file.read() - attachment = self.attachment_create(cnc.program_name + cmm_file_name[-1], data_bytes) - cnc.write({'cmm_id': attachment.id}) - file.close() - else: - return False diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index cea53c64..4d05de81 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -616,7 +616,7 @@ class ResProductMo(models.Model): item['model_width'] + model_type.embryo_tolerance) * ( item['model_height'] + model_type.embryo_tolerance), 'product_model_type_id': model_type.id, - 'model_processing_panel': 'R', + # 'model_processing_panel': 'R', 'model_machining_precision': item['model_machining_precision'], 'model_code': item['barcode'], 'length': item['model_long'], @@ -634,6 +634,7 @@ class ResProductMo(models.Model): 'model_process_parameters_ids': [(6, 0, [])] if not item.get( 'process_parameters_code') else self.get_process_parameters_id(item['process_parameters_code']), 'model_remark': item['remark'], + 'single_manufacturing': True, 'default_code': '%s-%s' % (order_number, i), 'manual_quotation': item['manual_quotation'] or False, 'part_number': item.get('part_number') or '', diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py index 49f00140..9234060f 100644 --- a/sf_manufacturing/models/stock.py +++ b/sf_manufacturing/models/stock.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- import base64 import qrcode +from itertools import groupby from collections import defaultdict, namedtuple import logging import io @@ -203,64 +204,18 @@ class StockRule(models.Model): productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions_values) - # self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) - self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) + self.env['stock.move'].sudo().create(productions._get_moves_raw_values()) + ''' 创建工单 ''' - productions._create_workorder() - + # productions._create_workorder() + # + 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() - for production_item in productions: - process_parameter_workorder = self.env['mrp.workorder'].search( - [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production_item.id), - ('is_subcontract', '=', True)]) - if process_parameter_workorder: - is_pick = False - consecutive_workorders = [] - m = 0 - sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.id) - for i in range(len(sorted_workorders) - 1): - if m == 0: - is_pick = False - if sorted_workorders[i].supplier_id.id == sorted_workorders[i + 1].supplier_id.id and \ - sorted_workorders[i].is_subcontract == sorted_workorders[i + 1].is_subcontract and \ - sorted_workorders[i].id == sorted_workorders[i + 1].id - 1: - if sorted_workorders[i] not in consecutive_workorders: - consecutive_workorders.append(sorted_workorders[i]) - consecutive_workorders.append(sorted_workorders[i + 1]) - m += 1 - continue - else: - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, - production_item) - if sorted_workorders[i] in consecutive_workorders: - is_pick = True - consecutive_workorders = [] - m = 0 - # 当前面的连续工序生成对应的外协出入库单再生成当前工序的外协出入库单 - if is_pick is False: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], - production_item) - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, - production_item) - if sorted_workorders[i] in consecutive_workorders: - is_pick = True - consecutive_workorders = [] - m = 0 - if m == len(consecutive_workorders) - 1 and m != 0: - self.env['stock.picking'].create_outcontract_picking(consecutive_workorders, production_item) - if is_pick is False and m == 0: - if len(sorted_workorders) == 1: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders, production_item) - else: - self.env['stock.picking'].create_outcontract_picking(sorted_workorders[i], production_item) - for production in productions: ''' 创建制造订单时生成序列号 @@ -306,6 +261,29 @@ class StockRule(models.Model): 'product_id': production.product_id.id, 'state': 'draft', }) + all_production = productions + grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} + # 初始化一个字典来存储每个product_id对应的生产订单名称列表 + product_id_to_production_names = {} + # 对于每个product_id,获取其所有生产订单的名称 + for product_id, all_production in grouped_product_ids.items(): + # 为同一个product_id创建一个生产订单名称列表 + product_id_to_production_names[product_id] = [production.name for production in all_production] + for production_item in productions: + if production_item.product_id.id in product_id_to_production_names: + # # 同一个产品多个制造订单对应一个编程单和模型库 + # # 只调用一次fetchCNC,并将所有生产订单的名称作为字符串传递 + if not production_item.programming_no: + production_programming = self.env['mrp.production'].search( + [('product_id.id', '=', production_item.product_id.id), + ('origin', '=', production_item.origin)], + limit=1, order='id asc') + if not production_programming.programming_no: + production_item.fetchCNC( + ', '.join(product_id_to_production_names[production_item.product_id.id])) + else: + production_item.write({'programming_no': production_programming.programming_no, + 'programming_state': '编程中'}) return True @@ -509,10 +487,10 @@ class StockPicking(models.Model): ('location_dest_id', '=', self.env['stock.location'].search( [('barcode', 'ilike', 'VL-SPOC')]).id), ('origin', '=', self.origin)]) - if self.id == move_out.picking_id.id: - if move_out.move_line_ids.workorder_id.state not in ['progress']: - raise UserError( - _('该出库单里源单据内的单号为%s的工单还未开始,不能进行验证操作!' % move_out.move_line_ids.workorder_id.name)) + # if self.id == move_out.picking_id.id: + # if move_out.move_line_ids.workorder_id.state not in ['progress']: + # raise UserError( + # _('该出库单里源单据内的单号为%s的工单还未开始,不能进行验证操作!' % move_out.move_line_ids.workorder_id.name)) # 入库单验证 move_in = self.env['stock.move'].search( [('location_dest_id', '=', self.env['stock.location'].search( @@ -528,18 +506,17 @@ class StockPicking(models.Model): res = super().button_validate() if res is True: if self.id == move_out.picking_id.id: - if move_out.move_line_ids.workorder_id.state == 'progress': - move_in = self.env['stock.move'].search( - [('location_dest_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), - ('location_id', '=', self.env['stock.location'].search( - [('barcode', 'ilike', 'VL-SPOC')]).id), - ('origin', '=', self.origin)]) - # purchase = self.env['purchase.order'].search([('origin', '=', self.origin)]) - if move_in: - move_in.write({'state': 'assigned'}) - purchase = self.env['purchase.order'].search([('origin', '=', self.origin)]) - self.env['stock.move.line'].create(move_in.get_move_line(purchase, None)) + # if move_out.move_line_ids.workorder_id.state == 'progress': + move_in = self.env['stock.move'].search( + [('location_dest_id', '=', self.env['stock.location'].search( + [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), + ('location_id', '=', self.env['stock.location'].search( + [('barcode', 'ilike', 'VL-SPOC')]).id), + ('origin', '=', self.origin)]) + production = self.env['mrp.production'].search([('name', '=', self.origin)]) + if move_in: + move_in.write({'state': 'assigned'}) + self.env['stock.move.line'].create(move_in.get_move_line(production, None)) return res @@ -621,7 +598,7 @@ class ReStockMove(models.Model): 'state': 'confirmed', } - def get_move_line(self, purchase, sorted_workorders): + def get_move_line(self, production_id, sorted_workorders): return { 'move_id': self.id, 'product_id': self.product_id.id, @@ -630,7 +607,7 @@ class ReStockMove(models.Model): 'location_dest_id': self.picking_id.location_dest_id.id, 'picking_id': self.picking_id.id, 'reserved_uom_qty': 1.0, - 'lot_id': purchase.picking_ids.move_line_ids.lot_id.id, + 'lot_id': production_id.move_line_raw_ids.lot_id.id, 'company_id': self.company_id.id, # 'workorder_id': '' if not sorted_workorders else sorted_workorders.id, # 'production_id': '' if not sorted_workorders else sorted_workorders.production_id.id, diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv index 3b75f102..ec2d5a76 100644 --- a/sf_manufacturing/security/ir.model.access.csv +++ b/sf_manufacturing/security/ir.model.access.csv @@ -1,8 +1,9 @@ id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink access_sf_cnc_processing_group_sf_mrp_user,sf_cnc_processing,model_sf_cnc_processing,sf_base.group_sf_mrp_user,1,0,0,0 access_sf_cnc_processing_manager,sf_cnc_processing,model_sf_cnc_processing,sf_base.group_sf_mrp_manager,1,1,1,0 -access_sf_cmm_program_group_sf_mrp_user_group_sf_mrp_user,sf_cmm_program_group_sf_mrp_user,model_sf_cmm_program,sf_base.group_sf_mrp_user,1,0,0,0 +access_sf_cmm_program_group_sf_mrp_user,sf_cmm_program_group_sf_mrp_user,model_sf_cmm_program,sf_base.group_sf_mrp_user,1,0,0,0 access_sf_cmm_program_group_sf_mrp_manager,sf_cmm_program_group_sf_mrp_manager,model_sf_cmm_program,sf_base.group_sf_mrp_manager,1,0,0,0 +access_sf_cmm_program_admin,sf_cmm_program_admin,model_sf_cmm_program,base.group_system,1,1,1,1 access_sf_model_type_group_sf_mrp_user,sf_model_type,model_sf_model_type,sf_base.group_sf_mrp_user,1,0,0,0 access_sf_model_type_admin,sf_model_type_admin,model_sf_model_type,base.group_system,1,1,1,0 access_sf_model_type_manager,sf_model_type,model_sf_model_type,sf_base.group_sf_mrp_manager,1,1,1,0 diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 55dbbe33..b65dfb2f 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -62,24 +62,25 @@ - - - progress,pending_cam,pending_processing,pending_era_cam,completed,done + + + + confirmed,pending_cam,progress,done - - + - + + + - - @@ -254,7 +255,9 @@ - {'invisible': [('schedule_state', '=', '未排')]} + {'invisible': ['|',('schedule_state', '=', '未排'),('workorder_ids', '=', + [])]} + @@ -279,8 +282,10 @@ - @@ -291,7 +296,8 @@ + groups="sf_base.group_sf_mrp_user" + confirm="是否确认完成?"/> - - - - + + + + + groups="sf_base.group_sf_mrp_user" confirm="是否确认完成?"/> - + @@ -21,6 +21,12 @@ + + + + + + @@ -31,7 +37,7 @@ - + {'invisible': ['|', '|', '|','|','|', ('production_state','in', ('draft', @@ -85,7 +91,7 @@ current [('state', '!=', 'cancel'),('schedule_state', '=', '已排')] - {'search_default_workcenter_id': active_id} + {'search_default_product': 1, 'search_default_workcenter_id': active_id} 没有工单要做! @@ -118,6 +124,7 @@ + @@ -129,7 +136,7 @@ - @@ -152,7 +159,12 @@ + attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done')]}"/> + + + + + @@ -171,8 +183,11 @@ - + + + 计划加工时间 @@ -204,20 +219,17 @@ + attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/> - - - - - + attrs='{"invisible": [("routing_type","in",("获取CNC加工程序","切割","装夹预调"))]}'/> + + + + + + @@ -230,23 +242,6 @@ - - - - - - - - - - - - - - - @@ -282,20 +277,26 @@ + + + + + + + + + + - - - - @@ -465,7 +466,10 @@ - + @@ -478,13 +482,13 @@ - - - - - - - + + + + + + + @@ -497,11 +501,11 @@ - - + + - @@ -511,8 +515,10 @@ + + - + @@ -520,20 +526,12 @@ - + - - - - - - - - - - + + @@ -563,9 +561,6 @@ - - - @@ -584,6 +579,17 @@ + + custom.workorder.search + mrp.workorder + + + + + + + + 工单 ir.actions.act_window @@ -703,7 +709,7 @@ - + 、 工件配送 sf.workpiece.delivery diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py index 78a0c05a..f32dedea 100644 --- a/sf_mrs_connect/controllers/controllers.py +++ b/sf_mrs_connect/controllers/controllers.py @@ -24,82 +24,95 @@ class Sf_Mrs_Connect(http.Controller): ret = json.loads(datas) ret = json.loads(ret['result']) logging.info('下发编程单:%s' % ret) - is_delete_file = False - # 查询状态为进行中且类型为获取CNC加工程序的工单 - cnc_production = request.env['mrp.production'].with_user( - request.env.ref("base.user_admin")).search([('name', '=', ret['production_order_no'].split(',')[0])]) - cnc_program = request.env['mrp.production'].with_user( + productions = request.env['mrp.production'].with_user( request.env.ref("base.user_admin")).search( - [('programming_no', '=', cnc_production.programming_no), ('id', '!=', cnc_production.id)]) - if cnc_production.workorder_ids.filtered(lambda a: a.routing_type == 'CNC加工').cnc_ids: - is_delete_file = True - cnc_production.workorder_ids.filtered( - lambda a1: a1.routing_type == 'CNC加工').cnc_ids.sudo().unlink() - request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(cnc_production) - if cnc_program.workorder_ids.filtered(lambda c: c.routing_type == 'CNC加工').cnc_ids: - cnc_program.workorder_ids.filtered( - lambda c1: c1.routing_type == 'CNC加工').cnc_ids.sudo().unlink() - request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan(cnc_program) - # cnc_program = request.env['mrp.production'].with_user( - # request.env.ref("base.user_admin")).search([('programming_no', '=', cnc_production.programming_no)]) - logging.info('制造订单号:%s' % cnc_production.name) - if cnc_production: - # if ret['glb_file']: - # cnc_production.glb_file = base64.b64encode(ret['glb_file']) - # 拉取所有加工面的程序文件 - if is_delete_file is True: - program_path_tmp_r = os.path.join('/tmp', ret['folder_name'], 'return', 'R') - files_r = os.listdir(program_path_tmp_r) - if files_r: - for file_name in files_r: - file_path = os.path.join(program_path_tmp_r, file_name) - os.remove(file_path) - for r in ret['processing_panel']: + [('programming_no', '=', ret['programming_no'])]) + if productions: + # # 拉取所有加工面的程序文件 + for r in ret['processing_panel'].split(','): + program_path_tmp_r = os.path.join('/tmp', ret['folder_name'], 'return', r) + if os.path.exists(program_path_tmp_r): + files_r = os.listdir(program_path_tmp_r) + if files_r: + for file_name in files_r: + file_path = os.path.join(program_path_tmp_r, file_name) + os.remove(file_path) download_state = request.env['sf.cnc.processing'].with_user( request.env.ref("base.user_admin")).download_file_tmp( ret['folder_name'], r) - if download_state == 0: + if download_state is False: res['status'] = -2 - res['message'] = '制造订单号为%s的CNC程序文件从FTP拉取失败' % (cnc_production.name) + res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no']) return json.JSONEncoder().encode(res) - logging.info('创建cnc工单') - program_path_tmp = os.path.join('/tmp', ret['folder_name'], 'return', r) - # program_path_tmp = "C://Users//43484//Desktop//机企猫工作文档//其他//model_analysis" - files = os.listdir(program_path_tmp) - cnc_processing_arr = [] - for f in files: - program_path = os.path.join(program_path_tmp, f) - logging.info('cnc程序路径 :%s' % program_path) - if f.endswith(".doc"): - # 插入cmm程序数据 - cmm_program = request.env['sf.cmm.program'].with_user( - request.env.ref("base.user_admin")).cmm_program_create(ret, program_path, program_path_tmp) - cnc_processing = request.env['sf.cnc.processing'].with_user( - request.env.ref("base.user_admin")).cnc_processing_create(cnc_production, ret, program_path, - program_path_tmp) - logging.info('cnc_processing111:%s' % cnc_processing) - if cnc_processing: - cnc_processing_arr.append(cnc_processing._json_cnc_processing(cnc_processing)) - if (cnc_program and cnc_processing_arr) or (not cnc_program and cnc_processing_arr): - cnc_production.workorder_ids.filtered(lambda g: g.routing_type == '装夹预调').write( - {'processing_drawing': cnc_production.workorder_ids.filtered( - lambda g1: g1.routing_type == 'CNC加工').cnc_worksheet}) - if cnc_program and cnc_processing_arr: - cnc_program.write({'programming_state': '已编程', 'work_state': '已编程'}) - cnc_program.workorder_ids.filtered(lambda d: d.routing_type == '装夹预调').write( - {'processing_drawing': cnc_production.workorder_ids.filtered( - lambda d1: d1.routing_type == 'CNC加工').cnc_worksheet}) - cnc_program.workorder_ids.filtered(lambda b: b.routing_type == 'CNC加工').write( - {'cnc_ids': cnc_processing_arr, 'cnc_worksheet': cnc_production.workorder_ids.filtered( - lambda b1: b1.routing_type == 'CNC加工').cnc_worksheet}) - cnc_program |= cnc_production - if not cnc_program and cnc_processing_arr: - cnc_program = cnc_production - cnc_program_ids = [item.id for item in cnc_program] - workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search( - [('production_id', 'in', cnc_program_ids)]) - if workpiece_delivery: - workpiece_delivery.write({'is_cnc_program_down': True}) + for production in productions: + if not production.workorder_ids: + production.product_id.model_processing_panel = ret['processing_panel'] + production._create_workorder(ret) + # else: + # for panel in ret['processing_panel'].split(','): + # # 查询状态为进行中且工序类型为CNC加工的工单 + # cnc_workorder = production.workorder_ids.filtered( + # lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', + # 'cancel'] and ac.processing_panel == panel) + # if cnc_workorder: + # if cnc_workorder.cnc_ids: + # cnc_workorder.cmm_ids.sudo().unlink() + # cnc_workorder.cnc_ids.sudo().unlink() + # request.env['sf.cam.work.order.program.knife.plan'].sudo().unlink_cam_plan( + # production) + # # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test', + # # panel) + # program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel) + # logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel) + # files_panel = os.listdir(program_path_tmp_panel) + # if files_panel: + # for file in files_panel: + # file_extension = os.path.splitext(file)[1] + # logging.info('file_extension:%s' % file_extension) + # if file_extension.lower() == '.pdf': + # panel_file_path = os.path.join(program_path_tmp_panel, file) + # logging.info('panel_file_path:%s' % panel_file_path) + # cnc_workorder.write( + # {'cnc_ids': cnc_workorder.cnc_ids.sudo()._json_cnc_processing(panel, ret), + # 'cmm_ids': cnc_workorder.cmm_ids.sudo()._json_cmm_program(panel, ret), + # 'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) + # pre_workorder = production.workorder_ids.filtered( + # lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', + # 'cancel'] and ap.processing_panel == panel) + # if pre_workorder: + # pre_workorder.write( + # {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) + for panel in ret['processing_panel'].split(','): + # 查询状态为进行中且工序类型为CNC加工的工单 + cnc_workorder = productions.workorder_ids.filtered( + lambda ac: ac.routing_type == 'CNC加工' and ac.state not in ['progress', 'done', + 'cancel'] and ac.processing_panel == panel) + if cnc_workorder: + # program_path_tmp_panel = os.path.join('C://Users//43484//Desktop//fsdownload//test', + # panel) + program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel) + logging.info('program_path_tmp_panel:%s' % program_path_tmp_panel) + files_panel = os.listdir(program_path_tmp_panel) + if files_panel: + for file in files_panel: + file_extension = os.path.splitext(file)[1] + logging.info('file_extension:%s' % file_extension) + if file_extension.lower() == '.pdf': + panel_file_path = os.path.join(program_path_tmp_panel, file) + logging.info('panel_file_path:%s' % panel_file_path) + cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) + pre_workorder = productions.workorder_ids.filtered( + lambda ap: ap.routing_type == '装夹预调' and ap.state not in ['done', + 'cancel'] and ap.processing_panel == panel) + if pre_workorder: + pre_workorder.write( + {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())}) + productions.write({'programming_state': '已编程', 'work_state': '已编程'}) + cnc_program_ids = [item.id for item in productions] + workpiece_delivery = request.env['sf.workpiece.delivery'].sudo().search( + [('production_id', 'in', cnc_program_ids)]) + if workpiece_delivery: + workpiece_delivery.write({'is_cnc_program_down': True}) return json.JSONEncoder().encode(res) else: res = {'status': 0, 'message': '该制造订单暂未开始'} diff --git a/sf_mrs_connect/models/ftp_operate.py b/sf_mrs_connect/models/ftp_operate.py index 4cd5e3ba..3183c0f6 100644 --- a/sf_mrs_connect/models/ftp_operate.py +++ b/sf_mrs_connect/models/ftp_operate.py @@ -21,6 +21,23 @@ class FtpController(): except Exception: logging.info("ftp连接失败") + def file_exists_1(self, path): + # 检查文件是否存在于FTP服务器上 + try: + logging.info("path:%s" % path) + logging.info("dirname:%s" % os.path.dirname(path)) + directories = os.path.normpath(path).split(os.path.sep) + # 切换到上级目录 + for directory in directories: + if directory: + # 检查目录是否存在 + if (directory in ['NC']) or (directory not in ['home', 'ftp', 'ftp_root', 'NC']): + self.ftp.cwd(directory) + return os.path.basename(path) + except Exception as e: + logging.error(f"Error checking file: {e}") + return False + def file_exists(self, path): # 检查文件是否存在于FTP服务器上 try: @@ -32,8 +49,25 @@ class FtpController(): logging.error(f"Error checking file: {e}") return False - - + # 下载目录下的pdf文件(程序单) + def download_program_file(self, target_dir, serverdir): + if not os.path.exists(serverdir): + os.makedirs(serverdir) + try: + logging.info('FTP目录:%s' % target_dir) + logging.info("进入FTP目录 ") + remotenames = self.ftp.nlst() + logging.info('FTP目录文件:%s' % remotenames) + for file in remotenames: + server = os.path.join(serverdir, file) + if file.find(".pdf") != -1: + self.download_file(server, file) + return True + except: + return False + finally: + self.ftp.quit() + logging.info("ftp已关闭") # # 检测字符串的编码 # def detect_encoding(self, s): diff --git a/sf_plan/models/custom_plan.py b/sf_plan/models/custom_plan.py index fd1e231a..0e56bf22 100644 --- a/sf_plan/models/custom_plan.py +++ b/sf_plan/models/custom_plan.py @@ -19,7 +19,7 @@ class sf_production_plan(models.Model): ('done', '已排程'), ('processing', '加工中'), ('finished', '已完成') - ], string='工单状态', tracking=True) + ], string='状态', tracking=True) state_order = fields.Integer(compute='_compute_state_order', store=True) @@ -36,8 +36,8 @@ class sf_production_plan(models.Model): _order = 'state_order asc, write_date desc' - name = fields.Char(string='工单编号') - active = fields.Boolean(string='已归档', default=True) + name = fields.Char(string='制造订单') + # active = fields.Boolean(string='已归档', default=True) # selected = fields.Boolean(default=False) # order_number = fields.Char(string='订单号') order_deadline = fields.Datetime(string='订单交期') @@ -52,7 +52,7 @@ class sf_production_plan(models.Model): schedule_setting = fields.Selection([ ('reverse', '倒排'), ('positive', '顺排')], string='排程设置', default='reverse') product_id = fields.Many2one('product.product', '关联产品') - origin = fields.Char(string='订单号') + origin = fields.Char(string='销售订单') # # 加工时长 # process_time = fields.Float(string='加工时长', digits=(16, 2)) # 实际加工时长、实际开始时间、实际结束时间 @@ -101,17 +101,17 @@ class sf_production_plan(models.Model): # return super(sf_production_plan, self.with_context(active_test=False))._search( # args, offset, limit, order, count, access_rights_uid) - def archive(self): - """ - 归档 - """ - self.write({'active': False}) - - def unarchive(self): - """ - 取消归档 - """ - self.write({'active': True}) + # def archive(self): + # """ + # 归档 + # """ + # self.write({'active': False}) + # + # def unarchive(self): + # """ + # 取消归档 + # """ + # self.write({'active': True}) @api.model def get_import_templates(self): @@ -200,22 +200,23 @@ class sf_production_plan(models.Model): raise ValidationError("未选择生产线") else: workorder_id_list = record.production_id.workorder_ids.ids - if record.production_id.workorder_ids: - for item in record.production_id.workorder_ids: - if item.name == 'CNC加工': - item.date_planned_finished = datetime.now() + timedelta(days=100) - # item.date_planned_start = record.date_planned_start - item.date_planned_start = self.date_planned_start if self.date_planned_start else datetime.now() - record.sudo().production_id.plan_start_processing_time = item.date_planned_start - item.date_planned_finished = item.date_planned_start + timedelta( - minutes=record.env['mrp.routing.workcenter'].sudo().search( - [('name', '=', 'CNC加工')]).time_cycle) - item.duration_expected = record.env['mrp.routing.workcenter'].sudo().search( - [('name', '=', 'CNC加工')]).time_cycle - record.calculate_plan_time_before(item, workorder_id_list) - record.calculate_plan_time_after(item, workorder_id_list) - record.date_planned_start, record.date_planned_finished = \ - item.date_planned_start, item.date_planned_finished + if record.production_id: + if record.production_id.workorder_ids: + for item in record.production_id.workorder_ids: + if item.name == 'CNC加工': + item.date_planned_finished = datetime.now() + timedelta(days=100) + # item.date_planned_start = record.date_planned_start + item.date_planned_start = self.date_planned_start if self.date_planned_start else datetime.now() + record.sudo().production_id.plan_start_processing_time = item.date_planned_start + item.date_planned_finished = item.date_planned_start + timedelta( + minutes=record.env['mrp.routing.workcenter'].sudo().search( + [('name', '=', 'CNC加工')]).time_cycle) + item.duration_expected = record.env['mrp.routing.workcenter'].sudo().search( + [('name', '=', 'CNC加工')]).time_cycle + record.calculate_plan_time_before(item, workorder_id_list) + record.calculate_plan_time_after(item, workorder_id_list) + record.date_planned_start, record.date_planned_finished = \ + item.date_planned_start, item.date_planned_finished record.state = 'done' # record.production_id.schedule_state = '已排' record.sudo().production_id.schedule_state = '已排' @@ -231,12 +232,12 @@ class sf_production_plan(models.Model): # record.production_id.date_planned_start = record.date_planned_start # record.production_id.date_planned_finished = record.date_planned_finished record.sudo().production_id.production_line_id = record.production_line_id.id - record.sudo().production_id.workorder_ids.filtered( - lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.write( - {'production_line_id': record.production_line_id.id, - 'plan_start_processing_time': record.date_planned_start}) - else: - raise ValidationError("未找到工单") + if record.production_id.workorder_ids: + record.sudo().production_id.workorder_ids.filtered( + lambda b: b.routing_type == "装夹预调").workpiece_delivery_ids.write( + {'production_line_id': record.production_line_id.id, + 'plan_start_processing_time': record.date_planned_start}) + # record.date_planned_finished = record.date_planned_start + timedelta(days=3) # record.state = 'done' return { diff --git a/sf_plan/views/view.xml b/sf_plan/views/view.xml index 3b53efc2..c3e18840 100644 --- a/sf_plan/views/view.xml +++ b/sf_plan/views/view.xml @@ -17,18 +17,18 @@ decoration-danger="state == 'finished'"/> - + - - - - - + + + + + + + @@ -63,12 +63,12 @@ - + - + @@ -152,16 +152,27 @@ sf.production.plan + - - + + - - - - + + + + + + + + + + + + + + @@ -188,30 +199,41 @@ + + + - 开始时间: - + 销售订单号: + - 结束时间: - - - - 名称: + 制造订单号: + + 订单交期: + + + + 产品名称: + + 数量: - 状态: - + 计划开始时间: + + + + 计划结束时间: + @@ -246,6 +268,8 @@ ir.actions.act_window sf.production.plan tree,gantt,form + + {'search_default_draft': 1, 'display_complete': True} 销售经理查看自己的订单 - ['|',('user_id','=',user.id),('create_uid', '=',user.id)] + ['|','|',('user_id','=',user.id),('user_id', '=', False),('create_uid', '=',user.id)] @@ -74,7 +74,7 @@ 采购岗查看自己的订单 - ['|',('user_id','=',user.id),('create_uid', '=',user.id)] + ['|','|',('user_id','=',user.id),('user_id', '=', False),('create_uid', '=',user.id)] diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml index 85f5b1c8..eda8e59d 100644 --- a/sf_sale/views/purchase_order_view.xml +++ b/sf_sale/views/purchase_order_view.xml @@ -79,6 +79,13 @@ sf_base.group_purchase,sf_base.group_purchase_director + + + mrp.group_mrp_user,sf_base.group_purchase,sf_base.group_purchase_director + + + {'readonly': [('state', 'in', ['purchase'])]} diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py index 7eea666b..182141cf 100644 --- a/sf_tool_management/models/base.py +++ b/sf_tool_management/models/base.py @@ -581,6 +581,15 @@ class FunctionalToolAssembly(models.Model): active = fields.Boolean(string='已归档', default=True) + def action_open_reference1(self): + self.ensure_one() + return { + 'res_model': self._name, + 'type': 'ir.actions.act_window', + 'views': [[False, "form"]], + 'res_id': self.id, + } + def put_start_preset(self): self.search([('start_preset_bool', '=', True)]).write({'start_preset_bool': False}) self.write({ @@ -750,7 +759,8 @@ class FunctionalToolDismantle(models.Model): return 'GNDJ-CJD-%s-%s' % (datetime, num) functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, - domain=[('functional_tool_status', '!=', '已拆除')]) + domain=[('functional_tool_status', '!=', '已拆除'), + ('current_location', '=', '刀具房')]) tool_type_id = fields.Many2one('sf.functional.cutting.tool.model', string='功能刀具类型', store=True, compute='_compute_functional_tool_num') tool_groups_id = fields.Many2one('sf.tool.groups', '刀具组', compute='_compute_functional_tool_num', store=True) @@ -934,13 +944,11 @@ class FunctionalToolDismantle(models.Model): functional_tool_assembly = self.functional_tool_id.functional_tool_name_id if self.scrap_boolean: # 刀柄报废 入库到Scrap - lot.create_stock_quant(location, location_dest_scrap_ids[-1], functional_tool_assembly.id, code, - functional_tool_assembly, functional_tool_assembly.tool_groups_id) + lot.create_stock_quant(location, location_dest_scrap_ids[-1], False, code, False, False) lot.tool_material_status = '报废' else: # 刀柄不报废 入库到刀具房 - lot.create_stock_quant(location, location_dest, functional_tool_assembly.id, code, - functional_tool_assembly, functional_tool_assembly.tool_groups_id) + lot.create_stock_quant(location, location_dest, False, code, False, False) lot.tool_material_status = '可用' # ==============功能刀具[报废]拆解================ diff --git a/sf_tool_management/models/functional_tool.py b/sf_tool_management/models/functional_tool.py index 34f26ff6..18f0a466 100644 --- a/sf_tool_management/models/functional_tool.py +++ b/sf_tool_management/models/functional_tool.py @@ -387,6 +387,22 @@ class StockMoveLine(models.Model): names = categories._search([], order=order, access_rights_uid=SUPERUSER_ID) return categories.browse(names) + def action_open_reference1(self): + self.ensure_one() + if self.functional_tool_name_id: + action = self.functional_tool_name_id.action_open_reference1() + return action + elif self.move_id: + action = self.move_id.action_open_reference() + if action['res_model'] != 'stock.move': + return action + return { + 'res_model': self._name, + 'type': 'ir.actions.act_window', + 'views': [[False, "form"]], + 'res_id': self.id, + } + class RealTimeDistributionOfFunctionalTools(models.Model): _name = 'sf.real.time.distribution.of.functional.tools' @@ -404,8 +420,8 @@ class RealTimeDistributionOfFunctionalTools(models.Model): side_shelf_num = fields.Integer(string='线边刀库数量', compute='_compute_stock_num', store=True) on_tool_stock_num = fields.Integer(string='机内刀库数量', compute='_compute_stock_num', store=True) tool_stock_total = fields.Integer(string='当前库存量', compute='_compute_tool_stock_total', store=True) - min_stock_num = fields.Integer('最低库存量') - max_stock_num = fields.Integer('最高库存量') + min_stock_num = fields.Integer('最低库存量', tracking=True) + max_stock_num = fields.Integer('最高库存量', tracking=True) batch_replenishment_num = fields.Integer('批次补货量', readonly=True, compute='_compute_batch_replenishment_num', store=True) unit = fields.Char('单位', default="件") diff --git a/sf_tool_management/models/maintenance_equipment.py b/sf_tool_management/models/maintenance_equipment.py index d308f1f1..48bc531f 100644 --- a/sf_tool_management/models/maintenance_equipment.py +++ b/sf_tool_management/models/maintenance_equipment.py @@ -3,6 +3,7 @@ import requests import logging from odoo import models, api, fields from odoo.exceptions import ValidationError +from datetime import datetime, timedelta from odoo.addons.sf_base.commons.common import Common @@ -101,7 +102,7 @@ class SfMaintenanceEquipment(models.Model): tool_install_time = {'Nomal': '正常', 'Warning': '报警'} equipment_tool_id.write({ 'functional_tool_name_id': functional_tool_id.id, - 'tool_install_time': time + 'tool_install_time': time - timedelta(hours=8) }) if functional_tool_id.current_location != '机内刀库': # 对功能刀具进行移动到生产线 diff --git a/sf_tool_management/models/tool_material_search.py b/sf_tool_management/models/tool_material_search.py index af8833fb..ededd64f 100644 --- a/sf_tool_management/models/tool_material_search.py +++ b/sf_tool_management/models/tool_material_search.py @@ -25,7 +25,8 @@ class ToolMaterial(models.Model): have_been_used_num = fields.Integer('在用数量', compute='_compute_number', store=True) scrap_num = fields.Integer('报废数量', compute='_compute_number', store=True) - barcode_ids = fields.One2many('stock.lot', 'tool_material_search_id', string='序列号', readonly=True) + barcode_ids = fields.One2many('stock.lot', 'tool_material_search_id', string='序列号', readonly=True, + domain=[('tool_material_status', '!=', '未入库')]) @api.depends('product_id.stock_quant_ids.quantity') def _compute_number(self): @@ -46,8 +47,6 @@ class ToolMaterial(models.Model): record.scrap_num = scrap_num record.number = usable_num + have_been_used_num + scrap_num - - @api.model def _read_group_cutting_tool_material_id(self, categories, domain, order): cutting_tool_material_id = categories._search([], order=order, access_rights_uid=SUPERUSER_ID) diff --git a/sf_tool_management/views/functional_tool_views.xml b/sf_tool_management/views/functional_tool_views.xml index 283eb2cc..6fde82a0 100644 --- a/sf_tool_management/views/functional_tool_views.xml +++ b/sf_tool_management/views/functional_tool_views.xml @@ -477,7 +477,8 @@ 功能刀具出入库记录 stock.move.line - + @@ -531,7 +532,7 @@ ref="sf_tool_management.sf_inbound_and_outbound_records_of_functional_tools_view_tree"/> - [('functional_tool_name', '!=', False)] + [('rfid', '!=', ''),('functional_tool_name', '!=', '')] diff --git a/sf_tool_management/views/tool_material_search.xml b/sf_tool_management/views/tool_material_search.xml index 8a740676..ef6209f3 100644 --- a/sf_tool_management/views/tool_material_search.xml +++ b/sf_tool_management/views/tool_material_search.xml @@ -63,6 +63,7 @@ + diff --git a/sf_warehouse/models/model.py b/sf_warehouse/models/model.py index 34fa57fe..ee212b2a 100644 --- a/sf_warehouse/models/model.py +++ b/sf_warehouse/models/model.py @@ -557,8 +557,10 @@ class SfStockMoveLine(models.Model): _name = 'stock.move.line' _inherit = ['stock.move.line', 'printing.utils'] + stock_lot_name = fields.Char('序列号名称', related='lot_id.name') current_location_id = fields.Many2one( - 'sf.shelf.location', string='当前货位', compute='_compute_current_location_id', store=True) + 'sf.shelf.location', string='当前货位', compute='_compute_current_location_id', store=True, readonly=False, + domain="[('product_id', '=', product_id),'|',('product_sn_id.name', '=', stock_lot_name),('product_sn_ids.lot_id.name','=',stock_lot_name)]") # location_dest_id = fields.Many2one('stock.location', string='目标库位') location_dest_id_product_type = fields.Many2many(related='location_dest_id.product_type') location_dest_id_value = fields.Integer(compute='_compute_location_dest_id_value', store=True) @@ -622,6 +624,11 @@ class SfStockMoveLine(models.Model): if not qr_code_data: raise UserError("没有找到二维码数据。") lot_name = self.lot_name + + # 增加"当为坯料时,只打印序列号的前面部分" + if self.lot_name: # 确保 lot_name 存在 + if self.product_id.categ_id.name == '坯料': + lot_name = lot_name.split('[', 1)[0] # host = "192.168.50.110" # 可以根据实际情况修改 # port = 9100 # 可以根据实际情况修改 @@ -851,7 +858,9 @@ class SfStockMoveLine(models.Model): def compute_destination_location_id(self): for record in self: obj = self.env['sf.shelf.location'].search([('name', '=', - self.destination_location_id.name)]) + record.destination_location_id.name)]) + if obj and obj.product_id and obj.product_id != record.product_id: + raise ValidationError('目标货位【%s】已被【%s】产品占用!' % (obj.code, obj.product_id)) if record.lot_id: if record.product_id.tracking == 'serial': shelf_location_obj = self.env['sf.shelf.location'].search( @@ -864,7 +873,7 @@ class SfStockMoveLine(models.Model): if obj: obj.product_sn_id = record.lot_id.id elif record.product_id.tracking == 'lot': - self.put_shelf_location(record) + record.put_shelf_location(record) if not obj.product_id: obj.product_id = record.product_id.id else: @@ -975,15 +984,26 @@ class SfStockPicking(models.Model): # 调用入库方法进行入库刀货位 line.compute_destination_location_id() else: - # 对除刀柄之外的刀具物料进行 目标货位必填校验 + # 对除刀柄之外的刀具物料入库到刀具房进行 目标货位必填校验 if self.location_dest_id.name == '刀具房' and line.product_id.cutting_tool_material_id.name not in ( '刀柄', False): raise ValidationError('请选择【%s】产品的目标货位!' % line.product_id.name) + if line.current_location_id: + # 对货位的批次产品进行出货 + line.put_shelf_location(line) + if line.current_location_id: + # 按序列号管理的产品 if line.current_location_id.product_sn_id: line.current_location_id.product_sn_id = False # line.current_location_id.location_status = '空闲' line.current_location_id.product_num = 0 + line.current_location_id.product_id = False + else: + # 对除刀柄之外的刀具物料从刀具房出库进行 当前货位必填校验 + if self.location_id.name == '刀具房' and line.product_id.cutting_tool_material_id.name not in ( + '刀柄', False): + raise ValidationError('请选择【%s】产品的当前货位!' % line.product_id.name) # 对入库作业的刀柄和托盘进行Rfid绑定校验 for move in self.move_ids: @@ -1110,6 +1130,7 @@ class CustomStockMove(models.Model): 采购入库扫码绑定Rfid码 """ for record in self: + logging.info('Rfid:%s' % barcode) if record: lot = self.env['stock.lot'].sudo().search([('rfid', '=', barcode)]) if lot: @@ -1121,7 +1142,9 @@ class CustomStockMove(models.Model): '该Rfid【%s】已经被序列号为【%s】的【%s】物料所占用!' % (barcode, lot.name, material)) if '刀柄' in (record.product_id.cutting_tool_material_id.name or '') or '托盘' in ( record.product_id.fixture_material_id.name or ''): + logging.info('开始录入Rfid:%s' % record.move_line_nosuggest_ids) for move_line_nosuggest_id in record.move_line_nosuggest_ids: + logging.info('录入的记录%s , Rfid:%s' % (move_line_nosuggest_id, move_line_nosuggest_id.rfid)) if move_line_nosuggest_id.rfid: if move_line_nosuggest_id.rfid == barcode: if record.product_id.cutting_tool_material_id.name: @@ -1130,7 +1153,9 @@ class CustomStockMove(models.Model): raise ValidationError('该托盘的Rfid已经录入,请勿重复录入!!!') else: line_id = int(re.sub(r"\D", "", str(move_line_nosuggest_id.id))) - self.env['stock.move.line'].sudo().search([('id', '=', line_id)]).write({'rfid': barcode}) + res = self.env['stock.move.line'].sudo().search([('id', '=', line_id)]).write( + {'rfid': barcode}) + logging.info('Rfid是否录入:%s' % res) move_line_nosuggest_id.rfid = barcode break else: @@ -1184,6 +1209,12 @@ class CustomStockMove(models.Model): # todo 待控制 if not lot_name: raise ValidationError("请先分配序列号") + + # 增加"当为坯料时,只打印序列号的前面部分" + if record.lot_name: # 确保 lot_name 存在 + if record.product_id.categ_id.name == '坯料': + lot_name = lot_name.split('[', 1)[0] + # host = "192.168.50.110" # 可以根据实际情况修改 # port = 9100 # 可以根据实际情况修改 diff --git a/sf_warehouse/views/change_stock_move_views.xml b/sf_warehouse/views/change_stock_move_views.xml index a77b916c..29d09926 100644 --- a/sf_warehouse/views/change_stock_move_views.xml +++ b/sf_warehouse/views/change_stock_move_views.xml @@ -7,16 +7,18 @@ - + + - - - - - + + + + + @@ -55,6 +57,7 @@ + diff --git a/web_gantt/static/src/js/gantt_row.js b/web_gantt/static/src/js/gantt_row.js index 420e3ad5..fb7ffab7 100644 --- a/web_gantt/static/src/js/gantt_row.js +++ b/web_gantt/static/src/js/gantt_row.js @@ -592,8 +592,23 @@ var GanttRow = Widget.extend({ }); pill.decorations = pillDecorations; - if (self.colorField) { - pill._color = self._getColor(pill[self.colorField]); + // let isDelay = false + // if(pill.state != 'processing' && pill.state != 'finished') { // 判断待加工 + // isDelay = pill.order_deadline.isBefore(new Date()) + // } + pill.exState = '' + if (self.colorField){ + // console.log(self.colorField, self, pill, '颜色') + // pill._color = self._getColor(pill[self.colorField]); + // 设置pill背景颜色2 修改时间2024年6月25日17:09:43 + let isDelay = false + if(pill.state != 'processing' && pill.state != 'finished') { // 判断待加工 + isDelay = pill.order_deadline.isBefore(new Date()) + } + if(isDelay) { + pill.disableDragdrop = true + } + pill._color = self._getColor2(isDelay ? 'delay' : pill.state); } if (self.progressField) { @@ -613,6 +628,13 @@ var GanttRow = Widget.extend({ } return 0; }, + _getColor2 (state) { + return { + 'finished': 'ccc', + 'delay': 9, + 'processing': 13 // 绿色 + }[state] + }, /** * Get context to evaluate decoration * @@ -867,10 +889,11 @@ var GanttRow = Widget.extend({ if ($pill.hasClass('ui-draggable-dragging')) { return; } - var self = this; var pill = _.findWhere(this.pills, { id: $pill.data('id') }); - + if(pill.state == 'finished'){ // 已完成状态不能拖拽 + return; + } // DRAGGABLE if (this.options.canEdit && !pill.disableStartResize && !pill.disableStopResize && !this.isGroup) { diff --git a/web_gantt/static/src/scss/web_gantt.scss b/web_gantt/static/src/scss/web_gantt.scss index 7dbca3e3..bc48bee5 100644 --- a/web_gantt/static/src/scss/web_gantt.scss +++ b/web_gantt/static/src/scss/web_gantt.scss @@ -754,3 +754,7 @@ left: -0.5 * $o-connector-creator-bullet-diameter; } } + +.o_gantt_view .o_gantt_row_nogroup .o_gantt_pill.o_gantt_color_ccc { + background-color: #ccc; +} \ No newline at end of file
没有工单要做! @@ -118,6 +124,7 @@ + @@ -129,7 +136,7 @@ - @@ -152,7 +159,12 @@ + attrs="{'invisible': ['|','|',('routing_type','!=','装夹预调'),('is_delivery','=',True),('state','!=','done')]}"/> + + + + +