Merge branch 'refs/heads/develop' into feature/tool_standard_library_process

# Conflicts:
#	sf_plan/models/custom_plan.py
This commit is contained in:
liaodanlong
2025-04-25 11:17:48 +08:00
49 changed files with 675 additions and 428 deletions

View File

@@ -157,11 +157,11 @@ td.o_required_modifier {
color: #aaa; color: #aaa;
} }
.o_kanban_primary_left { // .o_kanban_primary_left {
display: flex; // display: flex;
flex-direction: row-reverse; // flex-direction: row-reverse;
justify-content: flex-start; // justify-content: flex-start;
} // }
.o_list_view .o_list_table thead { .o_list_view .o_list_table thead {
position: sticky; position: sticky;

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
from . import models

View File

@@ -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',
}

View File

@@ -0,0 +1,5 @@
# -*- coding: utf-8 -*-
from . import jikimo_printing
from . import maintenance_printing
from . import workorder_printing

View File

@@ -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')

View File

@@ -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

View File

@@ -0,0 +1,33 @@
import logging
from odoo import models, fields, api
_logger = logging.getLogger(__name__)
class MrpWorkorder(models.Model):
_name = 'mrp.workorder'
_inherit = ['mrp.workorder']
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 = workorder.processing_drawing
if pdf_data:
try:
# 执行打印
self.env['jikimo.printing'].print_pdf(pdf_data)
wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
except Exception as e:
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")
class ProductTemplate(models.Model):
_inherit = 'product.template'
is_print_program = fields.Boolean(string='是否打印程序', default=False)

View File

@@ -0,0 +1,19 @@
<?xml version="1.0"?>
<odoo>
<record id="sf_maintenance_equipment_view_form_qrcode_print" model="ir.ui.view">
<field name="name">sf_equipment.form</field>
<field name="model">maintenance.equipment</field>
<field name="inherit_id" ref="sf_maintenance.sf_hr_equipment_view_form"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='qr_code_image']" position="after">
<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>
</field>
</record>
</odoo>

View File

@@ -100,6 +100,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
def check_group(self, request_lines): def check_group(self, request_lines):
# 去掉合并必须同一采购组的限制 # 去掉合并必须同一采购组的限制
pass pass
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel): class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):

View File

@@ -8,7 +8,6 @@
'category': 'sf', 'category': 'sf',
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'], 'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
'data': [ 'data': [
], ],
'application': True, 'application': True,

View File

@@ -2,29 +2,51 @@ import json
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_nc_files 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): class MainController(http.Controller):
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*') @http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*')
@api_log('人工线下加工编程文件传输', requester='报工系统')
def manual_download_program(self): def manual_download_program(self):
""" """
人工线下加工传输编程文件 人工线下加工传输编程文件
""" """
data = json.loads(request.httprequest.data) 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') 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': '参数错误'} 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: 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() 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 = { source_ftp_info = {
'host': ftp_resconfig['ftp_host'], 'host': ftp_resconfig['ftp_host'],
'port': int(ftp_resconfig['ftp_port']), 'port': int(ftp_resconfig['ftp_port']),
'username': ftp_resconfig['ftp_user'], 'username': ftp_resconfig['ftp_user'],
'password': ftp_resconfig['ftp_password'] '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 = { target_ftp_info = {
'host': maintenance_equipment.ftp_host, 'host': maintenance_equipment.ftp_host,
'port': int(maintenance_equipment.ftp_port), 'port': int(maintenance_equipment.ftp_port),
@@ -32,11 +54,17 @@ class MainController(http.Controller):
'password': maintenance_equipment.ftp_password 'password': maintenance_equipment.ftp_password
} }
# 传输nc文件 # 传输nc文件
if transfer_nc_files( try:
source_ftp_info, result = transfer_nc_files(
target_ftp_info, source_ftp_info,
'/' + str(model_id), target_ftp_info,
'/home/jikimo/testdir'): '/' + str(model_id),
return {'code': 200, 'message': 'success'} '/',
else: match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
return {'code': 500, 'message': '传输失败'} )
if result:
return {'code': 200, 'message': 'success'}
else:
return {'code': 404, 'message': '未找到编程文件'}
except Exception as e:
return {'code': 500, 'message': str(e)}

View File

@@ -258,6 +258,23 @@ class QualityCheck(models.Model):
line[field_name] = False line[field_name] = False
self.column_nums = self.column_nums - 1 self.column_nums = self.column_nums - 1
def upload_measure_line(self):
"""
上传测量值
"""
for record in self:
if not record.part_name or not record.part_number:
raise UserError(_('零件名称和零件图号均不能为空'))
# 如果验证通过,返回原动作
action = self.env.ref('quality_control.import_complex_model_wizard').read()[0]
action['context'] = {
'default_model_name': 'quality.check.measure.line',
'default_check_id': self.id,
}
return action
def do_preview(self): def do_preview(self):
""" """
预览出厂检验报告 预览出厂检验报告

View File

@@ -334,11 +334,15 @@
<div class="o_row"> <div class="o_row">
<button name="add_measure_line" type="object" class="btn-primary" string="添加测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/> <button name="add_measure_line" type="object" class="btn-primary" string="添加测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
<button name="remove_measure_line" type="object" class="btn-primary" string="删除测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/> <button name="remove_measure_line" type="object" class="btn-primary" string="删除测量值" attrs="{'invisible': [('publish_status', '=', 'published')]}"/>
<button name="%(quality_control.import_complex_model_wizard)d" string="上传" <!-- <button name="%(quality_control.import_complex_model_wizard)d" string="上传111" -->
type="action" <!-- type="action" -->
<!-- class="btn-primary" -->
<!-- attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}" -->
<!-- context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/> -->
<button name="upload_measure_line" string="上传"
type="object"
class="btn-primary" class="btn-primary"
attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}" attrs="{'force_show':1, 'invisible': [('publish_status', '=', 'published')]}"/>
context="{'default_model_name': 'quality.check.measure.line', 'default_check_id': id}"/>
</div> </div>
<br/> <br/>
<div class="o_row"> <div class="o_row">

