Merge branch 'develop' of https://e.coding.net/jikimo-hn/jikimo_sfs/jikimo_sf into feature/制造功能优化
This commit is contained in:
3
jikimo_work_reporting_api/__init__.py
Normal file
3
jikimo_work_reporting_api/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import controllers
|
||||||
|
from . import models
|
||||||
18
jikimo_work_reporting_api/__manifest__.py
Normal file
18
jikimo_work_reporting_api/__manifest__.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
{
|
||||||
|
'name': '机企猫 报工系统API',
|
||||||
|
'version': '1.0.0',
|
||||||
|
'summary': """ 机企猫 报工系统API """,
|
||||||
|
'author': '机企猫',
|
||||||
|
'website': 'https://xt.sf.jikimo.com',
|
||||||
|
'category': 'sf',
|
||||||
|
'depends': ['base', 'sf_maintenance', 'jikimo_mini_program'],
|
||||||
|
'data': [
|
||||||
|
|
||||||
|
],
|
||||||
|
|
||||||
|
'application': True,
|
||||||
|
'installable': True,
|
||||||
|
'auto_install': False,
|
||||||
|
'license': 'LGPL-3',
|
||||||
|
}
|
||||||
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
2
jikimo_work_reporting_api/controllers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from . import main
|
||||||
42
jikimo_work_reporting_api/controllers/main.py
Normal file
42
jikimo_work_reporting_api/controllers/main.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
import json
|
||||||
|
from odoo import http
|
||||||
|
from odoo.http import request
|
||||||
|
from odoo.addons.sf_machine_connect.models.ftp_operate import transfer_nc_files
|
||||||
|
|
||||||
|
class MainController(http.Controller):
|
||||||
|
|
||||||
|
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*')
|
||||||
|
def manual_download_program(self):
|
||||||
|
"""
|
||||||
|
人工线下加工传输编程文件
|
||||||
|
"""
|
||||||
|
data = json.loads(request.httprequest.data)
|
||||||
|
maintenance_equipment_name = data.get('maintenance_equipment_name')
|
||||||
|
model_id = data.get('model_id')
|
||||||
|
if not maintenance_equipment_name or not model_id:
|
||||||
|
return {'code': 400, 'message': '参数错误'}
|
||||||
|
maintenance_equipment = request.env['maintenance.equipment'].sudo().search([('name', '=', maintenance_equipment_name)], limit=1)
|
||||||
|
if not maintenance_equipment:
|
||||||
|
return {'code': 400, 'message': '机床不存在'}
|
||||||
|
ftp_resconfig = request.env['res.config.settings'].sudo().get_values()
|
||||||
|
source_ftp_info = {
|
||||||
|
'host': ftp_resconfig['ftp_host'],
|
||||||
|
'port': int(ftp_resconfig['ftp_port']),
|
||||||
|
'username': ftp_resconfig['ftp_user'],
|
||||||
|
'password': ftp_resconfig['ftp_password']
|
||||||
|
}
|
||||||
|
target_ftp_info = {
|
||||||
|
'host': maintenance_equipment.ftp_host,
|
||||||
|
'port': int(maintenance_equipment.ftp_port),
|
||||||
|
'username': maintenance_equipment.ftp_username,
|
||||||
|
'password': maintenance_equipment.ftp_password
|
||||||
|
}
|
||||||
|
# 传输nc文件
|
||||||
|
if transfer_nc_files(
|
||||||
|
source_ftp_info,
|
||||||
|
target_ftp_info,
|
||||||
|
'/' + str(model_id),
|
||||||
|
'/home/jikimo/testdir'):
|
||||||
|
return {'code': 200, 'message': 'success'}
|
||||||
|
else:
|
||||||
|
return {'code': 500, 'message': '传输失败'}
|
||||||
1
jikimo_work_reporting_api/models/__init__.py
Normal file
1
jikimo_work_reporting_api/models/__init__.py
Normal file
@@ -0,0 +1 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
@@ -52,10 +52,10 @@ class JikimoWorkorderException(models.Model):
|
|||||||
|
|
||||||
def _get_message(self, message_queue_ids):
|
def _get_message(self, message_queue_ids):
|
||||||
contents, _ = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
|
contents, _ = super(JikimoWorkorderException, self)._get_message(message_queue_ids)
|
||||||
url = self.env['ir.config_parameter'].get_param('web.base.url')
|
base_url = self.env['ir.config_parameter'].get_param('web.base.url')
|
||||||
action_id = self.env.ref('mrp.mrp_production_action').id
|
action_id = self.env.ref('mrp.mrp_production_action').id
|
||||||
for index, content in enumerate(contents):
|
for index, content in enumerate(contents):
|
||||||
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
|
exception_id = self.env['jikimo.workorder.exception'].browse(message_queue_ids[index].res_id)
|
||||||
url = url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
|
url = base_url + '/web#id=%s&view_type=form&action=%s' % (exception_id.workorder_id.production_id.id, action_id)
|
||||||
contents[index] = content.replace('{{url}}', url)
|
contents[index] = content.replace('{{url}}', url)
|
||||||
return contents, message_queue_ids
|
return contents, message_queue_ids
|
||||||
|
|||||||
@@ -206,7 +206,7 @@ class QualityCheck(models.Model):
|
|||||||
('NG', 'NG')
|
('NG', 'NG')
|
||||||
], string='出厂检验报告结果', default='OK')
|
], string='出厂检验报告结果', default='OK')
|
||||||
measure_operator = fields.Many2one('res.users', string='操机员')
|
measure_operator = fields.Many2one('res.users', string='操机员')
|
||||||
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager', store=True)
|
quality_manager = fields.Many2one('res.users', string='质检员', compute='_compute_quality_manager')
|
||||||
|
|
||||||
@api.depends('measure_line_ids')
|
@api.depends('measure_line_ids')
|
||||||
def _compute_quality_manager(self):
|
def _compute_quality_manager(self):
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ 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):
|
||||||
|
|||||||
@@ -2,7 +2,16 @@
|
|||||||
import time, datetime
|
import time, datetime
|
||||||
import hashlib
|
import hashlib
|
||||||
from odoo import models
|
from odoo import models
|
||||||
|
from typing import Optional
|
||||||
import socket
|
import socket
|
||||||
|
import os
|
||||||
|
import logging
|
||||||
|
import qrcode
|
||||||
|
from reportlab.pdfgen import canvas
|
||||||
|
from reportlab.lib.units import inch
|
||||||
|
from PyPDF2 import PdfFileReader, PdfFileWriter
|
||||||
|
from reportlab.pdfbase import pdfmetrics
|
||||||
|
from reportlab.pdfbase.ttfonts import TTFont
|
||||||
|
|
||||||
class Common(models.Model):
|
class Common(models.Model):
|
||||||
_name = 'sf.sync.common'
|
_name = 'sf.sync.common'
|
||||||
@@ -92,3 +101,120 @@ class PrintingUtils(models.AbstractModel):
|
|||||||
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
|
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
|
||||||
# port = 9100 # 可以作为参数传入,或者在此配置
|
# port = 9100 # 可以作为参数传入,或者在此配置
|
||||||
self.send_to_printer(host, port, zpl_code)
|
self.send_to_printer(host, port, zpl_code)
|
||||||
|
|
||||||
|
|
||||||
|
def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
|
||||||
|
"""
|
||||||
|
在PDF文件中添加二维码
|
||||||
|
:param pdf_path: PDF文件路径
|
||||||
|
:param content: 二维码内容
|
||||||
|
:param buttom_text: 二维码下方文字
|
||||||
|
:return: 是否成功
|
||||||
|
"""
|
||||||
|
if not os.path.exists(pdf_path):
|
||||||
|
logging.warning(f'PDF文件不存在: {pdf_path}')
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 生成二维码
|
||||||
|
qr = qrcode.QRCode(version=1, box_size=10, border=5)
|
||||||
|
qr.add_data(str(content))
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_img = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
|
||||||
|
# 保存二维码为临时文件
|
||||||
|
qr_temp_path = '/tmp/qr_temp.png'
|
||||||
|
qr_img.save(qr_temp_path)
|
||||||
|
|
||||||
|
# 创建一个临时PDF文件路径
|
||||||
|
output_temp_path = '/tmp/output_temp.pdf'
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 使用reportlab创建一个新的PDF
|
||||||
|
|
||||||
|
|
||||||
|
# 注册中文字体
|
||||||
|
font_paths = [
|
||||||
|
"/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
|
||||||
|
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
|
||||||
|
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
|
||||||
|
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
|
||||||
|
"/usr/share/fonts/chinese/TrueType/simsun.ttc", # 某些Linux发行版位置
|
||||||
|
]
|
||||||
|
|
||||||
|
font_found = False
|
||||||
|
for font_path in font_paths:
|
||||||
|
if os.path.exists(font_path):
|
||||||
|
try:
|
||||||
|
pdfmetrics.registerFont(TTFont('SimSun', font_path))
|
||||||
|
font_found = True
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 读取原始PDF
|
||||||
|
with open(pdf_path, "rb") as original_file:
|
||||||
|
existing_pdf = PdfFileReader(original_file)
|
||||||
|
output = PdfFileWriter()
|
||||||
|
|
||||||
|
# 处理第一页
|
||||||
|
page = existing_pdf.getPage(0)
|
||||||
|
# 获取页面尺寸
|
||||||
|
page_width = float(page.mediaBox.getWidth())
|
||||||
|
page_height = float(page.mediaBox.getHeight())
|
||||||
|
|
||||||
|
# 创建一个新的PDF页面用于放置二维码
|
||||||
|
c = canvas.Canvas(output_temp_path, pagesize=(page_width, page_height))
|
||||||
|
|
||||||
|
# 设置字体
|
||||||
|
if font_found:
|
||||||
|
c.setFont('SimSun', 14) # 增大字体大小到14pt
|
||||||
|
else:
|
||||||
|
# 如果没有找到中文字体,使用默认字体
|
||||||
|
c.setFont('Helvetica', 14)
|
||||||
|
logging.warning("未找到中文字体,将使用默认字体")
|
||||||
|
|
||||||
|
# 在右下角绘制二维码,预留边距
|
||||||
|
qr_size = 1.5 * inch # 二维码大小为2英寸
|
||||||
|
margin = 0.1 * inch # 边距为0.4英寸
|
||||||
|
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
|
||||||
|
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
|
||||||
|
|
||||||
|
if buttom_text:
|
||||||
|
# 在二维码下方绘制文字
|
||||||
|
text = buttom_text
|
||||||
|
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
|
||||||
|
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
|
||||||
|
text_y = margin + 20 # 文字位置靠近底部
|
||||||
|
c.drawString(text_x, text_y, text)
|
||||||
|
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
# 读取带有二维码的临时PDF
|
||||||
|
with open(output_temp_path, "rb") as qr_file:
|
||||||
|
qr_pdf = PdfFileReader(qr_file)
|
||||||
|
qr_page = qr_pdf.getPage(0)
|
||||||
|
|
||||||
|
# 合并原始页面和二维码页面
|
||||||
|
page.mergePage(qr_page)
|
||||||
|
output.addPage(page)
|
||||||
|
|
||||||
|
# 添加剩余的页面
|
||||||
|
for i in range(1, existing_pdf.getNumPages()):
|
||||||
|
output.addPage(existing_pdf.getPage(i))
|
||||||
|
|
||||||
|
# 保存最终的PDF到一个临时文件
|
||||||
|
final_temp_path = pdf_path + '.tmp'
|
||||||
|
with open(final_temp_path, "wb") as output_file:
|
||||||
|
output.write(output_file)
|
||||||
|
|
||||||
|
# 替换原始文件
|
||||||
|
os.replace(final_temp_path, pdf_path)
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 清理临时文件
|
||||||
|
if os.path.exists(qr_temp_path):
|
||||||
|
os.remove(qr_temp_path)
|
||||||
|
if os.path.exists(output_temp_path):
|
||||||
|
os.remove(output_temp_path)
|
||||||
@@ -9,6 +9,7 @@
|
|||||||
<field name="name"/>
|
<field name="name"/>
|
||||||
<field name="ip_address"/>
|
<field name="ip_address"/>
|
||||||
<field name="port"/>
|
<field name="port"/>
|
||||||
|
<field name="type"/>
|
||||||
<!-- 其他字段... -->
|
<!-- 其他字段... -->
|
||||||
</tree>
|
</tree>
|
||||||
</field>
|
</field>
|
||||||
@@ -24,6 +25,7 @@
|
|||||||
<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>
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
<field name="name">原材料</field>
|
<field name="name">原材料</field>
|
||||||
<field name="type">原材料</field>
|
<field name="type">原材料</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="product_category_surface_technics_sf" model="product.category">
|
<record id="product_category_surface_technics_sf" model="product.category">
|
||||||
<field name="name">表面工艺</field>
|
<field name="name">表面工艺</field>
|
||||||
<field name="type">表面工艺</field>
|
<field name="type">表面工艺</field>
|
||||||
|
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
|
||||||
|
<field name="property_cost_method">fifo</field>
|
||||||
|
<field name="property_valuation">manual_periodic</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<record id="product_category_cutting_tool_sf" model="product.category">
|
<record id="product_category_cutting_tool_sf" model="product.category">
|
||||||
@@ -40,10 +42,10 @@
|
|||||||
<!-- <field name="company_id" ref="base.main_company"/>-->
|
<!-- <field name="company_id" ref="base.main_company"/>-->
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
<!-- <record id="res_users_bfm" model="res.users">-->
|
<!-- <record id="res_users_bfm" model="res.users">-->
|
||||||
<!-- <field name="name">业务平台</field>-->
|
<!-- <field name="name">业务平台</field>-->
|
||||||
<!--<!– <field name="partner_id" ref="res_partner_bfm"/>–>-->
|
<!--<!– <field name="partner_id" ref="res_partner_bfm"/>–>-->
|
||||||
<!-- </record>-->
|
<!-- </record>-->
|
||||||
|
|
||||||
<record id="product_functional_tool_sf" model="product.product">
|
<record id="product_functional_tool_sf" model="product.product">
|
||||||
<field name="name">功能刀具</field>
|
<field name="name">功能刀具</field>
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from . import models
|
||||||
@@ -9,8 +9,9 @@
|
|||||||
""",
|
""",
|
||||||
'category': 'sf',
|
'category': 'sf',
|
||||||
'website': 'https://www.sf.jikimo.com',
|
'website': 'https://www.sf.jikimo.com',
|
||||||
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing','jikimo_attachment_viewer'],
|
'depends': ['sf_sale', 'sf_dlm', 'sf_manufacturing', 'jikimo_attachment_viewer'],
|
||||||
'data': [
|
'data': [
|
||||||
|
'data/sequence.xml',
|
||||||
'data/stock_data.xml',
|
'data/stock_data.xml',
|
||||||
'views/product_template_management_view.xml',
|
'views/product_template_management_view.xml',
|
||||||
],
|
],
|
||||||
|
|||||||
10
sf_dlm_management/data/sequence.xml
Normal file
10
sf_dlm_management/data/sequence.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<record id="sequence_production_process_parameter" model="ir.sequence">
|
||||||
|
<field name="name">工艺可选参数编码序列</field>
|
||||||
|
<field name="code">sf.production.process.parameter</field>
|
||||||
|
<field name="prefix">WKSP</field>
|
||||||
|
<field name="padding">9</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -1,2 +1,4 @@
|
|||||||
# from . import product_template
|
# from . import product_template
|
||||||
# from . import product_supplierinfo
|
# from . import product_supplierinfo
|
||||||
|
from . import sf_production_common
|
||||||
|
from . import mrp_routing_workcenter
|
||||||
16
sf_dlm_management/models/mrp_routing_workcenter.py
Normal file
16
sf_dlm_management/models/mrp_routing_workcenter.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import logging
|
||||||
|
from odoo import fields, models, api
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import str2bool
|
||||||
|
|
||||||
|
|
||||||
|
class ResMrpRoutingWorkcenter(models.Model):
|
||||||
|
_inherit = 'mrp.routing.workcenter'
|
||||||
|
def init(self):
|
||||||
|
super(ResMrpRoutingWorkcenter, self).init()
|
||||||
|
# 在模块初始化时触发计算字段的更新
|
||||||
|
records = self.search([])
|
||||||
|
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
|
||||||
|
return
|
||||||
|
records.optional_process_parameters_date()
|
||||||
|
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
|
||||||
77
sf_dlm_management/models/sf_production_common.py
Normal file
77
sf_dlm_management/models/sf_production_common.py
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import logging
|
||||||
|
from odoo import fields, models, api
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import str2bool
|
||||||
|
|
||||||
|
|
||||||
|
class SfProductionProcessParameter(models.Model):
|
||||||
|
_inherit = 'sf.production.process.parameter'
|
||||||
|
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def create(self, vals):
|
||||||
|
if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
|
||||||
|
vals['code'] = self.env['ir.sequence'].next_by_code('sf.production.process.parameter') or '/'
|
||||||
|
if not vals.get('process_id') and vals.get('routing_id'):
|
||||||
|
vals['gain_way'] = '外协'
|
||||||
|
routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id'))
|
||||||
|
if routing_id.surface_technics_id:
|
||||||
|
vals['process_id'] = routing_id.surface_technics_id.id
|
||||||
|
obj = super(SfProductionProcessParameter, self).create(vals)
|
||||||
|
return obj
|
||||||
|
def create_service_product(self):
|
||||||
|
service_categ = self.env.ref(
|
||||||
|
'sf_dlm.product_category_surface_technics_sf').sudo()
|
||||||
|
|
||||||
|
product_name = f"{self.process_id.name}{self.name}"
|
||||||
|
product_id = self.env['product.template'].search(
|
||||||
|
[("name", '=', product_name)])
|
||||||
|
if product_id:
|
||||||
|
product_id.server_product_process_parameters_id = self.id
|
||||||
|
else:
|
||||||
|
self.env['product.template'].create({
|
||||||
|
'detailed_type': 'service',
|
||||||
|
'name': product_name,
|
||||||
|
'invoice_policy': 'delivery',
|
||||||
|
'categ_id': service_categ.id,
|
||||||
|
'description': f"基于{self.name}创建的服务产品",
|
||||||
|
'sale_ok': True, # 可销售
|
||||||
|
'purchase_ok': True, # 可采购
|
||||||
|
'server_product_process_parameters_id': self.id,
|
||||||
|
})
|
||||||
|
|
||||||
|
def create_work_center(self):
|
||||||
|
production_process_parameter = self
|
||||||
|
if not production_process_parameter.process_id:
|
||||||
|
return
|
||||||
|
if not production_process_parameter.routing_id:
|
||||||
|
workcenter_id = self.env['mrp.routing.workcenter'].search(
|
||||||
|
[("surface_technics_id", '=', production_process_parameter.process_id.id)])
|
||||||
|
if not workcenter_id:
|
||||||
|
outsourcing_work_center = self.env['mrp.workcenter'].search(
|
||||||
|
[("name", '=', '外协工作中心')])
|
||||||
|
routing_id = self.env['mrp.routing.workcenter'].create({
|
||||||
|
'workcenter_ids': [(6, 0, outsourcing_work_center.ids)],
|
||||||
|
'routing_tag': 'special',
|
||||||
|
'routing_type': '表面工艺',
|
||||||
|
'is_outsource': True,
|
||||||
|
'surface_technics_id': production_process_parameter.process_id.id,
|
||||||
|
'name': production_process_parameter.process_id.name,
|
||||||
|
})
|
||||||
|
production_process_parameter.routing_id = routing_id.id
|
||||||
|
else:
|
||||||
|
production_process_parameter.routing_id = workcenter_id.id
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
super(SfProductionProcessParameter, self).init()
|
||||||
|
# 在模块初始化时触发计算字段的更新
|
||||||
|
records = self.search([])
|
||||||
|
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process',
|
||||||
|
default='False')):
|
||||||
|
return
|
||||||
|
for record in records:
|
||||||
|
if not record.outsourced_service_products:
|
||||||
|
record.create_service_product()
|
||||||
|
record.create_work_center()
|
||||||
|
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True)
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
|
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
|
||||||
<field name="materials_type_id" string="型号" placeholder="请选择" options="{'no_create': True}"
|
<field name="materials_type_id" string="型号" placeholder="请选择" options="{'no_create': True}"
|
||||||
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': ['|',('categ_type', '!=', '表面工艺'),('categ_type', '=', False)]}"/>
|
attrs="{'invisible': ['|',('categ_type', '!=', '表面工艺'),('categ_type', '=', False)]}"/>
|
||||||
<field name="cutting_tool_material_id" class="custom_required"
|
<field name="cutting_tool_material_id" class="custom_required"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
from ftplib import FTP
|
import os
|
||||||
|
from ftplib import FTP, error_perm
|
||||||
|
|
||||||
_logger = logging.getLogger(__name__)
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -52,8 +53,8 @@ class FtpController:
|
|||||||
print(self.username, self.port, self.host, self.password)
|
print(self.username, self.port, self.host, self.password)
|
||||||
ftp = FTP_P()
|
ftp = FTP_P()
|
||||||
_logger.info("===================connect==================")
|
_logger.info("===================connect==================")
|
||||||
# self.ftp.set_debuglevel(2) #打开调试级别2,显示详细信息
|
# ftp.set_debuglevel(2) #打开调试级别2,显示详细信息
|
||||||
ftp.set_pasv(0) # 0主动模式 1 #被动模式
|
# ftp.set_pasv(1) # 0主动模式 1 #被动模式
|
||||||
try:
|
try:
|
||||||
ftp.connect(self.host, self.port)
|
ftp.connect(self.host, self.port)
|
||||||
ftp.login(self.username, self.password)
|
ftp.login(self.username, self.password)
|
||||||
@@ -128,3 +129,126 @@ class FtpController:
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.ftp.delete(delpath)
|
self.ftp.delete(delpath)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def transfer_nc_files(source_ftp_info, target_ftp_info, source_dir, target_dir, keep_dir=False):
|
||||||
|
"""
|
||||||
|
从源FTP服务器下载所有.nc文件并上传到目标FTP服务器,保持目录结构
|
||||||
|
|
||||||
|
Args:
|
||||||
|
source_ftp_info: dict, 源FTP连接信息 {host, port, username, password}
|
||||||
|
target_ftp_info: dict, 目标FTP连接信息 {host, port, username, password}
|
||||||
|
source_dir: str, 源FTP上的起始目录
|
||||||
|
target_dir: str, 目标FTP上的目标目录
|
||||||
|
keep_dir: bool, 是否保持目录结构,默认False
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 连接源FTP
|
||||||
|
source_ftp = FtpController(
|
||||||
|
source_ftp_info['host'],
|
||||||
|
source_ftp_info['port'],
|
||||||
|
source_ftp_info['username'],
|
||||||
|
source_ftp_info['password']
|
||||||
|
)
|
||||||
|
source_ftp.ftp.set_pasv(1)
|
||||||
|
|
||||||
|
# 连接目标FTP
|
||||||
|
target_ftp = FtpController(
|
||||||
|
target_ftp_info['host'],
|
||||||
|
target_ftp_info['port'],
|
||||||
|
target_ftp_info['username'],
|
||||||
|
target_ftp_info['password']
|
||||||
|
)
|
||||||
|
source_ftp.ftp.set_pasv(1)
|
||||||
|
|
||||||
|
# 递归遍历源目录
|
||||||
|
def traverse_dir(current_dir, relative_path=''):
|
||||||
|
source_ftp.ftp.cwd(current_dir)
|
||||||
|
file_list = source_ftp.ftp.nlst()
|
||||||
|
|
||||||
|
for item in file_list:
|
||||||
|
try:
|
||||||
|
# 尝试进入目录
|
||||||
|
source_ftp.ftp.cwd(f"{current_dir}/{item}")
|
||||||
|
# 如果成功则是目录
|
||||||
|
new_relative_path = os.path.join(relative_path, item)
|
||||||
|
# 在目标FTP创建对应目录
|
||||||
|
try:
|
||||||
|
if keep_dir:
|
||||||
|
target_ftp.ftp.mkd(f"{target_dir}/{new_relative_path}")
|
||||||
|
except:
|
||||||
|
pass # 目录可能已存在
|
||||||
|
# 递归遍历子目录
|
||||||
|
traverse_dir(f"{current_dir}/{item}", new_relative_path)
|
||||||
|
source_ftp.ftp.cwd('..')
|
||||||
|
except:
|
||||||
|
# 如果是.nc文件则传输
|
||||||
|
if item.lower().endswith('.nc'):
|
||||||
|
# 下载到临时文件
|
||||||
|
temp_path = f"/tmp/{item}"
|
||||||
|
with open(temp_path, 'wb') as f:
|
||||||
|
source_ftp.ftp.retrbinary(f'RETR {item}', f.write)
|
||||||
|
|
||||||
|
# 上传到目标FTP对应目录
|
||||||
|
if keep_dir:
|
||||||
|
target_path = f"{target_dir}/{relative_path}/{item}"
|
||||||
|
else:
|
||||||
|
target_path = f"{target_dir}/{item}"
|
||||||
|
with open(temp_path, 'rb') as f:
|
||||||
|
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
|
||||||
|
|
||||||
|
# 删除临时文件
|
||||||
|
os.remove(temp_path)
|
||||||
|
logging.info(f"已传输文件: {item}")
|
||||||
|
|
||||||
|
# 清空目标目录下的所有内容
|
||||||
|
try:
|
||||||
|
target_ftp.ftp.cwd(target_dir)
|
||||||
|
files = target_ftp.ftp.nlst()
|
||||||
|
|
||||||
|
for f in files:
|
||||||
|
try:
|
||||||
|
# 尝试删除文件
|
||||||
|
target_ftp.ftp.delete(f)
|
||||||
|
except:
|
||||||
|
try:
|
||||||
|
# 如果删除失败,可能是目录,递归删除目录
|
||||||
|
def remove_dir(path):
|
||||||
|
target_ftp.ftp.cwd(path)
|
||||||
|
sub_files = target_ftp.ftp.nlst()
|
||||||
|
for sf in sub_files:
|
||||||
|
try:
|
||||||
|
target_ftp.ftp.delete(sf)
|
||||||
|
except:
|
||||||
|
remove_dir(f"{path}/{sf}")
|
||||||
|
target_ftp.ftp.cwd('..')
|
||||||
|
target_ftp.ftp.rmd(path)
|
||||||
|
|
||||||
|
remove_dir(f"{target_dir}/{f}")
|
||||||
|
except:
|
||||||
|
logging.error(f"无法删除 {f}")
|
||||||
|
pass
|
||||||
|
|
||||||
|
logging.info(f"已清空目标目录 {target_dir}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"清空目标目录失败: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# 开始遍历
|
||||||
|
traverse_dir(source_dir)
|
||||||
|
|
||||||
|
logging.info("所有.nc文件传输完成")
|
||||||
|
return True
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"传输过程出错: {str(e)}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
finally:
|
||||||
|
# 关闭FTP连接
|
||||||
|
try:
|
||||||
|
source_ftp.ftp.quit()
|
||||||
|
target_ftp.ftp.quit()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
@@ -4,3 +4,4 @@ from . import sf_maintenance_oee
|
|||||||
from . import sf_maintenance_logs
|
from . import sf_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
|
||||||
|
|||||||
92
sf_maintenance/models/maintenance_printer.py
Normal file
92
sf_maintenance/models/maintenance_printer.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
import qrcode
|
||||||
|
import base64
|
||||||
|
from io import BytesIO
|
||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
class MaintenanceEquipment(models.Model):
|
||||||
|
_name = 'maintenance.equipment'
|
||||||
|
_inherit = ['maintenance.equipment', 'printing.utils']
|
||||||
|
|
||||||
|
qr_code_image = fields.Binary(string='二维码', compute='_generate_qr_code')
|
||||||
|
|
||||||
|
@api.depends('name')
|
||||||
|
def _generate_qr_code(self):
|
||||||
|
for record in self:
|
||||||
|
# Generate QR code
|
||||||
|
qr = qrcode.QRCode(
|
||||||
|
version=1,
|
||||||
|
error_correction=qrcode.constants.ERROR_CORRECT_L,
|
||||||
|
box_size=10,
|
||||||
|
border=4,
|
||||||
|
)
|
||||||
|
qr.add_data(record.name)
|
||||||
|
qr.make(fit=True)
|
||||||
|
qr_image = qr.make_image(fill_color="black", back_color="white")
|
||||||
|
|
||||||
|
# Encode the image data in base64
|
||||||
|
image_stream = BytesIO()
|
||||||
|
qr_image.save(image_stream, format="PNG")
|
||||||
|
encoded_image = base64.b64encode(image_stream.getvalue())
|
||||||
|
|
||||||
|
record.qr_code_image = encoded_image
|
||||||
|
|
||||||
|
def print_single_method(self):
|
||||||
|
|
||||||
|
print('self.name========== %s' % self.name)
|
||||||
|
self.ensure_one()
|
||||||
|
qr_code_data = self.qr_code_image
|
||||||
|
if not qr_code_data:
|
||||||
|
raise UserError("没有找到二维码数据。")
|
||||||
|
maintenance_equipment_name = self.name
|
||||||
|
# host = "192.168.50.110" # 可以根据实际情况修改
|
||||||
|
# port = 9100 # 可以根据实际情况修改
|
||||||
|
|
||||||
|
# 获取默认打印机配置
|
||||||
|
printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name)], limit=1)
|
||||||
|
if not printer_config:
|
||||||
|
raise UserError('请先配置打印机')
|
||||||
|
host = printer_config.printer_id.ip_address
|
||||||
|
port = printer_config.printer_id.port
|
||||||
|
self.print_qr_code(maintenance_equipment_name, host, port)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_zpl_code(self, code):
|
||||||
|
"""生成ZPL代码用于打印二维码标签
|
||||||
|
Args:
|
||||||
|
code: 需要编码的内容
|
||||||
|
Returns:
|
||||||
|
str: ZPL指令字符串
|
||||||
|
"""
|
||||||
|
zpl_code = "^XA\n" # 开始ZPL格式
|
||||||
|
|
||||||
|
# 设置打印参数
|
||||||
|
zpl_code += "^LH0,0\n" # 设置标签起始位置
|
||||||
|
zpl_code += "^CI28\n" # 设置中文编码
|
||||||
|
zpl_code += "^PW400\n" # 设置打印宽度为400点
|
||||||
|
zpl_code += "^LL300\n" # 设置标签长度为300点
|
||||||
|
|
||||||
|
# 打印标题
|
||||||
|
zpl_code += "^FO10,20\n" # 设置标题位置
|
||||||
|
zpl_code += "^A0N,30,30\n" # 设置字体大小
|
||||||
|
zpl_code += "^FD机床二维码^FS\n" # 打印标题文本
|
||||||
|
|
||||||
|
# 打印二维码
|
||||||
|
zpl_code += "^FO50,60\n" # 设置二维码位置
|
||||||
|
zpl_code += f"^BQN,2,8\n" # 设置二维码参数:模式2,放大倍数8
|
||||||
|
zpl_code += f"^FDLA,{code}^FS\n" # 二维码内容
|
||||||
|
|
||||||
|
# 打印编码文本
|
||||||
|
zpl_code += "^FO50,220\n" # 设置编码文本位置
|
||||||
|
zpl_code += "^A0N,25,25\n" # 设置字体大小
|
||||||
|
zpl_code += f"^FD编码: {code}^FS\n" # 打印编码文本
|
||||||
|
|
||||||
|
# 打印日期
|
||||||
|
zpl_code += "^FO50,260\n"
|
||||||
|
zpl_code += "^A0N,20,20\n"
|
||||||
|
zpl_code += f"^FD打印日期: {fields.Date.today()}^FS\n"
|
||||||
|
|
||||||
|
zpl_code += "^PQ1\n" # 打印1份
|
||||||
|
zpl_code += "^XZ\n" # 结束ZPL格式
|
||||||
|
|
||||||
|
return zpl_code
|
||||||
|
|
||||||
@@ -826,6 +826,11 @@ class SfMaintenanceEquipment(models.Model):
|
|||||||
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
|
image_lq_id = fields.Many2many('maintenance.equipment.image', 'equipment_lq_id', string='冷却方式',
|
||||||
domain="[('type', '=', '冷却方式')]")
|
domain="[('type', '=', '冷却方式')]")
|
||||||
|
|
||||||
|
ftp_host = fields.Char('FTP 主机')
|
||||||
|
ftp_port = fields.Char('FTP 端口')
|
||||||
|
ftp_username = fields.Char('FTP 用户名')
|
||||||
|
ftp_password = fields.Char('FTP 密码')
|
||||||
|
|
||||||
|
|
||||||
class SfRobotAxisNum(models.Model):
|
class SfRobotAxisNum(models.Model):
|
||||||
_name = 'sf.robot.axis.num'
|
_name = 'sf.robot.axis.num'
|
||||||
|
|||||||
@@ -1053,6 +1053,26 @@
|
|||||||
</page>
|
</page>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
|
||||||
|
<xpath expr="//group/field[@name='location']" position="after">
|
||||||
|
<field name="qr_code_image" widget="image" readonly="1" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" />
|
||||||
|
<label for="print_single_method"/>
|
||||||
|
<div class="col-12 col-lg-6 o_setting_box" style="white-space: nowrap">
|
||||||
|
<button type="object" class="oe_highlight" name='print_single_method' string="打印机床二维码"
|
||||||
|
attrs="{'invisible': [('equipment_type', '!=', '机床')]}"/>
|
||||||
|
</div>
|
||||||
|
</xpath>
|
||||||
|
<xpath expr="//page[@name='maintenance']" position="after">
|
||||||
|
<page name="network_config" string="网络配置" attrs="{'invisible': [('equipment_type', '!=', '机床')]}" >
|
||||||
|
<group>
|
||||||
|
<group string="ftp配置">
|
||||||
|
<field name="ftp_host" string="主机"/>
|
||||||
|
<field name="ftp_port" string="端口"/>
|
||||||
|
<field name="ftp_username" string="用户名"/>
|
||||||
|
<field name="ftp_password" string="密码" password="True"/>
|
||||||
|
</group>
|
||||||
|
</group>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
</data>
|
</data>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
""",
|
""",
|
||||||
'category': 'sf',
|
'category': 'sf',
|
||||||
'website': 'https://www.sf.jikimo.com',
|
'website': 'https://www.sf.jikimo.com',
|
||||||
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse','jikimo_attachment_viewer', 'jikimo_sale_multiple_supply_methods'],
|
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer',
|
||||||
|
'jikimo_sale_multiple_supply_methods', 'product'],
|
||||||
'data': [
|
'data': [
|
||||||
'data/cron_data.xml',
|
'data/cron_data.xml',
|
||||||
'data/stock_data.xml',
|
'data/stock_data.xml',
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
'data/panel_data.xml',
|
'data/panel_data.xml',
|
||||||
'data/sf_work_individuation_page.xml',
|
'data/sf_work_individuation_page.xml',
|
||||||
'data/agv_scheduling_data.xml',
|
'data/agv_scheduling_data.xml',
|
||||||
|
'data/product_data.xml',
|
||||||
'security/group_security.xml',
|
'security/group_security.xml',
|
||||||
'security/ir.model.access.csv',
|
'security/ir.model.access.csv',
|
||||||
'wizard/workpiece_delivery_views.xml',
|
'wizard/workpiece_delivery_views.xml',
|
||||||
@@ -28,6 +30,7 @@
|
|||||||
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
|
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
|
||||||
'wizard/sf_programming_reason_views.xml',
|
'wizard/sf_programming_reason_views.xml',
|
||||||
'wizard/sale_order_cancel_views.xml',
|
'wizard/sale_order_cancel_views.xml',
|
||||||
|
'wizard/process_outsourcing.xml',
|
||||||
'views/mrp_views_menus.xml',
|
'views/mrp_views_menus.xml',
|
||||||
'views/agv_scheduling_views.xml',
|
'views/agv_scheduling_views.xml',
|
||||||
'views/stock_lot_views.xml',
|
'views/stock_lot_views.xml',
|
||||||
@@ -44,6 +47,7 @@
|
|||||||
'views/sale_order_views.xml',
|
'views/sale_order_views.xml',
|
||||||
'views/mrp_workorder_batch_replan.xml',
|
'views/mrp_workorder_batch_replan.xml',
|
||||||
'views/purchase_order_view.xml',
|
'views/purchase_order_view.xml',
|
||||||
|
'views/product_template_views.xml',
|
||||||
],
|
],
|
||||||
'assets': {
|
'assets': {
|
||||||
|
|
||||||
|
|||||||
23
sf_manufacturing/data/product_data.xml
Normal file
23
sf_manufacturing/data/product_data.xml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="product_category_service" model="product.category">
|
||||||
|
<field name="name">服务</field>
|
||||||
|
<field name="parent_id" ref="product.product_category_all"/>
|
||||||
|
<field name="property_cost_method">fifo</field>
|
||||||
|
<field name="property_valuation">manual_periodic</field>
|
||||||
|
</record>
|
||||||
|
<record id="product_category_outsource_process" model="product.category">
|
||||||
|
<field name="name">工序外协</field>
|
||||||
|
<field name="parent_id" ref="sf_manufacturing.product_category_service"/>
|
||||||
|
<field name="property_cost_method">fifo</field>
|
||||||
|
<field name="property_valuation">manual_periodic</field>
|
||||||
|
</record>
|
||||||
|
<record id="product_category_outsource_other_process" model="product.category">
|
||||||
|
<field name="name">其他</field>
|
||||||
|
<field name="parent_id" ref="sf_manufacturing.product_category_outsource_process"/>
|
||||||
|
<field name="property_cost_method">fifo</field>
|
||||||
|
<field name="property_valuation">manual_periodic</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -17,3 +17,5 @@ from . import sale_order
|
|||||||
from . import quick_easy_order
|
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 workorder_printer
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import json
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import traceback
|
import traceback
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from itertools import groupby
|
from itertools import groupby
|
||||||
@@ -238,7 +239,8 @@ class MrpProduction(models.Model):
|
|||||||
programming_no = fields.Char('编程单号')
|
programming_no = fields.Char('编程单号')
|
||||||
work_state = fields.Char('业务状态')
|
work_state = fields.Char('业务状态')
|
||||||
programming_state = fields.Selection(
|
programming_state = fields.Selection(
|
||||||
[('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'), ('已取消', '已取消')],
|
[('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'),
|
||||||
|
('已取消', '已取消')],
|
||||||
string='编程状态',
|
string='编程状态',
|
||||||
tracking=True)
|
tracking=True)
|
||||||
glb_file = fields.Binary("glb模型文件")
|
glb_file = fields.Binary("glb模型文件")
|
||||||
@@ -267,6 +269,7 @@ class MrpProduction(models.Model):
|
|||||||
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
|
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
|
||||||
|
|
||||||
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
|
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
|
||||||
|
|
||||||
@api.depends('product_id')
|
@api.depends('product_id')
|
||||||
def _compute_part_info(self):
|
def _compute_part_info(self):
|
||||||
try:
|
try:
|
||||||
@@ -400,8 +403,10 @@ class MrpProduction(models.Model):
|
|||||||
and production.schedule_state == '已排' and production.is_rework is False):
|
and production.schedule_state == '已排' and production.is_rework is False):
|
||||||
production.state = 'pending_cam'
|
production.state = 'pending_cam'
|
||||||
if any((wo.test_results == '返工' and wo.state == 'done' and
|
if any((wo.test_results == '返工' and wo.state == 'done' and
|
||||||
(production.programming_state in ['已编程'] or(wo.individuation_page_list and 'PTD' in wo.individuation_page_list)))
|
(production.programming_state in ['已编程'] or (
|
||||||
or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程'])
|
wo.individuation_page_list and 'PTD' in wo.individuation_page_list)))
|
||||||
|
or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中',
|
||||||
|
'已编程'])
|
||||||
for wo in production.workorder_ids) or production.is_rework is True:
|
for wo in production.workorder_ids) or production.is_rework is True:
|
||||||
production.state = 'rework'
|
production.state = 'rework'
|
||||||
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
|
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
|
||||||
@@ -889,11 +894,44 @@ class MrpProduction(models.Model):
|
|||||||
workorders_values.append(
|
workorders_values.append(
|
||||||
self.env[
|
self.env[
|
||||||
'mrp.workorder']._json_workorder_surface_process_str(
|
'mrp.workorder']._json_workorder_surface_process_str(
|
||||||
production, route, product_production_process.seller_ids[0].partner_id.id))
|
production, route, product_production_process.seller_ids[
|
||||||
|
0].partner_id.id if product_production_process.seller_ids else False))
|
||||||
production.workorder_ids = workorders_values
|
production.workorder_ids = workorders_values
|
||||||
for workorder in production.workorder_ids:
|
for workorder in production.workorder_ids:
|
||||||
workorder.duration_expected = workorder._get_duration_expected()
|
workorder.duration_expected = workorder._get_duration_expected()
|
||||||
|
|
||||||
|
def _create_subcontract_purchase_request(self, purchase_request_line):
|
||||||
|
sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
|
||||||
|
grouped_purchase_request_line = {
|
||||||
|
k: list(g)
|
||||||
|
for k, g in groupby(sorted_list, key=itemgetter('name'))
|
||||||
|
}
|
||||||
|
for name, request_line in grouped_purchase_request_line.items():
|
||||||
|
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
|
||||||
|
grouped_purchase_request_line_sorted_list = {
|
||||||
|
k: list(g)
|
||||||
|
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
|
||||||
|
}
|
||||||
|
purchase_request_model = self.env["purchase.request"]
|
||||||
|
origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')})
|
||||||
|
pr = purchase_request_model.create({
|
||||||
|
"origin": origin,
|
||||||
|
"company_id": self.company_id.id,
|
||||||
|
"picking_type_id": self.env.ref('stock.picking_type_in').id,
|
||||||
|
"group_id": request_line[0].get('group_id'),
|
||||||
|
"requested_by": self.env.context.get("uid", self.env.uid),
|
||||||
|
"assigned_to": False,
|
||||||
|
"bom_id": self[0].bom_id.id,
|
||||||
|
})
|
||||||
|
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
|
||||||
|
cur_request_line = request_line_list[0]
|
||||||
|
cur_request_line['product_qty'] = len(request_line_list)
|
||||||
|
cur_request_line['request_id'] = pr.id
|
||||||
|
cur_request_line['origin'] = ", ".join({item['production_name'] for item in request_line_list if item.get('production_name')})
|
||||||
|
cur_request_line.pop('group_id', None)
|
||||||
|
cur_request_line.pop('production_name', None)
|
||||||
|
self.env["purchase.request.line"].create(cur_request_line)
|
||||||
|
|
||||||
# 外协出入库单处理
|
# 外协出入库单处理
|
||||||
def get_subcontract_pick_purchase(self):
|
def get_subcontract_pick_purchase(self):
|
||||||
production_all = self.sorted(lambda x: x.id)
|
production_all = self.sorted(lambda x: x.id)
|
||||||
@@ -903,6 +941,7 @@ class MrpProduction(models.Model):
|
|||||||
for product_id, pd in grouped_product_ids.items():
|
for product_id, pd in grouped_product_ids.items():
|
||||||
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 = []
|
||||||
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(
|
||||||
@@ -919,7 +958,10 @@ class MrpProduction(models.Model):
|
|||||||
return
|
return
|
||||||
for workorders in reversed(sorted_workorders):
|
for workorders in reversed(sorted_workorders):
|
||||||
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
|
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
|
||||||
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(
|
||||||
|
workorders, production)
|
||||||
|
self._create_subcontract_purchase_request(purchase_request_line)
|
||||||
|
|
||||||
# 工单排序
|
# 工单排序
|
||||||
def _reset_work_order_sequence1(self, k):
|
def _reset_work_order_sequence1(self, k):
|
||||||
@@ -1728,7 +1770,8 @@ class MrpProduction(models.Model):
|
|||||||
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
|
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
|
||||||
for production in self:
|
for production in self:
|
||||||
if production.state not in ['confirmed', 'pending_cam'] or production.programming_state != '已编程':
|
if production.state not in ['confirmed', 'pending_cam'] or production.programming_state != '已编程':
|
||||||
raise UserError('不可操作。所选制造订单必须同时满足如下条件:\n1、制造订单状态:待排程 或 待加工;\n2、制造订单编程状态:已编程。\n请检查!')
|
raise UserError(
|
||||||
|
'不可操作。所选制造订单必须同时满足如下条件:\n1、制造订单状态:待排程 或 待加工;\n2、制造订单编程状态:已编程。\n请检查!')
|
||||||
cloud_programming = production._cron_get_programming_state()
|
cloud_programming = production._cron_get_programming_state()
|
||||||
if cloud_programming['programming_state'] in ['待编程', '已编程', '编程中']:
|
if cloud_programming['programming_state'] in ['待编程', '已编程', '编程中']:
|
||||||
raise UserError("当前编程单正在重新编程,请注意查看当前制造订单的“编程记录”确认进度!")
|
raise UserError("当前编程单正在重新编程,请注意查看当前制造订单的“编程记录”确认进度!")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import logging
|
import logging
|
||||||
from odoo import fields, models, api
|
from odoo import fields, models, api
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError
|
||||||
|
from odoo.tools import str2bool
|
||||||
|
|
||||||
|
|
||||||
class ResMrpRoutingWorkcenter(models.Model):
|
class ResMrpRoutingWorkcenter(models.Model):
|
||||||
@@ -24,10 +25,41 @@ class ResMrpRoutingWorkcenter(models.Model):
|
|||||||
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
|
||||||
bom_id = fields.Many2one('mrp.bom', required=False)
|
bom_id = fields.Many2one('mrp.bom', required=False)
|
||||||
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
|
||||||
|
optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数')
|
||||||
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
reserved_duration = fields.Float('预留时长', default=30, tracking=True)
|
||||||
is_outsource = fields.Boolean('外协', default=False)
|
is_outsource = fields.Boolean('外协', default=False)
|
||||||
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
|
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
|
||||||
|
|
||||||
|
@api.onchange('surface_technics_id')
|
||||||
|
def optional_process_parameters_date(self):
|
||||||
|
for record in self:
|
||||||
|
if not record.surface_technics_id:
|
||||||
|
continue
|
||||||
|
parameter_ids = self.env['sf.production.process.parameter'].search([
|
||||||
|
('process_id', '=', record.surface_technics_id.id),
|
||||||
|
])
|
||||||
|
record.optional_process_parameters = parameter_ids.ids
|
||||||
|
|
||||||
|
# @api.model
|
||||||
|
# def _auto_init(self):
|
||||||
|
# # 先执行标准初始化
|
||||||
|
# res = super(ResMrpRoutingWorkcenter, self)._auto_init()
|
||||||
|
# # 然后执行自定义初始化
|
||||||
|
# records = self.search([])
|
||||||
|
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',
|
||||||
|
# default='False')):
|
||||||
|
# return
|
||||||
|
# records.optional_process_parameters_date()
|
||||||
|
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
|
||||||
|
# return res
|
||||||
|
# def init(self):
|
||||||
|
# super(ResMrpRoutingWorkcenter, self).init()
|
||||||
|
# # 在模块初始化时触发计算字段的更新
|
||||||
|
# records = self.search([])
|
||||||
|
# if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
|
||||||
|
# return
|
||||||
|
# records.optional_process_parameters_date()
|
||||||
|
# self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)
|
||||||
def get_no(self):
|
def get_no(self):
|
||||||
international_standards = self.search(
|
international_standards = self.search(
|
||||||
[('code', '!=', ''), ('active', 'in', [True, False])],
|
[('code', '!=', ''), ('active', 'in', [True, False])],
|
||||||
|
|||||||
@@ -21,7 +21,16 @@ class ResWorkcenter(models.Model):
|
|||||||
related='equipment_id.production_line_id', store=True)
|
related='equipment_id.production_line_id', store=True)
|
||||||
is_process_outsourcing = fields.Boolean('工艺外协')
|
is_process_outsourcing = fields.Boolean('工艺外协')
|
||||||
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
|
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
|
||||||
|
@api.constrains('name')
|
||||||
|
def _check_unique_name_code(self):
|
||||||
|
for record in self:
|
||||||
|
# 检查是否已经存在相同的 name 和 code 组合
|
||||||
|
existing = self.search([
|
||||||
|
('name', '=', record.name),
|
||||||
|
('id', '!=', record.id) # 排除当前记录
|
||||||
|
])
|
||||||
|
if existing:
|
||||||
|
raise ValueError('记录已存在')
|
||||||
def write(self, vals):
|
def write(self, vals):
|
||||||
if 'users_ids' in vals:
|
if 'users_ids' in vals:
|
||||||
old_users = self.users_ids
|
old_users = self.users_ids
|
||||||
|
|||||||
@@ -19,7 +19,6 @@ 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'
|
||||||
_order = 'sequence asc'
|
|
||||||
_description = '工单'
|
_description = '工单'
|
||||||
|
|
||||||
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')
|
||||||
@@ -108,7 +107,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
||||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||||
detection_result.routing_type == cur_workorder.routing_type and
|
detection_result.routing_type == cur_workorder.routing_type and
|
||||||
cur_workorder.tag_type !='重新加工' and
|
cur_workorder.tag_type != '重新加工' and
|
||||||
detection_result.test_results != '合格'
|
detection_result.test_results != '合格'
|
||||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||||
):
|
):
|
||||||
@@ -124,7 +123,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
|
||||||
detection_result.processing_panel == cur_workorder.processing_panel and
|
detection_result.processing_panel == cur_workorder.processing_panel and
|
||||||
detection_result.routing_type == cur_workorder.routing_type and
|
detection_result.routing_type == cur_workorder.routing_type and
|
||||||
cur_workorder.tag_type !='重新加工' and
|
cur_workorder.tag_type != '重新加工' and
|
||||||
detection_result.test_results != '合格'
|
detection_result.test_results != '合格'
|
||||||
for detection_result in cur_workorder.production_id.detection_result_ids
|
for detection_result in cur_workorder.production_id.detection_result_ids
|
||||||
):
|
):
|
||||||
@@ -429,6 +428,8 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
def _compute_surface_technics_purchase_ids(self):
|
def _compute_surface_technics_purchase_ids(self):
|
||||||
for order in self:
|
for order in self:
|
||||||
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
|
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
|
||||||
|
# domain = [('group_id', '=', self.production_id.procurement_group_id.id),
|
||||||
|
# ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
|
||||||
domain = [('purchase_type', '=', 'consignment'),
|
domain = [('purchase_type', '=', 'consignment'),
|
||||||
('origin', 'like', '%' + self.production_id.name + '%'),
|
('origin', 'like', '%' + self.production_id.name + '%'),
|
||||||
('state', '!=', 'cancel')]
|
('state', '!=', 'cancel')]
|
||||||
@@ -473,12 +474,13 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
|
|
||||||
def _get_surface_technics_purchase_ids(self):
|
def _get_surface_technics_purchase_ids(self):
|
||||||
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
|
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
|
||||||
purchase_orders = self.env['purchase.order'].search(domain)
|
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
|
||||||
|
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前)
|
||||||
|
limit=1)
|
||||||
purchase_orders_id = self.env['purchase.order']
|
purchase_orders_id = self.env['purchase.order']
|
||||||
for po in purchase_orders:
|
for po in purchase_orders:
|
||||||
for line in po.order_line:
|
for line in po.order_line:
|
||||||
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
|
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
|
||||||
if line.product_qty == 1:
|
|
||||||
purchase_orders_id = line.order_id
|
purchase_orders_id = line.order_id
|
||||||
return purchase_orders_id
|
return purchase_orders_id
|
||||||
|
|
||||||
@@ -1200,6 +1202,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
|
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
|
||||||
}]
|
}]
|
||||||
return workorders_values_str
|
return workorders_values_str
|
||||||
|
|
||||||
def _process_compute_state(self):
|
def _process_compute_state(self):
|
||||||
for workorder in self:
|
for workorder in self:
|
||||||
# 如果工单的工序没有进行排序则跳出循环
|
# 如果工单的工序没有进行排序则跳出循环
|
||||||
@@ -1287,6 +1290,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
mo.get_move_line(workorder.production_id, workorder))
|
mo.get_move_line(workorder.production_id, workorder))
|
||||||
else:
|
else:
|
||||||
workorder.state = 'waiting'
|
workorder.state = 'waiting'
|
||||||
|
|
||||||
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
|
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
|
||||||
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
|
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
|
||||||
'production_id.programming_state')
|
'production_id.programming_state')
|
||||||
@@ -1301,6 +1305,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
for check_id in workorder.check_ids:
|
for check_id in workorder.check_ids:
|
||||||
if not check_id.is_inspect:
|
if not check_id.is_inspect:
|
||||||
check_id.quality_state = 'none'
|
check_id.quality_state = 'none'
|
||||||
|
|
||||||
# 重写工单开始按钮方法
|
# 重写工单开始按钮方法
|
||||||
def button_start(self):
|
def button_start(self):
|
||||||
# 判断工单状态是否为等待组件
|
# 判断工单状态是否为等待组件
|
||||||
@@ -1552,7 +1557,8 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
# 如果工单包含了外协工序,需要预留数量
|
# 如果工单包含了外协工序,需要预留数量
|
||||||
if self.move_raw_ids.move_orig_ids.subcontract_workorder_id:
|
if self.move_raw_ids.move_orig_ids.subcontract_workorder_id:
|
||||||
location_id = self.move_raw_ids.location_id
|
location_id = self.move_raw_ids.location_id
|
||||||
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(lambda q: q.location_id.id == location_id.id)
|
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(
|
||||||
|
lambda q: q.location_id.id == location_id.id)
|
||||||
if quant.reserved_quantity == 0:
|
if quant.reserved_quantity == 0:
|
||||||
self.env['stock.quant']._update_reserved_quantity(
|
self.env['stock.quant']._update_reserved_quantity(
|
||||||
self.move_raw_ids.product_id,
|
self.move_raw_ids.product_id,
|
||||||
@@ -1706,7 +1712,8 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
store=True, string='工序作业')
|
store=True, string='工序作业')
|
||||||
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
|
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
|
||||||
related='routing_work_center_id.individuation_page_ids')
|
related='routing_work_center_id.individuation_page_ids')
|
||||||
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True)
|
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids',
|
||||||
|
store=True)
|
||||||
|
|
||||||
@api.depends('name')
|
@api.depends('name')
|
||||||
def _compute_routing_work_center_id(self):
|
def _compute_routing_work_center_id(self):
|
||||||
@@ -1727,6 +1734,7 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
individuation_page_list = [item.code for item in mw.individuation_page_ids]
|
individuation_page_list = [item.code for item in mw.individuation_page_ids]
|
||||||
if individuation_page_list:
|
if individuation_page_list:
|
||||||
mw.individuation_page_list = list(set(individuation_page_list))
|
mw.individuation_page_list = list(set(individuation_page_list))
|
||||||
|
|
||||||
# =============================================================================================
|
# =============================================================================================
|
||||||
|
|
||||||
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
|
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
|
||||||
@@ -1750,6 +1758,23 @@ class ResMrpWorkOrder(models.Model):
|
|||||||
self.check_ids.filtered(lambda ch: ch.is_inspect is True and ch.quality_state == 'waiting').write(
|
self.check_ids.filtered(lambda ch: ch.is_inspect is True and ch.quality_state == 'waiting').write(
|
||||||
{'quality_state': 'none'})
|
{'quality_state': 'none'})
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||||
|
aggregate_field = 'create_date:max'
|
||||||
|
if aggregate_field not in fields:
|
||||||
|
fields.append(aggregate_field)
|
||||||
|
orderby = "create_date desc"
|
||||||
|
|
||||||
|
return super(ResMrpWorkOrder, self).read_group(
|
||||||
|
domain,
|
||||||
|
fields,
|
||||||
|
groupby,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
orderby=orderby,
|
||||||
|
lazy=lazy
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class CNCprocessing(models.Model):
|
class CNCprocessing(models.Model):
|
||||||
_name = 'sf.cnc.processing'
|
_name = 'sf.cnc.processing'
|
||||||
@@ -2040,6 +2065,7 @@ class WorkPieceDelivery(models.Model):
|
|||||||
|
|
||||||
def _get_agv_route_type_selection(self):
|
def _get_agv_route_type_selection(self):
|
||||||
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
|
||||||
|
|
||||||
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
|
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
|
||||||
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
|
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
|
||||||
status = fields.Selection(
|
status = fields.Selection(
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ class ResProductMo(models.Model):
|
|||||||
# domain="[('materials_id', '=', materials_id)]")
|
# domain="[('materials_id', '=', materials_id)]")
|
||||||
# cutting_tool_model_id.material_model_id
|
# cutting_tool_model_id.material_model_id
|
||||||
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
|
server_product_process_parameters_id = fields.Many2one('sf.production.process.parameter',
|
||||||
string='表面工艺参数(服务产品)')
|
string='工艺参数(服务产品)')
|
||||||
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
|
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter', 'process_parameter_rel',
|
||||||
string='表面工艺参数')
|
string='表面工艺参数')
|
||||||
|
|
||||||
|
|||||||
@@ -66,6 +66,37 @@ class PurchaseOrder(models.Model):
|
|||||||
raise UserError('请对【产品】中的【数量】进行输入')
|
raise UserError('请对【产品】中的【数量】进行输入')
|
||||||
if line.price_unit <= 0:
|
if line.price_unit <= 0:
|
||||||
raise UserError('请对【产品】中的【单价】进行输入')
|
raise UserError('请对【产品】中的【单价】进行输入')
|
||||||
|
if record.purchase_type == 'consignment':
|
||||||
|
bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids
|
||||||
|
replenish = self.env['stock.warehouse.orderpoint'].search([
|
||||||
|
('product_id', '=', bom_line_id.product_id.id),
|
||||||
|
(
|
||||||
|
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
|
||||||
|
# ('state', 'in', ['draft', 'confirmed'])
|
||||||
|
], limit=1)
|
||||||
|
if not replenish:
|
||||||
|
replenish_model = self.env['stock.warehouse.orderpoint']
|
||||||
|
replenish = replenish_model.create({
|
||||||
|
'product_id': bom_line_id.product_id.id,
|
||||||
|
'location_id': self.env.ref(
|
||||||
|
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
|
||||||
|
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
|
||||||
|
'group_id': record.group_id.id,
|
||||||
|
'qty_to_order': 1,
|
||||||
|
'origin': record.name,
|
||||||
|
})
|
||||||
|
else:
|
||||||
|
replenish.write({
|
||||||
|
'product_id': bom_line_id.product_id.id,
|
||||||
|
'location_id': self.env.ref(
|
||||||
|
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
|
||||||
|
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
|
||||||
|
'group_id': record.group_id.id,
|
||||||
|
'qty_to_order': 1 + replenish.qty_to_order,
|
||||||
|
'origin': record.name + ',' + replenish.origin,
|
||||||
|
})
|
||||||
|
replenish.action_replenish()
|
||||||
|
|
||||||
return super(PurchaseOrder, self).button_confirm()
|
return super(PurchaseOrder, self).button_confirm()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
27
sf_manufacturing/models/purchase_request_line.py
Normal file
27
sf_manufacturing/models/purchase_request_line.py
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import base64
|
||||||
|
import datetime
|
||||||
|
import logging
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import traceback
|
||||||
|
from operator import itemgetter
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from itertools import groupby
|
||||||
|
from collections import defaultdict, namedtuple
|
||||||
|
|
||||||
|
from odoo import api, fields, models, SUPERUSER_ID, _
|
||||||
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRequestLine(models.Model):
|
||||||
|
_inherit = 'purchase.request.line'
|
||||||
|
is_subcontract = fields.Boolean(string='是否外协')
|
||||||
|
|
||||||
|
|
||||||
|
class PurchaseRequest(models.Model):
|
||||||
|
_inherit = 'purchase.request'
|
||||||
|
bom_id = fields.Many2one('mrp.bom')
|
||||||
@@ -1,12 +1,50 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import logging
|
import logging
|
||||||
from odoo import fields, models, api
|
from odoo import fields, models, api
|
||||||
from odoo.exceptions import UserError
|
from odoo.exceptions import UserError, ValidationError
|
||||||
|
from odoo.tools import str2bool
|
||||||
|
|
||||||
|
|
||||||
class SfProductionProcessParameter(models.Model):
|
class SfProductionProcessParameter(models.Model):
|
||||||
_inherit = 'sf.production.process.parameter'
|
_inherit = 'sf.production.process.parameter'
|
||||||
|
outsourced_service_products = fields.One2many(
|
||||||
|
'product.template', # 另一个模型的名称
|
||||||
|
'server_product_process_parameters_id', # 对应的 Many2one 字段名称
|
||||||
|
string='外协服务产品'
|
||||||
|
)
|
||||||
|
is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
|
||||||
|
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
|
||||||
|
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
|
||||||
|
|
||||||
|
@api.constrains('outsourced_service_products')
|
||||||
|
def _validate_partner_limit(self):
|
||||||
|
for record in self:
|
||||||
|
if len(record.outsourced_service_products) > 1:
|
||||||
|
raise ValidationError("工艺参数不能与多个产品关联")
|
||||||
|
@api.depends('outsourced_service_products')
|
||||||
|
def _compute_is_product_button(self):
|
||||||
|
for record in self:
|
||||||
|
if record.outsourced_service_products:
|
||||||
|
record.is_product_button = True
|
||||||
|
else:
|
||||||
|
record.is_product_button = False
|
||||||
|
|
||||||
|
def has_wksp_prefix(self,code):
|
||||||
|
"""
|
||||||
|
判断字符串是否以WKSP开头(不区分大小写)
|
||||||
|
:param text: 要检查的字符串
|
||||||
|
:return: True/False
|
||||||
|
"""
|
||||||
|
return code.upper().startswith('WKSP')
|
||||||
|
@api.depends('outsourced_service_products','code')
|
||||||
|
def _compute_is_delete_button(self):
|
||||||
|
for record in self:
|
||||||
|
if record.outsourced_service_products and self.has_wksp_prefix(record.code):
|
||||||
|
record.is_delete_button = False
|
||||||
|
elif record.outsourced_service_products:
|
||||||
|
record.is_delete_button = True
|
||||||
|
else:
|
||||||
|
record.is_delete_button = True
|
||||||
@api.model
|
@api.model
|
||||||
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
|
||||||
if self._context.get('route_id'):
|
if self._context.get('route_id'):
|
||||||
@@ -21,3 +59,33 @@ class SfProductionProcessParameter(models.Model):
|
|||||||
domain = [('process_id', '=', routing.surface_technics_id.id), ('id', 'not in', parameter)]
|
domain = [('process_id', '=', routing.surface_technics_id.id), ('id', 'not in', parameter)]
|
||||||
return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
|
return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
|
||||||
return super()._name_search(name, args, operator, limit, name_get_uid)
|
return super()._name_search(name, args, operator, limit, name_get_uid)
|
||||||
|
|
||||||
|
def action_create_service_product(self):
|
||||||
|
if self.id: # 如果是已存在的记录
|
||||||
|
self.write({}) # 空写入会触发保存
|
||||||
|
else: # 如果是新记录
|
||||||
|
self = self.create(self._convert_to_write(self.read()[0]))
|
||||||
|
return {
|
||||||
|
'type': 'ir.actions.act_window',
|
||||||
|
'name': '向导名称',
|
||||||
|
'res_model': 'product.creation.wizard',
|
||||||
|
'view_mode': 'form',
|
||||||
|
'target': 'new',
|
||||||
|
'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID
|
||||||
|
}
|
||||||
|
#
|
||||||
|
# return {
|
||||||
|
# 'name': '创建服务产品',
|
||||||
|
# 'type': 'ir.actions.act_window',
|
||||||
|
# 'res_model': 'product.product',
|
||||||
|
# 'view_mode': 'form',
|
||||||
|
# 'view_id': self.env.ref('product.product_normal_form_view').id,
|
||||||
|
# 'target': 'new', # 关键参数,使窗口以弹窗形式打开
|
||||||
|
# 'context': {
|
||||||
|
# 'default_' + k: v for k, v in default_values.items()
|
||||||
|
# },
|
||||||
|
# }
|
||||||
|
|
||||||
|
def action_hide_service_products(self):
|
||||||
|
# self.outsourced_service_products.active = False
|
||||||
|
self.active = False
|
||||||
|
|||||||
@@ -755,6 +755,24 @@ class StockPicking(models.Model):
|
|||||||
if move_id.product_id.tracking in ['serial', 'lot'] and not move_id.move_line_nosuggest_ids:
|
if move_id.product_id.tracking in ['serial', 'lot'] and not move_id.move_line_nosuggest_ids:
|
||||||
move_id.action_show_details()
|
move_id.action_show_details()
|
||||||
|
|
||||||
|
@api.model
|
||||||
|
def read_group(self, domain, fields, groupby, offset=0, limit=None, orderby=False, lazy=True):
|
||||||
|
aggregate_field = 'create_date:max'
|
||||||
|
if aggregate_field not in fields:
|
||||||
|
fields.append(aggregate_field)
|
||||||
|
|
||||||
|
orderby = "create_date desc"
|
||||||
|
|
||||||
|
return super(StockPicking, self).read_group(
|
||||||
|
domain,
|
||||||
|
fields,
|
||||||
|
groupby,
|
||||||
|
offset=offset,
|
||||||
|
limit=limit,
|
||||||
|
orderby=orderby,
|
||||||
|
lazy=lazy
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class ReStockMove(models.Model):
|
class ReStockMove(models.Model):
|
||||||
_inherit = 'stock.move'
|
_inherit = 'stock.move'
|
||||||
|
|||||||
134
sf_manufacturing/models/workorder_printer.py
Normal file
134
sf_manufacturing/models/workorder_printer.py
Normal file
@@ -0,0 +1,134 @@
|
|||||||
|
import qrcode
|
||||||
|
import base64
|
||||||
|
import logging
|
||||||
|
import tempfile
|
||||||
|
import os
|
||||||
|
import platform
|
||||||
|
import socket
|
||||||
|
import subprocess
|
||||||
|
from io import BytesIO
|
||||||
|
from odoo import models, fields, api
|
||||||
|
from odoo.exceptions import UserError
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class MrpWorkorder(models.Model):
|
||||||
|
_name = 'mrp.workorder'
|
||||||
|
_inherit = ['mrp.workorder', 'printing.utils']
|
||||||
|
|
||||||
|
def print_pdf(self, printer_config, pdf_data):
|
||||||
|
"""跨平台打印函数,支持网络打印机(IP:端口)"""
|
||||||
|
# 将PDF数据保存到临时文件
|
||||||
|
with tempfile.NamedTemporaryFile(delete=False, suffix='.pdf') as temp_file:
|
||||||
|
pdf_binary = base64.b64decode(pdf_data)
|
||||||
|
temp_file.write(pdf_binary)
|
||||||
|
temp_file_path = temp_file.name
|
||||||
|
|
||||||
|
_logger.info(f"开始打印PDF文件: {temp_file_path}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# 获取打印机名称或IP地址
|
||||||
|
printer_name = printer_config.printer_id.name
|
||||||
|
if not printer_name:
|
||||||
|
raise UserError('打印机名称未配置')
|
||||||
|
|
||||||
|
# 使用打印机配置中的IP地址和端口
|
||||||
|
printer_ip = printer_config.printer_id.ip_address
|
||||||
|
printer_port = printer_config.printer_id.port
|
||||||
|
_logger.info(f"使用网络打印机: IP={printer_ip}, 端口={printer_port}")
|
||||||
|
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
_logger.info(f"Windows环境不支持网络打印机")
|
||||||
|
else: # Linux环境
|
||||||
|
# try:
|
||||||
|
# import cups
|
||||||
|
|
||||||
|
# # 处理网络打印机情况
|
||||||
|
# _logger.info(f"Linux环境下连接网络打印机: {printer_ip}:{printer_port}")
|
||||||
|
|
||||||
|
# # 创建连接
|
||||||
|
# conn = cups.Connection()
|
||||||
|
|
||||||
|
# # 检查打印机是否已经添加到系统
|
||||||
|
# printers = conn.getPrinters()
|
||||||
|
# _logger.info(f"可用打印机列表: {list(printers.keys())}")
|
||||||
|
|
||||||
|
# network_printer_name = f"IP_{printer_ip}_{printer_port}"
|
||||||
|
|
||||||
|
# # 如果打印机不存在,尝试添加
|
||||||
|
# if network_printer_name not in printers:
|
||||||
|
# _logger.info(f"添加网络打印机: {network_printer_name}")
|
||||||
|
# conn.addPrinter(
|
||||||
|
# network_printer_name,
|
||||||
|
# device=f"socket://{printer_ip}:{printer_port}",
|
||||||
|
# info=f"Network Printer {printer_ip}:{printer_port}",
|
||||||
|
# location="Network"
|
||||||
|
# )
|
||||||
|
# # 设置打印机为启用状态
|
||||||
|
# conn.enablePrinter(network_printer_name)
|
||||||
|
# _logger.info(f"网络打印机添加成功: {network_printer_name}")
|
||||||
|
|
||||||
|
# # 打印文件
|
||||||
|
# _logger.info(f"开始打印到网络打印机: {network_printer_name}")
|
||||||
|
# job_id = conn.printFile(network_printer_name, temp_file_path, "工单打印", {})
|
||||||
|
# _logger.info(f"打印作业ID: {job_id}")
|
||||||
|
|
||||||
|
|
||||||
|
# except ImportError as ie:
|
||||||
|
# _logger.error(f"导入CUPS库失败: {str(ie)}")
|
||||||
|
|
||||||
|
# 尝试使用lp命令打印
|
||||||
|
try:
|
||||||
|
_logger.info("尝试使用lp命令打印...")
|
||||||
|
|
||||||
|
# 使用socket设置打印
|
||||||
|
cmd = f"lp -h {printer_ip}:{printer_port} -d {printer_name} {temp_file_path}"
|
||||||
|
|
||||||
|
_logger.info(f"执行lp打印命令: {cmd}")
|
||||||
|
result = subprocess.run(cmd, shell=True, check=True, capture_output=True)
|
||||||
|
_logger.info(f"lp打印结果: {result.stdout.decode()}")
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"lp命令打印失败: {str(e)}")
|
||||||
|
raise UserError(f'打印失败,请安装cups打印库: pip install pycups 或确保lp命令可用')
|
||||||
|
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"打印失败详细信息: {str(e)}")
|
||||||
|
raise UserError(f'打印失败: {str(e)}')
|
||||||
|
finally:
|
||||||
|
# 清理临时文件
|
||||||
|
if os.path.exists(temp_file_path):
|
||||||
|
try:
|
||||||
|
os.unlink(temp_file_path)
|
||||||
|
_logger.info(f"临时文件已清理: {temp_file_path}")
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f"清理临时文件失败: {str(e)}")
|
||||||
|
|
||||||
|
def _compute_state(self):
|
||||||
|
super(MrpWorkorder, self)._compute_state()
|
||||||
|
for workorder in self:
|
||||||
|
work_ids = workorder.production_id.workorder_ids.filtered(lambda w: w.routing_type == '装夹预调' or w.routing_type == '人工线下加工')
|
||||||
|
for wo in work_ids:
|
||||||
|
if wo.state == 'ready' and not wo.production_id.product_id.is_print_program:
|
||||||
|
# 触发打印程序
|
||||||
|
pdf_data = self.processing_drawing
|
||||||
|
try:
|
||||||
|
if pdf_data:
|
||||||
|
# 获取默认打印机配置
|
||||||
|
printer_config = self.env['printer.configuration'].sudo().search([('model', '=', self._name), ('printer_id.type', '=', 'normal')], limit=1)
|
||||||
|
if not printer_config:
|
||||||
|
raise UserError('请先配置打印机')
|
||||||
|
|
||||||
|
# 执行打印
|
||||||
|
if self.print_pdf(printer_config, pdf_data):
|
||||||
|
wo.production_id.product_id.is_print_program = True
|
||||||
|
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
_logger.error(f'打印配置错误: {str(e)}')
|
||||||
|
|
||||||
|
class ProductTemplate(models.Model):
|
||||||
|
_inherit = 'product.template'
|
||||||
|
|
||||||
|
is_print_program = fields.Boolean(string='是否打印程序', default=False)
|
||||||
|
|
||||||
@@ -194,3 +194,5 @@ access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_indiv
|
|||||||
access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0
|
access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0
|
||||||
access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0
|
access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0
|
||||||
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
|
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
|
||||||
|
|
||||||
|
access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0
|
||||||
|
@@ -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">sequence</attribute>
|
<attribute name="default_order">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>
|
||||||
|
|||||||
@@ -22,6 +22,26 @@
|
|||||||
<field name="is_repeat"/>
|
<field name="is_repeat"/>
|
||||||
<field name="reserved_duration"/>
|
<field name="reserved_duration"/>
|
||||||
</field>
|
</field>
|
||||||
|
<xpath expr="//notebook/page[1]" position="before">
|
||||||
|
<page string="可选工艺参数">
|
||||||
|
<field name="optional_process_parameters">
|
||||||
|
<tree editable="bottom">
|
||||||
|
<field name="is_product_button" invisible="1"/>
|
||||||
|
<field name="is_delete_button" invisible="1"/>
|
||||||
|
<field name="code" attrs="{'readonly': True}"/>
|
||||||
|
<field name="name"/>
|
||||||
|
<field name="outsourced_service_products" widget="many2many_tags"/>
|
||||||
|
<!-- 按钮列 -->
|
||||||
|
<button name="action_create_service_product" string="创建服务产品" type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
|
||||||
|
<button name="action_hide_service_products" string="删除" type="object"
|
||||||
|
class="oe_highlight"
|
||||||
|
attrs="{'invisible': [('is_delete_button', '=', True)]}"/>
|
||||||
|
</tree>
|
||||||
|
</field>
|
||||||
|
</page>
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
15
sf_manufacturing/views/product_template_views.xml
Normal file
15
sf_manufacturing/views/product_template_views.xml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data>
|
||||||
|
<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="model">product.template</field>
|
||||||
|
<field name="inherit_id" ref="sf_dlm_management.view_product_template_only_form_inherit_sf"/>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<xpath expr="//page[@name='general_information']/group/group[@name='group_standard_price']/field[@name='barcode']" position="after">
|
||||||
|
<field name="model_id" readonly="1"/>
|
||||||
|
</xpath>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -50,6 +50,9 @@
|
|||||||
<xpath expr="//field[@name='origin']" position="after">
|
<xpath expr="//field[@name='origin']" position="after">
|
||||||
<field name="retrospect_ref"/>
|
<field name="retrospect_ref"/>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<xpath expr="//tree" position="attributes">
|
||||||
|
<attribute name="default_order">create_date desc</attribute>
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -6,3 +6,4 @@ from . import production_technology_re_adjust_wizard
|
|||||||
from . import mrp_workorder_batch_replan_wizard
|
from . import mrp_workorder_batch_replan_wizard
|
||||||
from . import sf_programming_reason
|
from . import sf_programming_reason
|
||||||
from . import sale_order_cancel
|
from . import sale_order_cancel
|
||||||
|
from . import process_outsourcing
|
||||||
42
sf_manufacturing/wizard/process_outsourcing.py
Normal file
42
sf_manufacturing/wizard/process_outsourcing.py
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
from odoo import models, fields, api
|
||||||
|
|
||||||
|
class ProductCreationWizard(models.TransientModel):
|
||||||
|
_name = 'product.creation.wizard'
|
||||||
|
_description = '产品创建向导'
|
||||||
|
|
||||||
|
# 唯一需要用户输入的字段:产品类别
|
||||||
|
categ_id = fields.Many2one(
|
||||||
|
'product.category',
|
||||||
|
string='产品类别',
|
||||||
|
required=True,default=lambda self: self.env.ref(
|
||||||
|
'sf_manufacturing.product_category_outsource_other_process',
|
||||||
|
raise_if_not_found=False
|
||||||
|
).sudo(),
|
||||||
|
)
|
||||||
|
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.process_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):
|
||||||
|
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
|
||||||
|
default_values = {
|
||||||
|
'detailed_type': 'service',
|
||||||
|
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}",
|
||||||
|
'invoice_policy': 'delivery',
|
||||||
|
'categ_id': self.categ_id.id,
|
||||||
|
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
|
||||||
|
'sale_ok': True, # 可销售
|
||||||
|
'purchase_ok': True, # 可采购
|
||||||
|
'server_product_process_parameters_id': self.process_parameter_id.id,
|
||||||
|
}
|
||||||
|
self.env['product.template'].create(default_values)
|
||||||
37
sf_manufacturing/wizard/process_outsourcing.xml
Normal file
37
sf_manufacturing/wizard/process_outsourcing.xml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
<odoo>
|
||||||
|
<record id="view_product_creation_wizard_form" model="ir.ui.view">
|
||||||
|
<field name="name">product.creation.wizard.form</field>
|
||||||
|
<field name="model">product.creation.wizard</field>
|
||||||
|
<field name="arch" type="xml">
|
||||||
|
<form string="创建产品">
|
||||||
|
<sheet>
|
||||||
|
<group>
|
||||||
|
<field name="categ_id" required="1"/>
|
||||||
|
</group>
|
||||||
|
</sheet>
|
||||||
|
<footer>
|
||||||
|
<button
|
||||||
|
name="action_create_product"
|
||||||
|
string="创建"
|
||||||
|
type="object"
|
||||||
|
class="btn-primary"
|
||||||
|
/>
|
||||||
|
<button
|
||||||
|
string="取消"
|
||||||
|
special="cancel"
|
||||||
|
class="btn-secondary"
|
||||||
|
/>
|
||||||
|
</footer>
|
||||||
|
</form>
|
||||||
|
</field>
|
||||||
|
</record>
|
||||||
|
|
||||||
|
<!-- 向导动作 -->
|
||||||
|
<record id="action_product_creation_wizard" model="ir.actions.act_window">
|
||||||
|
<field name="name">快速创建产品</field>
|
||||||
|
<field name="res_model">product.creation.wizard</field>
|
||||||
|
<field name="view_mode">form</field>
|
||||||
|
<field name="target">new</field>
|
||||||
|
<field name="binding_model_id" ref="product.model_product_product"/>
|
||||||
|
</record>
|
||||||
|
</odoo>
|
||||||
@@ -8,6 +8,7 @@ from odoo.http import request
|
|||||||
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
|
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
||||||
|
|
||||||
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
|
||||||
@@ -22,6 +23,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
|||||||
try:
|
try:
|
||||||
res = {'status': 1, 'message': '成功'}
|
res = {'status': 1, 'message': '成功'}
|
||||||
datas = request.httprequest.data
|
datas = request.httprequest.data
|
||||||
|
model_id = None
|
||||||
ret = json.loads(datas)
|
ret = json.loads(datas)
|
||||||
ret = json.loads(ret['result'])
|
ret = json.loads(ret['result'])
|
||||||
logging.info('下发编程单:%s' % ret)
|
logging.info('下发编程单:%s' % ret)
|
||||||
@@ -57,6 +59,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
|||||||
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
|
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
|
||||||
return json.JSONEncoder().encode(res)
|
return json.JSONEncoder().encode(res)
|
||||||
for production in productions:
|
for production in productions:
|
||||||
|
model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
|
||||||
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
|
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
|
||||||
for panel in ret['processing_panel'].split(','):
|
for panel in ret['processing_panel'].split(','):
|
||||||
# 查询状态为进行中且工序类型为CNC加工的工单
|
# 查询状态为进行中且工序类型为CNC加工的工单
|
||||||
@@ -83,12 +86,16 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
|||||||
# panel)
|
# panel)
|
||||||
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
|
||||||
files_panel = os.listdir(program_path_tmp_panel)
|
files_panel = os.listdir(program_path_tmp_panel)
|
||||||
|
panel_file_path = ''
|
||||||
if files_panel:
|
if files_panel:
|
||||||
for file in files_panel:
|
for file in files_panel:
|
||||||
file_extension = os.path.splitext(file)[1]
|
file_extension = os.path.splitext(file)[1]
|
||||||
if file_extension.lower() == '.pdf':
|
if file_extension.lower() == '.pdf':
|
||||||
panel_file_path = os.path.join(program_path_tmp_panel, file)
|
panel_file_path = os.path.join(program_path_tmp_panel, file)
|
||||||
logging.info('panel_file_path:%s' % panel_file_path)
|
logging.info('panel_file_path:%s' % panel_file_path)
|
||||||
|
|
||||||
|
# 向编程单中添加二维码
|
||||||
|
request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "扫码获取工单")
|
||||||
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
|
||||||
pre_workorder = productions.workorder_ids.filtered(
|
pre_workorder = productions.workorder_ids.filtered(
|
||||||
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
|
lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
|
||||||
@@ -268,3 +275,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
|
|||||||
request.cr.rollback()
|
request.cr.rollback()
|
||||||
logging.info('get_cnc_processing_create error:%s' % e)
|
logging.info('get_cnc_processing_create error:%s' % e)
|
||||||
return json.JSONEncoder().encode(res)
|
return json.JSONEncoder().encode(res)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version="1.0" encoding='UTF-8'?>
|
<?xml version="1.0" encoding='UTF-8'?>
|
||||||
<odoo>
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
<record model="ir.cron" id="ir_cron_sf_static_resource_datasync">
|
<record model="ir.cron" id="ir_cron_sf_static_resource_datasync">
|
||||||
<field name="name">制造-配置:每日定时同步cloud的静态资源库</field>
|
<field name="name">制造-配置:每日定时同步cloud的静态资源库</field>
|
||||||
<field name="model_id" ref="model_res_config_settings"/>
|
<field name="model_id" ref="model_res_config_settings"/>
|
||||||
@@ -220,4 +220,5 @@
|
|||||||
<!-- <field name="numbercall">-1</field>-->
|
<!-- <field name="numbercall">-1</field>-->
|
||||||
<!-- <field name="doall" eval="False"/>-->
|
<!-- <field name="doall" eval="False"/>-->
|
||||||
<!-- </record>-->
|
<!-- </record>-->
|
||||||
|
</data>
|
||||||
</odoo>
|
</odoo>
|
||||||
@@ -1135,8 +1135,10 @@ class sfProductionProcessParameter(models.Model):
|
|||||||
[("code", '=', item['code']), ('active', 'in', [True, False])])
|
[("code", '=', item['code']), ('active', 'in', [True, False])])
|
||||||
process = self.env['sf.production.process'].search(
|
process = self.env['sf.production.process'].search(
|
||||||
[('code', '=', item['process_id_code'])], limit=1)
|
[('code', '=', item['process_id_code'])], limit=1)
|
||||||
|
production_process_parameter = self.search(
|
||||||
|
[("code", '=', item['code']), ('active', 'in', [True, False])])
|
||||||
if not production_process_parameter:
|
if not production_process_parameter:
|
||||||
self.create({
|
production_process_parameter = self.create({
|
||||||
"name": item['name'],
|
"name": item['name'],
|
||||||
"process_description": item['process_description'],
|
"process_description": item['process_description'],
|
||||||
"processing_day": item['processing_day'],
|
"processing_day": item['processing_day'],
|
||||||
@@ -1148,6 +1150,7 @@ class sfProductionProcessParameter(models.Model):
|
|||||||
[('materials_no', 'in', item['materials_model_ids_codes'])]),
|
[('materials_no', 'in', item['materials_model_ids_codes'])]),
|
||||||
'processing_mm': item['processing_mm']
|
'processing_mm': item['processing_mm']
|
||||||
})
|
})
|
||||||
|
production_process_parameter.create_service_product()
|
||||||
else:
|
else:
|
||||||
production_process_parameter.name = item['name']
|
production_process_parameter.name = item['name']
|
||||||
production_process_parameter.process_description = item['process_description']
|
production_process_parameter.process_description = item['process_description']
|
||||||
@@ -1158,6 +1161,9 @@ class sfProductionProcessParameter(models.Model):
|
|||||||
[('materials_no', 'in', item['materials_model_ids_codes'])])
|
[('materials_no', 'in', item['materials_model_ids_codes'])])
|
||||||
production_process_parameter.active = item['active']
|
production_process_parameter.active = item['active']
|
||||||
production_process_parameter.processing_mm = item['processing_mm']
|
production_process_parameter.processing_mm = item['processing_mm']
|
||||||
|
if not production_process_parameter.outsourced_service_products:
|
||||||
|
production_process_parameter.create_service_product()
|
||||||
|
production_process_parameter.create_work_center()
|
||||||
else:
|
else:
|
||||||
raise ValidationError("表面工艺可选参数认证未通过")
|
raise ValidationError("表面工艺可选参数认证未通过")
|
||||||
|
|
||||||
|
|||||||
@@ -343,6 +343,11 @@ class RePurchaseOrder(models.Model):
|
|||||||
if order_line.product_id.id in product_list:
|
if order_line.product_id.id in product_list:
|
||||||
purchase.purchase_type = 'outsourcing'
|
purchase.purchase_type = 'outsourcing'
|
||||||
break
|
break
|
||||||
|
request_lines = self.order_line.mapped('purchase_request_lines')
|
||||||
|
# 检查是否存在 is_subcontract 为 True 的行
|
||||||
|
if any(line.is_subcontract for line in request_lines):
|
||||||
|
purchase.purchase_type = 'consignment'
|
||||||
|
|
||||||
|
|
||||||
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
|
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
|
||||||
string='交期状态', default='normal',
|
string='交期状态', default='normal',
|
||||||
@@ -376,6 +381,27 @@ class RePurchaseOrder(models.Model):
|
|||||||
if not line.taxes_id:
|
if not line.taxes_id:
|
||||||
raise UserError('请对【产品】中的【税】进行选择')
|
raise UserError('请对【产品】中的【税】进行选择')
|
||||||
|
|
||||||
|
def get_purchase_request(self, consecutive_process_parameters, production):
|
||||||
|
result = []
|
||||||
|
for pp in consecutive_process_parameters:
|
||||||
|
server_template = self.env['product.template'].search(
|
||||||
|
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
|
||||||
|
('detailed_type', '=', 'service')])
|
||||||
|
result.append({
|
||||||
|
"product_id": server_template.product_variant_id.id,
|
||||||
|
"name": production.procurement_group_id.name,
|
||||||
|
"date_required": fields.Datetime.now(),
|
||||||
|
"product_uom_id":server_template.uom_id.id,
|
||||||
|
"product_qty": 1,
|
||||||
|
"request_id": False,
|
||||||
|
"move_dest_ids": False,
|
||||||
|
"orderpoint_id": False,
|
||||||
|
'is_subcontract':True,
|
||||||
|
'group_id':production.procurement_group_id.id,
|
||||||
|
'production_name':pp.production_id.name,
|
||||||
|
})
|
||||||
|
return result
|
||||||
|
|
||||||
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
|
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
|
||||||
for pp in consecutive_process_parameters:
|
for pp in consecutive_process_parameters:
|
||||||
server_product_process = []
|
server_product_process = []
|
||||||
@@ -396,14 +422,14 @@ class RePurchaseOrder(models.Model):
|
|||||||
'manual_part_name': pp.part_name,
|
'manual_part_name': pp.part_name,
|
||||||
}))
|
}))
|
||||||
# 获取服务商品最后一个供应商的采购员
|
# 获取服务商品最后一个供应商的采购员
|
||||||
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id
|
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id if server_template.seller_ids else False
|
||||||
purchase_order = self.env['purchase.order'].sudo().create({
|
purchase_order = self.env['purchase.order'].sudo().create({
|
||||||
'partner_id': server_template.seller_ids[0].partner_id.id,
|
'partner_id': server_template.seller_ids[0].partner_id.id if server_template.seller_ids else False,
|
||||||
'origin': production.name,
|
'origin': production.name,
|
||||||
'state': 'draft',
|
'state': 'draft',
|
||||||
'purchase_type': 'consignment',
|
'purchase_type': 'consignment',
|
||||||
'order_line': server_product_process,
|
'order_line': server_product_process,
|
||||||
'user_id': purchase_user_id.id
|
'user_id': purchase_user_id.id if purchase_user_id else False,
|
||||||
})
|
})
|
||||||
purchase_order.order_line._compute_part_number()
|
purchase_order.order_line._compute_part_number()
|
||||||
pp.purchase_id = [(6, 0, [purchase_order.id])]
|
pp.purchase_id = [(6, 0, [purchase_order.id])]
|
||||||
|
|||||||
@@ -298,6 +298,10 @@
|
|||||||
<xpath expr="//field[@name='name']" position="attributes">
|
<xpath expr="//field[@name='name']" position="attributes">
|
||||||
<attribute name="class">purchase_order_list_name</attribute>
|
<attribute name="class">purchase_order_list_name</attribute>
|
||||||
</xpath>
|
</xpath>
|
||||||
|
<!-- 修改 tree 视图的排序规则 -->
|
||||||
|
<xpath expr="//tree" position="attributes">
|
||||||
|
<attribute name="default_order">date_approve desc</attribute>
|
||||||
|
</xpath>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|
||||||
|
|||||||
@@ -14,16 +14,14 @@
|
|||||||
<!-- name="Orders"-->
|
<!-- name="Orders"-->
|
||||||
<!-- sequence="10">-->
|
<!-- sequence="10">-->
|
||||||
|
|
||||||
<!-- <menuitem id="menu_sale_quotations"-->
|
|
||||||
<!-- action="action_quotations_with_onboarding"-->
|
|
||||||
<!-- groups="sales_team.group_sale_salesman"-->
|
|
||||||
<!-- sequence="10"/>-->
|
|
||||||
|
|
||||||
<!-- <menuitem id="menu_sale_order"-->
|
<menuitem id="sale.menu_sale_quotations"
|
||||||
<!-- name="Orders"-->
|
parent="sale.sale_order_menu"
|
||||||
<!-- action="action_orders"-->
|
sequence="20"/>
|
||||||
<!-- groups="sales_team.group_sale_salesman"-->
|
|
||||||
<!-- sequence="20"/>-->
|
<menuitem id="sale.menu_sale_order"
|
||||||
|
parent="sale.sale_order_menu"
|
||||||
|
sequence="10"/>
|
||||||
|
|
||||||
|
|
||||||
<!-- <menuitem id="report_sales_team"-->
|
<!-- <menuitem id="report_sales_team"-->
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
# always loaded
|
# always loaded
|
||||||
'data': [
|
'data': [
|
||||||
# 'security/ir.model.access.csv',
|
# 'security/ir.model.access.csv',
|
||||||
|
'data/stock_location_data.xml',
|
||||||
'views/stock_picking.xml',
|
'views/stock_picking.xml',
|
||||||
'views/stock_product_template.xml',
|
'views/stock_product_template.xml',
|
||||||
],
|
],
|
||||||
|
|||||||
13
sf_stock/data/stock_location_data.xml
Normal file
13
sf_stock/data/stock_location_data.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<odoo>
|
||||||
|
<data noupdate="1">
|
||||||
|
<record id="stock_location_outsourcing_material_receiving_area" model="stock.location">
|
||||||
|
<field name="name">外协收料区</field>
|
||||||
|
<field name="usage">internal</field>
|
||||||
|
</record>
|
||||||
|
<record id="stock_route_process_outsourcing" model='stock.route'>
|
||||||
|
<field name="name">工序外协</field>
|
||||||
|
<field name="company_id"></field>
|
||||||
|
</record>
|
||||||
|
</data>
|
||||||
|
</odoo>
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
from . import stock_picking
|
from . import stock_picking
|
||||||
|
from . import stock_warehouse_orderpoint
|
||||||
11
sf_stock/models/stock_warehouse_orderpoint.py
Normal file
11
sf_stock/models/stock_warehouse_orderpoint.py
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import logging
|
||||||
|
|
||||||
|
from odoo import models, fields
|
||||||
|
|
||||||
|
_logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class StockWarehouseOrderpoint(models.Model):
|
||||||
|
_inherit = 'stock.warehouse.orderpoint'
|
||||||
|
|
||||||
|
origin = fields.Char('补货来源')
|
||||||
@@ -1242,7 +1242,7 @@ class FunctionalToolDismantle(models.Model):
|
|||||||
|
|
||||||
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
|
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
|
||||||
domain=[('functional_tool_status', '!=', '已拆除'),
|
domain=[('functional_tool_status', '!=', '已拆除'),
|
||||||
('current_location', '=', '刀具房')])
|
('current_location', 'in', ['刀具房', '线边刀库'])])
|
||||||
|
|
||||||
@api.onchange('functional_tool_id')
|
@api.onchange('functional_tool_id')
|
||||||
def _onchange_functional_tool_id(self):
|
def _onchange_functional_tool_id(self):
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ from odoo.exceptions import ValidationError
|
|||||||
|
|
||||||
class FunctionalCuttingToolEntity(models.Model):
|
class FunctionalCuttingToolEntity(models.Model):
|
||||||
_name = 'sf.functional.cutting.tool.entity'
|
_name = 'sf.functional.cutting.tool.entity'
|
||||||
|
_inherit = ['mail.thread']
|
||||||
_description = '功能刀具列表'
|
_description = '功能刀具列表'
|
||||||
_order = 'functional_tool_status'
|
_order = 'functional_tool_status'
|
||||||
|
|
||||||
@@ -41,7 +42,7 @@ class FunctionalCuttingToolEntity(models.Model):
|
|||||||
max_lifetime_value = fields.Integer(string='最大寿命值(min)', readonly=True)
|
max_lifetime_value = fields.Integer(string='最大寿命值(min)', readonly=True)
|
||||||
alarm_value = fields.Integer(string='报警值(min)', readonly=True)
|
alarm_value = fields.Integer(string='报警值(min)', readonly=True)
|
||||||
used_value = fields.Integer(string='已使用值(min)', readonly=True)
|
used_value = fields.Integer(string='已使用值(min)', readonly=True)
|
||||||
functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')],
|
functional_tool_status = fields.Selection([('正常', '正常'), ('报警', '报警'), ('已拆除', '已拆除')], tracking=True,
|
||||||
string='状态', store=True, default='正常')
|
string='状态', store=True, default='正常')
|
||||||
current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id',
|
current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id',
|
||||||
store=True)
|
store=True)
|
||||||
@@ -62,6 +63,17 @@ class FunctionalCuttingToolEntity(models.Model):
|
|||||||
for item in self:
|
for item in self:
|
||||||
if item:
|
if item:
|
||||||
if item.functional_tool_status == '报警':
|
if item.functional_tool_status == '报警':
|
||||||
|
self.create_tool_dismantle()
|
||||||
|
|
||||||
|
def set_functional_tool_status(self):
|
||||||
|
# self.write({
|
||||||
|
# 'functional_tool_status': '报警'
|
||||||
|
# })
|
||||||
|
self.functional_tool_status = '报警'
|
||||||
|
self.create_tool_dismantle()
|
||||||
|
|
||||||
|
def create_tool_dismantle(self):
|
||||||
|
for item in self:
|
||||||
# 创建报警刀具拆解单
|
# 创建报警刀具拆解单
|
||||||
self.env['sf.functional.tool.dismantle'].sudo().create({
|
self.env['sf.functional.tool.dismantle'].sudo().create({
|
||||||
'functional_tool_id': item.ids[0],
|
'functional_tool_id': item.ids[0],
|
||||||
@@ -263,7 +275,7 @@ class FunctionalCuttingToolEntity(models.Model):
|
|||||||
functional_tool_model_ids.append(functional_tool_model.id)
|
functional_tool_model_ids.append(functional_tool_model.id)
|
||||||
return [(6, 0, functional_tool_model_ids)]
|
return [(6, 0, functional_tool_model_ids)]
|
||||||
|
|
||||||
dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', store=True)
|
dismantle_num = fields.Integer('拆解单数量', compute='_compute_dismantle_num', tracking=True, store=True)
|
||||||
dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单')
|
dismantle_ids = fields.One2many('sf.functional.tool.dismantle', 'functional_tool_id', '拆解单')
|
||||||
|
|
||||||
@api.depends('dismantle_ids')
|
@api.depends('dismantle_ids')
|
||||||
|
|||||||
@@ -107,11 +107,17 @@ class SfMaintenanceEquipment(models.Model):
|
|||||||
if functional_tool_id.current_location != '机内刀库':
|
if functional_tool_id.current_location != '机内刀库':
|
||||||
# 对功能刀具进行移动到生产线
|
# 对功能刀具进行移动到生产线
|
||||||
functional_tool_id.tool_inventory_displacement_out()
|
functional_tool_id.tool_inventory_displacement_out()
|
||||||
functional_tool_id.write({
|
data_tool = {
|
||||||
'max_lifetime_value': data['MaxLife'],
|
'max_lifetime_value': data['MaxLife'],
|
||||||
'used_value': data['UseLife'],
|
'used_value': data['UseLife'],
|
||||||
'functional_tool_status': tool_install_time.get(data['State'])
|
'functional_tool_status': tool_install_time.get(data['State'])
|
||||||
})
|
}
|
||||||
|
if (functional_tool_id.functional_tool_status != '报警'
|
||||||
|
and tool_install_time.get(data['State']) == '报警'):
|
||||||
|
functional_tool_id.write(data_tool)
|
||||||
|
functional_tool_id.create_tool_dismantle()
|
||||||
|
else:
|
||||||
|
functional_tool_id.write(data_tool)
|
||||||
else:
|
else:
|
||||||
logging.info('获取的【%s】设备不存在!!!' % data['DeviceId'])
|
logging.info('获取的【%s】设备不存在!!!' % data['DeviceId'])
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -22,15 +22,21 @@
|
|||||||
color: #999;
|
color: #999;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
.custom_group:has(.text-success){
|
||||||
|
position: relative;
|
||||||
&::after{
|
&::after{
|
||||||
content: '';
|
content: '';
|
||||||
display: block;
|
display: block;
|
||||||
width: 18px;
|
width: 72px;
|
||||||
height: 18px;
|
height: 72px;
|
||||||
background: url('/sf_tool_management/static/images/replaceIcon.png') no-repeat center center;
|
background: url('/sf_tool_management/static/images/replaceIcon.png') no-repeat center center;
|
||||||
background-size: 100%;
|
background-size: 100%;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 20px;
|
||||||
|
left: 300px;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
.o_field_widget.o_readonly_modifier.o_field_char.text-success[name=handle_freight_rfid] {
|
.o_field_widget.o_readonly_modifier.o_field_char.text-success[name=handle_freight_rfid] {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@@ -42,6 +42,7 @@
|
|||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<form create="0" edit="0" delete="0">
|
<form create="0" edit="0" delete="0">
|
||||||
<header>
|
<header>
|
||||||
|
<button name="set_functional_tool_status" string="报警" type="object" invisible="1"/>
|
||||||
<!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"-->
|
<!-- <button name="enroll_functional_tool_entity" string="功能刀具注册" type="object"-->
|
||||||
<!-- class="btn-primary"/>-->
|
<!-- class="btn-primary"/>-->
|
||||||
<field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/>
|
<field name="functional_tool_status" widget="statusbar" statusbar_visible="正常,报警,已拆除"/>
|
||||||
@@ -192,6 +193,10 @@
|
|||||||
</page>
|
</page>
|
||||||
</notebook>
|
</notebook>
|
||||||
</sheet>
|
</sheet>
|
||||||
|
<div class="oe_chatter">
|
||||||
|
<field name="message_follower_ids"/>
|
||||||
|
<field name="message_ids"/>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</field>
|
</field>
|
||||||
</record>
|
</record>
|
||||||
|
|||||||
@@ -432,7 +432,7 @@
|
|||||||
<field name="name">功能刀具组装</field>
|
<field name="name">功能刀具组装</field>
|
||||||
<field name="model">sf.functional.tool.assembly</field>
|
<field name="model">sf.functional.tool.assembly</field>
|
||||||
<field name="arch" type="xml">
|
<field name="arch" type="xml">
|
||||||
<tree create="0" delete="0">
|
<tree create="0" delete="0" default_order="create_date desc">
|
||||||
<field name="assembly_order_code"/>
|
<field name="assembly_order_code"/>
|
||||||
<field name="barcode_id" optional="hide"/>
|
<field name="barcode_id" optional="hide"/>
|
||||||
<field name="code" optional="hide"/>
|
<field name="code" optional="hide"/>
|
||||||
@@ -531,7 +531,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<separator string="刀柄:" style="font-size: 13px;"/>
|
<separator string="刀柄:" style="font-size: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group class="custom_group" >
|
||||||
<field name="handle_code_id" string="序列号" placeholder="请选择"
|
<field name="handle_code_id" string="序列号" placeholder="请选择"
|
||||||
options="{'no_create': True, 'no_quick_create': True}"/>
|
options="{'no_create': True, 'no_quick_create': True}"/>
|
||||||
<field name="handle_freight_rfid" string="Rfid" decoration-success="handle_freight_rfid"/>
|
<field name="handle_freight_rfid" string="Rfid" decoration-success="handle_freight_rfid"/>
|
||||||
@@ -554,7 +554,7 @@
|
|||||||
<separator string="整体式刀具:" style="font-size: 13px;"/>
|
<separator string="整体式刀具:" style="font-size: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group class="custom_group">
|
||||||
<field name="integral_freight_barcode_id" string="货位" decoration-success="integral_verify == True"/>
|
<field name="integral_freight_barcode_id" string="货位" decoration-success="integral_verify == True"/>
|
||||||
<field name="integral_lot_id" string="批次"/>
|
<field name="integral_lot_id" string="批次"/>
|
||||||
<field name="integral_product_id" string="名称"/>
|
<field name="integral_product_id" string="名称"/>
|
||||||
@@ -582,8 +582,8 @@
|
|||||||
<separator string="刀片:" style="font-size: 13px;"/>
|
<separator string="刀片:" style="font-size: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group class="custom_group">
|
||||||
<field name="blade_freight_barcode_id" string="货位"/>
|
<field name="blade_freight_barcode_id" string="货位" decoration-success="blade_verify == True"/>
|
||||||
<field name="blade_lot_id" string="批次"/>
|
<field name="blade_lot_id" string="批次"/>
|
||||||
<field name="blade_product_id" string="名称"/>
|
<field name="blade_product_id" string="名称"/>
|
||||||
<field name="cutting_tool_blade_model_id" string="型号"/>
|
<field name="cutting_tool_blade_model_id" string="型号"/>
|
||||||
@@ -607,8 +607,8 @@
|
|||||||
<separator string="刀杆:" style="font-size: 13px;"/>
|
<separator string="刀杆:" style="font-size: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group class="custom_group">
|
||||||
<field name="bar_freight_barcode_id" string="货位"/>
|
<field name="bar_freight_barcode_id" string="货位" decoration-success="bar_verify == True"/>
|
||||||
<field name="bar_lot_id" string="批次"/>
|
<field name="bar_lot_id" string="批次"/>
|
||||||
<field name="bar_product_id" string="名称"/>
|
<field name="bar_product_id" string="名称"/>
|
||||||
<field name="cutting_tool_cutterbar_model_id" string="型号"/>
|
<field name="cutting_tool_cutterbar_model_id" string="型号"/>
|
||||||
@@ -631,8 +631,8 @@
|
|||||||
<separator string="刀盘:" style="font-size: 13px;"/>
|
<separator string="刀盘:" style="font-size: 13px;"/>
|
||||||
</div>
|
</div>
|
||||||
<group>
|
<group>
|
||||||
<group>
|
<group class="custom_group">
|
||||||
<field name="pad_freight_barcode_id" string="货位"/>
|
<field name="pad_freight_barcode_id" string="货位" decoration-success="pad_verify == True"/>
|
||||||
<field name="pad_lot_id" string="批次"/>
|
<field name="pad_lot_id" string="批次"/>
|
||||||
<field name="pad_product_id" string="名称"/>
|
<field name="pad_product_id" string="名称"/>
|
||||||
<field name="cutting_tool_cutterpad_model_id" string="型号"/>
|
<field name="cutting_tool_cutterpad_model_id" string="型号"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user