diff --git a/jikimo_work_reporting_api/__init__.py b/jikimo_work_reporting_api/__init__.py
new file mode 100644
index 00000000..9e5827f9
--- /dev/null
+++ b/jikimo_work_reporting_api/__init__.py
@@ -0,0 +1,3 @@
+# -*- coding: utf-8 -*-
+from . import controllers
+from . import models
diff --git a/jikimo_work_reporting_api/__manifest__.py b/jikimo_work_reporting_api/__manifest__.py
new file mode 100644
index 00000000..502ce9d5
--- /dev/null
+++ b/jikimo_work_reporting_api/__manifest__.py
@@ -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',
+}
diff --git a/jikimo_work_reporting_api/controllers/__init__.py b/jikimo_work_reporting_api/controllers/__init__.py
new file mode 100644
index 00000000..757b12a1
--- /dev/null
+++ b/jikimo_work_reporting_api/controllers/__init__.py
@@ -0,0 +1,2 @@
+# -*- coding: utf-8 -*-
+from . import main
diff --git a/jikimo_work_reporting_api/controllers/main.py b/jikimo_work_reporting_api/controllers/main.py
new file mode 100644
index 00000000..76b55dc9
--- /dev/null
+++ b/jikimo_work_reporting_api/controllers/main.py
@@ -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': '传输失败'}
diff --git a/jikimo_work_reporting_api/models/__init__.py b/jikimo_work_reporting_api/models/__init__.py
new file mode 100644
index 00000000..40a96afc
--- /dev/null
+++ b/jikimo_work_reporting_api/models/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/jikimo_workorder_exception_notify/models/jikimo_workorder_exception.py b/jikimo_workorder_exception_notify/models/jikimo_workorder_exception.py
index 0ab0d6ae..f4a49c1f 100644
--- a/jikimo_workorder_exception_notify/models/jikimo_workorder_exception.py
+++ b/jikimo_workorder_exception_notify/models/jikimo_workorder_exception.py
@@ -52,10 +52,10 @@ class JikimoWorkorderException(models.Model):
def _get_message(self, 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
for index, content in enumerate(contents):
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)
return contents, message_queue_ids
diff --git a/quality_control/models/quality.py b/quality_control/models/quality.py
index 47b21dc7..9be466f5 100644
--- a/quality_control/models/quality.py
+++ b/quality_control/models/quality.py
@@ -206,7 +206,7 @@ class QualityCheck(models.Model):
('NG', 'NG')
], string='出厂检验报告结果', default='OK')
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')
def _compute_quality_manager(self):
diff --git a/sf_base/commons/Printer.py b/sf_base/commons/Printer.py
index 563d9dea..e9d31282 100644
--- a/sf_base/commons/Printer.py
+++ b/sf_base/commons/Printer.py
@@ -8,6 +8,7 @@ class Printer(models.Model):
name = fields.Char(string='名称', required=True)
ip_address = fields.Char(string='IP 地址', required=True)
port = fields.Integer(string='端口', default=9100)
+ type = fields.Selection([('zpl', 'ZPL'), ('normal', '普通')], string='类型', default='zpl')
class TableStyle(models.Model):
diff --git a/sf_base/commons/common.py b/sf_base/commons/common.py
index 9f359c9c..27b56038 100644
--- a/sf_base/commons/common.py
+++ b/sf_base/commons/common.py
@@ -2,7 +2,16 @@
import time, datetime
import hashlib
from odoo import models
+from typing import Optional
import socket
+import os
+import logging
+import qrcode
+from reportlab.pdfgen import canvas
+from reportlab.lib.units import inch
+from PyPDF2 import PdfFileReader, PdfFileWriter
+from reportlab.pdfbase import pdfmetrics
+from reportlab.pdfbase.ttfonts import TTFont
class Common(models.Model):
_name = 'sf.sync.common'
@@ -92,3 +101,120 @@ class PrintingUtils(models.AbstractModel):
# host = "192.168.50.110" # 可以作为参数传入,或者在此配置
# port = 9100 # 可以作为参数传入,或者在此配置
self.send_to_printer(host, port, zpl_code)
+
+
+ def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
+ """
+ 在PDF文件中添加二维码
+ :param pdf_path: PDF文件路径
+ :param content: 二维码内容
+ :param buttom_text: 二维码下方文字
+ :return: 是否成功
+ """
+ if not os.path.exists(pdf_path):
+ logging.warning(f'PDF文件不存在: {pdf_path}')
+ return False
+
+ # 生成二维码
+ qr = qrcode.QRCode(version=1, box_size=10, border=5)
+ qr.add_data(str(content))
+ qr.make(fit=True)
+ qr_img = qr.make_image(fill_color="black", back_color="white")
+
+ # 保存二维码为临时文件
+ qr_temp_path = '/tmp/qr_temp.png'
+ qr_img.save(qr_temp_path)
+
+ # 创建一个临时PDF文件路径
+ output_temp_path = '/tmp/output_temp.pdf'
+
+ try:
+ # 使用reportlab创建一个新的PDF
+
+
+ # 注册中文字体
+ font_paths = [
+ "/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
+ "c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
+ "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
+ "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
+ "/usr/share/fonts/chinese/TrueType/simsun.ttc", # 某些Linux发行版位置
+ ]
+
+ font_found = False
+ for font_path in font_paths:
+ if os.path.exists(font_path):
+ try:
+ pdfmetrics.registerFont(TTFont('SimSun', font_path))
+ font_found = True
+ break
+ except:
+ continue
+
+ # 读取原始PDF
+ with open(pdf_path, "rb") as original_file:
+ existing_pdf = PdfFileReader(original_file)
+ output = PdfFileWriter()
+
+ # 处理第一页
+ page = existing_pdf.getPage(0)
+ # 获取页面尺寸
+ page_width = float(page.mediaBox.getWidth())
+ page_height = float(page.mediaBox.getHeight())
+
+ # 创建一个新的PDF页面用于放置二维码
+ c = canvas.Canvas(output_temp_path, pagesize=(page_width, page_height))
+
+ # 设置字体
+ if font_found:
+ c.setFont('SimSun', 14) # 增大字体大小到14pt
+ else:
+ # 如果没有找到中文字体,使用默认字体
+ c.setFont('Helvetica', 14)
+ logging.warning("未找到中文字体,将使用默认字体")
+
+ # 在右下角绘制二维码,预留边距
+ qr_size = 1.5 * inch # 二维码大小为2英寸
+ margin = 0.1 * inch # 边距为0.4英寸
+ qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
+ c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
+
+ if buttom_text:
+ # 在二维码下方绘制文字
+ text = buttom_text
+ text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
+ text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
+ text_y = margin + 20 # 文字位置靠近底部
+ c.drawString(text_x, text_y, text)
+
+ c.save()
+
+ # 读取带有二维码的临时PDF
+ with open(output_temp_path, "rb") as qr_file:
+ qr_pdf = PdfFileReader(qr_file)
+ qr_page = qr_pdf.getPage(0)
+
+ # 合并原始页面和二维码页面
+ page.mergePage(qr_page)
+ output.addPage(page)
+
+ # 添加剩余的页面
+ for i in range(1, existing_pdf.getNumPages()):
+ output.addPage(existing_pdf.getPage(i))
+
+ # 保存最终的PDF到一个临时文件
+ final_temp_path = pdf_path + '.tmp'
+ with open(final_temp_path, "wb") as output_file:
+ output.write(output_file)
+
+ # 替换原始文件
+ os.replace(final_temp_path, pdf_path)
+
+ return True
+
+ finally:
+ # 清理临时文件
+ if os.path.exists(qr_temp_path):
+ os.remove(qr_temp_path)
+ if os.path.exists(output_temp_path):
+ os.remove(output_temp_path)
\ No newline at end of file
diff --git a/sf_base/views/Printer.xml b/sf_base/views/Printer.xml
index 0e199b32..157121c9 100644
--- a/sf_base/views/Printer.xml
+++ b/sf_base/views/Printer.xml
@@ -9,6 +9,7 @@
+
@@ -24,6 +25,7 @@
+
diff --git a/sf_dlm/data/product_data.xml b/sf_dlm/data/product_data.xml
index b8cab582..06bed6ea 100644
--- a/sf_dlm/data/product_data.xml
+++ b/sf_dlm/data/product_data.xml
@@ -14,10 +14,12 @@
原材料
原材料
-
表面工艺
表面工艺
+
+ fifo
+ manual_periodic
@@ -40,10 +42,10 @@
-
-
-
-
+
+
+
+
功能刀具
diff --git a/sf_dlm_management/__init__.py b/sf_dlm_management/__init__.py
index e69de29b..9a7e03ed 100644
--- a/sf_dlm_management/__init__.py
+++ b/sf_dlm_management/__init__.py
@@ -0,0 +1 @@
+from . import models
\ No newline at end of file
diff --git a/sf_dlm_management/__manifest__.py b/sf_dlm_management/__manifest__.py
index 9a94082a..96344803 100644
--- a/sf_dlm_management/__manifest__.py
+++ b/sf_dlm_management/__manifest__.py
@@ -9,8 +9,9 @@
""",
'category': 'sf',
'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/sequence.xml',
'data/stock_data.xml',
'views/product_template_management_view.xml',
],
diff --git a/sf_dlm_management/data/sequence.xml b/sf_dlm_management/data/sequence.xml
new file mode 100644
index 00000000..39379169
--- /dev/null
+++ b/sf_dlm_management/data/sequence.xml
@@ -0,0 +1,10 @@
+
+
+
+ 工艺可选参数编码序列
+ sf.production.process.parameter
+ WKSP
+ 9
+
+
+
\ No newline at end of file
diff --git a/sf_dlm_management/models/__init__.py b/sf_dlm_management/models/__init__.py
index 8c38257e..a46a54df 100644
--- a/sf_dlm_management/models/__init__.py
+++ b/sf_dlm_management/models/__init__.py
@@ -1,2 +1,4 @@
# from . import product_template
# from . import product_supplierinfo
+from . import sf_production_common
+from . import mrp_routing_workcenter
\ No newline at end of file
diff --git a/sf_dlm_management/models/mrp_routing_workcenter.py b/sf_dlm_management/models/mrp_routing_workcenter.py
new file mode 100644
index 00000000..4a04eb87
--- /dev/null
+++ b/sf_dlm_management/models/mrp_routing_workcenter.py
@@ -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)
\ No newline at end of file
diff --git a/sf_dlm_management/models/sf_production_common.py b/sf_dlm_management/models/sf_production_common.py
new file mode 100644
index 00000000..cd19e938
--- /dev/null
+++ b/sf_dlm_management/models/sf_production_common.py
@@ -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)
diff --git a/sf_dlm_management/views/product_template_management_view.xml b/sf_dlm_management/views/product_template_management_view.xml
index 01c2f51b..d6420417 100644
--- a/sf_dlm_management/views/product_template_management_view.xml
+++ b/sf_dlm_management/views/product_template_management_view.xml
@@ -39,7 +39,7 @@
attrs="{'invisible': [('categ_type', 'not in', ['成品','坯料', '原材料'])],'readonly': [('id', '!=', False)]}"/>
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sf_manufacturing/__manifest__.py b/sf_manufacturing/__manifest__.py
index ba0c6751..24c8a4b0 100644
--- a/sf_manufacturing/__manifest__.py
+++ b/sf_manufacturing/__manifest__.py
@@ -10,7 +10,8 @@
""",
'category': 'sf',
'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/cron_data.xml',
'data/stock_data.xml',
@@ -18,6 +19,7 @@
'data/panel_data.xml',
'data/sf_work_individuation_page.xml',
'data/agv_scheduling_data.xml',
+ 'data/product_data.xml',
'security/group_security.xml',
'security/ir.model.access.csv',
'wizard/workpiece_delivery_views.xml',
@@ -28,6 +30,7 @@
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
'wizard/sf_programming_reason_views.xml',
'wizard/sale_order_cancel_views.xml',
+ 'wizard/process_outsourcing.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',
@@ -44,6 +47,7 @@
'views/sale_order_views.xml',
'views/mrp_workorder_batch_replan.xml',
'views/purchase_order_view.xml',
+ 'views/product_template_views.xml',
],
'assets': {
diff --git a/sf_manufacturing/data/product_data.xml b/sf_manufacturing/data/product_data.xml
new file mode 100644
index 00000000..8331c373
--- /dev/null
+++ b/sf_manufacturing/data/product_data.xml
@@ -0,0 +1,23 @@
+
+
+
+
+ 服务
+
+ fifo
+ manual_periodic
+
+
+ 工序外协
+
+ fifo
+ manual_periodic
+
+
+ 其他
+
+ fifo
+ manual_periodic
+
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/models/__init__.py b/sf_manufacturing/models/__init__.py
index e6845317..9bad61c6 100644
--- a/sf_manufacturing/models/__init__.py
+++ b/sf_manufacturing/models/__init__.py
@@ -16,4 +16,6 @@ from . import sf_production_common
from . import sale_order
from . import quick_easy_order
from . import purchase_order
-from . import quality_check
\ No newline at end of file
+from . import quality_check
+from . import purchase_request_line
+from . import workorder_printer
diff --git a/sf_manufacturing/models/mrp_production.py b/sf_manufacturing/models/mrp_production.py
index 52c5f463..02125aca 100644
--- a/sf_manufacturing/models/mrp_production.py
+++ b/sf_manufacturing/models/mrp_production.py
@@ -6,6 +6,7 @@ import json
import os
import re
import traceback
+from operator import itemgetter
import requests
from itertools import groupby
@@ -238,7 +239,8 @@ class MrpProduction(models.Model):
programming_no = fields.Char('编程单号')
work_state = fields.Char('业务状态')
programming_state = fields.Selection(
- [('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'), ('已取消', '已取消')],
+ [('编程中', '编程中'), ('已编程', '已编程'), ('已编程未下发', '已编程未下发'), ('已下发', '已下发'),
+ ('已取消', '已取消')],
string='编程状态',
tracking=True)
glb_file = fields.Binary("glb模型文件")
@@ -267,6 +269,7 @@ class MrpProduction(models.Model):
quality_standard = fields.Binary('质检标准', related='product_id.quality_standard', readonly=True)
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
+
@api.depends('product_id')
def _compute_part_info(self):
try:
@@ -400,8 +403,10 @@ class MrpProduction(models.Model):
and production.schedule_state == '已排' and production.is_rework is False):
production.state = 'pending_cam'
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)))
- or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程'])
+ (production.programming_state in ['已编程'] or (
+ 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:
production.state = 'rework'
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
@@ -617,7 +622,7 @@ class MrpProduction(models.Model):
for rp in reproduction:
if rp.programming_no == item['programming_no']:
rp.write({'programming_state': '已编程未下发' if item[
- 'programming_state'] == '已编程' else '编程中'})
+ 'programming_state'] == '已编程' else '编程中'})
else:
return item
@@ -889,11 +894,44 @@ class MrpProduction(models.Model):
workorders_values.append(
self.env[
'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
for workorder in production.workorder_ids:
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):
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():
product_id_to_production_names[product_id] = [p.name for p in pd]
sorted_workorders = None
+ purchase_request_line = []
for production in production_all:
proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search(
@@ -919,7 +958,10 @@ class MrpProduction(models.Model):
return
for workorders in reversed(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):
@@ -1341,7 +1383,7 @@ class MrpProduction(models.Model):
'default_reprogramming_num': cloud_programming.get('reprogramming_num') if cloud_programming else '',
'default_programming_state': cloud_programming.get('programming_state') if cloud_programming else '',
'default_is_reprogramming': True if cloud_programming and (
- cloud_programming.get('programming_state') in ['已下发']) else False
+ cloud_programming.get('programming_state') in ['已下发']) else False
}
}
@@ -1728,7 +1770,8 @@ class MrpProduction(models.Model):
raise UserError('仅支持选择单个制造订单进行编程申请,请重新选择')
for production in self:
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()
if cloud_programming['programming_state'] in ['待编程', '已编程', '编程中']:
raise UserError("当前编程单正在重新编程,请注意查看当前制造订单的“编程记录”确认进度!")
diff --git a/sf_manufacturing/models/mrp_routing_workcenter.py b/sf_manufacturing/models/mrp_routing_workcenter.py
index 666be375..c869f01f 100644
--- a/sf_manufacturing/models/mrp_routing_workcenter.py
+++ b/sf_manufacturing/models/mrp_routing_workcenter.py
@@ -1,6 +1,7 @@
import logging
from odoo import fields, models, api
from odoo.exceptions import UserError
+from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model):
@@ -24,10 +25,41 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False)
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)
is_outsource = fields.Boolean('外协', default=False)
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):
international_standards = self.search(
[('code', '!=', ''), ('active', 'in', [True, False])],
diff --git a/sf_manufacturing/models/mrp_workcenter.py b/sf_manufacturing/models/mrp_workcenter.py
index 27fdb37c..9434651c 100644
--- a/sf_manufacturing/models/mrp_workcenter.py
+++ b/sf_manufacturing/models/mrp_workcenter.py
@@ -21,7 +21,16 @@ class ResWorkcenter(models.Model):
related='equipment_id.production_line_id', store=True)
is_process_outsourcing = fields.Boolean('工艺外协')
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):
if 'users_ids' in vals:
old_users = self.users_ids
diff --git a/sf_manufacturing/models/mrp_workorder.py b/sf_manufacturing/models/mrp_workorder.py
index f187d506..8d5ecbf1 100644
--- a/sf_manufacturing/models/mrp_workorder.py
+++ b/sf_manufacturing/models/mrp_workorder.py
@@ -19,7 +19,6 @@ from odoo.addons.sf_mrs_connect.models.ftp_operate import FtpController
class ResMrpWorkOrder(models.Model):
_inherit = 'mrp.workorder'
- _order = 'sequence asc'
_description = '工单'
product_tmpl_name = fields.Char('坯料产品名称', related='production_bom_id.bom_line_ids.product_id.name')
@@ -106,11 +105,11 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False
# tag_type
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.routing_type == cur_workorder.routing_type and
- cur_workorder.tag_type !='重新加工' and
- detection_result.test_results != '合格'
- for detection_result in cur_workorder.production_id.detection_result_ids
+ detection_result.processing_panel == cur_workorder.processing_panel and
+ detection_result.routing_type == cur_workorder.routing_type and
+ cur_workorder.tag_type != '重新加工' and
+ detection_result.test_results != '合格'
+ for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
else:
@@ -122,11 +121,11 @@ class ResMrpWorkOrder(models.Model):
else:
record.back_button_display = False
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.routing_type == cur_workorder.routing_type and
- cur_workorder.tag_type !='重新加工' and
- detection_result.test_results != '合格'
- for detection_result in cur_workorder.production_id.detection_result_ids
+ detection_result.processing_panel == cur_workorder.processing_panel and
+ detection_result.routing_type == cur_workorder.routing_type and
+ cur_workorder.tag_type != '重新加工' and
+ detection_result.test_results != '合格'
+ for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
@@ -429,6 +428,8 @@ class ResMrpWorkOrder(models.Model):
def _compute_surface_technics_purchase_ids(self):
for order in self:
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'),
('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')]
@@ -473,13 +474,14 @@ class ResMrpWorkOrder(models.Model):
def _get_surface_technics_purchase_ids(self):
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']
for po in purchase_orders:
for line in po.order_line:
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
supplier_id = fields.Many2one('res.partner', string='外协供应商')
@@ -1200,6 +1202,7 @@ class ResMrpWorkOrder(models.Model):
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
}]
return workorders_values_str
+
def _process_compute_state(self):
for workorder in self:
# 如果工单的工序没有进行排序则跳出循环
@@ -1224,7 +1227,7 @@ class ResMrpWorkOrder(models.Model):
and workorder.production_id.schedule_state == '已排'
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
- # and workorder.production_id.programming_state == '已编程'
+ # and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework':
workorder.state = 'waiting'
@@ -1287,6 +1290,7 @@ class ResMrpWorkOrder(models.Model):
mo.get_move_line(workorder.production_id, workorder))
else:
workorder.state = 'waiting'
+
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state')
@@ -1301,6 +1305,7 @@ class ResMrpWorkOrder(models.Model):
for check_id in workorder.check_ids:
if not check_id.is_inspect:
check_id.quality_state = 'none'
+
# 重写工单开始按钮方法
def button_start(self):
# 判断工单状态是否为等待组件
@@ -1534,7 +1539,7 @@ class ResMrpWorkOrder(models.Model):
# workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
-
+
if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids:
@@ -1552,7 +1557,8 @@ class ResMrpWorkOrder(models.Model):
# 如果工单包含了外协工序,需要预留数量
if self.move_raw_ids.move_orig_ids.subcontract_workorder_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:
self.env['stock.quant']._update_reserved_quantity(
self.move_raw_ids.product_id,
@@ -1706,7 +1712,8 @@ class ResMrpWorkOrder(models.Model):
store=True, string='工序作业')
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
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')
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]
if individuation_page_list:
mw.individuation_page_list = list(set(individuation_page_list))
+
# =============================================================================================
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(
{'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):
_name = 'sf.cnc.processing'
@@ -2040,6 +2065,7 @@ class WorkPieceDelivery(models.Model):
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
+
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
status = fields.Selection(
diff --git a/sf_manufacturing/models/product_template.py b/sf_manufacturing/models/product_template.py
index 3554967c..1af6226a 100644
--- a/sf_manufacturing/models/product_template.py
+++ b/sf_manufacturing/models/product_template.py
@@ -51,7 +51,7 @@ class ResProductMo(models.Model):
# domain="[('materials_id', '=', materials_id)]")
# cutting_tool_model_id.material_model_id
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',
string='表面工艺参数')
diff --git a/sf_manufacturing/models/purchase_order.py b/sf_manufacturing/models/purchase_order.py
index 044b68e1..a919b3be 100644
--- a/sf_manufacturing/models/purchase_order.py
+++ b/sf_manufacturing/models/purchase_order.py
@@ -66,6 +66,37 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0:
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()
diff --git a/sf_manufacturing/models/purchase_request_line.py b/sf_manufacturing/models/purchase_request_line.py
new file mode 100644
index 00000000..385594e4
--- /dev/null
+++ b/sf_manufacturing/models/purchase_request_line.py
@@ -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')
diff --git a/sf_manufacturing/models/sf_production_common.py b/sf_manufacturing/models/sf_production_common.py
index 113858c1..708199bb 100644
--- a/sf_manufacturing/models/sf_production_common.py
+++ b/sf_manufacturing/models/sf_production_common.py
@@ -1,12 +1,50 @@
# -*- coding: utf-8 -*-
import logging
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):
_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
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
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)]
return self._search(domain, limit=limit, access_rights_uid=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
diff --git a/sf_manufacturing/models/stock.py b/sf_manufacturing/models/stock.py
index 2bf1ea35..3a1b3cd9 100644
--- a/sf_manufacturing/models/stock.py
+++ b/sf_manufacturing/models/stock.py
@@ -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:
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):
_inherit = 'stock.move'
diff --git a/sf_manufacturing/models/workorder_printer.py b/sf_manufacturing/models/workorder_printer.py
new file mode 100644
index 00000000..46eb3b8b
--- /dev/null
+++ b/sf_manufacturing/models/workorder_printer.py
@@ -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)
+
diff --git a/sf_manufacturing/security/ir.model.access.csv b/sf_manufacturing/security/ir.model.access.csv
index 982506a5..852aa05f 100644
--- a/sf_manufacturing/security/ir.model.access.csv
+++ b/sf_manufacturing/security/ir.model.access.csv
@@ -193,4 +193,6 @@ access_sf_programming_record,sf_programming_record,model_sf_programming_record,b
access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,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_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
\ No newline at end of file
+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
\ No newline at end of file
diff --git a/sf_manufacturing/views/mrp_production_addional_change.xml b/sf_manufacturing/views/mrp_production_addional_change.xml
index 020c2691..9a9db4ac 100644
--- a/sf_manufacturing/views/mrp_production_addional_change.xml
+++ b/sf_manufacturing/views/mrp_production_addional_change.xml
@@ -455,7 +455,7 @@
- sequence
+ create_date desc
delivery_warning == 'warning'
delivery_warning == 'overdue'
diff --git a/sf_manufacturing/views/mrp_routing_workcenter_view.xml b/sf_manufacturing/views/mrp_routing_workcenter_view.xml
index 67bc0c97..d1eb0618 100644
--- a/sf_manufacturing/views/mrp_routing_workcenter_view.xml
+++ b/sf_manufacturing/views/mrp_routing_workcenter_view.xml
@@ -22,6 +22,26 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sf_manufacturing/views/product_template_views.xml b/sf_manufacturing/views/product_template_views.xml
new file mode 100644
index 00000000..a99a332a
--- /dev/null
+++ b/sf_manufacturing/views/product_template_views.xml
@@ -0,0 +1,15 @@
+
+
+
+
+ product.template.product.form.inherit.sf_manufacture
+ product.template
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/sf_manufacturing/views/stock_picking_view.xml b/sf_manufacturing/views/stock_picking_view.xml
index f5b1941b..cc3c1585 100644
--- a/sf_manufacturing/views/stock_picking_view.xml
+++ b/sf_manufacturing/views/stock_picking_view.xml
@@ -50,6 +50,9 @@
+
+ create_date desc
+
diff --git a/sf_manufacturing/wizard/__init__.py b/sf_manufacturing/wizard/__init__.py
index 7a2541c3..43b9bcf7 100644
--- a/sf_manufacturing/wizard/__init__.py
+++ b/sf_manufacturing/wizard/__init__.py
@@ -6,3 +6,4 @@ from . import production_technology_re_adjust_wizard
from . import mrp_workorder_batch_replan_wizard
from . import sf_programming_reason
from . import sale_order_cancel
+from . import process_outsourcing
\ No newline at end of file
diff --git a/sf_manufacturing/wizard/process_outsourcing.py b/sf_manufacturing/wizard/process_outsourcing.py
new file mode 100644
index 00000000..71f852b6
--- /dev/null
+++ b/sf_manufacturing/wizard/process_outsourcing.py
@@ -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)
\ No newline at end of file
diff --git a/sf_manufacturing/wizard/process_outsourcing.xml b/sf_manufacturing/wizard/process_outsourcing.xml
new file mode 100644
index 00000000..69dd296f
--- /dev/null
+++ b/sf_manufacturing/wizard/process_outsourcing.xml
@@ -0,0 +1,37 @@
+
+
+ product.creation.wizard.form
+ product.creation.wizard
+
+
+
+
+
+
+
+ 快速创建产品
+ product.creation.wizard
+ form
+ new
+
+
+
\ No newline at end of file
diff --git a/sf_mrs_connect/controllers/controllers.py b/sf_mrs_connect/controllers/controllers.py
index 7599ed6c..c30d3d53 100644
--- a/sf_mrs_connect/controllers/controllers.py
+++ b/sf_mrs_connect/controllers/controllers.py
@@ -8,6 +8,7 @@ from odoo.http import request
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
+
class Sf_Mrs_Connect(http.Controller, MultiInheritController):
@http.route('/api/cnc_processing/create', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
@@ -22,6 +23,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
try:
res = {'status': 1, 'message': '成功'}
datas = request.httprequest.data
+ model_id = None
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret)
@@ -57,6 +59,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
return json.JSONEncoder().encode(res)
for production in productions:
+ model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单
@@ -83,19 +86,23 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
# panel)
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
files_panel = os.listdir(program_path_tmp_panel)
+ panel_file_path = ''
if files_panel:
for file in files_panel:
file_extension = os.path.splitext(file)[1]
if file_extension.lower() == '.pdf':
panel_file_path = os.path.join(program_path_tmp_panel, file)
logging.info('panel_file_path:%s' % panel_file_path)
- cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
- pre_workorder = productions.workorder_ids.filtered(
- lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
- 'cancel'] and ap.processing_panel == panel)
- if pre_workorder:
- pre_workorder.write(
- {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
+
+ # 向编程单中添加二维码
+ request.env['printing.utils'].add_qr_code_to_pdf(panel_file_path, model_id, "扫码获取工单")
+ cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())})
+ pre_workorder = productions.workorder_ids.filtered(
+ lambda ap: ap.routing_type in ['装夹预调', '人工线下加工'] and ap.state not in ['done', 'rework'
+ 'cancel'] and ap.processing_panel == panel)
+ if pre_workorder:
+ pre_workorder.write(
+ {'processing_drawing': base64.b64encode(open(panel_file_path, 'rb').read())})
productions.write({'programming_state': '已编程', 'work_state': '已编程'})
productions.filtered(lambda p: p.production_type == '人工线下加工').write({'manual_quotation': True})
logging.info('已更新制造订单编程状态:%s' % productions.ids)
@@ -268,3 +275,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
request.cr.rollback()
logging.info('get_cnc_processing_create error:%s' % e)
return json.JSONEncoder().encode(res)
+
+
+
\ No newline at end of file
diff --git a/sf_mrs_connect/data/ir_cron_data.xml b/sf_mrs_connect/data/ir_cron_data.xml
index 7cc18c8e..a2d43891 100644
--- a/sf_mrs_connect/data/ir_cron_data.xml
+++ b/sf_mrs_connect/data/ir_cron_data.xml
@@ -1,16 +1,16 @@
-
-
- 制造-配置:每日定时同步cloud的静态资源库
-
- code
- model.sf_all_sync()
- 1
- days
- -1
-
-
+
+
+ 制造-配置:每日定时同步cloud的静态资源库
+
+ code
+ model.sf_all_sync()
+ 1
+ days
+ -1
+
+
@@ -220,4 +220,5 @@
+
\ No newline at end of file
diff --git a/sf_mrs_connect/models/sync_common.py b/sf_mrs_connect/models/sync_common.py
index 652ec3eb..b2b65e29 100644
--- a/sf_mrs_connect/models/sync_common.py
+++ b/sf_mrs_connect/models/sync_common.py
@@ -1135,8 +1135,10 @@ class sfProductionProcessParameter(models.Model):
[("code", '=', item['code']), ('active', 'in', [True, False])])
process = self.env['sf.production.process'].search(
[('code', '=', item['process_id_code'])], limit=1)
+ production_process_parameter = self.search(
+ [("code", '=', item['code']), ('active', 'in', [True, False])])
if not production_process_parameter:
- self.create({
+ production_process_parameter = self.create({
"name": item['name'],
"process_description": item['process_description'],
"processing_day": item['processing_day'],
@@ -1148,6 +1150,7 @@ class sfProductionProcessParameter(models.Model):
[('materials_no', 'in', item['materials_model_ids_codes'])]),
'processing_mm': item['processing_mm']
})
+ production_process_parameter.create_service_product()
else:
production_process_parameter.name = item['name']
production_process_parameter.process_description = item['process_description']
@@ -1158,6 +1161,9 @@ class sfProductionProcessParameter(models.Model):
[('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.active = item['active']
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:
raise ValidationError("表面工艺可选参数认证未通过")
diff --git a/sf_sale/models/sale_order.py b/sf_sale/models/sale_order.py
index 1c8480dd..51a88d82 100644
--- a/sf_sale/models/sale_order.py
+++ b/sf_sale/models/sale_order.py
@@ -343,6 +343,11 @@ class RePurchaseOrder(models.Model):
if order_line.product_id.id in product_list:
purchase.purchase_type = 'outsourcing'
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', '已逾期')],
string='交期状态', default='normal',
@@ -376,6 +381,27 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id:
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):
for pp in consecutive_process_parameters:
server_product_process = []
@@ -396,14 +422,14 @@ class RePurchaseOrder(models.Model):
'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({
- '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,
'state': 'draft',
'purchase_type': 'consignment',
'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()
pp.purchase_id = [(6, 0, [purchase_order.id])]
diff --git a/sf_sale/views/purchase_order_view.xml b/sf_sale/views/purchase_order_view.xml
index 6624c7fa..946e3d97 100644
--- a/sf_sale/views/purchase_order_view.xml
+++ b/sf_sale/views/purchase_order_view.xml
@@ -298,6 +298,10 @@
purchase_order_list_name
+
+
+ date_approve desc
+
diff --git a/sf_sale/views/sale_team.xml b/sf_sale/views/sale_team.xml
index 3c75f70e..b3062f03 100644
--- a/sf_sale/views/sale_team.xml
+++ b/sf_sale/views/sale_team.xml
@@ -14,16 +14,14 @@
-
-
-
-
-
-
-
-
-
+
+
+
diff --git a/sf_stock/__manifest__.py b/sf_stock/__manifest__.py
index 0f1f72f6..e107ce55 100644
--- a/sf_stock/__manifest__.py
+++ b/sf_stock/__manifest__.py
@@ -24,6 +24,7 @@
# always loaded
'data': [
# 'security/ir.model.access.csv',
+ 'data/stock_location_data.xml',
'views/stock_picking.xml',
'views/stock_product_template.xml',
],
diff --git a/sf_stock/data/stock_location_data.xml b/sf_stock/data/stock_location_data.xml
new file mode 100644
index 00000000..14a09eb5
--- /dev/null
+++ b/sf_stock/data/stock_location_data.xml
@@ -0,0 +1,13 @@
+
+
+
+
+ 外协收料区
+ internal
+
+
+ 工序外协
+
+
+
+
diff --git a/sf_stock/models/__init__.py b/sf_stock/models/__init__.py
index c62a4dff..841d9846 100644
--- a/sf_stock/models/__init__.py
+++ b/sf_stock/models/__init__.py
@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
-from . import stock_picking
\ No newline at end of file
+from . import stock_picking
+from . import stock_warehouse_orderpoint
\ No newline at end of file
diff --git a/sf_stock/models/stock_warehouse_orderpoint.py b/sf_stock/models/stock_warehouse_orderpoint.py
new file mode 100644
index 00000000..6bd38ff3
--- /dev/null
+++ b/sf_stock/models/stock_warehouse_orderpoint.py
@@ -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('补货来源')
diff --git a/sf_tool_management/models/base.py b/sf_tool_management/models/base.py
index 3d9ec5ee..3cbaf974 100644
--- a/sf_tool_management/models/base.py
+++ b/sf_tool_management/models/base.py
@@ -1242,7 +1242,7 @@ class FunctionalToolDismantle(models.Model):
functional_tool_id = fields.Many2one('sf.functional.cutting.tool.entity', '功能刀具', required=True, tracking=True,
domain=[('functional_tool_status', '!=', '已拆除'),
- ('current_location', '=', '刀具房')])
+ ('current_location', 'in', ['刀具房', '线边刀库'])])
@api.onchange('functional_tool_id')
def _onchange_functional_tool_id(self):
diff --git a/sf_tool_management/models/functional_tool.py b/sf_tool_management/models/functional_tool.py
index 63a31b82..498d9436 100644
--- a/sf_tool_management/models/functional_tool.py
+++ b/sf_tool_management/models/functional_tool.py
@@ -10,6 +10,7 @@ from odoo.exceptions import ValidationError
class FunctionalCuttingToolEntity(models.Model):
_name = 'sf.functional.cutting.tool.entity'
+ _inherit = ['mail.thread']
_description = '功能刀具列表'
_order = 'functional_tool_status'
@@ -41,7 +42,7 @@ class FunctionalCuttingToolEntity(models.Model):
max_lifetime_value = fields.Integer(string='最大寿命值(min)', readonly=True)
alarm_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='正常')
current_location_id = fields.Many2one('stock.location', string='当前位置', compute='_compute_current_location_id',
store=True)
@@ -62,16 +63,27 @@ class FunctionalCuttingToolEntity(models.Model):
for item in self:
if item:
if item.functional_tool_status == '报警':
- # 创建报警刀具拆解单
- self.env['sf.functional.tool.dismantle'].sudo().create({
- 'functional_tool_id': item.ids[0],
- 'dismantle_cause': '寿命到期报废'
- })
- # 创建刀具报警记录
- self.env['sf.functional.tool.warning'].sudo().create({
- 'rfid': item.rfid,
- 'functional_tool_id': item.ids[0]
- })
+ 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({
+ 'functional_tool_id': item.ids[0],
+ 'dismantle_cause': '寿命到期报废'
+ })
+ # 创建刀具报警记录
+ self.env['sf.functional.tool.warning'].sudo().create({
+ 'rfid': item.rfid,
+ 'functional_tool_id': item.ids[0]
+ })
@api.depends('barcode_id.quant_ids', 'barcode_id.quant_ids.location_id', 'functional_tool_status',
'current_shelf_location_id', 'stock_num')
@@ -263,7 +275,7 @@ class FunctionalCuttingToolEntity(models.Model):
functional_tool_model_ids.append(functional_tool_model.id)
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', '拆解单')
@api.depends('dismantle_ids')
diff --git a/sf_tool_management/models/maintenance_equipment.py b/sf_tool_management/models/maintenance_equipment.py
index 12c7f8f3..a3375075 100644
--- a/sf_tool_management/models/maintenance_equipment.py
+++ b/sf_tool_management/models/maintenance_equipment.py
@@ -107,11 +107,17 @@ class SfMaintenanceEquipment(models.Model):
if functional_tool_id.current_location != '机内刀库':
# 对功能刀具进行移动到生产线
functional_tool_id.tool_inventory_displacement_out()
- functional_tool_id.write({
- 'max_lifetime_value': data['MaxLife'],
- 'used_value': data['UseLife'],
- 'functional_tool_status': tool_install_time.get(data['State'])
- })
+ data_tool = {
+ 'max_lifetime_value': data['MaxLife'],
+ 'used_value': data['UseLife'],
+ '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:
logging.info('获取的【%s】设备不存在!!!' % data['DeviceId'])
else:
diff --git a/sf_tool_management/static/src/change.scss b/sf_tool_management/static/src/change.scss
index 20298bdf..9cf26b0a 100644
--- a/sf_tool_management/static/src/change.scss
+++ b/sf_tool_management/static/src/change.scss
@@ -22,15 +22,21 @@
color: #999;
}
}
+
+}
+.custom_group:has(.text-success){
+ position: relative;
&::after{
content: '';
display: block;
- width: 18px;
- height: 18px;
+ width: 72px;
+ height: 72px;
background: url('/sf_tool_management/static/images/replaceIcon.png') no-repeat center center;
background-size: 100%;
+ position: absolute;
+ bottom: 20px;
+ left: 300px;
}
-
}
.o_field_widget.o_readonly_modifier.o_field_char.text-success[name=handle_freight_rfid] {
display: flex;
diff --git a/sf_tool_management/views/functional_tool_views.xml b/sf_tool_management/views/functional_tool_views.xml
index 2b872053..25b9346a 100644
--- a/sf_tool_management/views/functional_tool_views.xml
+++ b/sf_tool_management/views/functional_tool_views.xml
@@ -42,6 +42,7 @@
diff --git a/sf_tool_management/views/tool_base_views.xml b/sf_tool_management/views/tool_base_views.xml
index be0b5a3f..b0259249 100644
--- a/sf_tool_management/views/tool_base_views.xml
+++ b/sf_tool_management/views/tool_base_views.xml
@@ -432,7 +432,7 @@
功能刀具组装
sf.functional.tool.assembly
-
+
@@ -531,7 +531,7 @@
-
+
@@ -554,7 +554,7 @@
-
+
@@ -582,8 +582,8 @@
-
-
+
+
@@ -607,8 +607,8 @@
-
-
+
+
@@ -631,8 +631,8 @@
-
-
+
+