View File

@@ -22,6 +22,7 @@ _logger = logging.getLogger(__name__)
class ImportComplexModelWizard(models.TransientModel): class ImportComplexModelWizard(models.TransientModel):
_name = 'quality.check.import.complex.model.wizard' _name = 'quality.check.import.complex.model.wizard'
file_data = fields.Binary("数据文件") file_data = fields.Binary("数据文件")
filename = fields.Char(string='文件名')
model_name = fields.Char(string='Model Name') model_name = fields.Char(string='Model Name')
field_basis = fields.Char(string='Field Basis') field_basis = fields.Char(string='Field Basis')
check_id = fields.Many2one(string='质检单', comodel_name='quality.check') check_id = fields.Many2one(string='质检单', comodel_name='quality.check')
@@ -93,11 +94,16 @@ class ImportComplexModelWizard(models.TransientModel):
return repeats return repeats
def convert_float(self, value):
if isinstance(value, float) and value.is_integer():
return int(value)
return value
def import_data(self): def import_data(self):
"""导入Excel数据""" """导入Excel数据"""
if not self.file_data: if not self.file_data:
raise UserError(_('请先上传Excel文件')) raise UserError(_('请先上传Excel文件'))
if self.check_id.measure_line_ids: if self.check_id.measure_line_ids:
self.sudo().check_id.measure_line_ids.unlink() self.sudo().check_id.measure_line_ids.unlink()
@@ -161,6 +167,20 @@ class ImportComplexModelWizard(models.TransientModel):
# 从第二行开始读取数据(跳过表头) # 从第二行开始读取数据(跳过表头)
max_columns = 1 max_columns = 1
for row_index in range(1, sheet.nrows):
row = sheet.row_values(row_index)
# 检查行是否有数据
if not any(row):
continue
if row[2] == '':
continue
logging.info('================%s, %s==' % (row[1], type(row[1])))
compare_value = self.convert_float(row[1])
if str(compare_value) != quality_check.part_number:
print(sheet.row_values(row_index))
raise UserError(_('上传内容图号错误,请修改'))
for row_index in range(1, sheet.nrows): for row_index in range(1, sheet.nrows):
row = sheet.row_values(row_index) row = sheet.row_values(row_index)
@@ -177,14 +197,14 @@ class ImportComplexModelWizard(models.TransientModel):
'sequence': len(quality_check.measure_line_ids) + 1, 'sequence': len(quality_check.measure_line_ids) + 1,
'product_name': str(row[0]) if row[0] else '', # 产品名称列 'product_name': str(row[0]) if row[0] else '', # 产品名称列
'drawing_no': str(row[1]) if row[1] else '', # 图号列 'drawing_no': str(row[1]) if row[1] else '', # 图号列
'measure_item': row[2] or '', # 检测项目列 'measure_item': str(self.convert_float(row[2])) or '', # 检测项目列
'measure_value1': str(row[4]) if row[4] else '', # 测量值1 'measure_value1': str(self.convert_float(row[4])) if row[4] else '', # 测量值1
'measure_value2': str(row[5]) if row[5] else '', # 测量值2 'measure_value2': str(self.convert_float(row[5])) if row[5] else '', # 测量值2
'measure_value3': str(row[6]) if len(row) > 6 and row[6] else '', # 测量值3 'measure_value3': str(self.convert_float(row[6])) if len(row) > 6 and row[6] else '', # 测量值3
'measure_value4': str(row[7]) if len(row) > 7 and row[7] else '', # 测量值4 'measure_value4': str(self.convert_float(row[7])) if len(row) > 7 and row[7] else '', # 测量值4
'measure_value5': str(row[8]) if len(row) > 8 and row[8] else '', # 测量值5 'measure_value5': str(self.convert_float(row[8])) if len(row) > 8 and row[8] else '', # 测量值5
'measure_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列 'measure_result': 'NG' if row[9] == 'NG' else 'OK', # 判定列
'remark': row[10] if len(row) > 10 and row[10] else '', # 备注列 'remark': self.convert_float(row[10]) if len(row) > 10 and row[10] else '', # 备注列
} }
for i in range(1, 6): for i in range(1, 6):
@@ -194,7 +214,7 @@ class ImportComplexModelWizard(models.TransientModel):
self.env['quality.check.measure.line'].create(measure_line_vals) self.env['quality.check.measure.line'].create(measure_line_vals)
valid_data_imported = True valid_data_imported = True
quality_check.column_nums = max_columns quality_check.column_nums = max_columns
# 检查是否有有效数据被导入 # 检查是否有有效数据被导入
@@ -424,7 +444,8 @@ class ImportComplexModelWizard(models.TransientModel):
# ) # )
def download_excel_template(self): def download_excel_template(self):
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx' base_url = self.env['ir.config_parameter'].sudo().get_param(
'web.base.url') + '/quality_control/static/src/binary/出厂检验报告上传模版.xlsx'
# 只有当原始 URL 使用 http 时才替换为 https # 只有当原始 URL 使用 http 时才替换为 https
if base_url.startswith("http://"): if base_url.startswith("http://"):

View File

@@ -6,7 +6,8 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<form> <form>
<group> <group>
<field name="file_data" widget="binary" options="{'accepted_file_extensions': '.xls,.xlsx'}"/> <field name="file_data" widget="binary" filename="filename" options="{'accepted_file_extensions': '.xls,.xlsx'}"/>
<field name="filename" invisible="1"/>
</group> </group>
<footer> <footer>
<button string="确认导入" name="import_data" type="object" class="btn-primary"/> <button string="确认导入" name="import_data" type="object" class="btn-primary"/>

View File

@@ -1,3 +1,4 @@
from . import models from . import models
from . import commons from . import commons
from . import controllers from . import controllers
from . import decorators

View File

@@ -25,7 +25,7 @@
'views/menu_fixture_view.xml', 'views/menu_fixture_view.xml',
'views/change_base_view.xml', 'views/change_base_view.xml',
'views/Printer.xml', 'views/Printer.xml',
'views/api_log_views.xml',
], ],
'demo': [ 'demo': [
], ],

