Compare commits

..

2 Commits

56 changed files with 421 additions and 1055 deletions

View File

@@ -3,12 +3,8 @@ import qrcode
from reportlab.pdfgen import canvas from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import A4 from reportlab.lib.pagesizes import A4
from PIL import Image from PIL import Image
import logging
from reportlab.lib.utils import ImageReader from reportlab.lib.utils import ImageReader
from odoo import models, fields, api from odoo import models, fields, api
import base64
_logger = logging.getLogger(__name__)
class JikimoPrinting(models.AbstractModel): class JikimoPrinting(models.AbstractModel):
_name = 'jikimo.printing' _name = 'jikimo.printing'
@@ -17,11 +13,6 @@ class JikimoPrinting(models.AbstractModel):
""" """
打印二维码 打印二维码
""" """
printer = self.env['printing.printer'].get_default()
if not printer:
_logger.error("未找到默认打印机")
return False
# 生成二维码 # 生成二维码
qr = qrcode.QRCode(version=1, box_size=10, border=5) qr = qrcode.QRCode(version=1, box_size=10, border=5)
qr.add_data(data) qr.add_data(data)
@@ -50,38 +41,16 @@ class JikimoPrinting(models.AbstractModel):
# 获取PDF内容并打印 # 获取PDF内容并打印
pdf_content = pdf_buffer.getvalue() pdf_content = pdf_buffer.getvalue()
# _logger.info(f"打印内容: {pdf_content}") printer = self.env['printing.printer'].get_default()
printer.print_document(report=None, content=pdf_content, doc_format='pdf') printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源 # 清理资源
pdf_buffer.close() pdf_buffer.close()
temp_image.close() temp_image.close()
return True
def print_pdf(self, pdf_data): def print_pdf(self, pdf_data):
""" """
打印PDF 打印PDF
""" """
printer = self.env['printing.printer'].get_default() printer = self.env['printing.printer'].get_default()
if not printer: printer.print_document(report=None, content = pdf_data, doc_format='pdf')
_logger.error("未找到默认打印机")
return False
pdf_data_str = pdf_data.decode('ascii', errors='ignore')
decoded_data = base64.b64decode(pdf_data_str)
# 处理二进制数据
pdf_buffer = BytesIO()
pdf_buffer.write(decoded_data)
pdf_buffer.seek(0)
# 获取PDF内容
pdf_content = pdf_buffer.getvalue()
printer.print_document(report=None, content=pdf_content, doc_format='pdf')
# 清理资源
pdf_buffer.close()
_logger.info("成功打印PDF")
return True

View File

@@ -21,6 +21,8 @@ class MrpWorkorder(models.Model):
# 执行打印 # 执行打印
self.env['jikimo.printing'].print_pdf(pdf_data) self.env['jikimo.printing'].print_pdf(pdf_data)
wo.production_id.product_id.is_print_program = True wo.production_id.product_id.is_print_program = True
_logger.info(f"工单 {wo.name} 的PDF已成功打印")
except Exception as e: except Exception as e:
_logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}") _logger.error(f"工单 {wo.name} 的PDF打印失败: {str(e)}")

View File

@@ -12,14 +12,12 @@
'views/mrp_production.xml', 'views/mrp_production.xml',
'views/purchase_request_view.xml', 'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml', 'wizard/purchase_request_line_make_purchase_order_view.xml',
'views/purchase_request_line_view.xml',
'views/stock_picking_views.xml',
], ],
'assets': { # 'assets': {
'web.assets_backend': [ # 'web.assets_backend': [
'jikimo_purchase_request/static/src/**/*' # 'jikimo_purchase_request/static/src/**/*'
], # ],
}, # },
'application': True, 'application': True,
'installable': True, 'installable': True,
'auto_install': False, 'auto_install': False,

View File

@@ -1043,7 +1043,7 @@ msgstr "询价单"
#. module: purchase_request #. module: purchase_request
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__purchased_qty #: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__purchased_qty
msgid "RFQ/PO Qty" msgid "RFQ/PO Qty"
msgstr "已订购数" msgstr ""
#. module: purchase_request #. module: purchase_request
#. odoo-python #. odoo-python

View File

@@ -5,4 +5,3 @@ from . import sale_order
from . import mrp_production from . import mrp_production
from . import purchase_order from . import purchase_order
from . import stock_rule from . import stock_rule
from . import stock_picking

View File

@@ -9,39 +9,18 @@ class MrpProduction(models.Model):
@api.depends('state') @api.depends('state')
def _compute_pr_mp_count(self): def _compute_pr_mp_count(self):
for item in self: for item in self:
# if item.product_id.product_tmpl_id.single_manufacturing == True and not item.is_remanufacture: pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
# first_order = self.env['mrp.production'].search( if pr_ids:
# [('origin', '=', item.origin), ('product_id', '=', item.product_id.id)], limit=1, order='id asc') item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)]) else:
# item.pr_mp_count = len(pr_ids) item.pr_mp_count = 0
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# item.pr_mp_count = len(pr_ids)
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', item.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
def action_view_pr_mp(self): def action_view_pr_mp(self):
""" """
采购请求 采购请求
""" """
self.ensure_one() self.ensure_one()
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)]) pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
# if self.product_id.product_tmpl_id.single_manufacturing == True and not self.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', self.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
action = { action = {
'res_model': 'purchase.request', 'res_model': 'purchase.request',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',

View File

@@ -13,11 +13,7 @@ class ProductTemplate(models.Model):
template_id.purchase_request = product_id.purchase_request template_id.purchase_request = product_id.purchase_request
return template_id return template_id
class ProdcutProduct(models.Model):
_inherit = 'product.product'
def copy_template(self, product_template_id): def copy_template(self, product_template_id):
""" 复制成品模板时,复制采购申请 """ """ 复制成品模板时,复制采购申请 """
super(ProdcutProduct, self).copy_template(product_template_id) super(ProductTemplate, self).copy_template(product_template_id)
self.purchase_request = product_template_id.purchase_request self.purchase_request = product_template_id.purchase_request

View File

@@ -47,19 +47,6 @@ class PurchaseRequestLine(models.Model):
('outsourcing', "委外加工"), ('outsourcing', "委外加工"),
], string='供货方式', compute='_compute_supply_method', store=True) ], string='供货方式', compute='_compute_supply_method', store=True)
purchase_request_count = fields.Integer(string='采购申请数量', compute='_compute_purchase_request_count', readonly=True)
purchase_count = fields.Integer(string="采购订单数量", compute="_compute_purchase_count", readonly=True)
@api.depends("purchase_lines")
def _compute_purchase_count(self):
for rec in self:
rec.purchase_count = len(rec.mapped("purchase_lines.order_id"))
@api.depends('request_id')
def _compute_purchase_request_count(self):
for order in self:
order.purchase_request_count = len(order.request_id)
@api.depends('origin') @api.depends('origin')
def _compute_supply_method(self): def _compute_supply_method(self):
for prl in self: for prl in self:
@@ -111,36 +98,3 @@ class PurchaseRequestLine(models.Model):
else: else:
record.part_number = record.product_id.part_number record.part_number = record.product_id.part_number
record.part_name = record.product_id.part_name record.part_name = record.product_id.part_name
def _compute_qty_to_buy(self):
for pr in self:
qty_to_buy = sum(pr.mapped("product_qty"))
if pr.purchase_count > 0:
qty_to_buy -= sum(pr.mapped("purchase_lines").filtered(lambda po: po.state != 'cancel').mapped(
"product_qty"))
pr.qty_to_buy = qty_to_buy > 0.0
pr.pending_qty_to_receive = qty_to_buy
def action_view_purchase_request(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase_request.purchase_request_form_action")
action.update({
'res_id': self.request_id.id,
'views': [[False, 'form']],
})
return action
def action_view_purchase_order(self):
action = self.env["ir.actions.actions"]._for_xml_id("purchase.purchase_rfq")
lines = self.mapped("purchase_lines.order_id")
if len(lines) > 1:
action["domain"] = [("id", "in", lines.ids)]
elif lines:
action["views"] = [
(self.env.ref("purchase.purchase_order_form").id, "form")
]
action["res_id"] = lines.id
origin_context = ast.literal_eval(action['context'])
if 'search_default_draft' in origin_context:
origin_context.pop('search_default_draft')
action['context'] = origin_context
return action

View File

@@ -1,35 +0,0 @@
from odoo import fields, api, models, _
class StockPicking(models.Model):
_inherit = "stock.picking"
purchase_request_count = fields.Integer('采购订单数量', compute='_compute_purchase_request')
@api.depends('name')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', record.name)])
record.purchase_request_count = len(purchase_request_ids)
def action_view_purchase_request(self):
self.ensure_one()
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', self.name)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action

View File

@@ -1,5 +1,4 @@
from odoo import api, fields, models from odoo import api, fields, models
from collections import defaultdict
class StockRule(models.Model): class StockRule(models.Model):
@@ -45,40 +44,7 @@ class StockRule(models.Model):
purchase_request_line_model.create(request_line_data) purchase_request_line_model.create(request_line_data)
def _run_buy(self, procurements): def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并 res = super(StockRule, self)._run_buy(procurements)
procurements_dict = defaultdict()
for procurement, rule in procurements:
if (procurement.product_id.id, procurement.values['group_id'], rule.id) not in procurements_dict:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)] = {
'product_id': procurement.product_id,
'product_qty': procurement.product_qty,
'product_uom': procurement.product_uom,
'location_id': procurement.location_id,
'name': procurement.name,
'origin': procurement.origin,
'company_id': procurement.company_id,
'values': procurement.values,
'rule': rule
}
else:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['product_qty'] += procurement.product_qty
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['values']['move_dest_ids'] |= procurement.values['move_dest_ids']
new_procurements = []
for k, p in procurements_dict.items():
new_procurements.append((
self.env['procurement.group'].Procurement(
product_id=p['product_id'],
product_qty=p['product_qty'],
product_uom=p['product_uom'],
location_id=p['location_id'],
name=p['name'],
origin=p['origin'],
company_id=p['company_id'],
values=p['values']
), p['rule'])
)
res = super(StockRule, self)._run_buy(new_procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved # 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements])) origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins: for origin in origins:

View File

@@ -1,3 +0,0 @@
th[data-name=keep_description] {
min-width: 220px;
}

View File

@@ -1,22 +0,0 @@
<odoo>
<record id="purchase_request_line_form_sf" model="ir.ui.view">
<field name="name">purchase.request.line.sf.form</field>
<field name="model">purchase.request.line</field>
<field name="inherit_id" ref="purchase_request.purchase_request_line_form"/>
<field name="arch" type="xml">
<xpath expr="//h1" position="before">
<div class="oe_button_box" name="button_box">
<button type="object" name="action_view_purchase_request" class="oe_stat_button"
icon="fa-file">
<field name="purchase_request_count" widget="statinfo" string="采购申请"/>
</button>
<button type="object" name="action_view_purchase_order" class="oe_stat_button"
attrs="{'invisible': [('purchase_count', '=', 0)]}" icon="fa-shopping-cart">
<field name="purchase_count" widget="statinfo" string="采购订单"/>
</button>
</div>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_pikcing_inherited_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">stock.pikcing.inherited.form.jikimo.purchase.request</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']/button" position="before">
<button class="oe_stat_button" name="action_view_purchase_request" type="object" icon="fa-credit-card"
attrs="{'invisible': [('purchase_request_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="purchase_request_count"/>
</span>
<span class="o_stat_text">采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -104,26 +104,9 @@ class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
# 去掉合并必须同一采购组的限制 # 去掉合并必须同一采购组的限制
pass pass
def get_items(self, request_line_ids):
request_line_obj = self.env["purchase.request.line"]
items = []
request_lines = request_line_obj.browse(request_line_ids).filtered(lambda line: line.pending_qty_to_receive > 0)
self._check_valid_request_line(request_line_ids)
self.check_group(request_lines)
for line in request_lines:
items.append([0, 0, self._prepare_item(line)])
return items
class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel): class PurchaseRequestLineMakePurchaseOrderItem(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order.item" _inherit = "purchase.request.line.make.purchase.order.item"
supply_method = fields.Selection(related='line_id.supply_method', string='供货方式') supply_method = fields.Selection(related='line_id.supply_method', string='供货方式')
wiz_id = fields.Many2one(
comodel_name="purchase.request.line.make.purchase.order",
string="Wizard",
required=False,
ondelete="cascade",
readonly=True,
)

View File

@@ -6,10 +6,6 @@ class ProductTemplate(models.Model):
is_manual_processing = fields.Boolean(string='人工线下加工') is_manual_processing = fields.Boolean(string='人工线下加工')
is_customer_provided = fields.Boolean(string='客供料') is_customer_provided = fields.Boolean(string='客供料')
class ProductProduct(models.Model):
_inherit = 'product.product'
def copy_template(self, product_template_id): def copy_template(self, product_template_id):
if not isinstance(product_template_id, ProductTemplate): if not isinstance(product_template_id, ProductTemplate):
raise ValueError('%s必须是ProductTemplate类型' % product_template_id) raise ValueError('%s必须是ProductTemplate类型' % product_template_id)

View File

@@ -6,7 +6,7 @@ from odoo.addons.sf_base.decorators.api_log import api_log
class MainController(http.Controller): class MainController(http.Controller):
@http.route('/api/manual_download_program', type='json', methods=['POST'], auth='wechat_token', cors='*') @http.route('/api/manual_download_program', type='json', methods=['POST'], auth='public', cors='*')
@api_log('人工线下加工编程文件传输', requester='报工系统') @api_log('人工线下加工编程文件传输', requester='报工系统')
def manual_download_program(self): def manual_download_program(self):
""" """
@@ -59,7 +59,7 @@ class MainController(http.Controller):
target_ftp_info, target_ftp_info,
'/' + str(model_id), '/' + str(model_id),
'/', '/',
match_str=r'^\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$' match_str=r'^\d*_\d*-' + tool_groups_id.name + r'-\w{2}-all\.nc$'
) )
if len(result) > 0: if len(result) > 0:
return {'code': 200, 'message': '传输成功', 'file_list': result} return {'code': 200, 'message': '传输成功', 'file_list': result}

