Accept Merge Request #2007: (feature/6694 -> develop)
Merge Request: 增加工单下发时打印编程文件pdf的功能 Created By: @胡尧 Accepted By: @胡尧 URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2007?initial=true
This commit is contained in:
3
jikimo_work_reporting_api/__init__.py
Normal file
3
jikimo_work_reporting_api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import controllers
|
||||
from . import models
|
||||
18
jikimo_work_reporting_api/__manifest__.py
Normal file
18
jikimo_work_reporting_api/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
{
|
||||
'name': '机企猫 报工系统API',
|
||||
'version': '1.0.0',
|
||||
'summary': """ 机企猫 报工系统API """,
|
||||
'author': '机企猫',
|
||||
'website': 'https://xt.sf.jikimo.com',
|
||||
'category': 'sf',
|
||||
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
|
||||
'data': [
|
||||
|
||||
],
|
||||
|
||||
'application': True,
|
||||
'installable': True,
|
||||
'auto_install': False,
|
||||
'license': 'LGPL-3',
|
||||
}
|
||||
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from . import main
|
||||
42
jikimo_work_reporting_api/controllers/main.py
Normal file
42
jikimo_work_reporting_api/controllers/main.py
Normal file
@@ -0,0 +1,42 @@
|
||||
import json
|
||||
from odoo import http
|
||||
from odoo.http import request
|
||||
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_nc_files
|
||||
|
||||
class MainController(http.Controller):
|
||||
|
||||
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*')
|
||||
def manual_download_program(self):
|
||||
"""
|
||||
人工线下加工传输编程文件
|
||||
"""
|
||||
data = json.loads(request.httprequest.data)
|
||||
maintenance_equipment_name = data.get('maintenance_equipment_name')
|
||||
model_id = data.get('model_id')
|
||||
if not maintenance_equipment_name or not model_id:
|
||||
return {'code': 400, 'message': '参数错误'}
|
||||
maintenance_equipment = request.env['maintenance.equipment'].sudo().search([('name', '=', maintenance_equipment_name)], limit=1)
|
||||
if not maintenance_equipment:
|
||||
return {'code': 400, 'message': '机床不存在'}
|
||||
ftp_resconfig = request.env['res.config.settings'].sudo().get_values()
|
||||
source_ftp_info = {
|
||||
'host': ftp_resconfig['ftp_host'],
|
||||
'port': int(ftp_resconfig['ftp_port']),
|
||||
'username': ftp_resconfig['ftp_user'],
|
||||
'password': ftp_resconfig['ftp_password']
|
||||
}
|
||||
target_ftp_info = {
|
||||
'host': maintenance_equipment.ftp_host,
|
||||
'port': int(maintenance_equipment.ftp_port),
|
||||
'username': maintenance_equipment.ftp_username,
|
||||
'password': maintenance_equipment.ftp_password
|
||||
}
|
||||
# 传输nc文件
|
||||
if transfer_nc_files(
|
||||
source_ftp_info,
|
||||
target_ftp_info,
|
||||
'/' + str(model_id),
|
||||
'/home/jikimo/testdir'):
|
||||
return {'code': 200, 'message': 'success'}
|
||||
else:
|
||||
return {'code': 500, 'message': '传输失败'}
|
||||
1
jikimo_work_reporting_api/models/__init__.py
Normal file
1
jikimo_work_reporting_api/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
@@ -8,6 +8,7 @@ class Printer(models.Model):
|
||||
name = fields.Char(string='名称', required=True)
|
||||
ip_address = fields.Char(string='IP 地址', required=True)
|
||||
port = fields.Integer(string='端口', default=9100)
|
||||
type = fields.Selection([('zpl', 'ZPL'), ('normal', '普通')], string='类型', default='zpl')
|
||||
|
||||
|
||||
class TableStyle(models.Model):
|
||||
|
||||
@@ -2,7 +2,16 @@
|
||||
import time, datetime
|
||||
import hashlib
|
||||
from odoo import models
|
||||
from typing import Optional
|
||||
import socket
|
||||
import os
|
||||
import logging
|
||||
import qrcode
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.units import inch
|
||||
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
class Common(models.Model):
|
||||
_name = 'sf.sync.common'
|
||||
@@ -92,3 +101,120 @@ class PrintingUtils(models.AbstractModel):
|
||||
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
|
||||
# port = 9100 # 可以作为参数传入,或者在此配置
|
||||
self.send_to_printer(host, port, zpl_code)
|
||||
|
||||
|
||||
def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
|
||||
"""
|
||||
在PDF文件中添加二维码
|
||||
:param pdf_path: PDF文件路径
|
||||
:param content: 二维码内容
|
||||
:param buttom_text: 二维码下方文字
|
||||
:return: 是否成功
|
||||
"""
|
||||
if not os.path.exists(pdf_path):
|
||||
logging.warning(f'PDF文件不存在: {pdf_path}')
|
||||
return False
|
||||
|
||||
# 生成二维码
|
||||
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||
qr.add_data(str(content))
|
||||
qr.make(fit=True)
|
||||
qr_img = qr.make_image(fill_color="black", back_color="white")
|
||||
|
||||
# 保存二维码为临时文件
|
||||
qr_temp_path = '/tmp/qr_temp.png'
|
||||
qr_img.save(qr_temp_path)
|
||||
|
||||
# 创建一个临时PDF文件路径
|
||||
output_temp_path = '/tmp/output_temp.pdf'
|
||||
|
||||
try:
|
||||
# 使用reportlab创建一个新的PDF
|
||||
|
||||
|
||||
# 注册中文字体
|
||||
font_paths = [
|
||||
"/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
|
||||
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
|
||||
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
|
||||
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
|
||||
"/usr/share/fonts/chinese/TrueType/simsun.ttc", # 某些Linux发行版位置
|
||||
]
|
||||
|
||||
font_found = False
|
||||
for font_path in font_paths:
|
||||
if os.path.exists(font_path):
|
||||
try:
|
||||
pdfmetrics.registerFont(TTFont('SimSun', font_path))
|
||||
font_found = True
|
||||
break
|
||||
except:
|
||||
continue
|
||||
|
||||
# 读取原始PDF
|
||||
with open(pdf_path, "rb") as original_file:
|
||||
existing_pdf = PdfFileReader(original_file)
|
||||
output = PdfFileWriter()
|
||||
|
||||
# 处理第一页
|
||||
page = existing_pdf.getPage(0)
|
||||
# 获取页面尺寸
|
||||
page_width = float(page.mediaBox.getWidth())
|
||||
page_height = float(page.mediaBox.getHeight())
|
||||
|
||||
# 创建一个新的PDF页面用于放置二维码
|
||||
c = canvas.Canvas(output_temp_path, pagesize=(page_width, page_height))
|
||||
|
||||
# 设置字体
|
||||
if font_found:
|
||||
c.setFont('SimSun', 14) # 增大字体大小到14pt
|
||||
else:
|
||||
# 如果没有找到中文字体,使用默认字体
|
||||
c.setFont('Helvetica', 14)
|
||||
logging.warning("未找到中文字体,将使用默认字体")
|
||||
|
||||
# 在右下角绘制二维码,预留边距
|
||||
qr_size = 1.5 * inch # 二维码大小为2英寸
|
||||
margin = 0.1 * inch # 边距为0.4英寸
|
||||
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
|
||||
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
|
||||
|
||||
if buttom_text:
|
||||
# 在二维码下方绘制文字
|
||||
text = buttom_text
|
||||
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
|
||||
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
|
||||
text_y = margin + 20 # 文字位置靠近底部
|
||||
c.drawString(text_x, text_y, text)
|
||||
|
||||
c.save()
|
||||
|
||||
# 读取带有二维码的临时PDF
|
||||
with open(output_temp_path, "rb") as qr_file:
|
||||
qr_pdf = PdfFileReader(qr_file)
|
||||
qr_page = qr_pdf.getPage(0)
|
||||
|
||||
# 合并原始页面和二维码页面
|
||||
page.mergePage(qr_page)
|
||||
output.addPage(page)
|
||||
|
||||
# 添加剩余的页面
|
||||
for i in range(1, existing_pdf.getNumPages()):
|
||||
output.addPage(existing_pdf.getPage(i))
|
||||
|
||||
# 保存最终的PDF到一个临时文件
|
||||
final_temp_path = pdf_path + '.tmp'
|
||||
with open(final_temp_path, "wb") as output_file:
|
||||
output.write(output_file)
|
||||
|
||||
# 替换原始文件
|
||||
os.replace(final_temp_path, pdf_path)
|
||||
|
||||
return True
|
||||
|
||||
finally:
|
||||
# 清理临时文件
|
||||
if os.path.exists(qr_temp_path):
|
||||
os.remove(qr_temp_path)
|
||||
if os.path.exists(output_temp_path):
|
||||
os.remove(output_temp_path)
|
||||
@@ -9,6 +9,7 @@
|
||||
<field name="name"/>
|
||||
<field name="ip_address"/>
|
||||
<field name="port"/>
|
||||
<field name="type"/>
|
||||
<!-- 其他字段... -->
|
||||
</tree>
|
||||
</field>
|
||||
@@ -24,6 +25,7 @@
|
||||
<field name="name"/>
|
||||
<field name="ip_address"/>
|
||||
<field name="port"/>
|
||||
<field name="type"/>
|
||||
</group>
|
||||
</sheet>
|
||||
</form>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import logging
|
||||
from ftplib import FTP
|
||||
import os
|
||||
from ftplib import FTP, error_perm
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -52,8 +53,8 @@ class FtpController:
|
||||
print(self.username, self.port, self.host, self.password)
|
||||
ftp = FTP_P()
|
||||
_logger.info("===================connect==================")
|
||||
# self.ftp.set_debuglevel(2) #打开调试级别2,显示详细信息
|
||||
ftp.set_pasv(0) # 0主动模式 1 #被动模式
|
||||
# ftp.set_debuglevel(2) #打开调试级别2,显示详细信息
|
||||
# ftp.set_pasv(1) # 0主动模式 1 #被动模式
|
||||
try:
|
||||
ftp.connect(self.host, self.port)
|
||||
ftp.login(self.username, self.password)
|
||||
@@ -128,3 +129,126 @@ class FtpController:
|
||||
:return:
|
||||
"""
|
||||
self.ftp.delete(delpath)
|
||||
|
||||
|
||||
|
||||
def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir, keep_dir=False):
|
||||
"""
|
||||
从源FTP服务器下载所有.nc文件并上传到目标FTP服务器,保持目录结构
|
||||
|
||||
Args:
|
||||
source_ftp_info: dict, 源FTP连接信息 {host, port, username, password}
|
||||
target_ftp_info: dict, 目标FTP连接信息 {host, port, username, password}
|
||||
source_dir: str, 源FTP上的起始目录
|
||||
target_dir: str, 目标FTP上的目标目录
|
||||
keep_dir: bool, 是否保持目录结构,默认False
|
||||
"""
|
||||
try:
|
||||
# 连接源FTP
|
||||
source_ftp = FtpController(
|
||||
source_ftp_info['host'],
|
||||
source_ftp_info['port'],
|
||||
source_ftp_info['username'],
|
||||
source_ftp_info['password']
|
||||
)
|
||||
source_ftp.ftp.set_pasv(1)
|
||||
|
||||
# 连接目标FTP
|
||||
target_ftp = FtpController(
|
||||
target_ftp_info['host'],
|
||||
target_ftp_info['port'],
|
||||
target_ftp_info['username'],
|
||||
target_ftp_info['password']
|
||||
)
|
||||
source_ftp.ftp.set_pasv(1)
|
||||
|
||||
# 递归遍历源目录
|
||||
def traverse_dir(current_dir, relative_path=''):
|
||||
source_ftp.ftp.cwd(current_dir)
|
||||
file_list = source_ftp.ftp.nlst()
|
||||
|
||||
for item in file_list:
|
||||
try:
|
||||
# 尝试进入目录
|
||||
source_ftp.ftp.cwd(f"{current_dir}/{item}")
|
||||
# 如果成功则是目录
|
||||
new_relative_path = os.path.join(relative_path, item)
|
||||
# 在目标FTP创建对应目录
|
||||
try:
|
||||
if keep_dir:
|
||||
target_ftp.ftp.mkd(f"{target_dir}/{new_relative_path}")
|
||||
except:
|
||||
pass # 目录可能已存在
|
||||
# 递归遍历子目录
|
||||
traverse_dir(f"{current_dir}/{item}", new_relative_path)
|
||||
source_ftp.ftp.cwd('..')
|
||||
except:
|
||||
# 如果是.nc文件则传输
|
||||
if item.lower().endswith('.nc'):
|
||||
# 下载到临时文件
|
||||
temp_path = f"/tmp/{item}"
|
||||
with open(temp_path, 'wb') as f:
|
||||
source_ftp.ftp.retrbinary(f'RETR {item}', f.write)
|
||||
|
||||
# 上传到目标FTP对应目录
|
||||
if keep_dir:
|
||||
target_path = f"{target_dir}/{relative_path}/{item}"
|
||||
else:
|
||||
target_path = f"{target_dir}/{item}"
|
||||
with open(temp_path, 'rb') as f:
|
||||
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
|
||||
|
||||
# 删除临时文件
|
||||
os.remove(temp_path)
|
||||
logging.info(f"已传输文件: {item}")
|
||||
|
||||
# 清空目标目录下的所有内容
|
||||
try:
|
||||
target_ftp.ftp.cwd(target_dir)
|
||||
files = target_ftp.ftp.nlst()
|
||||
|
||||
for f in files:
|
||||
try:
|
||||
# 尝试删除文件
|
||||
target_ftp.ftp.delete(f)
|
||||
except:
|
||||
try:
|
||||
# 如果删除失败,可能是目录,递归删除目录
|
||||
def remove_dir(path):
|
||||
target_ftp.ftp.cwd(path)
|
||||
sub_files = target_ftp.ftp.nlst()
|
||||
for sf in sub_files:
|
||||
try:
|
||||
target_ftp.ftp.delete(sf)
|
||||
except:
|
||||
remove_dir(f"{path}/{sf}")
|
||||
target_ftp.ftp.cwd('..')
|
||||
target_ftp.ftp.rmd(path)
|
||||
|
||||
remove_dir(f"{target_dir}/{f}")
|
||||
except:
|
||||
logging.error(f"无法删除 {f}")
|
||||
pass
|
||||
|
||||
logging.info(f"已清空目标目录 {target_dir}")
|
||||
except Exception as e:
|
||||
logging.error(f"清空目标目录失败: {str(e)}")
|
||||
return False
|
||||
|
||||
# 开始遍历
|
||||
traverse_dir(source_dir)
|
||||
|
||||
logging.info("所有.nc文件传输完成")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logging.error(f"传输过程出错: {str(e)}")
|
||||
return False
|
||||
|
||||
finally:
|
||||
# 关闭FTP连接
|
||||
try:
|
||||
source_ftp.ftp.quit()
|
||||
target_ftp.ftp.quit()
|
||||
except:
|
||||
pass
|
||||
@@ -4,3 +4,4 @@ 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
|
||||
|
||||
92
sf_maintenance/models/maintenance_printer.py
Normal file
92
sf_maintenance/models/maintenance_printer.py
Normal file
@@ -0,0 +1,92 @@
|
||||
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
|
||||
|
||||
@@ -826,6 +826,11 @@ class SfMaintenanceEquipment(models.Model):
|
||||
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
|
||||
domain="[('type', '=', '冷却方式')]")
|
||||
|
||||
ftp_host = fields.Char('FTP 主机')
|
||||
ftp_port = fields.Char('FTP 端口')
|
||||
ftp_username = fields.Char('FTP 用户名')
|
||||
ftp_password = fields.Char('FTP 密码')
|
||||
|
||||
|
||||
class SfRobotAxisNum(models.Model):
|
||||
_name = 'sf.robot.axis.num'
|
||||
|
||||
@@ -1053,6 +1053,26 @@
|
||||
</page>
|
||||
</xpath>
|
||||
|
||||
<xpath expr="//group/field[@name='location']" position="after">
|
||||
<field name="qr_code_image" widget="image" readonly="1" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" />
|
||||
<label for="print_single_method"/>
|
||||
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||
<button type="object" class="oe_highlight" name='print_single_method' string="打印机床二维码"
|
||||
attrs="{'invisible': [('equipment_type', '!=', '机床')]}"/>
|
||||
</div>
|
||||
</xpath>
|
||||
<xpath expr="//page[@name='maintenance']" position="after">
|
||||
<page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" >
|
||||
<group>
|
||||
<group string="ftp配置">
|
||||
<field name="ftp_host" string="主机"/>
|
||||
<field name="ftp_port" string="端口"/>
|
||||
<field name="ftp_username" string="用户名"/>
|
||||
<field name="ftp_password" string="密码" password="True"/>
|
||||
</group>
|
||||
</group>
|
||||
</page>
|
||||
</xpath>
|
||||
</data>
|
||||
</field>
|
||||
</record>
|
||||
|
||||
@@ -17,3 +17,4 @@ from . import sale_order
|
||||
from . import quick_easy_order
|
||||
from . import purchase_order
|
||||
from . import quality_check
|
||||
from . import workorder_printer
|
||||
|
||||
134
sf_manufacturing/models/workorder_printer.py
Normal file
134
sf_manufacturing/models/workorder_printer.py
Normal file
@@ -0,0 +1,134 @@
|
||||
import qrcode
|
||||
import base64
|
||||
import logging
|
||||
import tempfile
|
||||
import os
|
||||
import platform
|
||||
import socket
|
||||
import subprocess
|
||||
from io import BytesIO
|
||||
from odoo import models, fields, api
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
_logger = logging.getLogger(__name__)
|
||||
|
||||
class MrpWorkorder(models.Model):
|
||||
_name = 'mrp.workorder'
|
||||
_inherit = ['mrp.workorder', 'printing.utils']
|
||||
|
||||
def print_pdf(self, printer_config, pdf_data):
|
||||
"""跨平台打印函数,支持网络打印机(IP:端口)"""
|
||||
# 将PDF数据保存到临时文件
|
||||
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
|
||||
pdf_binary = base64.b64decode(pdf_data)
|
||||
temp_file.write(pdf_binary)
|
||||
temp_file_path = temp_file.name
|
||||
|
||||
_logger.info(f"开始打印PDF文件: {temp_file_path}")
|
||||
|
||||
try:
|
||||
# 获取打印机名称或IP地址
|
||||
printer_name = printer_config.printer_id.name
|
||||
if not printer_name:
|
||||
raise UserError('打印机名称未配置')
|
||||
|
||||
# 使用打印机配置中的IP地址和端口
|
||||
printer_ip = printer_config.printer_id.ip_address
|
||||
printer_port = printer_config.printer_id.port
|
||||
_logger.info(f"使用网络打印机: IP={printer_ip}, 端口={printer_port}")
|
||||
|
||||
if platform.system() == 'Windows':
|
||||
_logger.info(f"Windows环境不支持网络打印机")
|
||||
else: # Linux环境
|
||||
# try:
|
||||
# import cups
|
||||
|
||||
# # 处理网络打印机情况
|
||||
# _logger.info(f"Linux环境下连接网络打印机: {printer_ip}:{printer_port}")
|
||||
|
||||
# # 创建连接
|
||||
# conn = cups.Connection()
|
||||
|
||||
# # 检查打印机是否已经添加到系统
|
||||
# printers = conn.getPrinters()
|
||||
# _logger.info(f"可用打印机列表: {list(printers.keys())}")
|
||||
|
||||
# network_printer_name = f"IP_{printer_ip}_{printer_port}"
|
||||
|
||||
# # 如果打印机不存在,尝试添加
|
||||
# if network_printer_name not in printers:
|
||||
# _logger.info(f"添加网络打印机: {network_printer_name}")
|
||||
# conn.addPrinter(
|
||||
# network_printer_name,
|
||||
# device=f"socket://{printer_ip}:{printer_port}",
|
||||
# info=f"Network Printer {printer_ip}:{printer_port}",
|
||||
# location="Network"
|
||||
# )
|
||||
# # 设置打印机为启用状态
|
||||
# conn.enablePrinter(network_printer_name)
|
||||
# _logger.info(f"网络打印机添加成功: {network_printer_name}")
|
||||
|
||||
# # 打印文件
|
||||
# _logger.info(f"开始打印到网络打印机: {network_printer_name}")
|
||||
# job_id = conn.printFile(network_printer_name, temp_file_path, "工单打印", {})
|
||||
# _logger.info(f"打印作业ID: {job_id}")
|
||||
|
||||
|
||||
# except ImportError as ie:
|
||||
# _logger.error(f"导入CUPS库失败: {str(ie)}")
|
||||
|
||||
# 尝试使用lp命令打印
|
||||
try:
|
||||
_logger.info("尝试使用lp命令打印...")
|
||||
|
||||
# 使用socket设置打印
|
||||
cmd = f"lp -h {printer_ip}:{printer_port} -d {printer_name} {temp_file_path}"
|
||||
|
||||
_logger.info(f"执行lp打印命令: {cmd}")
|
||||
result = subprocess.run(cmd, shell=True, check=True, capture_output=True)
|
||||
_logger.info(f"lp打印结果: {result.stdout.decode()}")
|
||||
except Exception as e:
|
||||
_logger.error(f"lp命令打印失败: {str(e)}")
|
||||
raise UserError(f'打印失败,请安装cups打印库: pip install pycups 或确保lp命令可用')
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
_logger.error(f"打印失败详细信息: {str(e)}")
|
||||
raise UserError(f'打印失败: {str(e)}')
|
||||
finally:
|
||||
# 清理临时文件
|
||||
if os.path.exists(temp_file_path):
|
||||
try:
|
||||
os.unlink(temp_file_path)
|
||||
_logger.info(f"临时文件已清理: {temp_file_path}")
|
||||
except Exception as e:
|
||||
_logger.error(f"清理临时文件失败: {str(e)}")
|
||||
|
||||
def _compute_state(self):
|
||||
super(MrpWorkorder, self)._compute_state()
|
||||
for workorder in self:
|
||||
work_ids = workorder.production_id.workorder_ids.filtered(lambda w: w.routing_type == '装夹预调' or w.routing_type == '人工线下加工')
|
||||
for wo in work_ids:
|
||||
if wo.state == 'ready' and not wo.production_id.product_id.is_print_program:
|
||||
# 触发打印程序
|
||||
pdf_data = self.processing_drawing
|
||||
try:
|
||||
if pdf_data:
|
||||
# 获取默认打印机配置
|
||||
printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name), ('printer_id.type', '=', 'normal')], limit=1)
|
||||
if not printer_config:
|
||||
raise UserError('请先配置打印机')
|
||||
|
||||
# 执行打印
|
||||
if self.print_pdf(printer_config, pdf_data):
|
||||
wo.production_id.product_id.is_print_program = True
|
||||
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
|
||||
|
||||
except Exception as e:
|
||||
_logger.error(f'打印配置错误: {str(e)}')
|
||||
|
||||
class ProductTemplate(models.Model):
|
||||
_inherit = 'product.template'
|
||||
|
||||
is_print_program = fields.Boolean(string='是否打印程序', default=False)
|
||||
|
||||
@@ -8,6 +8,7 @@ from odoo.http import request
|
||||
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
|
||||
|
||||
|
||||
|
||||
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
|
||||
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||
@@ -22,6 +23,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
try:
|
||||
res = {'status': 1, 'message': '成功'}
|
||||
datas = request.httprequest.data
|
||||
model_id = None
|
||||
ret = json.loads(datas)
|
||||
ret = json.loads(ret['result'])
|
||||
logging.info('下发编程单:%s' % ret)
|
||||
@@ -57,6 +59,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
|
||||
return json.JSONEncoder().encode(res)
|
||||
for production in productions:
|
||||
model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
|
||||
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
|
||||
for panel in ret['processing_panel'].split(','):
|
||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||
@@ -83,12 +86,16 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
# panel)
|
||||
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
||||
files_panel = os.listdir(program_path_tmp_panel)
|
||||
panel_file_path = ''
|
||||
if files_panel:
|
||||
for file in files_panel:
|
||||
file_extension = os.path.splitext(file)[1]
|
||||
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)
|
||||
|
||||
# 向编程单中添加二维码
|
||||
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "扫码获取工单")
|
||||
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||
pre_workorder = productions.workorder_ids.filtered(
|
||||
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
|
||||
@@ -268,3 +275,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||
request.cr.rollback()
|
||||
logging.info('get_cnc_processing_create error:%s' % e)
|
||||
return json.JSONEncoder().encode(res)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user