View File

@@ -8,8 +8,6 @@ class Printer(models.Model):
name = fields.Char(string='名称', required=True) name = fields.Char(string='名称', required=True)
ip_address = fields.Char(string='IP 地址', required=True) ip_address = fields.Char(string='IP 地址', required=True)
port = fields.Integer(string='端口', default=9100) port = fields.Integer(string='端口', default=9100)
type = fields.Selection([('zpl', 'ZPL'), ('normal', '普通')], string='类型', default='zpl')
class TableStyle(models.Model): class TableStyle(models.Model):
_name = 'table.style' _name = 'table.style'

View File

@@ -1,10 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import json import json
import base64 import logging
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
_logger = logging.getLogger(__name__)
class Manufacturing_Connect(http.Controller): class Manufacturing_Connect(http.Controller):

View File

@@ -0,0 +1 @@
from . import api_log

View File

@@ -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

View File

@@ -6,3 +6,4 @@ from . import functional_fixture
from . import tool_other_features from . import tool_other_features
from . import basic_parameters_fixture from . import basic_parameters_fixture
from . import ir_sequence from . import ir_sequence
from . import api_log

18
sf_base/models/api_log.py Normal file
View File

@@ -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('响应方')

View File

@@ -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,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_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
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
254
255
256
257
258
259

View File