View File

@@ -4,7 +4,6 @@ import json
import logging import logging
from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect from odoo.addons.sf_mrs_connect.controllers.controllers import Sf_Mrs_Connect
from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect from odoo.addons.sf_manufacturing.controllers.controllers import Manufacturing_Connect
from odoo.addons.sf_base.decorators.api_log import api_log
from datetime import datetime from datetime import datetime
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -13,7 +12,6 @@ class WorkorderExceptionConroller(http.Controller):
@http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/BillError', type='json', auth='public', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单对接错误', requester='中控系统')
def workder_exception(self, **kw): def workder_exception(self, **kw):
""" """
记录工单异常 记录工单异常

View File

@@ -141,7 +141,7 @@ class QualityCheck(models.Model):
# # 出厂检验报告编号 # # 出厂检验报告编号
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True) # report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
# 总数量值为调拨单_产品明细_数量 # 总数量值为调拨单_产品明细_数量
total_qty = fields.Char('总数量', compute='_compute_total_qty', store=True) total_qty = fields.Char('总数量', compute='_compute_total_qty', readonly=False, store=True)
column_nums = fields.Integer('测量值列数', default=1) column_nums = fields.Integer('测量值列数', default=1)
@@ -153,9 +153,9 @@ class QualityCheck(models.Model):
for move in record.picking_id.move_ids_without_package: for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id: if move.product_id == record.product_id:
total_qty = int(move.product_uom_qty) total_qty = int(move.product_uom_qty)
record.total_qty = total_qty if total_qty > 0 else 0 record.total_qty = total_qty if total_qty > 0 else ''
else: else:
record.total_qty = 0 record.total_qty = ''
# 检验数 # 检验数
check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty()) check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty())
@@ -281,7 +281,42 @@ class QualityCheck(models.Model):
""" """
pass pass
def check_total_quality(self):
"""
校验总数量是否等于拣货数量
"""
for record in self:
if record.picking_id:
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
picking_qty = int(move.product_uom_qty)
if int(record.total_qty) != picking_qty:
return True
return False
def do_publish(self): def do_publish(self):
"""打开二次确认弹窗"""
self.ensure_one()
if self.check_total_quality():
return self._show_confirmation_wizard()
else:
return self._do_actual_publish()
def _show_confirmation_wizard(self):
"""显示确认向导的通用方法"""
return {
'name': _('发布确认'),
'type': 'ir.actions.act_window',
'res_model': 'quality.check.publish.wizard',
'view_mode': 'form',
'target': 'new',
'context': {
'default_check_id': self.id,
# 其他默认字段...
}
}
def _do_actual_publish(self):
"""发布出厂检验报告""" """发布出厂检验报告"""
self.ensure_one() self.ensure_one()
self._check_part_number() self._check_part_number()
@@ -735,9 +770,8 @@ class QualityCheck(models.Model):
def _compute_qty_to_test(self): def _compute_qty_to_test(self):
for qc in self: for qc in self:
if qc.is_lot_tested_fractionally: if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100, qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100,
precision_rounding=rounding, rounding_method="UP") precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP")
else: else:
qc.qty_to_test = qc.qty_line qc.qty_to_test = qc.qty_line

View File

@@ -23,8 +23,8 @@ class QualityCheckWizard(models.TransientModel):
lot_name = fields.Char(related='current_check_id.lot_name') lot_name = fields.Char(related='current_check_id.lot_name')
lot_line_id = fields.Many2one(related='current_check_id.lot_line_id') lot_line_id = fields.Many2one(related='current_check_id.lot_line_id')
qty_line = fields.Float(related='current_check_id.qty_line') qty_line = fields.Float(related='current_check_id.qty_line')
qty_to_test = fields.Float(related='current_check_id.qty_to_test', string='待检') qty_to_test = fields.Float(related='current_check_id.qty_to_test')
qty_tested = fields.Float(related='current_check_id.qty_tested', string='已检', readonly=False) qty_tested = fields.Float(related='current_check_id.qty_tested', readonly=False)
measure = fields.Float(related='current_check_id.measure', readonly=False) measure = fields.Float(related='current_check_id.measure', readonly=False)
measure_on = fields.Selection(related='current_check_id.measure_on') measure_on = fields.Selection(related='current_check_id.measure_on')
quality_state = fields.Selection(related='current_check_id.quality_state') quality_state = fields.Selection(related='current_check_id.quality_state')

View File

@@ -1,6 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from odoo import api, fields, models, _ from odoo import api, fields, models, _
class QualityCheckPublishWizard(models.TransientModel): class QualityCheckPublishWizard(models.TransientModel):
_name = 'quality.check.publish.wizard' _name = 'quality.check.publish.wizard'
_description = '质检报告发布确认' _description = '质检报告发布确认'
@@ -12,9 +13,10 @@ class QualityCheckPublishWizard(models.TransientModel):
measure_count = fields.Integer('测量件数', readonly=True) measure_count = fields.Integer('测量件数', readonly=True)
item_count = fields.Integer('检验项目数', readonly=True) item_count = fields.Integer('检验项目数', readonly=True)
old_report_name = fields.Char('旧出厂检验报告编号', readonly=True) old_report_name = fields.Char('旧出厂检验报告编号', readonly=True)
publish_status = fields.Selection([('draft', '草稿'), ('published', '已发布'), ('canceled', '已撤销')], string='发布状态', readonly=True) publish_status = fields.Selection([('draft', '草稿'), ('published', '已发布'), ('canceled', '已撤销')], string='发布状态',
readonly=True)
def action_confirm_publish(self): def action_confirm_publish(self):
"""确认发布""" """确认发布"""
self.ensure_one() self.ensure_one()
return self.check_id._do_publish_implementation() return self.check_id._do_publish_implementation()

View File

@@ -1,7 +1,3 @@
pystrich pystrich
cpca==0.5.5 cpca
wechatpy==1.8.18 pycryptodome==3.20
pycryptodome==3.22.0
openupgradelib==3.10.0
opcua==0.98.13
openpyxl

View File

@@ -103,19 +103,12 @@ class PrintingUtils(models.AbstractModel):
self.send_to_printer(host, port, zpl_code) self.send_to_printer(host, port, zpl_code)
def add_qr_code_to_pdf( def add_qr_code_to_pdf(self, pdf_path:str, content:str, buttom_text:Optional[str]=False):
self,
pdf_path:str,
content:str,
qr_code_buttom_text:Optional[str]=False,
buttom_text:Optional[str]=False,
):
""" """
在PDF文件中添加二维码 在PDF文件中添加二维码
:param pdf_path: PDF文件路径 :param pdf_path: PDF文件路径
:param content: 二维码内容 :param content: 二维码内容
:param qr_code_buttom_text: 二维码下方文字 :param buttom_text: 二维码下方文字
:param buttom_text: 正文下方文字
:return: 是否成功 :return: 是否成功
""" """
if not os.path.exists(pdf_path): if not os.path.exists(pdf_path):
@@ -141,7 +134,7 @@ class PrintingUtils(models.AbstractModel):
# 注册中文字体 # 注册中文字体
font_paths = [ font_paths = [
"/usr/share/fonts/chinese/simsun.ttc", # Windows系统宋体 "/usr/share/fonts/windows/simsun.ttc", # Windows系统宋体
"c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置 "c:/windows/fonts/simsun.ttc", # Windows系统宋体另一个位置
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体 "/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf", # Linux Droid字体
"/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑 "/usr/share/fonts/truetype/wqy/wqy-zenhei.ttc", # 文泉驿正黑
@@ -163,9 +156,8 @@ class PrintingUtils(models.AbstractModel):
existing_pdf = PdfFileReader(original_file) existing_pdf = PdfFileReader(original_file)
output = PdfFileWriter() output = PdfFileWriter()
# 处理最后一页 # 处理一页
last_page = existing_pdf.getNumPages() - 1 page = existing_pdf.getPage(0)
page = existing_pdf.getPage(last_page)
# 获取页面尺寸 # 获取页面尺寸
page_width = float(page.mediaBox.getWidth()) page_width = float(page.mediaBox.getWidth())
page_height = float(page.mediaBox.getHeight()) page_height = float(page.mediaBox.getHeight())
@@ -175,10 +167,10 @@ class PrintingUtils(models.AbstractModel):
# 设置字体 # 设置字体
if font_found: if font_found:
c.setFont('SimSun', 10) # 增大字体大小到14pt c.setFont('SimSun', 14) # 增大字体大小到14pt
else: else:
# 如果没有找到中文字体,使用默认字体 # 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 10) c.setFont('Helvetica', 14)
logging.warning("未找到中文字体,将使用默认字体") logging.warning("未找到中文字体,将使用默认字体")
# 在右下角绘制二维码,预留边距 # 在右下角绘制二维码,预留边距
@@ -187,19 +179,11 @@ class PrintingUtils(models.AbstractModel):
qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间 qr_y = margin + 20 # 将二维码向上移动一点,为文字留出空间
c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size) c.drawImage(qr_temp_path, page_width - qr_size - margin, qr_y, width=qr_size, height=qr_size)
if qr_code_buttom_text:
# 在二维码下方绘制文字
text = qr_code_buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度
text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text)
if buttom_text: if buttom_text:
# 在下方中间添加文字 # 在二维码下方绘制文字
text = button_text text = buttom_text
text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 10) # 准确计算文字宽度 text_width = c.stringWidth(text, "SimSun" if font_found else "Helvetica", 14) # 准确计算文字宽度
text_x = (page_width - text_width) / 2 # 文字居中对齐 text_x = page_width - qr_size - margin + (qr_size - text_width) / 2 # 文字居中对齐
text_y = margin + 20 # 文字位置靠近底部 text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text) c.drawString(text_x, text_y, text)
@@ -212,12 +196,11 @@ class PrintingUtils(models.AbstractModel):
# 合并原始页面和二维码页面 # 合并原始页面和二维码页面
page.mergePage(qr_page) page.mergePage(qr_page)
output.addPage(page)
# 添加剩余的页面 # 添加剩余的页面
for i in range(0, last_page): for i in range(1, existing_pdf.getNumPages()):
output.addPage(existing_pdf.getPage(i)) output.addPage(existing_pdf.getPage(i))
output.addPage(page)
# 保存最终的PDF到一个临时文件 # 保存最终的PDF到一个临时文件
final_temp_path = pdf_path + '.tmp' final_temp_path = pdf_path + '.tmp'

