diff --git a/jikimo_printing/__init__.py b/jikimo_printing/__init__.py new file mode 100644 index 00000000..a0fdc10f --- /dev/null +++ b/jikimo_printing/__init__.py @@ -0,0 +1,2 @@ +# -*- coding: utf-8 -*- +from . import models diff --git a/jikimo_printing/__manifest__.py b/jikimo_printing/__manifest__.py new file mode 100644 index 00000000..e6496401 --- /dev/null +++ b/jikimo_printing/__manifest__.py @@ -0,0 +1,18 @@ +# -*- coding: utf-8 -*- +{ + 'name': '机企猫 打印模块', + 'version': '1.0', + 'summary': """ 包含机台二维码,程序单打印等 """, + 'author': '机企猫', + 'website': 'https://www.jikimo.com', + 'category': '机企猫', + 'depends': ['sf_manufacturing', 'sf_maintenance', 'base_report_to_printer'], + 'data': [ + 'views/maintenance_views.xml', + ], + + 'application': True, + 'installable': True, + 'auto_install': False, + 'license': 'LGPL-3', +} diff --git a/jikimo_printing/models/__init__.py b/jikimo_printing/models/__init__.py new file mode 100644 index 00000000..79d24db8 --- /dev/null +++ b/jikimo_printing/models/__init__.py @@ -0,0 +1,5 @@ +# -*- coding: utf-8 -*- +from . import jikimo_printing +from . import maintenance_printing +from . import workorder_printing + diff --git a/jikimo_printing/models/jikimo_printing.py b/jikimo_printing/models/jikimo_printing.py new file mode 100644 index 00000000..738c90b5 --- /dev/null +++ b/jikimo_printing/models/jikimo_printing.py @@ -0,0 +1,56 @@ +from io import BytesIO +import qrcode +from reportlab.pdfgen import canvas +from reportlab.lib.pagesizes import A4 +from PIL import Image +from reportlab.lib.utils import ImageReader +from odoo import models, fields, api + +class JikimoPrinting(models.AbstractModel): + _name = 'jikimo.printing' + + def print_qr_code(self, data): + """ + 打印二维码 + """ + # 生成二维码 + qr = qrcode.QRCode(version=1, box_size=10, border=5) + qr.add_data(data) + qr.make(fit=True) + qr_image = qr.make_image(fill_color="black", back_color="white") + + # 将PIL Image转换为reportlab可用的格式 + temp_image = BytesIO() + qr_image.save(temp_image, format="PNG") + temp_image.seek(0) + + # 创建PDF + pdf_buffer = BytesIO() + c = canvas.Canvas(pdf_buffer, pagesize=A4) + + # 计算位置 + a4_width, a4_height = A4 + qr_width = 200 + qr_height = 200 + x = (a4_width - qr_width) / 2 + y = (a4_height - qr_height) / 2 + + # 直接从BytesIO绘制图片 + c.drawImage(ImageReader(Image.open(temp_image)), x, y, width=qr_width, height=qr_height) + c.save() + + # 获取PDF内容并打印 + pdf_content = pdf_buffer.getvalue() + printer = self.env['printing.printer'].get_default() + printer.print_document(report=None, content=pdf_content, doc_format='pdf') + + # 清理资源 + pdf_buffer.close() + temp_image.close() + + def print_pdf(self, pdf_data): + """ + 打印PDF + """ + printer = self.env['printing.printer'].get_default() + printer.print_document(report=None, content = pdf_data, doc_format='pdf') \ No newline at end of file diff --git a/jikimo_printing/models/maintenance_printing.py b/jikimo_printing/models/maintenance_printing.py new file mode 100644 index 00000000..641c0af4 --- /dev/null +++ b/jikimo_printing/models/maintenance_printing.py @@ -0,0 +1,69 @@ +from odoo import models, fields, api + +class MaintenancePrinting(models.Model): + _inherit = 'maintenance.equipment' + + def print_single_method(self): + + print('self.name========== %s' % self.name) + self.ensure_one() + # maintenance_equipment_id = self.id + # # host = "192.168.50.110" # 可以根据实际情况修改 + # # port = 9100 # 可以根据实际情况修改 + + # # 获取默认打印机配置 + # printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1) + # if not printer_config: + # raise UserError('请先配置打印机') + # host = printer_config.printer_id.ip_address + # port = printer_config.printer_id.port + # self.print_qr_code(maintenance_equipment_id, host, port) + + # 切换成A4打印机 + + try: + self.env['jikimo.printing'].print_qr_code(self.id) + except Exception as e: + raise UserError(f"打印失败: {str(e)}") + + + # def generate_zpl_code(self, code): + # """生成ZPL代码用于打印二维码标签 + # Args: + # code: 需要编码的内容 + # Returns: + # str: ZPL指令字符串 + # """ + # zpl_code = "^XA\n" # 开始ZPL格式 + + # # 设置打印参数 + # zpl_code += "^LH0,0\n" # 设置标签起始位置 + # zpl_code += "^CI28\n" # 设置中文编码 + # zpl_code += "^PW400\n" # 设置打印宽度为400点 + # zpl_code += "^LL300\n" # 设置标签长度为300点 + + # # 打印标题 + # zpl_code += "^FO10,20\n" # 设置标题位置 + # zpl_code += "^A0N,30,30\n" # 设置字体大小 + # zpl_code += "^FD机床二维码^FS\n" # 打印标题文本 + + # # 打印二维码 + # zpl_code += "^FO50,60\n" # 设置二维码位置 + # zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8 + # zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容 + + # # 打印编码文本 + # zpl_code += "^FO50,220\n" # 设置编码文本位置 + # zpl_code += "^A0N,25,25\n" # 设置字体大小 + # zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本 + + # # 打印日期 + # zpl_code += "^FO50,260\n" + # zpl_code += "^A0N,20,20\n" + # zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n" + + # zpl_code += "^PQ1\n" # 打印1份 + # zpl_code += "^XZ\n" # 结束ZPL格式 + + # return zpl_code + diff --git a/sf_manufacturing/models/workorder_printer.py b/jikimo_printing/models/workorder_printing.py similarity index 83% rename from sf_manufacturing/models/workorder_printer.py rename to jikimo_printing/models/workorder_printing.py index fde7f113..97c857ca 100644 --- a/sf_manufacturing/models/workorder_printer.py +++ b/jikimo_printing/models/workorder_printing.py @@ -1,8 +1,6 @@ import logging -from io import BytesIO from odoo import models, fields, api -from odoo.exceptions import UserError _logger = logging.getLogger(__name__) @@ -21,9 +19,7 @@ class MrpWorkorder(models.Model): if pdf_data: try: # 执行打印 - printer = self.env['printing.printer'].get_default() - printer.print_document(report=None, content = pdf_data, doc_format='pdf') - + self.env['jikimo.printing'].print_pdf(pdf_data) wo.production_id.product_id.is_print_program = True _logger.info(f"工单 {wo.name} 的PDF已成功打印") diff --git a/jikimo_printing/views/maintenance_views.xml b/jikimo_printing/views/maintenance_views.xml new file mode 100644 index 00000000..b02f5b60 --- /dev/null +++ b/jikimo_printing/views/maintenance_views.xml @@ -0,0 +1,19 @@ + + + + sf_equipment.form + maintenance.equipment + + + + + + + + + \ No newline at end of file diff --git a/jikimo_purchase_request/models/mrp_production.py b/jikimo_purchase_request/models/mrp_production.py index 4ae6bd61..ff352ff6 100644 --- a/jikimo_purchase_request/models/mrp_production.py +++ b/jikimo_purchase_request/models/mrp_production.py @@ -9,7 +9,7 @@ class MrpProduction(models.Model): @api.depends('state') def _compute_pr_mp_count(self): for item in self: - pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)]) + pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')]) if pr_ids: item.pr_mp_count = len(pr_ids) else: diff --git a/jikimo_purchase_request/wizard/purchase_request_line_make_purchase_order.py b/jikimo_purchase_request/wizard/purchase_request_line_make_purchase_order.py index 3c997f76..83bf1f39 100644 --- a/jikimo_purchase_request/wizard/purchase_request_line_make_purchase_order.py +++ b/jikimo_purchase_request/wizard/purchase_request_line_make_purchase_order.py @@ -100,6 +100,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel): def check_group(self, request_lines): # 去掉合并必须同一采购组的限制 pass + class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel): diff --git a/jikimo_work_reporting_api/__manifest__.py b/jikimo_work_reporting_api/__manifest__.py index 502ce9d5..c5567c41 100644 --- a/jikimo_work_reporting_api/__manifest__.py +++ b/jikimo_work_reporting_api/__manifest__.py @@ -8,7 +8,6 @@ 'category': 'sf', 'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'], 'data': [ - ], 'application': True, diff --git a/jikimo_work_reporting_api/controllers/main.py b/jikimo_work_reporting_api/controllers/main.py index 04535be0..9bafb01c 100644 --- a/jikimo_work_reporting_api/controllers/main.py +++ b/jikimo_work_reporting_api/controllers/main.py @@ -2,34 +2,51 @@ import json from odoo import http from odoo.http import request from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_nc_files +from odoo.addons.sf_base.decorators.api_log import api_log class MainController(http.Controller): @http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*') + @api_log('人工线下加工编程文件传输', requester='报工系统') def manual_download_program(self): """ 人工线下加工传输编程文件 """ data = json.loads(request.httprequest.data) - maintenance_equipment_name = data.get('maintenance_equipment_name') + maintenance_equipment_id = data.get('maintenance_equipment_id') model_id = data.get('model_id') - if not maintenance_equipment_name or not model_id: + if not maintenance_equipment_id or not model_id: return {'code': 400, 'message': '参数错误'} - maintenance_equipment = request.env['maintenance.equipment'].sudo().search([('name', '=', maintenance_equipment_name)], limit=1) + try: + maintenance_equipment_id = int(maintenance_equipment_id) + model_id = int(model_id) + except Exception as e: + return {'code': 400, 'message': '参数类型错误'} + maintenance_equipment = request.env['maintenance.equipment'].sudo().search( + [('id', '=', maintenance_equipment_id), ('category_id.equipment_type', '=', '机床')], + limit=1 + ) if not maintenance_equipment: - return {'code': 400, 'message': '机床不存在'} + return {'code': 400, 'message': '机台不存在,请扫描正确的机台二维码'} + product = request.env['product.template'].sudo().search([('model_id', '=', model_id)], limit=1) + if not product: + return {'code': 400, 'message': '请扫描正确的图纸'} # 获取刀具组 tool_groups_id = request.env['sf.tool.groups'].sudo().search([('equipment_ids', 'in', maintenance_equipment.id)], limit=1) if not tool_groups_id: return {'code': 400, 'message': '刀具组不存在'} ftp_resconfig = request.env['res.config.settings'].sudo().get_values() + if not ftp_resconfig['ftp_host'] or not ftp_resconfig['ftp_port'] or not ftp_resconfig['ftp_user'] or not ftp_resconfig['ftp_password']: + return {'code': 400, 'message': '编程文件FTP配置错误'} source_ftp_info = { 'host': ftp_resconfig['ftp_host'], 'port': int(ftp_resconfig['ftp_port']), 'username': ftp_resconfig['ftp_user'], 'password': ftp_resconfig['ftp_password'] } + if not maintenance_equipment.ftp_host or not maintenance_equipment.ftp_port or not maintenance_equipment.ftp_username or not maintenance_equipment.ftp_password: + return {'code': 400, 'message': '机台FTP配置错误'} target_ftp_info = { 'host': maintenance_equipment.ftp_host, 'port': int(maintenance_equipment.ftp_port), @@ -37,12 +54,17 @@ class MainController(http.Controller): 'password': maintenance_equipment.ftp_password } # 传输nc文件 - if transfer_nc_files( - source_ftp_info, - target_ftp_info, - '/' + str(model_id), - '/home/jikimo/testdir', - end_with=tool_groups_id.name + '-all.nc'): - return {'code': 200, 'message': 'success'} - else: - return {'code': 500, 'message': '传输失败'} + try: + result = transfer_nc_files( + source_ftp_info, + target_ftp_info, + '/' + str(model_id), + '/', + match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$' + ) + if result: + return {'code': 200, 'message': 'success'} + else: + return {'code': 404, 'message': '未找到编程文件'} + except Exception as e: + return {'code': 500, 'message': str(e)} diff --git a/sf_base/__init__.py b/sf_base/__init__.py index d76dba0b..ef113516 100644 --- a/sf_base/__init__.py +++ b/sf_base/__init__.py @@ -1,3 +1,4 @@ from . import models from . import commons from . import controllers +from . import decorators diff --git a/sf_base/__manifest__.py b/sf_base/__manifest__.py index b10c2630..14c61383 100644 --- a/sf_base/__manifest__.py +++ b/sf_base/__manifest__.py @@ -25,7 +25,7 @@ 'views/menu_fixture_view.xml', 'views/change_base_view.xml', 'views/Printer.xml', - + 'views/api_log_views.xml', ], 'demo': [ ], diff --git a/sf_base/controllers/controllers.py b/sf_base/controllers/controllers.py index 47c67322..c8447d4e 100644 --- a/sf_base/controllers/controllers.py +++ b/sf_base/controllers/controllers.py @@ -1,10 +1,11 @@ # -*- coding: utf-8 -*- import logging import json -import base64 +import logging from odoo import http from odoo.http import request +_logger = logging.getLogger(__name__) class Manufacturing_Connect(http.Controller): diff --git a/sf_base/decorators/__init__.py b/sf_base/decorators/__init__.py new file mode 100644 index 00000000..0f340289 --- /dev/null +++ b/sf_base/decorators/__init__.py @@ -0,0 +1 @@ +from . import api_log diff --git a/sf_base/decorators/api_log.py b/sf_base/decorators/api_log.py new file mode 100644 index 00000000..2838ec55 --- /dev/null +++ b/sf_base/decorators/api_log.py @@ -0,0 +1,59 @@ + +import functools +import json +import logging +from datetime import datetime +from odoo.http import request + +_logger = logging.getLogger(__name__) + +def api_log(name=None, requester=None): + """记录API请求日志的装饰器""" + def decorator(func): + @functools.wraps(func) + def wrapper(*args, **kwargs): + start_time = datetime.now() + + # 获取请求信息 + try: + # 获取请求数据 + request_data = json.loads(request.httprequest.data) if request.httprequest.data else {} + # 获取请求路径 + path = request.httprequest.path + # 获取请求方法 + method = request.httprequest.method + # 获取客户端IP + remote_addr = request.httprequest.remote_addr + + # 执行原始函数 + result = func(*args, **kwargs) + + # 计算响应时间 + end_time = datetime.now() + response_time = (end_time - start_time).total_seconds() + + # 创建日志记录 + log_vals = { + 'name': name or func.__name__, + 'path': path, + 'method': method, + 'request_data': json.dumps(request_data, ensure_ascii=False), + 'response_data': json.dumps(result, ensure_ascii=False), + 'remote_addr': remote_addr, + 'response_time': response_time, + 'status': result.get('code', 500), + 'requester': requester, + 'responser': '智能工厂' + } + + # 异步创建日志记录 + request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals) + + return result + + except Exception as e: + _logger.error(f"API日志记录失败: {str(e)}") + # 即使日志记录失败,也要返回原始结果 + return func(*args, **kwargs) + return wrapper + return decorator \ No newline at end of file diff --git a/sf_base/models/__init__.py b/sf_base/models/__init__.py index 82ed80c1..55d8d029 100644 --- a/sf_base/models/__init__.py +++ b/sf_base/models/__init__.py @@ -6,3 +6,4 @@ from . import functional_fixture from . import tool_other_features from . import basic_parameters_fixture from . import ir_sequence +from . import api_log diff --git a/sf_base/models/api_log.py b/sf_base/models/api_log.py new file mode 100644 index 00000000..4b630b88 --- /dev/null +++ b/sf_base/models/api_log.py @@ -0,0 +1,18 @@ +from odoo import models, fields, api + + +class ApiRequestLog(models.Model): + _name = 'api.request.log' + _description = '接口请求日志' + _order = 'id desc' + + name = fields.Char('接口名称') + path = fields.Char('请求路径') + method = fields.Char('请求方法') + request_data = fields.Text('请求数据') + response_data = fields.Text('响应数据') + remote_addr = fields.Char('客户端IP') + response_time = fields.Float('响应时间(秒)', digits=(16, 6)) + status = fields.Integer('状态码') + requester = fields.Char('请求方') + responser = fields.Char('响应方') diff --git a/sf_base/security/ir.model.access.csv b/sf_base/security/ir.model.access.csv index 8dd4a023..e98faa72 100644 --- a/sf_base/security/ir.model.access.csv +++ b/sf_base/security/ir.model.access.csv @@ -254,3 +254,6 @@ access_sf_machining_accuracy_admin,sf_machining_accuracy_admin,model_sf_machinin access_sf_embryo_redundancy,sf_embryo_redundancy,model_sf_embryo_redundancy,base.group_user,1,0,0,0 access_sf_embryo_redundancy_admin,sf_embryo_redundancy_admin,model_sf_embryo_redundancy,base.group_system,1,0,0,0 + +access_api_request_log_user,api.request.log.user,model_api_request_log,base.group_user,1,0,0,0 +access_api_request_log_admin,api.request.log.admin,model_api_request_log,base.group_system,1,1,1,1 \ No newline at end of file diff --git a/sf_base/views/api_log_views.xml b/sf_base/views/api_log_views.xml new file mode 100644 index 00000000..05389fc7 --- /dev/null +++ b/sf_base/views/api_log_views.xml @@ -0,0 +1,62 @@ + + + + api.request.log.tree + api.request.log + + + + + + + + + + + + + + + api.request.log.form + api.request.log + +
+ + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ + + API请求日志 + api.request.log + tree,form + + + +
\ No newline at end of file diff --git a/sf_dlm_management/models/sf_production_common.py b/sf_dlm_management/models/sf_production_common.py index 484295ae..637aeb26 100644 --- a/sf_dlm_management/models/sf_production_common.py +++ b/sf_dlm_management/models/sf_production_common.py @@ -33,6 +33,7 @@ class SfProductionProcessParameter(models.Model): if product_id: product_id.server_product_process_parameters_id = self.id else: + res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')]) self.env['product.template'].create({ 'detailed_type': 'service', 'name': product_name, @@ -42,6 +43,10 @@ class SfProductionProcessParameter(models.Model): 'sale_ok': True, # 可销售 'purchase_ok': True, # 可采购 'server_product_process_parameters_id': self.id, + 'seller_ids': [(0, 0, { + # 'delay': 1, + 'partner_id': res_partner.id, + 'price': 1, })], }) def create_work_center(self): diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml index c53c0dc4..672251e4 100644 --- a/sf_dlm_management/views/product_template_management_view.xml +++ b/sf_dlm_management/views/product_template_management_view.xml @@ -41,7 +41,7 @@ attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/> + attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"/> - - delivery.record.form.inherit.sf - mrp.workorder - - - - - - - - - - - - - - - - - - diff --git a/sf_machine_connect/views/machine_info_present.xml b/sf_machine_connect/views/machine_info_present.xml index 78571a47..7ac0db21 100644 --- a/sf_machine_connect/views/machine_info_present.xml +++ b/sf_machine_connect/views/machine_info_present.xml @@ -3,9 +3,9 @@ machine.info.form.inherit.sf mrp.workorder - + - + @@ -33,6 +33,15 @@ + + + + + + + + + diff --git a/sf_maintenance/models/__init__.py b/sf_maintenance/models/__init__.py index 4e925ec6..b177eb3c 100644 --- a/sf_maintenance/models/__init__.py +++ b/sf_maintenance/models/__init__.py @@ -4,4 +4,3 @@ from . import sf_maintenance_oee from . import sf_maintenance_logs from . import sf_equipment_maintenance_standards from . import sf_maintenance_requests -from . import maintenance_printer diff --git a/sf_maintenance/models/maintenance_printer.py b/sf_maintenance/models/maintenance_printer.py deleted file mode 100644 index b2c251aa..00000000 --- a/sf_maintenance/models/maintenance_printer.py +++ /dev/null @@ -1,92 +0,0 @@ -import qrcode -import base64 -from io import BytesIO -from odoo import models, fields, api - -class MaintenanceEquipment(models.Model): - _name = 'maintenance.equipment' - _inherit = ['maintenance.equipment', 'printing.utils'] - - qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code') - - @api.depends('name') - def _generate_qr_code(self): - for record in self: - # Generate QR code - qr = qrcode.QRCode( - version=1, - error_correction=qrcode.constants.ERROR_CORRECT_L, - box_size=10, - border=4, - ) - qr.add_data(record.name) - qr.make(fit=True) - qr_image = qr.make_image(fill_color="black", back_color="white") - - # Encode the image data in base64 - image_stream = BytesIO() - qr_image.save(image_stream, format="PNG") - encoded_image = base64.b64encode(image_stream.getvalue()) - - record.qr_code_image = encoded_image - - def print_single_method(self): - - print('self.name========== %s' % self.name) - self.ensure_one() - qr_code_data = self.qr_code_image - if not qr_code_data: - raise UserError("没有找到二维码数据。") - maintenance_equipment_name = self.name - # host = "192.168.50.110" # 可以根据实际情况修改 - # port = 9100 # 可以根据实际情况修改 - - # 获取默认打印机配置 - printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1) - if not printer_config: - raise UserError('请先配置打印机') - host = printer_config.printer_id.ip_address - port = printer_config.printer_id.port - self.print_qr_code(maintenance_equipment_name, host, port) - - - def generate_zpl_code(self, code): - """生成ZPL代码用于打印二维码标签 - Args: - code: 需要编码的内容 - Returns: - str: ZPL指令字符串 - """ - zpl_code = "^XA\n" # 开始ZPL格式 - - # 设置打印参数 - zpl_code += "^LH0,0\n" # 设置标签起始位置 - zpl_code += "^CI28\n" # 设置中文编码 - zpl_code += "^PW400\n" # 设置打印宽度为400点 - zpl_code += "^LL300\n" # 设置标签长度为300点 - - # 打印标题 - zpl_code += "^FO10,20\n" # 设置标题位置 - zpl_code += "^A0N,30,30\n" # 设置字体大小 - zpl_code += "^FD机床二维码^FS\n" # 打印标题文本 - - # 打印二维码 - zpl_code += "^FO50,60\n" # 设置二维码位置 - zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8 - zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容 - - # 打印编码文本 - zpl_code += "^FO50,220\n" # 设置编码文本位置 - zpl_code += "^A0N,25,25\n" # 设置字体大小 - zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本 - - # 打印日期 - zpl_code += "^FO50,260\n" - zpl_code += "^A0N,20,20\n" - zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n" - - zpl_code += "^PQ1\n" # 打印1份 - zpl_code += "^XZ\n" # 结束ZPL格式 - - return zpl_code - diff --git a/sf_maintenance/models/sf_maintenance.py b/sf_maintenance/models/sf_maintenance.py index b7c38916..1fdd4e3e 100644 --- a/sf_maintenance/models/sf_maintenance.py +++ b/sf_maintenance/models/sf_maintenance.py @@ -2,6 +2,8 @@ import json import base64 import logging +import qrcode +from io import BytesIO from datetime import timedelta import requests from odoo.addons.sf_base.commons.common import Common @@ -831,6 +833,29 @@ class SfMaintenanceEquipment(models.Model): ftp_username = fields.Char('FTP 用户名') ftp_password = fields.Char('FTP 密码') + qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code') + + @api.depends('name') + def _generate_qr_code(self): + for record in self: + # Generate QR code + qr = qrcode.QRCode( + version=1, + error_correction=qrcode.constants.ERROR_CORRECT_L, + box_size=10, + border=4, + ) + qr.add_data(record.id) + qr.make(fit=True) + qr_image = qr.make_image(fill_color="black", back_color="white") + + # Encode the image data in base64 + image_stream = BytesIO() + qr_image.save(image_stream, format="PNG") + encoded_image = base64.b64encode(image_stream.getvalue()) + + record.qr_code_image = encoded_image + class SfRobotAxisNum(models.Model): _name = 'sf.robot.axis.num' diff --git a/sf_maintenance/views/maintenance_views.xml b/sf_maintenance/views/maintenance_views.xml index 381319a6..f1207107 100644 --- a/sf_maintenance/views/maintenance_views.xml +++ b/sf_maintenance/views/maintenance_views.xml @@ -1055,11 +1055,6 @@ - diff --git a/sf_manufacturing/data/sf_work_individuation_page.xml b/sf_manufacturing/data/sf_work_individuation_page.xml index 0b23324d..cdb060aa 100644 --- a/sf_manufacturing/data/sf_work_individuation_page.xml +++ b/sf_manufacturing/data/sf_work_individuation_page.xml @@ -1,57 +1,72 @@ - - - PTD - 后置三元检测 - + WCP 工件装夹 + 10 ITD_PP 前置三元检测定位参数 - - - 2D_MD - 2D加工图纸 - - - QIS - 质检标准 + 20 WD 工件配送 + 30 CNC_P CNC程序 + 40 CMM_P CMM程序 + 50 - - MTI - 机床信息 - - - HDR - 下发记录 - - - ER - 异常记录 + + PTD + 后置三元检测 + 60 DCP 解除装夹 + 70 + + + 2D_MD + 2D加工图纸 + 80 + + + QIS + 质检标准 + 90 CMR 开料要求 + 100 + + MTI + 机床信息 + 110 + + + HDR + 下发记录 + 120 + + + ER + 异常记录 + 130 + + + diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py index 9bad61c6..bccaf634 100644 --- a/sf_manufacturing/models/__init__.py +++ b/sf_manufacturing/models/__init__.py @@ -18,4 +18,3 @@ from . import quick_easy_order from . import purchase_order from . import quality_check from . import purchase_request_line -from . import workorder_printer diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py index f73590bd..e82b1b40 100644 --- a/sf_manufacturing/models/mrp_production.py +++ b/sf_manufacturing/models/mrp_production.py @@ -924,6 +924,8 @@ class MrpProduction(models.Model): "bom_id": self[0].bom_id.id, "is_subcontract":True, }) + self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref( + 'sf_stock.stock_route_process_outsourcing').id)] for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items(): cur_request_line = request_line_list[0] cur_request_line['product_qty'] = len(request_line_list) @@ -943,6 +945,7 @@ class MrpProduction(models.Model): product_id_to_production_names[product_id] = [p.name for p in pd] sorted_workorders = None purchase_request_line = [] + all_workorders = [] for production in production_all: proc_workorders = [] process_parameter_workorder = self.env['mrp.workorder'].search( @@ -962,8 +965,10 @@ class MrpProduction(models.Model): # self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names) purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request( workorders, production) + all_workorders += workorders self._create_subcontract_purchase_request(purchase_request_line) - + for workorder in all_workorders: + workorder._compute_pr_mp_count() # 工单排序 def _reset_work_order_sequence1(self, k): for rec in self: @@ -1816,7 +1821,7 @@ class MrpProduction(models.Model): logging.info('update_programming_state error:%s' % e) raise UserError("更新编程单状态失败,请联系管理员") - model_id = fields.Char('模型id', related='product_id.model_id') + model_id = fields.Char('模型ID', related='product_id.model_id') # 编程记录 diff --git a/sf_manufacturing/models/mrp_routing_workcenter.py b/sf_manufacturing/models/mrp_routing_workcenter.py index c869f01f..6a2fb6f6 100644 --- a/sf_manufacturing/models/mrp_routing_workcenter.py +++ b/sf_manufacturing/models/mrp_routing_workcenter.py @@ -139,6 +139,8 @@ class ResMrpRoutingWorkcenter(models.Model): class WorkIndividuationPage(models.Model): _name = 'sf.work.individuation.page' + _order = 'sequence' code = fields.Char('编号') name = fields.Char('名称') + sequence = fields.Integer('序号') diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py index 9434651c..e18472d7 100644 --- a/sf_manufacturing/models/mrp_workcenter.py +++ b/sf_manufacturing/models/mrp_workcenter.py @@ -256,14 +256,12 @@ class ResWorkcenter(models.Model): date_planned_end), ('state', 'not in', ['draft', 'cancel'])]) - if plan_ids: - sum_qty = sum([p.product_qty for p in plan_ids]) - production_line_hour_capacity = self.production_line_hour_capacity - if sum_qty >= production_line_hour_capacity: - message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d件' % ( - production_line_hour_capacity, count) - raise UserError(message) - return False + sum_qty = sum([p.product_qty for p in plan_ids]) if plan_ids else count + production_line_hour_capacity = self.production_line_hour_capacity + if sum_qty > production_line_hour_capacity: + message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d件' % ( + production_line_hour_capacity, count) + raise UserError(message) return True diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py index f0e24398..a36e4b9d 100644 --- a/sf_manufacturing/models/mrp_workorder.py +++ b/sf_manufacturing/models/mrp_workorder.py @@ -20,6 +20,7 @@ from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController class ResMrpWorkOrder(models.Model): _inherit = 'mrp.workorder' _description = '工单' + _order = 'sequence' product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name') @@ -70,13 +71,16 @@ class ResMrpWorkOrder(models.Model): tracking=True) back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True) pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True) + @api.depends('state') def _compute_pr_mp_count(self): for item in self: if not item.is_subcontract: item.pr_mp_count = 0 continue - pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name),('is_subcontract','=','True')]) + pr_ids = self.env['purchase.request'].sudo().search( + [('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'), + ('state', '!=', 'rejected')]) if pr_ids: item.pr_mp_count = len(pr_ids) else: @@ -461,7 +465,9 @@ class ResMrpWorkOrder(models.Model): 采购请求 """ self.ensure_one() - pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '=', True)]) + pr_ids = self.env['purchase.request'].sudo().search( + [('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'), + ('state', '!=', 'rejected')]) action = { 'res_model': 'purchase.request', 'type': 'ir.actions.act_window', @@ -1808,7 +1814,7 @@ class ResMrpWorkOrder(models.Model): lazy=lazy ) - model_id = fields.Char('模型id', related='production_id.model_id') + model_id = fields.Char('模型ID', related='production_id.model_id') class CNCprocessing(models.Model): diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py index 2dd79adb..41637123 100644 --- a/sf_manufacturing/models/product_template.py +++ b/sf_manufacturing/models/product_template.py @@ -787,7 +787,7 @@ class ResProductMo(models.Model): glb_url = fields.Char('glb文件地址') area = fields.Float('表面积(m²)') auto_machining = fields.Boolean('自动化加工(模型识别)', default=False) - model_id = fields.Char('模型id') + model_id = fields.Char('模型ID') @api.depends('name') diff --git a/sf_manufacturing/models/sf_production_common.py b/sf_manufacturing/models/sf_production_common.py index 6285c133..bef5992b 100644 --- a/sf_manufacturing/models/sf_production_common.py +++ b/sf_manufacturing/models/sf_production_common.py @@ -7,6 +7,11 @@ from odoo.tools import str2bool class SfProductionProcessParameter(models.Model): _inherit = 'sf.production.process.parameter' + service_products = fields.Many2one( + 'product.template', + string='外协服务产品',compute='_compute_service_products',inverse='_inverse_service_products', + store=True + ) outsourced_service_products = fields.One2many( 'product.template', # 另一个模型的名称 'server_product_process_parameters_id', # 对应的 Many2one 字段名称 @@ -16,6 +21,25 @@ class SfProductionProcessParameter(models.Model): is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False) routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") + @api.depends('outsourced_service_products') + def _compute_service_products(self): + for record in self: + # 假设取第一条作为主明细 + record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False + + def _inverse_service_products(self): + for record in self: + if record.service_products: + # 确保关联关系正确 + record.outsourced_service_products = record.service_products.ids if record.service_products else False + else: + record.outsourced_service_products = False + def name_get(self): + result = [] + for record in self: + name = f"{record.process_id.name} - {record.name}" # 自定义显示格式 + result.append((record.id, name)) + return result @api.constrains('outsourced_service_products') def _validate_partner_limit(self): for record in self: @@ -35,17 +59,17 @@ class SfProductionProcessParameter(models.Model): else: record.is_product_button = False - def has_wksp_prefix(self,code): + def has_wksp_prefix(self): """ 判断字符串是否以WKSP开头(不区分大小写) :param text: 要检查的字符串 :return: True/False """ - return code.upper().startswith('WKSP') + return self.code.upper().startswith('101'+self.routing_id.code) @api.depends('outsourced_service_products','code') def _compute_is_delete_button(self): for record in self: - if record.outsourced_service_products and self.has_wksp_prefix(record.code): + if record.outsourced_service_products and record.has_wksp_prefix(): record.is_delete_button = False elif record.outsourced_service_products: record.is_delete_button = True diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml index 7a8591c0..ef4f6f25 100644 --- a/sf_manufacturing/views/mrp_production_addional_change.xml +++ b/sf_manufacturing/views/mrp_production_addional_change.xml @@ -110,12 +110,12 @@ + - @@ -455,7 +455,7 @@ - create_date desc + sequence,create_date desc delivery_warning == 'warning' delivery_warning == 'overdue' diff --git a/sf_manufacturing/views/mrp_routing_workcenter_view.xml b/sf_manufacturing/views/mrp_routing_workcenter_view.xml index 6dfe2c5b..6cd340ad 100644 --- a/sf_manufacturing/views/mrp_routing_workcenter_view.xml +++ b/sf_manufacturing/views/mrp_routing_workcenter_view.xml @@ -29,8 +29,8 @@ - - + +