@@ -148,12 +148,17 @@ td.o_required_modifier {
color: #aaa; color: #aaa;
} }
.o_kanban_primary_left { // .o_kanban_primary_left {
display: flex; // display: flex;
flex-direction: row-reverse; // flex-direction: row-reverse;
justify-content: flex-start; // justify-content: flex-start;
// }
.o_list_button {
min-width: 32px;
}
.o_list_record_remove {
padding-left: 0px !important;
} }
.diameter:before { .diameter:before {
content:"Ф"; content:"Ф";
display:inline; display:inline;

View File

@@ -9,7 +9,6 @@
<field name="name"/> <field name="name"/>
<field name="ip_address"/> <field name="ip_address"/>
<field name="port"/> <field name="port"/>
<field name="type"/>
<!-- 其他字段... --> <!-- 其他字段... -->
</tree> </tree>
</field> </field>
@@ -25,7 +24,6 @@
<field name="name"/> <field name="name"/>
<field name="ip_address"/> <field name="ip_address"/>
<field name="port"/> <field name="port"/>
<field name="type"/>
</group> </group>
</sheet> </sheet>
</form> </form>

View File

@@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_api_request_log_tree" model="ir.ui.view">
<field name="name">api.request.log.tree</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<tree>
<field name="create_date"/>
<field name="name"/>
<field name="path"/>
<field name="method"/>
<field name="remote_addr"/>
<field name="response_time"/>
<field name="status"/>
</tree>
</field>
</record>
<record id="view_api_request_log_form" model="ir.ui.view">
<field name="name">api.request.log.form</field>
<field name="model">api.request.log</field>
<field name="arch" type="xml">
<form>
<sheet>
<group>
<group>
<field name="name"/>
<field name="path"/>
<field name="method"/>
<field name="remote_addr"/>
</group>
<group>
<field name="response_time"/>
<field name="status"/>
<field name="create_date" string="请求时间"/>
</group>
</group>
<notebook>
<page string="请求数据">
<field name="request_data"/>
</page>
<page string="响应数据">
<field name="response_data"/>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
<record id="action_api_request_log" model="ir.actions.act_window">
<field name="name">API请求日志</field>
<field name="res_model">api.request.log</field>
<field name="view_mode">tree,form</field>
</record>
<menuitem id="menu_api_request_log"
name="API请求日志"
parent="base.next_id_9"
action="action_api_request_log"
sequence="100"/>
</odoo>

View File

@@ -1688,12 +1688,14 @@ class Sf_Dashboard_Connect(http.Controller):
FROM device_data FROM device_data
WHERE device_name = %s WHERE device_name = %s
AND power_on_time IS NOT NULL AND power_on_time IS NOT NULL
AND time::date = CURRENT_DATE -- ✅ 更高效的写法 AND time >= CURRENT_DATE
AND time < CURRENT_DATE + INTERVAL '1 day'
ORDER BY time ASC ORDER BY time ASC
LIMIT 1 LIMIT 1
); );
""", (item, item, item)) """, (item, item, item))
results = cur.fetchall() results = cur.fetchall()
logging.info('====================%s' % results)
if len(results) >= 1: if len(results) >= 1:
total_power_on_time += convert_to_seconds(results[0][0]) total_power_on_time += convert_to_seconds(results[0][0])
else: else:
@@ -1704,10 +1706,16 @@ class Sf_Dashboard_Connect(http.Controller):
month_power_on_time += 0 month_power_on_time += 0
if len(results) >= 3: if len(results) >= 3:
today_power_on_time += convert_to_seconds(results[2][0]) today_power_on_time += convert_to_seconds(results[2][0])
today_power_on_dict[item] = today_power_on_time # today_power_on_dict[item] = today_power_on_time
else: else:
today_power_on_time += 0 today_power_on_time += convert_to_seconds(results[0][0])
today_power_on_dict[item] = 0 # today_power_on_dict[item] = 0
if results[0][0] == '0H0M':
logging.info('=woshide=%s' % results[0][0])
logging.info(type(results[0][0]))
total_power_on_time += convert_to_seconds(results[1][0])
today_power_on_time += convert_to_seconds(results[1][0])
with conn.cursor() as cur: with conn.cursor() as cur:
cur.execute(""" cur.execute("""
@@ -1722,9 +1730,9 @@ class Sf_Dashboard_Connect(http.Controller):
for record in results: for record in results:
if record[0]: if record[0]:
if float(record[0]) >= 28800: if float(record[0]) >= 86400:
continue continue
total_alarm_time += float(record[0]) total_alarm_time += abs(float(record[0]))
else: else:
total_alarm_time += 0.0 total_alarm_time += 0.0
alarm_start = datetime.strptime(record[1], "%Y-%m-%d %H:%M:%S") alarm_start = datetime.strptime(record[1], "%Y-%m-%d %H:%M:%S")
@@ -1737,7 +1745,7 @@ class Sf_Dashboard_Connect(http.Controller):
if today[0]: if today[0]:
if float(today[0]) >= 28800: if float(today[0]) >= 28800:
continue continue
today_alarm_time += float(today[0]) today_alarm_time += abs(float(today[0]))
today_alarm_dict[item] = today_alarm_time today_alarm_dict[item] = today_alarm_time
else: else:
today_alarm_time += 0.0 today_alarm_time += 0.0
@@ -1748,12 +1756,14 @@ class Sf_Dashboard_Connect(http.Controller):
if month[0]: if month[0]:
if float(month[0]) >= 28800: if float(month[0]) >= 28800:
continue continue
month_alarm_time += float(month[0]) month_alarm_time += abs(float(month[0]))
else: else:
month_alarm_time += 0.0 month_alarm_time += 0.0
conn.close() conn.close()
logging.info('在线时间总月日=============%s, %s, %s' % (total_power_on_time, month_power_on_time, today_power_on_time))
logging.info('报警时间总月日=============%s, %s, %s' % (total_alarm_time, month_alarm_time, today_alarm_time)) logging.info('报警时间总月日=============%s, %s, %s' % (total_alarm_time, month_alarm_time, today_alarm_time))
# 计算时间开动率(累计、月、日) # 计算时间开动率(累计、月、日)
if total_power_on_time: if total_power_on_time:

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import logging import logging
import os import os
import re
from ftplib import FTP, error_perm from ftplib import FTP, error_perm
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -132,9 +133,16 @@ class FtpController:
def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir, keep_dir=False): def transfer_nc_files(
source_ftp_info,
target_ftp_info,
source_dir,
target_dir,
end_with=None,
match_str=None,
keep_dir=False):
""" """
从源FTP服务器下载所有.nc文件并上传到目标FTP服务器,保持目录结构 从源FTP服务器下载所有{end_with}结尾的文件并上传到目标FTP服务器,保持目录结构
Args: Args:
source_ftp_info: dict, 源FTP连接信息 {host, port, username, password} source_ftp_info: dict, 源FTP连接信息 {host, port, username, password}
@@ -143,23 +151,28 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
target_dir: str, 目标FTP上的目标目录 target_dir: str, 目标FTP上的目标目录
keep_dir: bool, 是否保持目录结构,默认False keep_dir: bool, 是否保持目录结构,默认False
""" """
trans_status = [False]
try: try:
# 连接源FTP # 连接源FTP
source_ftp = FtpController( source_ftp = FtpController(
source_ftp_info['host'], source_ftp_info['host'],
source_ftp_info['port'], source_ftp_info['port'],
source_ftp_info['username'], source_ftp_info['username'],
source_ftp_info['password'] source_ftp_info['password']
) )
if not source_ftp.ftp:
raise Exception("编程文件FTP连接失败")
source_ftp.ftp.set_pasv(1) source_ftp.ftp.set_pasv(1)
# 连接目标FTP # 连接目标FTP
target_ftp = FtpController( target_ftp = FtpController(
target_ftp_info['host'], target_ftp_info['host'],
target_ftp_info['port'], target_ftp_info['port'],
target_ftp_info['username'], target_ftp_info['username'],
target_ftp_info['password'] target_ftp_info['password']
) )
if not source_ftp.ftp:
raise Exception("机床FTP连接失败")
source_ftp.ftp.set_pasv(1) source_ftp.ftp.set_pasv(1)
# 递归遍历源目录 # 递归遍历源目录
@@ -183,8 +196,14 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
traverse_dir(f"{current_dir}/{item}", new_relative_path) traverse_dir(f"{current_dir}/{item}", new_relative_path)
source_ftp.ftp.cwd('..') source_ftp.ftp.cwd('..')
except: except:
# 如果是.nc文件则传输 matched = False
if item.lower().endswith('.nc'): # 文件名匹配字符串BT30-(两个字符)-all.nc, 例6667_20250422-BT30-ZM-all.nc
if match_str and re.match(match_str, item):
matched = True
elif end_with and item.lower().endswith(end_with):
matched = True
if matched:
# 下载到临时文件 # 下载到临时文件
temp_path = f"/tmp/{item}" temp_path = f"/tmp/{item}"
with open(temp_path, 'wb') as f: with open(temp_path, 'wb') as f:
@@ -198,6 +217,7 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
with open(temp_path, 'rb') as f: with open(temp_path, 'rb') as f:
target_ftp.ftp.storbinary(f'STOR {target_path}', f) target_ftp.ftp.storbinary(f'STOR {target_path}', f)
trans_status[0] = True
# 删除临时文件 # 删除临时文件
os.remove(temp_path) os.remove(temp_path)
logging.info(f"已传输文件: {item}") logging.info(f"已传输文件: {item}")
@@ -233,17 +253,17 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
logging.info(f"已清空目标目录 {target_dir}") logging.info(f"已清空目标目录 {target_dir}")
except Exception as e: except Exception as e:
logging.error(f"清空目标目录失败: {str(e)}") logging.error(f"清空目标目录失败: {str(e)}")
return False raise Exception(f"清空目标目录失败: {str(e)}")
# 开始遍历 # 开始遍历
traverse_dir(source_dir) traverse_dir(source_dir)
logging.info("所有.nc文件传输完成") logging.info("所有文件传输完成")
return True return trans_status[0]
except Exception as e: except Exception as e:
logging.error(f"传输过程出错: {str(e)}") logging.error(f"传输过程出错: {str(e)}")
return False raise e
finally: finally:
# 关闭FTP连接 # 关闭FTP连接

View File