View File

@@ -4,7 +4,6 @@ import json
import logging import logging
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
_logger = logging.getLogger(__name__) _logger = logging.getLogger(__name__)
@@ -12,7 +11,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/MachineToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('机床刀具组', requester='中控系统')
def get_maintenance_tool_groups_Info(self, **kw): def get_maintenance_tool_groups_Info(self, **kw):
""" """
机床刀具组接口 机床刀具组接口

View File

@@ -27,9 +27,6 @@ def api_log(name=None, requester=None):
# 执行原始函数 # 执行原始函数
result = func(*args, **kwargs) result = func(*args, **kwargs)
origin_result = result
if isinstance(result, str):
result = json.loads(result)
# 计算响应时间 # 计算响应时间
end_time = datetime.now() end_time = datetime.now()
@@ -44,7 +41,7 @@ def api_log(name=None, requester=None):
'response_data': json.dumps(result, ensure_ascii=False), 'response_data': json.dumps(result, ensure_ascii=False),
'remote_addr': remote_addr, 'remote_addr': remote_addr,
'response_time': response_time, 'response_time': response_time,
'status': result.get('code') or result.get('ErrorCode') or 500, 'status': result.get('code', 500),
'requester': requester, 'requester': requester,
'responser': '智能工厂' 'responser': '智能工厂'
} }
@@ -52,7 +49,7 @@ def api_log(name=None, requester=None):
# 异步创建日志记录 # 异步创建日志记录
request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals) request.env['api.request.log'].sudo().with_context(tracking_disable=True).create(log_vals)
return origin_result return result
except Exception as e: except Exception as e:
_logger.error(f"API日志记录失败: {str(e)}") _logger.error(f"API日志记录失败: {str(e)}")

View File

@@ -1,9 +1,4 @@
from odoo import models, fields, api from odoo import models, fields, api
import json, ast
import logging
import requests
_logger = logging.getLogger(__name__)
class ApiRequestLog(models.Model): class ApiRequestLog(models.Model):
@@ -21,52 +16,3 @@ class ApiRequestLog(models.Model):
status = fields.Integer('状态码') status = fields.Integer('状态码')
requester = fields.Char('请求方') requester = fields.Char('请求方')
responser = fields.Char('响应方') responser = fields.Char('响应方')
@api.model
def log_request(self, method, url, name=None, responser=None, **kwargs):
# Log the request
request_headers = kwargs.get('headers', {})
request_body = kwargs.get('json') or kwargs.get('params') or {}
_logger.info(f"Request: {method} {url} Headers: {request_headers} Body: {request_body}")
# Make the actual request
response = requests.request(method, url, **kwargs)
# Log the response
response_status = response.status_code
response_headers = response.headers
response_body = response.text
response_time = response.elapsed.total_seconds()
_logger.info(f"Response: Status: {response_status} Headers: {response_headers} Body: {response_body}")
try:
# 如果是字符串,先尝试用 ast.literal_eval 安全地转换成 Python 对象
if isinstance(response_body, str):
response_body_obj = json.loads(response_body)
else:
response_body_obj = response_body
# 再使用 json.dumps 转换成标准的 JSON 字符串
response_body = json.dumps(response_body_obj, ensure_ascii=False)
except Exception as e:
_logger.warning(f"转换 response_body 到标准 JSON 失败: {str(e)}")
# 如果转换失败,保持原样
# Save to database
self.sudo().create({
'name': name,
'path': url,
'method': method,
'request_data': request_body,
'response_data': response_body,
'remote_addr': None,
'response_time': response_time,
'status': response_status,
'requester': '智能工厂',
'responser': responser
})
return response

View File

@@ -1360,9 +1360,9 @@ class Sf_Dashboard_Connect(http.Controller):
if result[0]: if result[0]:
if float(result[0]) >= 28800: if float(result[0]) >= 28800:
continue continue
alarm_last_24_time += float(result[0]) alarm_last_24_time = float(result[0])
else: else:
alarm_last_24_time += 0.0 alarm_last_24_time = 0.0
alarm_all_nums = [] alarm_all_nums = []
with conn.cursor() as cur: with conn.cursor() as cur:

View File

@@ -261,50 +261,8 @@ def transfer_files(
target_path = f"{target_dir}/{relative_path}/{item}" target_path = f"{target_dir}/{relative_path}/{item}"
else: else:
target_path = f"{target_dir}/{item}" target_path = f"{target_dir}/{item}"
with open(temp_path, 'rb') as f:
# 规范化路径 target_ftp.ftp.storbinary(f'STOR {target_path}', f)
target_path = target_path.replace('\\', '/').strip('/')
# 确保目标目录存在
target_dir_path = '/'.join(target_path.split('/')[:-1])
try:
target_ftp.ftp.cwd('/') # 回到根目录
for dir_part in target_dir_path.split('/'):
if dir_part:
try:
target_ftp.ftp.cwd(dir_part)
except:
try:
target_ftp.ftp.mkd(dir_part)
target_ftp.ftp.cwd(dir_part)
except Exception as e:
logging.error(f"创建目录失败 {dir_part}: {str(e)}")
raise
except Exception as e:
logging.error(f"处理目标目录失败: {str(e)}")
raise
# 检查FTP连接状态
try:
target_ftp.ftp.voidcmd('NOOP')
except:
logging.error("FTP连接已断开尝试重新连接")
target_ftp.ftp.connect(target_ftp_info['host'], target_ftp_info['port'])
target_ftp.ftp.login(target_ftp_info['username'], target_ftp_info['password'])
# 上传文件
try:
with open(temp_path, 'rb') as f:
# 检查文件是否可读
content = f.read()
if not content:
raise Exception("临时文件为空")
f.seek(0) # 重置文件指针
target_ftp.ftp.storbinary(f'STOR {target_path}', f)
except Exception as e:
logging.error(f"上传文件失败: {str(e)}")
logging.error(f"目标路径: {target_path}")
raise
transfered_file_list.append(item) transfered_file_list.append(item)
# 删除临时文件 # 删除临时文件
@@ -312,37 +270,37 @@ def transfer_files(
logging.info(f"已传输文件: {item}") logging.info(f"已传输文件: {item}")
# 清空目标目录下的所有内容 # 清空目标目录下的所有内容
# try: try:
# target_ftp.ftp.cwd(target_dir) target_ftp.ftp.cwd(target_dir)
# files = target_ftp.ftp.nlst() files = target_ftp.ftp.nlst()
# for f in files: for f in files:
# try: try:
# # 尝试删除文件 # 尝试删除文件
# target_ftp.ftp.delete(f) target_ftp.ftp.delete(f)
# except: except:
# try: try:
# # 如果删除失败,可能是目录,递归删除目录 # 如果删除失败,可能是目录,递归删除目录
# def remove_dir(path): def remove_dir(path):
# target_ftp.ftp.cwd(path) target_ftp.ftp.cwd(path)
# sub_files = target_ftp.ftp.nlst() sub_files = target_ftp.ftp.nlst()
# for sf in sub_files: for sf in sub_files:
# try: try:
# target_ftp.ftp.delete(sf) target_ftp.ftp.delete(sf)
# except: except:
# remove_dir(f"{path}/{sf}") remove_dir(f"{path}/{sf}")
# target_ftp.ftp.cwd('..') target_ftp.ftp.cwd('..')
# target_ftp.ftp.rmd(path) target_ftp.ftp.rmd(path)
# remove_dir(f"{target_dir}/{f}") remove_dir(f"{target_dir}/{f}")
# except: except:
# logging.error(f"无法删除 {f}") logging.error(f"无法删除 {f}")
# pass pass
# logging.info(f"已清空目标目录 {target_dir}") logging.info(f"已清空目标目录 {target_dir}")
# except Exception as e: except Exception as e:
# logging.error(f"清空目标目录失败: {str(e)}") logging.error(f"清空目标目录失败: {str(e)}")
# raise Exception(f"清空目标目录失败: {str(e)}") raise Exception(f"清空目标目录失败: {str(e)}")
# 开始遍历 # 开始遍历
traverse_dir(source_dir) traverse_dir(source_dir)

View File

@@ -20,7 +20,6 @@
'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', 'data/product_data.xml',
'data/automation_folder_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',
@@ -49,7 +48,6 @@
'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', 'views/product_template_views.xml',
'views/stock_warehouse_orderpoint.xml',
], ],
'assets': { 'assets': {

View File

@@ -2,20 +2,16 @@
import logging import logging
import json import json
from datetime import datetime from datetime import datetime
import base64
from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException from odoo.addons.sf_manufacturing.models.agv_scheduling import RepeatTaskException
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.exceptions import MissingError
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller): class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/GetWoInfo', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('获取工单', requester='中控系统')
def get_Work_Info(self, **kw): def get_Work_Info(self, **kw):
""" """
自动化传递工单号获取工单信息 自动化传递工单号获取工单信息
@@ -56,9 +52,8 @@ class Manufacturing_Connect(http.Controller):
logging.info('get_Work_Info error:%s' % e) logging.info('get_Work_Info error:%s' % e)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/GetShiftPlan', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('获取日计划', requester='中控系统')
def get_ShiftPlan(self, **kw): def get_ShiftPlan(self, **kw):
""" """
自动化每天获取机台日计划 自动化每天获取机台日计划
@@ -110,9 +105,8 @@ class Manufacturing_Connect(http.Controller):
logging.info('get_ShiftPlan error:%s' % e) logging.info('get_ShiftPlan error:%s' % e)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
@http.route('/AutoDeviceApi/QcCheck', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/QcCheck', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工件预调(前置三元检测)', requester='中控系统')
def get_qcCheck(self, **kw): def get_qcCheck(self, **kw):
""" """
工件预调(前置三元检测) 工件预调(前置三元检测)
@@ -155,7 +149,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/FeedBackStart', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单开始', requester='中控系统')
def button_Work_START(self, **kw): def button_Work_START(self, **kw):
""" """
工单任务开始 工单任务开始
@@ -205,7 +198,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/FeedBackEnd', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('工单结束', requester='中控系统')
def button_Work_End(self, **kw): def button_Work_End(self, **kw):
""" """
工单任务结束 工单任务结束
@@ -257,7 +249,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/PartQualityInspect', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('零件检测(后置三元检测)', requester='中控系统')
def PartQualityInspect(self, **kw): def PartQualityInspect(self, **kw):
""" """
零件质检 零件质检
@@ -304,7 +295,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/CMMProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('CMM测量程序下载', requester='中控系统')
def CMMProgDolod(self, **kw): def CMMProgDolod(self, **kw):
""" """
中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件 中控系统传递RFID编号给MES获取测量程序文件。Ftp下载文件
@@ -345,7 +335,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/NCProgDolod', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('CAM加工程序下载', requester='中控系统')
def NCProgDolod(self, **kw): def NCProgDolod(self, **kw):
""" """
中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件 中控系统传递RFID编号给MES获取程序单及程序文件。Ftp下载文件
@@ -387,7 +376,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/LocationChange', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('库位变更', requester='中控系统')
def LocationChange(self, **kw): def LocationChange(self, **kw):
""" """
库位变更 库位变更
@@ -492,7 +480,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/AGVToProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('AGV运送上产线', requester='中控系统')
def AGVToProduct(self, **kw): def AGVToProduct(self, **kw):
""" """
AGV运送上产线完成 AGV运送上产线完成
@@ -565,7 +552,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/AGVDownProduct', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('AGV运送下产线', requester='中控系统')
def AGVDownProduct(self, **kw): def AGVDownProduct(self, **kw):
""" """
MES调度AGV搬运零件AGV托盘到产线接驳站。 MES调度AGV搬运零件AGV托盘到产线接驳站。
@@ -706,59 +692,3 @@ class Manufacturing_Connect(http.Controller):
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)} res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
logging.info('AGVDownProduct error:%s' % e) logging.info('AGVDownProduct error:%s' % e)
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
@http.route('/api/upload_three_check_data', type='http', auth='public', methods=['POST'], csrf=False, cros='*')
def upload_three_check_data(self):
res = {'Succeed': True, 'ErrorCode': 200, 'Messages': '上传成功'}
uploaded_files = request.httprequest.files.getlist('file')
if uploaded_files:
try:
for uploaded_file in uploaded_files:
file_content = uploaded_file.read()
file_name = uploaded_file.filename
production_name = '/'.join(file_name.split('_')[:-1])
processing_panel = file_name.split('_')[-1].split('.')[0]
# 找到对应的工单
production_id = request.env['mrp.production'].sudo().search([('name', '=', production_name)])
wo = production_id.workorder_ids.filtered(lambda wo: wo.processing_panel == processing_panel and wo.routing_type == '装夹预调')
if not wo:
raise MissingError('工单不存在')
folder_id = request.env.ref('sf_manufacturing.documents_pre_three_element_detection_folder')
document = request.env['documents.document'].sudo().search([('res_model', '=', 'mrp.workorder'), ('res_id', '=', wo.id)])
if document and document.attachment_id:
attachment = request.env['ir.attachment'].sudo().create({
'name': file_name,
'type': 'binary',
'datas': base64.b64encode(file_content),
'res_model': 'mrp.workorder',
'res_id': wo.id,
})
document.write({'attachment_id': attachment.id})
else:
# Create ir.attachment record
attachment = request.env['ir.attachment'].sudo().create({
'name': file_name,
'type': 'binary',
'datas': base64.b64encode(file_content),
'res_model': 'mrp.workorder',
'res_id': wo.id,
})
# 创建 documents.document 记录
request.env['documents.document'].sudo().create({
'name': file_name,
'attachment_id': attachment.id,
'folder_id': folder_id.id,
'res_model': 'mrp.workorder',
'res_id': wo.id,
})
except Exception as e:
res = {'Succeed': False, 'ErrorCode': 202, 'Error': str(e)}
return json.JSONEncoder().encode(res)

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<!-- 创建自动化文件夹 -->
<record id="documents_automation_folder" model="documents.folder">
<field name="name">自动化</field>
<field name="description">存放自动化生产流程相关文件</field>
<field name="sequence">20</field>
</record>
<record id="documents_pre_three_element_detection_folder" model="documents.folder">
<field name="name">前置三元检测报告</field>
<field name="parent_folder_id" ref="documents_automation_folder"/>
<field name="sequence">1</field>
</record>
</data>
</odoo>

