Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化

This commit is contained in:
mgw
2025-04-24 17:27:40 +08:00
49 changed files with 616 additions and 300 deletions

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

@@ -1,8 +1,6 @@
import logging import logging
from io import BytesIO
from odoo import models, fields, api from odoo import models, fields, api
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -21,9 +19,7 @@ class MrpWorkorder(models.Model):
if pdf_data: if pdf_data:
try: try:
# 执行打印 # 执行打印
printer = self.env['printing.printer'].get_default() self.env['jikimo.printing'].print_pdf(pdf_data)
printer.print_document(report=None, content = pdf_data, doc_format='pdf')
wo.production_id.product_id.is_print_program = True wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印") _logger.info(f"工单 {wo.name} 的PDF已成功打印")

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

@@ -9,7 +9,7 @@ class MrpProduction(models.Model):
@api.depends('state') @api.depends('state')
def _compute_pr_mp_count(self): def _compute_pr_mp_count(self):
for item in 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: if pr_ids:
item.pr_mp_count = len(pr_ids) item.pr_mp_count = len(pr_ids)
else: else:

View File

@@ -102,6 +102,7 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
pass pass
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel): class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order.item" _inherit = "purchase.request.line.make.purchase.order.item"

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,34 +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='public', 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) tool_groups_id = request.env['sf.tool.groups'].sudo().search([('equipment_ids', 'in', maintenance_equipment.id)], limit=1)
if not tool_groups_id: if not tool_groups_id:
return {'code': 400, 'message': '刀具组不存在'} 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),
@@ -37,12 +54,17 @@ class MainController(http.Controller):
'password': maintenance_equipment.ftp_password 'password': maintenance_equipment.ftp_password
} }
# 传输nc文件 # 传输nc文件
if transfer_nc_files( try:
result = transfer_nc_files(
source_ftp_info, source_ftp_info,
target_ftp_info, target_ftp_info,
'/' + str(model_id), '/' + str(model_id),
'/home/jikimo/testdir', '/',
end_with=tool_groups_id.name + '-all.nc'): match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
)
if result:
return {'code': 200, 'message': 'success'} return {'code': 200, 'message': 'success'}
else: else:
return {'code': 500, 'message': '传输失败'} return {'code': 404, 'message': '未找到编程文件'}
except Exception as e:
return {'code': 500, 'message': str(e)}

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

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

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

@@ -33,6 +33,7 @@ class SfProductionProcessParameter(models.Model):
if product_id: if product_id:
product_id.server_product_process_parameters_id = self.id product_id.server_product_process_parameters_id = self.id
else: else:
res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
self.env['product.template'].create({ self.env['product.template'].create({
'detailed_type': 'service', 'detailed_type': 'service',
'name': product_name, 'name': product_name,
@@ -42,6 +43,10 @@ class SfProductionProcessParameter(models.Model):
'sale_ok': True, # 可销售 'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购 'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.id, '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): def create_work_center(self):

View File

@@ -41,7 +41,7 @@
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/> attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
<field name="server_product_process_parameters_id" string="工艺参数" <field name="server_product_process_parameters_id" string="工艺参数"
options="{'no_create': True}" options="{'no_create': True}"
attrs="{'invisible': ['|',('detailed_type', '!=', '服务'),('detailed_type', '=', False)]}"/> attrs="{'invisible': ['|',('detailed_type', '!=', 'service'),('detailed_type', '=', False)]}"/>
<field name="cutting_tool_material_id" class="custom_required" <field name="cutting_tool_material_id" class="custom_required"
options="{'no_create': True}" options="{'no_create': True}"
attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}" attrs="{'invisible': [('categ_type', '!=', '刀具')],'required': [('categ_type', '=', '刀具')],'readonly': [('id', '!=', False)]}"

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,7 +133,14 @@ class FtpController:
def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir, end_with='.nc', 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服务器下载所有{end_with}结尾的文件并上传到目标FTP服务器,保持目录结构 从源FTP服务器下载所有{end_with}结尾的文件并上传到目标FTP服务器,保持目录结构
@@ -143,6 +151,7 @@ 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(
@@ -151,6 +160,8 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
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
@@ -160,6 +171,8 @@ def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir,
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(end_with): # 文件名匹配字符串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

@@ -924,6 +924,8 @@ class MrpProduction(models.Model):
"bom_id": self[0].bom_id.id, "bom_id": self[0].bom_id.id,
"is_subcontract":True, "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(): for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0] cur_request_line = request_line_list[0]
cur_request_line['product_qty'] = len(request_line_list) 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] product_id_to_production_names[product_id] = [p.name for p in pd]
sorted_workorders = None sorted_workorders = None
purchase_request_line = [] purchase_request_line = []
all_workorders = []
for production in production_all: for production in production_all:
proc_workorders = [] proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search( 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) # 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( purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(
workorders, production) workorders, production)
all_workorders += workorders
self._create_subcontract_purchase_request(purchase_request_line) 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): def _reset_work_order_sequence1(self, k):
for rec in self: for rec in self:
@@ -1816,7 +1821,7 @@ class MrpProduction(models.Model):
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') 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