@@ -1,26 +1,5 @@
<?xml version="1.0" encoding="utf-8" ?> <?xml version="1.0" encoding="utf-8" ?>
<odoo> <odoo>
<record id="view_delivery_record_form_inherit_sf" model="ir.ui.view">
<field name="name">delivery.record.form.inherit.sf</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before">
<!-- <page string="下发记录" attrs='{"invisible": [("routing_type","!=","CNC加工")]}'>-->
<page string="下发记录" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "HDR")]}'>
<field name="delivery_records">
<tree create="false">
<field name="delivery_type"/>
<field name="delivery_time"/>
<field name="influence_record"/>
</tree>
</field>
</page>
</xpath>
</field>
</record>
<!-- <record id="seqence_b_purchase_order" model='ir.sequence'> --> <!-- <record id="seqence_b_purchase_order" model='ir.sequence'> -->
<!-- <field name='name'>Purchase Order</field> --> <!-- <field name='name'>Purchase Order</field> -->
<!-- <field name='code'>sf_machine_connect.delivery.record</field> --> <!-- <field name='code'>sf_machine_connect.delivery.record</field> -->

View File

@@ -3,9 +3,9 @@
<record id="view_machine_info_form_inherit_sf" model="ir.ui.view"> <record id="view_machine_info_form_inherit_sf" model="ir.ui.view">
<field name="name">machine.info.form.inherit.sf</field> <field name="name">machine.info.form.inherit.sf</field>
<field name="model">mrp.workorder</field> <field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/> <field name="inherit_id" ref="sf_manufacturing.view_mrp_production_workorder_tray_form_inherit_sf"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[last()-3]" position="before"> <xpath expr="//page[@name='CMR']" position="after">
<page string="机床信息" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "MTI")]}'> <page string="机床信息" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "MTI")]}'>
<group string="机床信息"> <group string="机床信息">
<group> <group>
@@ -33,6 +33,15 @@
</group> </group>
</group> </group>
</page> </page>
<page string="下发记录" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "HDR")]}'>
<field name="delivery_records">
<tree create="false">
<field name="delivery_type"/>
<field name="delivery_time"/>
<field name="influence_record"/>
</tree>
</field>
</page>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -4,4 +4,3 @@ from . import sf_maintenance_oee
from . import sf_maintenance_logs from . import sf_maintenance_logs
from . import sf_equipment_maintenance_standards from . import sf_equipment_maintenance_standards
from . import sf_maintenance_requests from . import sf_maintenance_requests
from . import maintenance_printer

View File

@@ -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

View File

@@ -2,6 +2,8 @@
import json import json
import base64 import base64
import logging import logging
import qrcode
from io import BytesIO
from datetime import timedelta from datetime import timedelta
import requests import requests
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
@@ -831,6 +833,29 @@ class SfMaintenanceEquipment(models.Model):
ftp_username = fields.Char('FTP 用户名') ftp_username = fields.Char('FTP 用户名')
ftp_password = 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): class SfRobotAxisNum(models.Model):
_name = 'sf.robot.axis.num' _name = 'sf.robot.axis.num'

View File

@@ -1055,11 +1055,6 @@
<xpath expr="//group/field[@name='location']" position="after"> <xpath expr="//group/field[@name='location']" position="after">
<field name="qr_code_image" widget="image" readonly="1" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" /> <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>
<xpath expr="//page[@name='maintenance']" position="after"> <xpath expr="//page[@name='maintenance']" position="after">
<page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" > <page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" >

View File

@@ -1,57 +1,72 @@
<odoo> <odoo>
<data noupdate="1"> <data noupdate="0">
<record model="sf.work.individuation.page" id="sf_work_individuation_page_1">
<field name="code">PTD</field>
<field name="name">后置三元检测</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_2"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_2">
<field name="code">WCP</field> <field name="code">WCP</field>
<field name="name">工件装夹</field> <field name="name">工件装夹</field>
<field name="sequence">10</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_3"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_3">
<field name="code">ITD_PP</field> <field name="code">ITD_PP</field>
<field name="name">前置三元检测定位参数</field> <field name="name">前置三元检测定位参数</field>
</record> <field name="sequence">20</field>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_4">
<field name="code">2D_MD</field>
<field name="name">2D加工图纸</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_5">
<field name="code">QIS</field>
<field name="name">质检标准</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_6"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_6">
<field name="code">WD</field> <field name="code">WD</field>
<field name="name">工件配送</field> <field name="name">工件配送</field>
<field name="sequence">30</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_9"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_9">
<field name="code">CNC_P</field> <field name="code">CNC_P</field>
<field name="name">CNC程序</field> <field name="name">CNC程序</field>
<field name="sequence">40</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_10"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_10">
<field name="code">CMM_P</field> <field name="code">CMM_P</field>
<field name="name">CMM程序</field> <field name="name">CMM程序</field>
<field name="sequence">50</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_11"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_1">
<field name="code">MTI</field> <field name="code">PTD</field>
<field name="name">机床信息</field> <field name="name">后置三元检测</field>
</record> <field name="sequence">60</field>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_12">
<field name="code">HDR</field>
<field name="name">下发记录</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_13">
<field name="code">ER</field>
<field name="name">异常记录</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_14"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_14">
<field name="code">DCP</field> <field name="code">DCP</field>
<field name="name">解除装夹</field> <field name="name">解除装夹</field>
<field name="sequence">70</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_4">
<field name="code">2D_MD</field>
<field name="name">2D加工图纸</field>
<field name="sequence">80</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_5">
<field name="code">QIS</field>
<field name="name">质检标准</field>
<field name="sequence">90</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_15"> <record model="sf.work.individuation.page" id="sf_work_individuation_page_15">
<field name="code">CMR</field> <field name="code">CMR</field>
<field name="name">开料要求</field> <field name="name">开料要求</field>
<field name="sequence">100</field>
</record> </record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_11">
<field name="code">MTI</field>
<field name="name">机床信息</field>
<field name="sequence">110</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_12">
<field name="code">HDR</field>
<field name="name">下发记录</field>
<field name="sequence">120</field>
</record>
<record model="sf.work.individuation.page" id="sf_work_individuation_page_13">
<field name="code">ER</field>
<field name="name">异常记录</field>
<field name="sequence">130</field>
</record>
<!-- 原生页签先不进行配置 --> <!-- 原生页签先不进行配置 -->
<!-- <record model="sf.work.individuation.page" id="sf_work_individuation_page_7">--> <!-- <record model="sf.work.individuation.page" id="sf_work_individuation_page_7">-->