View File

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

View File

@@ -928,7 +928,7 @@ class MrpProduction(models.Model):
'sf_stock.stock_route_process_outsourcing').id)] 'sf_stock.stock_route_process_outsourcing').id)]
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items(): for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0] cur_request_line = request_line_list[0]
# cur_request_line['product_qty'] = cur_request_line['product_qty'] cur_request_line['product_qty'] = len(request_line_list)
cur_request_line['request_id'] = pr.id 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['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('group_id', None)

View File

@@ -6,7 +6,6 @@ import urllib.parse
from datetime import date from datetime import date
from datetime import datetime, timedelta from datetime import datetime, timedelta
import requests import requests
import tempfile
import os import os
import math import math
from lxml import etree from lxml import etree
@@ -86,7 +85,6 @@ class ResMrpWorkOrder(models.Model):
item.pr_mp_count = len(pr_ids) item.pr_mp_count = len(pr_ids)
else: else:
item.pr_mp_count = 0 item.pr_mp_count = 0
@api.depends('state') @api.depends('state')
def _compute_back_button_display(self): def _compute_back_button_display(self):
for record in self: for record in self:
@@ -486,7 +484,6 @@ class ResMrpWorkOrder(models.Model):
'view_mode': 'tree,form', 'view_mode': 'tree,form',
}) })
return action return action
def action_view_surface_technics_purchase(self): def action_view_surface_technics_purchase(self):
self.ensure_one() self.ensure_one()
# if self.routing_type == '表面工艺': # if self.routing_type == '表面工艺':
@@ -515,11 +512,10 @@ class ResMrpWorkOrder(models.Model):
return result return result
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')]
('state', '!=', 'cancel')]
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
# domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')] # domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc') 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:
@@ -772,170 +768,146 @@ class ResMrpWorkOrder(models.Model):
# 获取三次元检测点数据 # 获取三次元检测点数据
def get_three_check_datas(self): def get_three_check_datas(self):
ftp_resconfig = self.env['res.config.settings'].get_values() ftp_resconfig = self.env['res.config.settings'].get_values()
# ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']), ftp = FtpController(str(ftp_resconfig['ftp_host']), int(ftp_resconfig['ftp_port']),
# ftp_resconfig['ftp_user'], ftp_resconfig['ftp_user'],
# ftp_resconfig['ftp_password']) ftp_resconfig['ftp_password'])
# local_dir_path = '/ftp/before' local_dir_path = '/ftp/before'
# os.makedirs(local_dir_path, exist_ok=True) os.makedirs(local_dir_path, exist_ok=True)
local_filename = self.save_name + '.xls' local_filename = self.save_name + '.xls'
# local_file_path = os.path.join(local_dir_path, local_filename) local_file_path = os.path.join(local_dir_path, local_filename)
# logging.info('local_file_path:%s' % local_file_path) logging.info('local_file_path:%s' % local_file_path)
# # remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename # remote_path = '/home/ftp/ftp_root/ThreeTest/XT/Before/' + local_filename
# remote_path = '/ThreeTest/XT/Before/' + local_filename remote_path = '/ThreeTest/XT/Before/' + local_filename
# logging.info('remote_path:%s' % remote_path) logging.info('remote_path:%s' % remote_path)
# is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file') is_get_detection_file = self.env['ir.config_parameter'].sudo().get_param('is_get_detection_file')
# if not is_get_detection_file: if not is_get_detection_file:
base_url = self.env['ir.config_parameter'].sudo().get_param('web.base.url') paload_data = {
paload_data = { "filename": local_filename
"filename": local_filename, }
"sf_host": base_url if not ftp_resconfig['get_check_file_path']:
} raise UserError('请先配置获取检测报告地址')
if not ftp_resconfig['get_check_file_path']: url = ftp_resconfig['get_check_file_path'] + '/get/check/report'
raise UserError('请先配置获取检测报告地址') response = requests.post(url, json=paload_data)
url = ftp_resconfig['get_check_file_path'] + '/get/check/report' logging.info('response:%s' % response.json())
response = requests.post(url, json=paload_data) if response.json().get('detail'):
# logging.info('response:%s' % response.json()) raise UserError(response.json().get('detail'))
# if response.json().get('detail'):
# raise UserError(response.json().get('detail'))
# if not ftp.file_exists(remote_path): if not ftp.file_exists(remote_path):
# raise UserError(f"文件不存在: {remote_path}") raise UserError(f"文件不存在: {remote_path}")
# with open(local_file_path, 'wb') as local_file: with open(local_file_path, 'wb') as local_file:
# ftp.ftp.retrbinary('RETR ' + remote_path, local_file.write) ftp.ftp.retrbinary('RETR ' + remote_path, local_file.write)
# logging.info('下载文件成功') logging.info('下载文件成功')
# 解析本地文件
document = self.env['documents.document'].sudo().search([('res_model', '=', 'mrp.workorder'), ('res_id', '=', self.id)]) # file_path = 'WH_MO_00099.xls' # 使用下载的实际文件路径
if not document: parser = etree.XMLParser(recover=True) # Using recover to handle errors
raise UserError(f"未获取到检测数据") tree = etree.parse(local_file_path, parser)
binary_data = base64.b64decode(document.attachment_id.datas) logging.info('tree:%s' % tree)
root = tree.getroot()
logging.info('root:%s' % root)
# 创建临时文件保存响应内容 # 准备一个外部字典来存储以PT为键的坐标字典
with tempfile.NamedTemporaryFile(delete=False, suffix='.xls') as temp_file: pt_coordinates = {}
temp_file.write(binary_data) # 遍历每个工作表和行
temp_file_path = temp_file.name for worksheet in root.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Worksheet'):
sheet_name = worksheet.attrib.get('{urn:schemas-microsoft-com:office:spreadsheet}Name')
try: logging.info('sheet_name:%s' % sheet_name)
# 使用临时文件进行解析 if sheet_name == "Sheet1": # 确保我们只查看包含数据的工作表
parser = etree.XMLParser(recover=True) current_pt = None
tree = etree.parse(temp_file_path, parser) for row in worksheet.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Row'):
logging.info('tree:%s' % tree) cells = list(row.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Cell'))
root = tree.getroot() for i, cell in enumerate(cells):
logging.info('root:%s' % root) data_cell = cell.find('.//{urn:schemas-microsoft-com:office:spreadsheet}Data')
if data_cell is not None and data_cell.text is not None: # 添加检查以确保data_cell.text不为空
# 检查是否是PT标识
logging.info(f"Data in cell: {data_cell.text}") # 输出单元格数据
if "PT" in data_cell.text:
current_pt = data_cell.text
pt_coordinates[current_pt] = []
elif data_cell.text in ["X", "Y", "Z"] and current_pt is not None:
# 确保当前单元格后面还有单元格存在,以获取理论值
if i + 1 < len(cells):
next_cell = cells[i + 1]
theory_value = next_cell.find(
'.//{urn:schemas-microsoft-com:office:spreadsheet}Data')
if theory_value is not None:
# 为当前PT键添加坐标数据
pt_coordinates[current_pt].append({
data_cell.text: float(theory_value.text)
})
logging.info(f"PT: {current_pt} - {data_cell.text}: {theory_value.text}")
logging.info('pt_coordinates=====%s' % pt_coordinates)
# pt_coordinates:{'PT1': [{'X': 38.9221}, {'Y': -18.7304}, {'Z': 128.0783}],
# 'PT2': [{'X': 39.2456}, {'Y': -76.9169}, {'Z': 123.7541}]}
# 准备一个外部字典来存储以PT为键的坐标字典 # 检查是否存在PT1等键
pt_coordinates = {} if 'PT1' in pt_coordinates and pt_coordinates['PT1']:
# 遍历每个工作表和行 self.X1_axis = pt_coordinates['PT3'][0]['X']
for worksheet in root.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Worksheet'): self.Y1_axis = pt_coordinates['PT3'][1]['Y']
sheet_name = worksheet.attrib.get('{urn:schemas-microsoft-com:office:spreadsheet}Name') self.Z1_axis = pt_coordinates['PT3'][2]['Z']
logging.info('sheet_name:%s' % sheet_name) else:
if sheet_name == "Sheet1": # 确保我们只查看包含数据的工作表 raise UserError('PT1点未测或数据错误')
current_pt = None if 'PT2' in pt_coordinates and pt_coordinates['PT2']:
for row in worksheet.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Row'): self.X2_axis = pt_coordinates['PT4'][0]['X']
cells = list(row.iterfind('.//{urn:schemas-microsoft-com:office:spreadsheet}Cell')) self.Y2_axis = pt_coordinates['PT4'][1]['Y']
for i, cell in enumerate(cells): self.Z2_axis = pt_coordinates['PT4'][2]['Z']
data_cell = cell.find('.//{urn:schemas-microsoft-com:office:spreadsheet}Data') else:
if data_cell is not None and data_cell.text is not None: # 添加检查以确保data_cell.text不为空 raise UserError('PT2点未测或数据错误')
# 检查是否是PT标识 if 'PT3' in pt_coordinates and pt_coordinates['PT3']:
logging.info(f"Data in cell: {data_cell.text}") # 输出单元格数据 self.X3_axis = pt_coordinates['PT5'][0]['X']
if "PT" in data_cell.text: self.Y3_axis = pt_coordinates['PT5'][1]['Y']
current_pt = data_cell.text self.Z3_axis = pt_coordinates['PT5'][2]['Z']
pt_coordinates[current_pt] = [] else:
elif data_cell.text in ["X", "Y", "Z"] and current_pt is not None: raise UserError('PT3点未测或数据错误')
# 确保当前单元格后面还有单元格存在,以获取理论值 if 'PT4' in pt_coordinates and pt_coordinates['PT4']:
if i + 1 < len(cells): self.X4_axis = pt_coordinates['PT6'][0]['X']
next_cell = cells[i + 1] self.Y4_axis = pt_coordinates['PT6'][1]['Y']
theory_value = next_cell.find( self.Z4_axis = pt_coordinates['PT6'][2]['Z']
'.//{urn:schemas-microsoft-com:office:spreadsheet}Data') else:
if theory_value is not None: raise UserError('PT4点未测或数据错误')
# 为当前PT键添加坐标数据 if 'PT5' in pt_coordinates and pt_coordinates['PT5']:
pt_coordinates[current_pt].append({ self.X5_axis = pt_coordinates['PT7'][0]['X']
data_cell.text: float(theory_value.text) self.Y5_axis = pt_coordinates['PT7'][1]['Y']
}) self.Z5_axis = pt_coordinates['PT7'][2]['Z']
logging.info(f"PT: {current_pt} - {data_cell.text}: {theory_value.text}") else:
logging.info('pt_coordinates=====%s' % pt_coordinates) raise UserError('PT5点未测或数据错误')
# pt_coordinates:{'PT1': [{'X': 38.9221}, {'Y': -18.7304}, {'Z': 128.0783}], if 'PT6' in pt_coordinates and pt_coordinates['PT6']:
# 'PT2': [{'X': 39.2456}, {'Y': -76.9169}, {'Z': 123.7541}]} self.X6_axis = pt_coordinates['PT8'][0]['X']
self.Y6_axis = pt_coordinates['PT8'][1]['Y']
self.Z6_axis = pt_coordinates['PT8'][2]['Z']
else:
raise UserError('PT6点未测或数据错误')
if 'PT7' in pt_coordinates and pt_coordinates['PT7']:
self.X7_axis = pt_coordinates['PT9'][0]['X']
self.Y7_axis = pt_coordinates['PT9'][1]['Y']
self.Z7_axis = pt_coordinates['PT9'][2]['Z']
else:
raise UserError('PT7点未测或数据错误')
if 'PT8' in pt_coordinates and pt_coordinates['PT8']:
self.X8_axis = pt_coordinates['PT10'][0]['X']
self.Y8_axis = pt_coordinates['PT10'][1]['Y']
self.Z8_axis = pt_coordinates['PT10'][2]['Z']
else:
raise UserError('PT8点未测或数据错误')
if 'PT9' in pt_coordinates and pt_coordinates['PT9']:
self.X9_axis = pt_coordinates['PT1'][0]['X']
self.Y9_axis = pt_coordinates['PT1'][1]['Y']
self.Z9_axis = pt_coordinates['PT1'][2]['Z']
else:
raise UserError('PT9点未测或数据错误')
if 'PT10' in pt_coordinates and pt_coordinates['PT10']:
self.X10_axis = pt_coordinates['PT2'][0]['X']
self.Y10_axis = pt_coordinates['PT2'][1]['Y']
self.Z10_axis = pt_coordinates['PT2'][2]['Z']
else:
raise UserError('PT10点未测或数据错误')
# 检查是否存在PT1等键 self.data_state = True
if 'PT1' in pt_coordinates and pt_coordinates['PT1']: self.getcenter()
self.X1_axis = pt_coordinates['PT3'][0]['X']
self.Y1_axis = pt_coordinates['PT3'][1]['Y']
self.Z1_axis = pt_coordinates['PT3'][2]['Z']
else:
raise UserError('PT1点未测或数据错误')
if 'PT2' in pt_coordinates and pt_coordinates['PT2']:
self.X2_axis = pt_coordinates['PT4'][0]['X']
self.Y2_axis = pt_coordinates['PT4'][1]['Y']
self.Z2_axis = pt_coordinates['PT4'][2]['Z']
else:
raise UserError('PT2点未测或数据错误')
if 'PT3' in pt_coordinates and pt_coordinates['PT3']:
self.X3_axis = pt_coordinates['PT5'][0]['X']
self.Y3_axis = pt_coordinates['PT5'][1]['Y']
self.Z3_axis = pt_coordinates['PT5'][2]['Z']
else:
raise UserError('PT3点未测或数据错误')
if 'PT4' in pt_coordinates and pt_coordinates['PT4']:
self.X4_axis = pt_coordinates['PT6'][0]['X']
self.Y4_axis = pt_coordinates['PT6'][1]['Y']
self.Z4_axis = pt_coordinates['PT6'][2]['Z']
else:
raise UserError('PT4点未测或数据错误')
if 'PT5' in pt_coordinates and pt_coordinates['PT5']:
self.X5_axis = pt_coordinates['PT7'][0]['X']
self.Y5_axis = pt_coordinates['PT7'][1]['Y']
self.Z5_axis = pt_coordinates['PT7'][2]['Z']
else:
raise UserError('PT5点未测或数据错误')
if 'PT6' in pt_coordinates and pt_coordinates['PT6']:
self.X6_axis = pt_coordinates['PT8'][0]['X']
self.Y6_axis = pt_coordinates['PT8'][1]['Y']
self.Z6_axis = pt_coordinates['PT8'][2]['Z']
else:
raise UserError('PT6点未测或数据错误')
if 'PT7' in pt_coordinates and pt_coordinates['PT7']:
self.X7_axis = pt_coordinates['PT9'][0]['X']
self.Y7_axis = pt_coordinates['PT9'][1]['Y']
self.Z7_axis = pt_coordinates['PT9'][2]['Z']
else:
raise UserError('PT7点未测或数据错误')
if 'PT8' in pt_coordinates and pt_coordinates['PT8']:
self.X8_axis = pt_coordinates['PT10'][0]['X']
self.Y8_axis = pt_coordinates['PT10'][1]['Y']
self.Z8_axis = pt_coordinates['PT10'][2]['Z']
else:
raise UserError('PT8点未测或数据错误')
if 'PT9' in pt_coordinates and pt_coordinates['PT9']:
self.X9_axis = pt_coordinates['PT1'][0]['X']
self.Y9_axis = pt_coordinates['PT1'][1]['Y']
self.Z9_axis = pt_coordinates['PT1'][2]['Z']
else:
raise UserError('PT9点未测或数据错误')
if 'PT10' in pt_coordinates and pt_coordinates['PT10']:
self.X10_axis = pt_coordinates['PT2'][0]['X']
self.Y10_axis = pt_coordinates['PT2'][1]['Y']
self.Z10_axis = pt_coordinates['PT2'][2]['Z']
else:
raise UserError('PT10点未测或数据错误')
self.data_state = True return True
self.getcenter()
return True
except Exception as e:
logging.error('解析文件失败: %s' % str(e))
raise UserError(f'解析文件失败: {str(e)}')
finally:
# 清理临时文件
try:
os.unlink(temp_file_path)
except Exception as e:
logging.warning(f'清理临时文件失败: {str(e)}')
# ftp.download_file('three_check_datas.xls', '/home/ftpuser/three_check_datas.xls') # ftp.download_file('three_check_datas.xls', '/home/ftpuser/three_check_datas.xls')
# ftp.close() # ftp.close()
# data = xlrd.open_workbook('/home/ftpuser/three_check_datas.xls') # data = xlrd.open_workbook('/home/ftpuser/three_check_datas.xls')
@@ -1270,13 +1242,6 @@ class ResMrpWorkOrder(models.Model):
}] }]
return workorders_values_str return workorders_values_str
def check_lot_exists(self, picking_id, lot_id):
return bool(
picking_id.move_ids.move_line_ids.filtered(
lambda line: line.lot_id.id == lot_id
)
)
def _process_compute_state(self): def _process_compute_state(self):
sorted_workorders = sorted(self, key=lambda x: x.sequence) sorted_workorders = sorted(self, key=lambda x: x.sequence)
for workorder in sorted_workorders: for workorder in sorted_workorders:
@@ -1298,17 +1263,10 @@ class ResMrpWorkOrder(models.Model):
workorder.state = 'pending' workorder.state = 'pending'
continue continue
# ================= 如果制造订单制造类型为【人工线下加工】========================== # ================= 如果制造订单制造类型为【人工线下加工】==========================
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
# exists = any(
# move_line.lot_id == lot_id
# for picking in picking_ids
# for move in picking.move_ids
# for move_line in move.move_line_ids
# )
if (workorder.production_id.production_type == '人工线下加工' if (workorder.production_id.production_type == '人工线下加工'
and workorder.production_id.schedule_state == '已排'): 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.is_subcontract is True:
if workorder.production_id.state == 'rework': if workorder.production_id.state == 'rework':
@@ -1317,10 +1275,7 @@ class ResMrpWorkOrder(models.Model):
purchase_orders_id = self._get_surface_technics_purchase_ids() purchase_orders_id = self._get_surface_technics_purchase_ids()
if purchase_orders_id.state == 'purchase': if purchase_orders_id.state == 'purchase':
workorder.state = 'ready' workorder.state = 'ready'
picking_id = workorder.production_id.picking_ids.filtered( move_out = workorder.move_subcontract_workorder_ids[1]
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
# move_out = workorder.move_subcontract_workorder_ids[1]
for mo in move_out: for mo in move_out:
if mo.state != 'done': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
@@ -1359,10 +1314,7 @@ class ResMrpWorkOrder(models.Model):
if purchase_orders_id: if purchase_orders_id:
if purchase_orders_id.state == 'purchase': if purchase_orders_id.state == 'purchase':
workorder.state = 'ready' workorder.state = 'ready'
picking_id = workorder.production_id.picking_ids.filtered( move_out = workorder.move_subcontract_workorder_ids[1]
lambda
wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
for mo in move_out: for mo in move_out:
if mo.state != 'done': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
@@ -1372,6 +1324,7 @@ class ResMrpWorkOrder(models.Model):
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')
@@ -1403,11 +1356,11 @@ class ResMrpWorkOrder(models.Model):
# 判断是否有坯料的序列号信息 # 判断是否有坯料的序列号信息
boolean = False boolean = False
if self.production_id.move_raw_ids: if self.production_id.move_raw_ids:
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and \ if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料':
self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].move_line_ids: if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name: if self.production_id.move_raw_ids[0].move_line_ids:
boolean = True if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
else: else:
boolean = True boolean = True
if not boolean: if not boolean:
@@ -1436,10 +1389,7 @@ class ResMrpWorkOrder(models.Model):
# 表面工艺外协出库单 # 表面工艺外协出库单
if self.routing_type == '表面工艺': if self.routing_type == '表面工艺':
if self.is_subcontract is True: if self.is_subcontract is True:
picking_id = self.production_id.picking_ids.filtered( move_out = self.move_subcontract_workorder_ids[1]
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids
# move_out = self.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search( # move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search( # [('location_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id), # [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
@@ -1858,7 +1808,7 @@ class ResMrpWorkOrder(models.Model):
orderby=orderby, orderby=orderby,
lazy=lazy lazy=lazy
) )
model_id = fields.Char('模型ID', related='production_id.model_id') model_id = fields.Char('模型ID', related='production_id.model_id')

View File

@@ -1030,7 +1030,6 @@ class ResProductMo(models.Model):
'single_manufacturing': product_id.single_manufacturing, 'single_manufacturing': product_id.single_manufacturing,
'is_bfm': True, 'is_bfm': True,
'active': True, 'active': True,
'tracking': finish_product.tracking, # 坯料的跟踪方式跟随成品
} }
# 外协和采购生成的坯料需要根据材料型号绑定供应商 # 外协和采购生成的坯料需要根据材料型号绑定供应商
if route_type == 'subcontract' or route_type == 'purchase': if route_type == 'subcontract' or route_type == 'purchase':