@@ -256,14 +256,12 @@ class ResWorkcenter(models.Model):
date_planned_end), date_planned_end),
('state', 'not in', ['draft', 'cancel'])]) ('state', 'not in', ['draft', 'cancel'])])
if plan_ids: sum_qty = sum([p.product_qty for p in plan_ids]) if plan_ids else count
sum_qty = sum([p.product_qty for p in plan_ids])
production_line_hour_capacity = self.production_line_hour_capacity production_line_hour_capacity = self.production_line_hour_capacity
if sum_qty >= production_line_hour_capacity: if sum_qty > production_line_hour_capacity:
message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d' % ( message = '当前计划开始时间不能预约排程,超过生产线小时产能(%d件)%d' % (
production_line_hour_capacity, count) production_line_hour_capacity, count)
raise UserError(message) raise UserError(message)
return False
return True return True

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')
@@ -70,13 +71,16 @@ class ResMrpWorkOrder(models.Model):
tracking=True) tracking=True)
back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=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) pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True)
@api.depends('state') @api.depends('state')
def _compute_pr_mp_count(self): def _compute_pr_mp_count(self):
for item in self: for item in self:
if not item.is_subcontract: if not item.is_subcontract:
item.pr_mp_count = 0 item.pr_mp_count = 0
continue 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: if pr_ids:
item.pr_mp_count = len(pr_ids) item.pr_mp_count = len(pr_ids)
else: else:
@@ -461,7 +465,9 @@ class ResMrpWorkOrder(models.Model):
采购请求 采购请求
""" """
self.ensure_one() 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 = { action = {
'res_model': 'purchase.request', 'res_model': 'purchase.request',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -1808,7 +1814,7 @@ class ResMrpWorkOrder(models.Model):
lazy=lazy 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): 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

@@ -7,6 +7,11 @@ from odoo.tools import str2bool
class SfProductionProcessParameter(models.Model): class SfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter' _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( outsourced_service_products = fields.One2many(
'product.template', # 另一个模型的名称 'product.template', # 另一个模型的名称
'server_product_process_parameters_id', # 对应的 Many2one 字段名称 '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) is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") 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') @api.constrains('outsourced_service_products')
def _validate_partner_limit(self): def _validate_partner_limit(self):
for record in self: for record in self:
@@ -35,17 +59,17 @@ class SfProductionProcessParameter(models.Model):
else: else:
record.is_product_button = False record.is_product_button = False
def has_wksp_prefix(self,code): def has_wksp_prefix(self):
""" """
判断字符串是否以WKSP开头不区分大小写 判断字符串是否以WKSP开头不区分大小写
:param text: 要检查的字符串 :param text: 要检查的字符串
:return: True/False :return: True/False
""" """
return code.upper().startswith('WKSP') return self.code.upper().startswith('101'+self.routing_id.code)
@api.depends('outsourced_service_products','code') @api.depends('outsourced_service_products','code')
def _compute_is_delete_button(self): def _compute_is_delete_button(self):
for record in 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 record.is_delete_button = False
elif record.outsourced_service_products: elif record.outsourced_service_products:
record.is_delete_button = True record.is_delete_button = True

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

@@ -29,8 +29,8 @@
<field name="is_product_button" invisible="1"/> <field name="is_product_button" invisible="1"/>
<field name="is_delete_button" invisible="1"/> <field name="is_delete_button" invisible="1"/>
<field name="code" attrs="{'readonly': True}"/> <field name="code" attrs="{'readonly': True}"/>
<field name="name"/> <field name="name" required="1"/>
<field name="outsourced_service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> <field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/>
<!-- 按钮列 --> <!-- 按钮列 -->
<button name="action_create_service_product" string="创建服务产品" type="object" <button name="action_create_service_product" string="创建服务产品" type="object"
class="btn-primary" class="btn-primary"

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

@@ -152,7 +152,7 @@
<span class="o_stat_value"> <span class="o_stat_value">
<field name="pr_mp_count"/> <field name="pr_mp_count"/>
</span> </span>
<span class="o_stat_text">采购</span> <span class="o_stat_text">采购申请</span>
</div> </div>
</button> </button>
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button" <button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
@@ -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">-->

View File

@@ -4,9 +4,9 @@
<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="model_id" readonly="1"/>
</xpath> </xpath>
</field> </field>

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

@@ -14,29 +14,21 @@ class ProductCreationWizard(models.TransientModel):
).sudo(), ).sudo(),
) )
process_parameter_id = fields.Many2one('sf.production.process.parameter') process_parameter_id = fields.Many2one('sf.production.process.parameter')
def action_create_service_product(self):
# 获取产品分类(服务)
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
default_values = {
'detailed_type': 'service',
'name': f"{self.process_parameter_id.routing_id.name}_{self.process_parameter_id.name}",
'invoice_policy': 'delivery',
'categ_id': service_categ.id,
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.process_parameter_id.id,
}
def action_create_product(self): def action_create_product(self):
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo() res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
name = self.process_parameter_id.process_id.name or self.process_parameter_id.routing_id.name
default_values = { default_values = {
'detailed_type': 'service', 'detailed_type': 'service',
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}", 'name': f"{name}_{self.process_parameter_id.name}",
'invoice_policy': 'delivery', 'invoice_policy': 'delivery',
'categ_id': self.categ_id.id, 'categ_id': self.categ_id.id,
'description': f"基于{self.process_parameter_id.name}创建的服务产品", 'description': f"基于{self.process_parameter_id.name}创建的服务产品",
'sale_ok': True, # 可销售 'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购 'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.process_parameter_id.id, 'server_product_process_parameters_id': self.process_parameter_id.id,
'seller_ids': [(0, 0, {
# 'delay': 1,
'partner_id': res_partner.id,
'price': 1, })],
} }
self.env['product.template'].create(default_values) self.env['product.template'].create(default_values)