View File

@@ -18,4 +18,3 @@ from . import quick_easy_order
from . import purchase_order from . import purchase_order
from . import quality_check from . import quality_check
from . import purchase_request_line from . import purchase_request_line
from . import workorder_printer

View File

@@ -1820,6 +1820,8 @@ class MrpProduction(models.Model):
except Exception as e: except Exception as e:
logging.info('update_programming_state error:%s' % e) logging.info('update_programming_state error:%s' % e)
raise UserError("更新编程单状态失败,请联系管理员") raise UserError("更新编程单状态失败,请联系管理员")
model_id = fields.Char('模型ID', related='product_id.model_id')
# 编程记录 # 编程记录

View File

@@ -139,6 +139,8 @@ class ResMrpRoutingWorkcenter(models.Model):
class WorkIndividuationPage(models.Model): class WorkIndividuationPage(models.Model):
_name = 'sf.work.individuation.page' _name = 'sf.work.individuation.page'
_order = 'sequence'
code = fields.Char('编号') code = fields.Char('编号')
name = fields.Char('名称') name = fields.Char('名称')
sequence = fields.Integer('序号')

View File

@@ -20,6 +20,7 @@ from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController
class ResMrpWorkOrder(models.Model): class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder' _inherit = 'mrp.workorder'
_description = '工单' _description = '工单'
_order = 'sequence'
product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name') product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name')
@@ -1812,6 +1813,8 @@ class ResMrpWorkOrder(models.Model):
orderby=orderby, orderby=orderby,
lazy=lazy lazy=lazy
) )
model_id = fields.Char('模型ID', related='production_id.model_id')
class CNCprocessing(models.Model): class CNCprocessing(models.Model):

View File

@@ -787,7 +787,7 @@ class ResProductMo(models.Model):
glb_url = fields.Char('glb文件地址') glb_url = fields.Char('glb文件地址')
area = fields.Float('表面积(m²)') area = fields.Float('表面积(m²)')
auto_machining = fields.Boolean('自动化加工(模型识别)', default=False) auto_machining = fields.Boolean('自动化加工(模型识别)', default=False)
model_id = fields.Char('模型id') model_id = fields.Char('模型ID')
@api.depends('name') @api.depends('name')

View File

@@ -1,134 +0,0 @@
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)

View File

@@ -110,12 +110,12 @@
</xpath> </xpath>
<xpath expr="//sheet//group//group[2]//label" position="before"> <xpath expr="//sheet//group//group[2]//label" position="before">
<!-- <field name="process_state"/> --> <!-- <field name="process_state"/> -->
<field name="production_type" readonly="1"/>
<field name="state" readonly="1"/> <field name="state" readonly="1"/>
<!-- <field name="process_state"/> --> <!-- <field name="process_state"/> -->
</xpath> </xpath>
<xpath expr="//sheet//group//group//div[3]" position="after"> <xpath expr="//sheet//group//group//div[3]" position="after">
<field name="production_type" readonly="1"/>
<field name="production_product_type" invisible="1"/> <field name="production_product_type" invisible="1"/>
<field name="manual_quotation" readonly="1" <field name="manual_quotation" readonly="1"
attrs="{'invisible': ['|', ('production_type', 'not in', ['自动化产线加工', '人工线下加工']), ('production_product_type', '!=', '成品')]}"/> attrs="{'invisible': ['|', ('production_type', 'not in', ['自动化产线加工', '人工线下加工']), ('production_product_type', '!=', '成品')]}"/>
@@ -455,7 +455,7 @@
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/> <field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//tree" position="attributes"> <xpath expr="//tree" position="attributes">
<attribute name="default_order">create_date desc</attribute> <attribute name="default_order">sequence,create_date desc</attribute>
<attribute name="decoration-warning">delivery_warning == 'warning'</attribute> <attribute name="decoration-warning">delivery_warning == 'warning'</attribute>
<attribute name="decoration-danger">delivery_warning == 'overdue'</attribute> <attribute name="decoration-danger">delivery_warning == 'overdue'</attribute>
</xpath> </xpath>

View File

@@ -1,15 +1,15 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<odoo> <odoo>
<!-- 工作中心看板 --> <!-- 工作中心看板 -->
<record id="mrp_production_view_form_inherit_maintenance" model="ir.ui.view"> <!-- <record id="mrp_production_view_form_inherit_maintenance" model="ir.ui.view">
<field name="name">mrp.production.view.form.inherit.maintenance</field> <field name="name">mrp.production.view.form.inherit.maintenance</field>
<field name="model">mrp.production</field> <field name="model">mrp.production</field>
<field name="inherit_id" ref="mrp.mrp_production_form_view"/> <field name="inherit_id" ref="mrp.mrp_production_form_view"/>
<field name="arch" type="xml"> <field name="arch" type="xml"> -->
<!-- <button name="action_cancel" position="before"> --> <!-- <button name="action_cancel" position="before"> -->
<!-- <button name="button_maintenance_req" type="object" string="维修请求"/> --> <!-- <button name="button_maintenance_req" type="object" string="维修请求"/> -->
<!-- </button> --> <!-- </button> -->
<div name="button_box" position="inside"> <!-- <div name="button_box" position="inside">
<button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench" <button name="open_maintenance_request_mo" type="object" class="oe_stat_button" icon="fa-wrench"
attrs="{'invisible': [('maintenance_count', '=', 0)]}" attrs="{'invisible': [('maintenance_count', '=', 0)]}"
context="{'search_default_production_id': active_id}"> context="{'search_default_production_id': active_id}">
@@ -22,7 +22,7 @@
</button> </button>
</div> </div>
</field> </field>
</record> </record> -->
<record id="custom_model_form_view_inherit" model="ir.ui.view"> <record id="custom_model_form_view_inherit" model="ir.ui.view">
<field name="name">custom.model.form.view.inherit</field> <field name="name">custom.model.form.view.inherit</field>
@@ -451,6 +451,7 @@
</div> </div>
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="model_file" string="产品模型" readonly="1" widget="Viewer3D" attrs="{'invisible': [('model_file', '=', False)]}"/> <field name="model_file" string="产品模型" readonly="1" widget="Viewer3D" attrs="{'invisible': [('model_file', '=', False)]}"/>
<field name="model_id" readonly="1"/>
<field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1" <field name="glb_url" widget="Viewer3D" string="模型" readonly="1" force_save="1"
attrs="{'invisible': [('glb_url', '=', False)]}"/> attrs="{'invisible': [('glb_url', '=', False)]}"/>
</field> </field>