View File

@@ -113,13 +113,13 @@ class PurchaseOrder(models.Model):
# production.bom_id.bom_line_ids.product_id # production.bom_id.bom_line_ids.product_id
location_id = self.env['stock.location'].search([('name', '=', '制造前')]) location_id = self.env['stock.location'].search([('name', '=', '制造前')])
quants = self.env['stock.quant'].search([ quants = self.env['stock.quant'].search([
('product_id', '=', productions.bom_id.bom_line_ids.product_id.id), ('product_id', '=', product_id),
('location_id', '=', location_id.id) ('location_id', '=', location_id.id)
]) ])
total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量 total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量
is_available = total_qty > 0 is_available = total_qty > 0
if not is_available: if not is_available:
raise UserError('请先完成坯料入库') continue
for production_id in productions: for production_id in productions:
work_ids = production_id.workorder_ids.filtered( work_ids = production_id.workorder_ids.filtered(
lambda wk: wk.state not in ['done', 'rework', 'cancel']) lambda wk: wk.state not in ['done', 'rework', 'cancel'])
@@ -129,7 +129,7 @@ class PurchaseOrder(models.Model):
if min_sequence_wk.is_subcontract: if min_sequence_wk.is_subcontract:
picking_id = production_id.picking_ids.filtered( picking_id = production_id.picking_ids.filtered(
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
move_out = picking_id.move_ids move_out = picking_id.move_id
for mo in move_out: for mo in move_out:
if mo.state != 'done': if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False}) mo.write({'state': 'assigned', 'production_id': False})
@@ -146,15 +146,8 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入') raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0: if line.price_unit <= 0:
raise UserError('请对【产品】中的【单价】进行输入') raise UserError('请对【产品】中的【单价】进行输入')
record.outsourcing_service_replenishment() record.outsourcing_service_replenishment()
return super(PurchaseOrder, self).button_confirm()
res = super(PurchaseOrder, self).button_confirm()
for line in self.order_line:
# 将产品不追踪序列号的行项目设置qty_done
if line.move_ids and line.move_ids[0].product_id.tracking == 'none':
line.move_ids[0].quantity_done = line.move_ids[0].product_qty
return res
origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id') origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id')