View File

@@ -2,6 +2,7 @@
import logging import logging
from itertools import groupby from itertools import groupby
from odoo import models, api, fields, _ from odoo import models, api, fields, _
from odoo.exceptions import UserError
class ProductionTechnologyReAdjustWizard(models.TransientModel): class ProductionTechnologyReAdjustWizard(models.TransientModel):
@@ -76,4 +77,11 @@ class ProductionTechnologyReAdjustWizard(models.TransientModel):
if workorders[ if workorders[
0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': 0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程':
workorders[0].state = 'waiting' workorders[0].state = 'waiting'
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.name), ('is_subcontract', '=', 'True'), ('state', '!=', 'rejected')])
if not pr_ids:
continue
if not all(pr.state == 'draft' for pr in pr_ids):
# 如果发现有记录的 state 不是 'draft',抛出异常
raise UserError("有采购申请的状态不是 '草稿'")
pr_ids.state = 'rejected'

View File

@@ -224,10 +224,11 @@ class sf_production_plan(models.Model):
return num return num
def do_production_schedule(self): def do_production_schedule(self, date_planned_start):
""" """
排程方法 排程方法
""" """
self.deal_processing_schedule(date_planned_start)
for record in self: for record in self:
if not record.production_line_id: if not record.production_line_id:
raise ValidationError("未选择生产线") raise ValidationError("未选择生产线")

View File

@@ -40,6 +40,5 @@ class Action_Plan_All_Wizard(models.TransientModel):
self.plan_ids.date_planned_start = self.date_planned_start self.plan_ids.date_planned_start = self.date_planned_start
# 在这里添加您的逻辑来处理这些ID # 在这里添加您的逻辑来处理这些ID
# 判断能否排成 # 判断能否排成
self.plan_ids.deal_processing_schedule(self.date_planned_start) self.plan_ids.do_production_schedule(self.date_planned_start)
self.plan_ids.do_production_schedule()
_logger.info('处理计划: %s 完成', self.plan_ids.ids) _logger.info('处理计划: %s 完成', self.plan_ids.ids)

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('交货截止日期')
@@ -387,6 +387,7 @@ class RePurchaseOrder(models.Model):
server_template = self.env['product.template'].search( server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id), [('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')]) ('detailed_type', '=', 'service')])
# route_ids
result.append({ result.append({
"product_id": server_template.product_variant_id.id, "product_id": server_template.product_variant_id.id,
"name": production.procurement_group_id.name, "name": production.procurement_group_id.name,

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