View File

@@ -237,22 +237,6 @@
<!-- string="返工"--> <!-- string="返工"-->
<!-- attrs='{"invisible": [("rework_flag","=",True)]}' confirm="是否返工"/>--> <!-- attrs='{"invisible": [("rework_flag","=",True)]}' confirm="是否返工"/>-->
</xpath> </xpath>
<xpath expr="//page[1]" position="before">
<page string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
<group>
<group>
<field name="product_tmpl_id_materials_id" widget="many2one"/>
<field name="product_tmpl_id_materials_type_id" widget="many2one"/>
</group>
<group>
<field name="product_tmpl_id_length"/>
<field name="product_tmpl_id_width"/>
<field name="product_tmpl_id_height"/>
</group>
</group>
</page>
</xpath>
<xpath expr="//label[1]" position="before"> <xpath expr="//label[1]" position="before">
<!-- --> <!-- -->
<field name="production_id" invisible="0"/> <field name="production_id" invisible="0"/>
@@ -336,6 +320,8 @@
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/>
<field name="individuation_page_list" invisible="1"/>
<page string="工件装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WCP")]}'> <page string="工件装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WCP")]}'>
<group> <group>
<!-- <field name="_barcode_scanned" widget="barcode_handler"/> --> <!-- <field name="_barcode_scanned" widget="barcode_handler"/> -->
@@ -514,7 +500,6 @@
<field name='X_deviation_angle' readonly="1"/> <field name='X_deviation_angle' readonly="1"/>
</group> </group>
</page> </page>
<page string="工件配送" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WD")]}'> <page string="工件配送" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "WD")]}'>
<field name="workpiece_delivery_ids"> <field name="workpiece_delivery_ids">
<tree editable="bottom"> <tree editable="bottom">
@@ -534,58 +519,6 @@
</tree> </tree>
</field> </field>
</page> </page>
</xpath>
<xpath expr="//form//header" position="inside">
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
string="获取数据"
attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/>
</xpath>
<!-- =====原生页签,暂时不进行配置===== -->
<!-- <xpath expr="//page[@name='components']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "ML")]}</attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//page[@name='time_tracking']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "TT")]}</attribute>-->
<!-- </xpath>-->
<!-- ============================= -->
<xpath expr="//page[1]" position="before">
<field name="results" invisible="1"/>
<field name="individuation_page_list" invisible="1"/>
<page string="后置三元检测" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "PTD")]}'>
<group>
<field name="test_results"
attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])],
"invisible":[("results","!=",False)]}'/>
<!-- <field name="is_remanufacture" attrs='{"invisible":[("test_results","!=","报废")]}'/>-->
<!-- <field name="is_fetchcnc"-->
<!-- attrs='{"invisible":["|",("test_results","=","合格"),("is_remanufacture","=",False)]}'/>-->
<field name="reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<field name="detailed_reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<!-- <field name="results" readonly="1" attrs='{"invisible":[("results","!=","合格")]}'/>-->
<field name="detection_report" attrs='{"invisible":[("results","!=",False)]}'
widget="pdf_viewer" readonly="1"/>
</group>
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="recreateManufacturingOrWorkerOrder"-->
<!-- string="检测确认"-->
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>-->
</page>
<page string="2D加工图纸" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "2D_MD")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "QIS")]}'>
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
</xpath>
<xpath expr="//page[1]" position="before">
<page string="CNC程序" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CNC_P")]}'> <page string="CNC程序" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CNC_P")]}'>
<field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id" <field name="cnc_ids" widget="one2many" string="工作程序" default_order="sequence_number,id"
readonly="0"> readonly="0">
@@ -621,8 +554,28 @@
</tree> </tree>
</field> </field>
</page> </page>
</xpath> <page string="后置三元检测" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "PTD")]}'>
<xpath expr="//page[1]" position="before"> <group>
<field name="test_results"
attrs='{"readonly":["&amp;","|",("state","!=","to be detected"), "|",("routing_type","=","CNC加工"),("is_inspect", "=", True),("state","in",["done","rework"])],
"invisible":[("results","!=",False)]}'/>
<!-- <field name="is_remanufacture" attrs='{"invisible":[("test_results","!=","报废")]}'/>-->
<!-- <field name="is_fetchcnc"-->
<!-- attrs='{"invisible":["|",("test_results","=","合格"),("is_remanufacture","=",False)]}'/>-->
<field name="reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<field name="detailed_reason"
attrs='{"required":[("test_results","!=","合格")],"invisible":[("test_results","=","合格")],"readonly":[("state","in",("done", "rework"))]}'/>
<!-- <field name="results" readonly="1" attrs='{"invisible":[("results","!=","合格")]}'/>-->
<field name="detection_report" attrs='{"invisible":[("results","!=",False)]}'
widget="pdf_viewer" readonly="1"/>
</group>
<!-- <div class="col-12 col-lg-6 o_setting_box">-->
<!-- <button type="object" class="oe_highlight" name="recreateManufacturingOrWorkerOrder"-->
<!-- string="检测确认"-->
<!-- attrs='{"invisible": ["|","|",("state","!=","progress"),("user_permissions","=",False),("results","=","合格")]}'/>-->
<!-- </div>-->
</page>
<page string="解除装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "DCP")]}'> <page string="解除装夹" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "DCP")]}'>
<!-- <field name="tray_id" readonly="1"/>--> <!-- <field name="tray_id" readonly="1"/>-->
<!-- <div class="col-12 col-lg-6 o_setting_box">--> <!-- <div class="col-12 col-lg-6 o_setting_box">-->
@@ -636,7 +589,45 @@
<!-- </div>--> <!-- </div>-->
</page> </page>
<page string="2D加工图纸" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "2D_MD")]}'>
<field name="machining_drawings" widget="adaptive_viewer"/>
</page>
<page string="质检标准" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "QIS")]}'>
<field name="quality_standard" widget="adaptive_viewer"/>
</page>
<page name="CMR" string="开料要求" attrs='{"invisible": ["!", ("individuation_page_list", "ilike", "CMR")]}'>
<group>
<group>
<field name="product_tmpl_id_materials_id" widget="many2one"/>
<field name="product_tmpl_id_materials_type_id" widget="many2one"/>
</group>
<group>
<field name="product_tmpl_id_length"/>
<field name="product_tmpl_id_width"/>
<field name="product_tmpl_id_height"/>
</group>
</group>
</page>
</xpath> </xpath>
<xpath expr="//form//header" position="inside">
<button type="object" class="oe_highlight jikimo_button_confirm" name="get_three_check_datas"
string="获取数据"
attrs='{"invisible": ["|", ("state","!=","progress"), ("routing_type","!=","装夹预调")]}'/>
</xpath>
<!-- =====原生页签,暂时不进行配置===== -->
<!-- <xpath expr="//page[@name='components']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "ML")]}</attribute>-->
<!-- </xpath>-->
<!-- <xpath expr="//page[@name='time_tracking']" position="attributes">-->
<!-- <attribute name="attrs">{"invisible": ["!", ("individuation_page_list", "ilike", "TT")]}</attribute>-->
<!-- </xpath>-->
<!-- ============================= -->
<!-- <xpath expr="//form//sheet//group//group//div[1]" position="after">--> <!-- <xpath expr="//form//sheet//group//group//div[1]" position="after">-->
<!-- <label for="date_start" string="实际加工时间"/>--> <!-- <label for="date_start" string="实际加工时间"/>-->
<!-- <div class="oe_inline">--> <!-- <div class="oe_inline">-->
@@ -687,6 +678,7 @@
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="part_number" string="成品零件图号"/> <field name="part_number" string="成品零件图号"/>
<field name="model_id" string="模型id"/>
</field> </field>
<xpath expr="//filter[@name='progress']" position="after"> <xpath expr="//filter[@name='progress']" position="after">
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/> <filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>