View File

@@ -56,10 +56,9 @@ class SaleOrder(models.Model):
'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo() 'jikimo_sale_multiple_supply_methods.product_template_manual_processing').sudo()
# 复制成品模板上的属性 # 复制成品模板上的属性
line.product_id.copy_template(product_template_id) line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上 # 将模板上的single_manufacturing属性复制到成品上
# line.product_id.single_manufacturing = product_template_id.single_manufacturing line.product_id.single_manufacturing = product_template_id.single_manufacturing
# line.product_id.tracking = product_template_id.tracking
order_id = self order_id = self
product = line.product_id product = line.product_id
@@ -76,7 +75,7 @@ class SaleOrder(models.Model):
'embryo_redundancy_id': line.embryo_redundancy_id, 'embryo_redundancy_id': line.embryo_redundancy_id,
} }
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d*)', product.name) match = re.search(r'(S\d{5}-\d)', product.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

@@ -2,7 +2,7 @@
import logging import logging
from odoo import fields, models, api from odoo import fields, models, api
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
# from odoo.tools import str2bool from odoo.tools import str2bool
class SfProductionProcessParameter(models.Model): class SfProductionProcessParameter(models.Model):
@@ -25,7 +25,7 @@ class SfProductionProcessParameter(models.Model):
def _compute_service_products(self): def _compute_service_products(self):
for record in self: for record in self:
# 假设取第一条作为主明细 # 假设取第一条作为主明细
record.service_products = record.outsourced_service_products.ids if record.outsourced_service_products else False record.service_products = record.outsourced_service_products.id if record.outsourced_service_products else False
def _inverse_service_products(self): def _inverse_service_products(self):
for record in self: for record in self:

View File

@@ -631,62 +631,28 @@ class StockPicking(models.Model):
move.action_clear_lines_show_details() move.action_clear_lines_show_details()
move.action_show_details() move.action_show_details()
res = super().button_validate() res = super().button_validate()
lot_ids = None picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id
product_ids = self.move_ids.mapped('product_id') if res is True and self.picking_type_id.id == picking_type_in:
if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none':
lot_ids = self.move_ids.move_line_ids.mapped('lot_id')
production_ids = self.sale_order_id.mrp_production_ids if self.sale_order_id else self.env['mrp.production']
if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# 如果是最后一张外协入库单,则设置库存位置的预留数量 # 如果是最后一张外协入库单,则设置库存位置的预留数量
for production_id in production_ids: move_in = self.move_ids
if lot_ids: if move_in:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id workorder = move_in.subcontract_workorder_id
# picking_ids = production_id.picking_ids.filtered( workorders = workorder.production_id.workorder_ids
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') subcontract_workorders = workorders.filtered(
if lot_id in lot_ids: lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
workorder_id = production_id.workorder_ids.filtered( # if workorder == subcontract_workorders[-1]:
lambda a: a.state == 'progress' and a.is_subcontract) # self.env['stock.quant']._update_reserved_quantity(
if not workorder_id: # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
continue # lot_id=move_in.move_line_ids.lot_id,
workorder_id.button_finish() # package_id=False, owner_id=False, strict=False
else: # )
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract) workorder.button_finish()
if not workorder_id: picking_type_out = self.env.ref('sf_manufacturing.outcontract_picking_out').id
continue if res and self.picking_type_id.id == picking_type_out:
workorder_id.button_finish() move_out = self.move_ids
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id if move_out:
# picking_ids = workorder.production_id.picking_ids.filtered( workorder = move_out.subcontract_workorder_id
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前') workorder.button_start()
# if move_in:
# workorder = move_in.subcontract_workorder_id
# workorders = workorder.production_id.workorder_ids
# subcontract_workorders = workorders.filtered(
# lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # if workorder == subcontract_workorders[-1]:
# # self.env['stock.quant']._update_reserved_quantity(
# # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # lot_id=move_in.move_line_ids.lot_id,
# # package_id=False, owner_id=False, strict=False
# # )
# workorder.button_finish()
if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
for production_id in production_ids:
if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered(
# lambda wk: wk.location_id.name == '外协收料区' and wk.location_dest_id.name == '制造前')
if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_finish()
else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
if not workorder_id:
continue
workorder_id.button_start()
if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户': if self.location_id.name == '成品存货区' and self.location_dest_id.name == '客户':
sale_id = self.env['sale.order'].sudo().search( sale_id = self.env['sale.order'].sudo().search(
[('name', '=', self.origin)]) [('name', '=', self.origin)])
@@ -709,7 +675,6 @@ class StockPicking(models.Model):
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id
for workorder in workorders: for workorder in workorders:
if workorder.move_subcontract_workorder_ids: if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'cancel'}) workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
@@ -741,7 +706,7 @@ class StockPicking(models.Model):
}) })
moves_in = self.env['stock.move'].sudo().with_context(context).create( moves_in = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
procurement_group_id.id, move_dest_id, production.product_uom_qty)) procurement_group_id.id, move_dest_id))
picking_in = self.create( picking_in = self.create(
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/')) moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
# pick_ids.append(picking_in.id) # pick_ids.append(picking_in.id)
@@ -751,7 +716,7 @@ class StockPicking(models.Model):
# self.env.context.get('default_production_id') # self.env.context.get('default_production_id')
moves_out = self.env['stock.move'].sudo().with_context(context).create( moves_out = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
procurement_group_id.id, moves_in.id, production.product_uom_qty)) procurement_group_id.id, moves_in.id))
workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]}) workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]})
picking_out = self.create( picking_out = self.create(
moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/')) moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/'))
@@ -883,7 +848,7 @@ class ReStockMove(models.Model):
traceback_error = traceback.format_exc() traceback_error = traceback.format_exc()
logging.error("零件图号 零件名称获取失败:%s" % traceback_error) logging.error("零件图号 零件名称获取失败:%s" % traceback_error)
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False, product_uom_qty=1.0): def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False):
route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id
stock_rule = self.env['stock.rule'].sudo().search( stock_rule = self.env['stock.rule'].sudo().search(
[('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)]) [('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)])
@@ -892,7 +857,7 @@ class ReStockMove(models.Model):
'company_id': item.company_id.id, 'company_id': item.company_id.id,
'product_id': item.bom_id.bom_line_ids.product_id.id, 'product_id': item.bom_id.bom_line_ids.product_id.id,
'product_uom': item.bom_id.bom_line_ids.product_uom_id.id, 'product_uom': item.bom_id.bom_line_ids.product_uom_id.id,
'product_uom_qty': product_uom_qty, 'product_uom_qty': 1.0,
'location_id': stock_rule.location_src_id.id, 'location_id': stock_rule.location_src_id.id,
'location_dest_id': stock_rule.location_dest_id.id, 'location_dest_id': stock_rule.location_dest_id.id,
'origin': item.name, 'origin': item.name,

View File

@@ -1,11 +0,0 @@
# -*- coding: utf-8 -*-
# Part of SmartGo. See LICENSE file for full copyright and licensing details.
import base64
from io import BytesIO
from odoo import api, fields, models, SUPERUSER_ID, _
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
origin = fields.Char(string='来源')
_order = 'create_date DESC'

View File

@@ -4,32 +4,30 @@
name="Manufacturing" name="Manufacturing"
groups="mrp.group_mrp_user,mrp.group_mrp_manager,sf_base.group_sf_mrp_user,sf_base.group_sf_mrp_manager" groups="mrp.group_mrp_user,mrp.group_mrp_manager,sf_base.group_sf_mrp_user,sf_base.group_sf_mrp_manager"
web_icon="mrp,static/description/icon.svg" web_icon="mrp,static/description/icon.svg"
sequence="145"/> sequence="145">
<menuitem id="mrp.menu_mrp_manufacturing" <menuitem id="mrp.menu_mrp_manufacturing"
name="Operations" name="Operations"
parent="mrp.menu_mrp_root" sequence="10"/>
sequence="10"/>
<menuitem id="mrp.mrp_planning_menu_root" <menuitem id="mrp.mrp_planning_menu_root"
name="Planning" name="Planning"
parent="mrp.menu_mrp_root" sequence="15"/>
sequence="15"/>
<menuitem id="mrp.menu_mrp_bom" <menuitem id="mrp.enu_mrp_bom"
name="Products" name="Products"
parent="mrp.menu_mrp_root" sequence="20"/>
sequence="20"/>
<menuitem id="mrp.menu_mrp_reporting" <menuitem id="mrp.menu_mrp_reporting"
name="Reporting" name="Reporting"
parent="mrp.menu_mrp_root" sequence="25"/>
sequence="25"/>
<menuitem id="mrp.menu_mrp_configuration"
name="Configuration"
groups="mrp.group_mrp_manager,sf_base.group_sf_mrp_manager"
sequence="100"/>
</menuitem>
<menuitem id="mrp.menu_mrp_configuration"
name="Configuration"
parent="mrp.menu_mrp_root"
groups="mrp.group_mrp_manager,sf_base.group_sf_mrp_manager"
sequence="100"/>
</odoo> </odoo>

View File

@@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<record id="view_warehouse_orderpoint_tree_editable_inherit" model="ir.ui.view">
<field name="name">补货</field>
<field name="model">stock.warehouse.orderpoint</field>
<field name="inherit_id" ref="stock.view_warehouse_orderpoint_tree_editable"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='qty_to_order']" position="after">
<field name="origin"/>
</xpath>
</field>
</record>
<!-- 继承补货单的搜索视图 -->
</data>
</odoo>