View File

@@ -4,10 +4,11 @@
<record model="ir.ui.view" id="view_product_template_form_inherit_sf_manufacturing"> <record model="ir.ui.view" id="view_product_template_form_inherit_sf_manufacturing">
<field name="name">product.template.product.form.inherit.sf_manufacture</field> <field name="name">product.template.product.form.inherit.sf_manufacture</field>
<field name="model">product.template</field> <field name="model">product.template</field>
<field name="inherit_id" ref="sf_dlm_management.view_product_template_only_form_inherit_sf"/> <field name="inherit_id" ref="sf_sale.view_product_template_form_inherit_sf"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='barcode']" position="after"> <xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='product_tag_ids']" position="after">
<field name="model_id" readonly="1"/> <field name="categ_type" invisible="1"/>
<field name="model_id" readonly="1" attrs="{'invisible': [('categ_type', '!=', '成品')]}"/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

@@ -101,7 +101,7 @@
action="action_quotations_supply_method" action="action_quotations_supply_method"
parent="sale.sale_order_menu" parent="sale.sale_order_menu"
groups="sf_base.group_production_engineer" groups="sf_base.group_production_engineer"
sequence="2"/> sequence="20"/>
<record id="sale.menu_sale_order" model="ir.ui.menu"> <record id="sale.menu_sale_order" model="ir.ui.menu">
<field name="groups_id" eval="[(4, ref('sf_base.group_production_engineer'))]"/> <field name="groups_id" eval="[(4, ref('sf_base.group_production_engineer'))]"/>

View File

@@ -271,7 +271,7 @@ class ResaleOrderLine(models.Model):
embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余') embryo_redundancy_id = fields.Many2one('sf.embryo.redundancy', '坯料冗余')
manual_quotation = fields.Boolean('人工编程', default=False) manual_quotation = fields.Boolean('人工编程', default=False)
model_url = fields.Char('模型文件地址') model_url = fields.Char('模型文件地址')
model_id = fields.Char('模型id') model_id = fields.Char('模型ID')
delivery_end_date = fields.Date('交货截止日期') delivery_end_date = fields.Date('交货截止日期')

View File

@@ -107,6 +107,7 @@
<field name="glb_url" widget="Viewer3D" optional="show" <field name="glb_url" widget="Viewer3D" optional="show"
string="模型文件" readonly="1" attrs="{'column_invisible': [('parent.model_display_version', '!=', 'v2')], 'isInList': True}"/> string="模型文件" readonly="1" attrs="{'column_invisible': [('parent.model_display_version', '!=', 'v2')], 'isInList': True}"/>
<field name="part_name" optional="show"/> <field name="part_name" optional="show"/>
<field name="model_id" optional="hide"/>
</xpath> </xpath>
<xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after"> <xpath expr="//field[@name='order_line']/tree/field[@name='price_subtotal']" position="after">
<field name="remark"/> <field name="remark"/>
@@ -359,10 +360,10 @@
<field name="categ_id" position="replace"> <field name="categ_id" position="replace">
<field name='categ_id' invisible="1"/> <field name='categ_id' invisible="1"/>
</field> </field>
<field name="product_tag_ids" position="after"> <!-- <field name="product_tag_ids" position="after">
<field name="default_code" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/> <field name="default_code" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/>
<field name="barcode" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/> <field name="barcode" attrs="{'invisible': [('product_variant_count', '&gt;', 1)]}"/>
</field> </field> -->
</field> </field>
</record> </record>
<record id="sale.product_template_action" model="ir.actions.act_window"> <record id="sale.product_template_action" model="ir.actions.act_window">