View File

@@ -213,11 +213,11 @@ class ReworkWizard(models.TransientModel):
self.production_id.get_new_program(panel_name) self.production_id.get_new_program(panel_name)
if self.reprogramming_num >= 0 and self.programming_state == '已下发': if self.reprogramming_num >= 0 and self.programming_state == '已下发':
# ============= 处理CNC加工加工工单的 CNC程序和cmm程序 信息============= # ============= 处理CNC加工加工工单的 CNC程序和cmm程序 信息=============
for cnc_work in new_work_ids.filtered(lambda wk: wk.name == 'CNC加工' or wk.name == '人工线下加工'): for cnc_work in new_work_ids.filtered(lambda wk: wk.name == 'CNC加工'):
ret = {'programming_list': []} ret = {'programming_list': []}
old_cnc_rework = max(self.production_id.workorder_ids.filtered( old_cnc_rework = max(self.production_id.workorder_ids.filtered(
lambda crw: crw.processing_panel == cnc_work.processing_panel lambda crw: crw.processing_panel == cnc_work.processing_panel
and crw.state == 'rework' and (crw.routing_type == 'CNC加工' or crw.routing_type == '人工线下加工')), and crw.state == 'rework' and crw.routing_type == 'CNC加工'),
key=lambda w: w.create_date key=lambda w: w.create_date
) )
# 获取当前工单的CNC程序和cmm程序 # 获取当前工单的CNC程序和cmm程序
@@ -259,7 +259,7 @@ class ReworkWizard(models.TransientModel):
new_cnc_workorder = self.production_id.workorder_ids.filtered( new_cnc_workorder = self.production_id.workorder_ids.filtered(
lambda ap1: ap1.processing_panel == cnc_work.processing_panel lambda ap1: ap1.processing_panel == cnc_work.processing_panel
and ap1.state not in ( and ap1.state not in (
'rework', 'done') and (ap1.routing_type == 'CNC加工' or ap1.routing_type == '人工线下加工') 'rework', 'done') and ap1.routing_type == 'CNC加工'
) )
if not new_cnc_workorder.cnc_ids: if not new_cnc_workorder.cnc_ids:
new_cnc_workorder.write({ new_cnc_workorder.write({
@@ -268,8 +268,6 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program( 'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(
cnc_work.processing_panel, ret), cnc_work.processing_panel, ret),
'cnc_worksheet': old_cnc_rework.cnc_worksheet}) 'cnc_worksheet': old_cnc_rework.cnc_worksheet})
# 复制装夹图纸
new_cnc_workorder.processing_drawing = old_cnc_rework.processing_drawing
# ========== 处理装夹预调 【装夹图纸】 数据 ================ # ========== 处理装夹预调 【装夹图纸】 数据 ================
for new_pre_work in new_pre_workorder_ids: for new_pre_work in new_pre_workorder_ids:
pre_rework = max(self.production_id.workorder_ids.filtered( pre_rework = max(self.production_id.workorder_ids.filtered(
@@ -305,22 +303,18 @@ class ReworkWizard(models.TransientModel):
@api.onchange('production_id') @api.onchange('production_id')
def onchange_processing_panel_id(self): def onchange_processing_panel_id(self):
for item in self: for item in self:
panel_ids = []
domain = [('id', '=', False)] domain = [('id', '=', False)]
production_id = item.production_id production_id = item.production_id
if production_id: if production_id:
if self.env.user.has_group('sf_base.group_sf_order_user'): if self.env.user.has_group('sf_base.group_sf_order_user'):
panel_ids = []
panel_arr = production_id.product_id.model_processing_panel panel_arr = production_id.product_id.model_processing_panel
if panel_arr is False: if panel_arr is False:
break break
for p in production_id.detection_result_ids.filtered( for p in production_id.detection_result_ids.filtered(
lambda ap1: ap1.handle_result == '待处理'): lambda ap1: ap1.handle_result == '待处理'):
if p.processing_panel is not False and p.processing_panel not in panel_arr: if p.processing_panel is not False and p.processing_panel not in panel_arr:
if len(panel_arr)>0: panel_arr += ','.join(p.processing_panel)
panel_arr += ','.join(p.processing_panel)
else:
panel_arr = p.processing_panel
for item in panel_arr.split(','): for item in panel_arr.split(','):
panel = self.env['sf.processing.panel'].search( panel = self.env['sf.processing.panel'].search(
[('name', 'ilike', item)]) [('name', 'ilike', item)])

View File

@@ -95,7 +95,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
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, "模型ID%s" % model_id) 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'

View File

@@ -1135,6 +1135,8 @@ 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:
production_process_parameter = self.create({ production_process_parameter = self.create({
"name": item['name'], "name": item['name'],

View File

@@ -228,7 +228,7 @@ class sf_production_plan(models.Model):
""" """
排程方法 排程方法
""" """
self.deal_processing_schedule(self[0].date_planned_start) self.deal_processing_schedule(self.date_planned_start)
for record in self: for record in self:
if not record.production_line_id: if not record.production_line_id:
raise ValidationError("未选择生产线") raise ValidationError("未选择生产线")

View File

@@ -26,6 +26,7 @@
'views/quality_check_view.xml', 'views/quality_check_view.xml',
'views/quality_company.xml', 'views/quality_company.xml',
'wizard/check_picking_wizard_view.xml', 'wizard/check_picking_wizard_view.xml',
'wizard/confirmation_wizard_views.xml',
], ],
'assets': { 'assets': {

View File

@@ -5,7 +5,6 @@ from odoo import fields, models, api
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from datetime import datetime from datetime import datetime
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_round
class QualityCheck(models.Model): class QualityCheck(models.Model):
@@ -126,86 +125,10 @@ class QualityCheck(models.Model):
# todo 需修改 # todo 需修改
val = ['0037818516'] val = ['0037818516']
logging.info('获取到的工单信息%s' % val) logging.info('获取到的工单信息%s' % val)
# r = requests.post(crea_url, json=val, headers=headers) r = requests.post(crea_url, json=val, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='零件特采',
responser='中控系统',
json=val,
headers=headers
)
ret = r.json() ret = r.json()
logging.info('_register_quality_check:%s' % ret) logging.info('_register_quality_check:%s' % ret)
if ret['Succeed']: if ret['Succeed']:
return "零件特采发送成功" return "零件特采发送成功"
else: else:
raise ValidationError("零件特采发送失败") raise ValidationError("零件特采发送失败")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if 'point_id' in val and 'measure_on' not in val:
# 如果没有控制方式字段,则从检查点读取质量方式
point_id = self.env['quality.point'].browse(val['point_id'])
val.update({'measure_on': point_id.measure_on})
return super(QualityCheck, self).create(vals_list)
@api.depends('total_qty','testing_percentage_within_lot', 'is_lot_tested_fractionally')
def _compute_workorder_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.workorder_qty_to_test = float_round(float(qc.total_qty) * qc.testing_percentage_within_lot / 100,
precision_rounding=rounding, rounding_method="UP")
else:
qc.workorder_qty_to_test = qc.total_qty
@api.depends('picking_id', 'workorder_id')
def _compute_total_qty(self):
super(QualityCheck, self)._compute_total_qty()
for qc in self:
if not qc.picking_id and qc.workorder_id:
qc.total_qty = qc.workorder_id.production_id.product_qty
@api.depends('workorder_qty_to_test')
def _compute_workorder_qty_tested(self):
for qc in self:
qc.workorder_qty_tested = qc.workorder_qty_to_test
workorder_qty_to_test = fields.Float('应检', compute='_compute_workorder_qty_to_test', store=True)
workorder_qty_tested = fields.Float('已检', compute='_compute_workorder_qty_tested', store=True)
workorder_qty_test_failed = fields.Float('不合格数')
@api.onchange('total_qty', 'workorder_qty_test_failed', 'workorder_qty_to_test', 'workorder_qty_tested')
def _onchage_qty(self):
for record in self:
if record.total_qty and record.workorder_qty_to_test and record.workorder_qty_to_test > float(record.total_qty):
record.workorder_qty_to_test = float(record.total_qty)
return {
'warning': {
'title': '警告',
'message': '待检数量不能超过总数量'
}
}
if record.workorder_qty_to_test and record.workorder_qty_tested and record.workorder_qty_tested > record.workorder_qty_to_test:
record.workorder_qty_tested = record.workorder_qty_to_test
return {
'warning': {
'title': '警告',
'message': '已检数量不能超过待检数量'
}
}
if record.workorder_qty_tested and record.workorder_qty_test_failed and record.workorder_qty_test_failed > record.workorder_qty_tested:
record.workorder_qty_test_failed = record.workorder_qty_tested
return {
'warning': {
'title': '警告',
'message': '不合格数量不能超过已检数量'
}
}

View File

@@ -74,5 +74,6 @@ access_quality_cnc_test_group_quality_director,quality_cnc_test_group_quality_di
access_quality_cnc_test_group_sf_equipment_user,quality_cnc_test_group_sf_equipment_user,model_quality_cnc_test,sf_base.group_sf_equipment_user,1,1,0,0 access_quality_cnc_test_group_sf_equipment_user,quality_cnc_test_group_sf_equipment_user,model_quality_cnc_test,sf_base.group_sf_equipment_user,1,1,0,0
access_picking_validate_check_wizard,access.picking_validate_check_wizard,model_picking_validate_check_wizard,quality.group_quality_user,1,1,1,0 access_picking_validate_check_wizard,access.picking_validate_check_wizard,model_picking_validate_check_wizard,quality.group_quality_user,1,1,1,0
access_confirmation_wizard,access.confirmation.wizard,model_confirmation_wizard,quality.group_quality_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
74
75
76
77
78
79

View File

@@ -88,42 +88,6 @@
<button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/> <button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/>
<button name="do_re_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/> <button name="do_re_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/>
</xpath> </xpath>
<xpath expr="//field[@name='total_qty']" position="attributes">
<attribute name="attrs">{
'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'readonly': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)]
}</attribute>
</xpath>
<xpath expr="//field[@name='total_qty']" position="after">
<label for="workorder_qty_to_test"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_to_test" attrs="{'readonly': 0, 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_tested"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_tested" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_test_failed"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_test_failed" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
</xpath>
<xpath expr="//label[@for='qty_tested']" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
<xpath expr="//div[@class='o_row'][.//field[@name='qty_tested']]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
</field> </field>
</record> </record>

View File

@@ -1 +1,2 @@
from . import check_picking_wizard from . import check_picking_wizard
from . import confirmation_wizard

View File

@@ -0,0 +1,32 @@
from odoo import models, fields, api
class ConfirmationWizard(models.TransientModel):
_name = 'confirmation.wizard'
_description = '二次确认向导'
# 可根据需要传递上下文参数
check_id = fields.Many2one('quality.check', string='质检单', required=True)
picking_name = fields.Char(string='拣货单', related='check_id.picking_id.name', store=True)
number = fields.Char(string='数量', related='check_id.total_qty', store=True)
picking_num = fields.Integer(string='拣货数量', compute='_compute_picking_num', store=True)
@api.depends('check_id.picking_id')
def _compute_picking_num(self):
for record in self.check_id:
if record.picking_id:
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
self.picking_num = int(move.product_uom_qty)
else:
self.picking_num = 0
button_text = fields.Char(string='确认按钮文字')
def action_confirm(self):
self.ensure_one()
# 获取原始记录
check = self.env['quality.check'].browse(self.check_id.id)
# 调用实际发布方法
return check._do_actual_publish()

View File

@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record id="view_confirmation_wizard_form" model="ir.ui.view">
<field name="name">confirmation.wizard.form</field>
<field name="model">confirmation.wizard</field>
<field name="arch" type="xml">
<form>
<div class="alert alert-warning">
<p>拣货调拨单号<strong><field name="picking_name" class="oe_inline"/></strong>需求数量为<strong><field name="picking_num" class="oe_inline"/></strong>,当前质量检查单产品数量为<strong><field name="number" class="oe_inline"/></strong>,数量不一致,是否确认继续?</p>
</div>
<footer>
<button name="action_confirm" string="确定" type="object" class="btn-primary"/>
<button string="取消" class="btn-secondary" special="cancel"/>
</footer>
</form>
</field>
</record>
</odoo>

View File

@@ -343,9 +343,6 @@ 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
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
request_lines = self.order_line.mapped('purchase_request_lines') request_lines = self.order_line.mapped('purchase_request_lines')
# 检查是否存在 is_subcontract 为 True 的行 # 检查是否存在 is_subcontract 为 True 的行
if any(line.is_subcontract for line in request_lines): if any(line.is_subcontract for line in request_lines):
@@ -393,11 +390,11 @@ class RePurchaseOrder(models.Model):
# route_ids # route_ids
result.append({ result.append({
"product_id": server_template.product_variant_id.id, "product_id": server_template.product_variant_id.id,
'related_product': production.product_id.id, 'related_product':production.product_id.id,
"name": production.procurement_group_id.name, "name": production.procurement_group_id.name,
"date_required": fields.Datetime.now(), "date_required": fields.Datetime.now(),
"product_uom_id":server_template.uom_id.id, "product_uom_id":server_template.uom_id.id,
"product_qty": production.product_qty, "product_qty": 1,
"request_id": False, "request_id": False,
"move_dest_ids": False, "move_dest_ids": False,
"orderpoint_id": False, "orderpoint_id": False,
@@ -420,7 +417,7 @@ class RePurchaseOrder(models.Model):
('detailed_type', '=', 'service')]) ('detailed_type', '=', 'service')])
server_product_process.append((0, 0, { server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id, 'product_id': server_template.product_variant_id.id,
'product_qty': production.product_uom_qty, 'product_qty': 1,
'product_uom': server_template.uom_id.id, 'product_uom': server_template.uom_id.id,
'related_product': production.product_id.id, 'related_product': production.product_id.id,
'manual_part_number': pp.part_number, 'manual_part_number': pp.part_number,

View File

@@ -4,14 +4,12 @@ import json
import base64 import base64
from odoo import http from odoo import http
from odoo.http import request from odoo.http import request
from odoo.addons.sf_base.decorators.api_log import api_log
class Manufacturing_Connect(http.Controller): class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/ToolGroup', type='json', auth='sf_token', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('刀具组', requester='中控系统')
def get_functional_tool_groups_Info(self, **kw): def get_functional_tool_groups_Info(self, **kw):
""" """
刀具组接口 刀具组接口
@@ -41,7 +39,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolInventory', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/ToolInventory', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('功能刀具清单', requester='中控系统')
def get_functional_tool_inventory_Info(self, **kw): def get_functional_tool_inventory_Info(self, **kw):
""" """
功能刀具清单接口 功能刀具清单接口
@@ -71,7 +68,6 @@ class Manufacturing_Connect(http.Controller):
@http.route('/AutoDeviceApi/ToolEntity', type='json', auth='none', methods=['GET', 'POST'], csrf=False, @http.route('/AutoDeviceApi/ToolEntity', type='json', auth='none', methods=['GET', 'POST'], csrf=False,
cors="*") cors="*")
@api_log('功能刀具', requester='中控系统')
def get_functional_tool_entity_Info(self, **kw): def get_functional_tool_entity_Info(self, **kw):
""" """
功能刀具列表接口 功能刀具列表接口

View File

@@ -51,15 +51,7 @@ class SfMaintenanceEquipment(models.Model):
headers = {'Authorization': config['center_control_Authorization']} headers = {'Authorization': config['center_control_Authorization']}
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetToolInfos" crea_url = config['center_control_url'] + "/AutoDeviceApi/GetToolInfos"
params = {"DeviceId": self.name} params = {"DeviceId": self.name}
# r = requests.get(crea_url, params=params, headers=headers) r = requests.get(crea_url, params=params, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='机床刀库',
responser='中控系统',
params=params,
headers=headers
)
ret = r.json() ret = r.json()
logging.info('机床刀库register_equipment_tool():%s' % ret) logging.info('机床刀库register_equipment_tool():%s' % ret)
datas = ret['Datas'] datas = ret['Datas']

View File

@@ -514,15 +514,7 @@ class ShelfLocation(models.Model):
crea_url = config['center_control_url'] + "/AutoDeviceApi/GetLocationInfos" crea_url = config['center_control_url'] + "/AutoDeviceApi/GetLocationInfos"
params = {'DeviceId': device_id} params = {'DeviceId': device_id}
# r = requests.get(crea_url, params=params, headers=headers) r = requests.get(crea_url, params=params, headers=headers)
r = self.env['api.request.log'].log_request(
'get',
crea_url,
name='库位信息',
responser='中控系统',
params=params,
headers=headers
)
ret = r.json() ret = r.json()
@@ -820,49 +812,40 @@ class SfStockMoveLine(models.Model):
# # 从目标stock.move对象获取目标stock.picking对象 # # 从目标stock.move对象获取目标stock.picking对象
# dest_picking = dest_move.picking_id if dest_move else False # dest_picking = dest_move.picking_id if dest_move else False
# # 现在dest_picking就是current_picking的下一步 # # 现在dest_picking就是current_picking的下一步
# 添加所有需要的依赖字段
@api.depends('location_id') @api.depends('location_id')
def _compute_current_location_id(self): def _compute_current_location_id(self):
# 批量获取所有相关记录的picking
pickings = self.mapped('picking_id')
# 构建源picking的移库行与目标位置的映射
origin_location_map = {}
for picking in pickings:
# 获取源picking
origin_move = picking.move_ids[:1].move_orig_ids[:1]
if not origin_move:
continue
origin_picking = origin_move.picking_id
if not origin_picking:
continue
# 为每个picking构建lot_id到location的映射
origin_location_map[picking.id] = {
move_line.lot_id.id: move_line.destination_location_id
for move_line in origin_picking.move_line_ids.filtered(
lambda ml: ml.destination_location_id and ml.lot_id
)
}
# 批量更新current_location_id
for record in self: for record in self:
current_picking = record.picking_id # 使用record代替self来引用当前遍历到的记录
if not current_picking: logging.info('record.picking_id.name: %s' % record.picking_id.name)
record.current_location_id = False logging.info('record.env: %s' % record.env['stock.picking'].search([('name', '=', record.picking_id.name)]))
continue
# 获取当前的stock.picking对象
# 获取当前picking对应的lot_location映射 current_picking = record.env['stock.picking'].search([('name', '=', record.picking_id.name)], limit=1)
lot_dest_map = origin_location_map.get(current_picking.id, {})
# 获取当前picking的第一个stock.move对象
# 查找匹配的lot_id current_move = current_picking.move_ids[0] if current_picking.move_ids else False
for move_line in current_picking.move_line_ids:
if move_line.lot_id and move_line.lot_id.id in lot_dest_map: # 如果存在相关的stock.move对象
record.current_location_id = lot_dest_map[move_line.lot_id.id] if current_move:
break # 获取源stock.move对象
else: origin_move = current_move.move_orig_ids[0] if current_move.move_orig_ids else False
record.current_location_id = False
# 从源stock.move对象获取源stock.picking对象
origin_picking = origin_move.picking_id if origin_move else False
# 如果前一个调拨单有目标货位
if origin_picking:
for i in current_picking.move_line_ids:
for j in origin_picking.move_line_ids:
if j.destination_location_id and i.lot_id == j.lot_id:
# 更新当前记录的current_location_id字段
record.current_location_id = j.destination_location_id
# # 获取目标stock.move对象
# dest_move = current_move.move_dest_ids[0] if current_move.move_dest_ids else False
#
# # 从目标stock.move对象获取目标stock.picking对象
# dest_picking = dest_move.picking_id if dest_move else False
# # 现在dest_picking就是current_picking的下一步
# 是一张单据一张单据往下走的,所以这里的目标货位是上一张单据的当前货位,且这样去计算是可以的。 # 是一张单据一张单据往下走的,所以这里的目标货位是上一张单据的当前货位,且这样去计算是可以的。
@api.depends('location_dest_id') @api.depends('location_dest_id')