Compare commits

..

47 Commits

Author SHA1 Message Date
guanhuan
0e753b1c85 命名修改 2025-06-11 08:59:41 +08:00
guanhuan
48316c55b7 命名修改 2025-06-11 08:57:51 +08:00
guanhuan
b33ba9c354 命名修改 2025-06-11 08:56:44 +08:00
guanhuan
796e9b0cef 需求计划新增合同日期等字段 2025-06-10 15:49:22 +08:00
guanhuan
1926375d58 需求计划文件打印 2025-06-09 10:30:12 +08:00
guanhuan
8224f36567 需求计划页面显示 2025-06-06 15:24:12 +08:00
guanhuan
b11b6ef283 合同编号显示 2025-06-06 09:11:03 +08:00
guanhuan
865d2216af 未齐套提示按钮修改 2025-06-05 17:02:39 +08:00
guanhuan
1d01e3ad2e 未齐套提示 2025-06-05 16:17:18 +08:00
guanhuan
fc41f30244 未齐套提示 2025-06-04 17:31:10 +08:00
guanhuan
fbcd8c57c5 未齐套提示 2025-06-04 16:38:46 +08:00
guanhuan
b0f2fe6a8e 未齐套提示 2025-06-04 13:38:40 +08:00
guanhuan
de951b1b45 未齐套提示 2025-06-03 16:16:11 +08:00
guanhuan
b8cebe07fe 未齐套提示 2025-05-30 15:06:41 +08:00
guanhuan
1bdb81f5f7 待下达生产状态修改 2025-05-29 17:05:19 +08:00
guanhuan
007f39f137 程序工时修改 2025-05-29 11:07:46 +08:00
guanhuan
9b01254b3c 下达生产 2025-05-28 10:47:03 +08:00
guanhuan
53a67d7c76 下达生产 2025-05-26 15:27:07 +08:00
guanhuan
f4babfcd24 下达生产 2025-05-23 17:26:21 +08:00
guanhuan
a5243970d5 需求计划 2025-05-23 14:14:50 +08:00
guanhuan
e46e6dfc2a 需求计划 2025-05-22 17:40:56 +08:00
guanhuan
50f8bf5ab1 需求计划 2025-05-22 11:37:41 +08:00
管欢
1856a1a4ef Accept Merge Request #2134: (feature/搜索优化 -> develop)
Merge Request: 采购申请按钮优化

Created By: @管欢
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @管欢
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2134
2025-05-19 11:04:44 +08:00
guanhuan
8bd09cddf0 采购申请按钮优化 2025-05-19 11:02:43 +08:00
guanhuan
52579673de 采购申请单完成操作优化 2025-05-19 08:54:02 +08:00
胡尧
31cd131993 Accept Merge Request #2133: (feature/6694 -> develop)
Merge Request: 在调拨单确认生成欠单时,修改对应采购申请明细行的目标移动

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2133?initial=true
2025-05-16 11:50:47 +08:00
胡尧
9f94a4e06f 在调拨单确认生成欠单时,修改对应采购申请明细行的目标移动 2025-05-16 11:44:06 +08:00
禹翔辉
e66c6b1e1b Accept Merge Request #2132: (feature/销售委外加工按钮优化_1 -> develop)
Merge Request: 处理  销售-外购,委外的按钮关联订单数据显示不全  bug

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2132
2025-05-16 09:21:46 +08:00
yuxianghui
e97d0af941 1 2025-05-16 09:18:25 +08:00
yuxianghui
65122d38d5 处理 销售-外购,委外的按钮关联订单数据显示不全 bug 2025-05-16 09:17:13 +08:00
胡尧
b626cbe217 Accept Merge Request #2131: (feature/6694 -> develop)
Merge Request: 解决库存单据零件图号零件名称不对的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2131?initial=true
2025-05-16 09:15:46 +08:00
胡尧
4b443e65f6 解决库存单据零件图号零件名称不对的问题 2025-05-16 09:14:37 +08:00
胡尧
14700d6bf0 Accept Merge Request #2130: (feature/6694 -> develop)
Merge Request: 解决工艺外协采购单不显示的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2130
2025-05-15 16:49:05 +08:00
胡尧
a3c0fd3ccf 解决工艺外协采购单不显示的问题 2025-05-15 16:48:34 +08:00
胡尧
a29265f17d 修改正则匹配成品名字序号 2025-05-15 16:39:57 +08:00
胡尧
0821ed021a Accept Merge Request #2129: (feature/6694 -> develop)
Merge Request: 解决采购申请中零件图号不匹配的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2129?initial=true
2025-05-15 16:30:28 +08:00
胡尧
ac4883db66 解决采购申请中零件图号不匹配的问题 2025-05-15 16:29:33 +08:00
胡尧
5706aa0052 Accept Merge Request #2127: (feature/6694 -> develop)
Merge Request: 增加零件图号

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2127?initial=true
2025-05-15 16:12:18 +08:00
胡尧
f780e4f7ce 增加零件图号 2025-05-15 16:11:31 +08:00
胡尧
e6d8ebb7b3 修改 2025-05-15 14:39:40 +08:00
胡尧
3663e04b34 Accept Merge Request #2126: (feature/6694 -> develop)
Merge Request: 解决产品模板复制的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2126?initial=true
2025-05-15 12:14:51 +08:00
胡尧
c2e4085b50 解决产品模板复制的问题 2025-05-15 12:14:33 +08:00
胡尧
33c881b12f Accept Merge Request #2125: (feature/6694 -> develop)
Merge Request: 回退工艺外协代码

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2125?initial=true
2025-05-15 11:07:51 +08:00
胡尧
c80e12d731 回退工艺外协代码 2025-05-15 11:07:22 +08:00
胡尧
5446ef18e2 回退代码 2025-05-15 10:36:38 +08:00
胡尧
9c4d545915 Merge branch 'develop' into feature/6694 2025-05-15 10:33:42 +08:00
胡尧
61bcd72a41 表面工艺外协调拨单数量问题 2025-05-09 17:24:24 +08:00
55 changed files with 1744 additions and 769 deletions

View File

@@ -8,18 +8,19 @@
'category': 'purchase', 'category': 'purchase',
'depends': ['sf_manufacturing', 'purchase_request'], 'depends': ['sf_manufacturing', 'purchase_request'],
'data': [ 'data': [
'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'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/purchase_request_line_view.xml',
'views/stock_picking_views.xml', 'wizard/purchase_request_wizard_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

@@ -410,7 +410,7 @@ msgstr "显示名称"
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form #: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_form
#: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search #: model_terms:ir.ui.view,arch_db:purchase_request.view_purchase_request_search
msgid "Done" msgid "Done"
msgstr "完成" msgstr "关闭"
#. module: purchase_request #. module: purchase_request
#: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids #: model:ir.model.fields,field_description:purchase_request.field_purchase_request_line__move_dest_ids

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

@@ -1,6 +1,7 @@
import re import re
import ast import ast
from odoo import models, fields, api from odoo import models, fields, api, _
from itertools import groupby
class PurchaseRequest(models.Model): class PurchaseRequest(models.Model):
@@ -29,6 +30,54 @@ class PurchaseRequest(models.Model):
action['context'] = origin_context action['context'] = origin_context
return action return action
def button_done(self):
product_qty_map = {key: sum(line.product_qty for line in group) for key, group in
groupby(self.line_ids, key=lambda x: x.product_id.id)}
lines = self.mapped("line_ids.purchase_lines.order_id")
# 采购单产品和数量
product_summary = {}
if lines:
for line in lines:
for line_item in line.order_line:
product_id = line_item.product_id.id
qty = line_item.product_qty
if product_id in product_summary:
product_summary[product_id] += qty
else:
product_summary[product_id] = qty
# 校验产品数量
discrepancies = []
for product_id, qty in product_qty_map.items():
if product_id in product_summary:
if product_summary[product_id] != qty:
discrepancies.append((product_id, qty, product_summary[product_id]))
else:
discrepancies.append((product_id, qty, 0))
if discrepancies:
# 弹出提示框
message = "产品数量不一致:\n"
for product_id, required_qty, order_qty in discrepancies:
product_name = self.env['product.product'].browse(product_id).display_name # 获取产品名称
message += f"产品 {product_name},需求数量 {required_qty},关联采购订单数量 {order_qty}(含询价状态)\n"
# 添加确认框
message += "确认关闭?"
return {
'name': _('采购申请'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'jikimo_purchase_request.purchase_request_wizard_wizard_form_view').id,
'form')],
'res_model': 'purchase.request.wizard',
'target': 'new',
'context': {
'default_purchase_request_id': self.id,
'default_message': message,
}}
return super(PurchaseRequest, self).button_done()
class PurchaseRequestLine(models.Model): class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line' _inherit = 'purchase.request.line'
_description = '采购申请明细' _description = '采购申请明细'
@@ -92,7 +141,7 @@ class PurchaseRequestLine(models.Model):
continue continue
if record.product_id.categ_id.name == '坯料': if record.product_id.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.product_id.name) match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

@@ -33,3 +33,13 @@ class StockPicking(models.Model):
'view_mode': 'tree,form', 'view_mode': 'tree,form',
}) })
return action return action
def _action_done(self):
res = super(StockPicking, self)._action_done()
# 将采购申请明细行的move_dest_ids设置为backorder_ids
backorder_ids = self.backorder_ids
if backorder_ids:
purchase_request_lines = self.move_ids.move_orig_ids.purchase_line_id.purchase_request_lines
if purchase_request_lines:
purchase_request_lines.move_dest_ids = [(4, x.id) for x in backorder_ids.move_ids]
return res

View File

@@ -0,0 +1,2 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_purchase_request_wizard_group_user,purchase.request.wizard,model_purchase_request_wizard,base.group_user,1,1,1,1
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_purchase_request_wizard_group_user purchase.request.wizard model_purchase_request_wizard base.group_user 1 1 1 1

View File

@@ -15,6 +15,12 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name"/> <field name="part_name"/>
</xpath> </xpath>
<xpath expr="//button[@name='button_done']" position="attributes">
<attribute name="class"/>
</xpath>
<xpath expr="//button[@name='button_in_progress']/following-sibling::button[1]" position="attributes">
<attribute name="class">oe_highlight</attribute>
</xpath>
</field> </field>
</record> </record>

View File

@@ -1,3 +1,4 @@
# License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0) # License LGPL-3.0 or later (https://www.gnu.org/licenses/lgpl-3.0)
from . import purchase_request_line_make_purchase_order from . import purchase_request_line_make_purchase_order
from . import purchase_request_wizard

View File

@@ -0,0 +1,12 @@
from odoo import models, fields, api
class PurchaseRequestWizard(models.TransientModel):
_name = 'purchase.request.wizard'
_description = '采购申请向导'
purchase_request_id = fields.Many2one('purchase.request', string='采购申请')
message = fields.Char(string='提示', readonly=True)
def confirm(self):
return self.purchase_request_id.write({"state": "done"})

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="purchase_request_wizard_wizard_form_view">
<field name="name">purchase.request.wizard.form.view</field>
<field name="model">purchase.request.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="message"/>
</div>
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
</odoo>

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

@@ -133,6 +133,7 @@ class QualityCheck(models.Model):
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=False, store=True)
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=False, store=True)
material_name = fields.Char('材料名称', compute='_compute_material_name') material_name = fields.Char('材料名称', compute='_compute_material_name')
model_id = fields.Char('模型ID', related='product_id.model_id')
# # 总数量值为调拨单_产品明细_数量 # # 总数量值为调拨单_产品明细_数量
# total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True) # total_qty = fields.Float('总数量', compute='_compute_total_qty', readonly=True)

View File

@@ -493,6 +493,9 @@
<field name="picking_id"/> <field name="picking_id"/>
<field name="lot_id"/> <field name="lot_id"/>
<field name="team_id"/> <field name="team_id"/>
<field name="part_number"/>
<field name="part_name"/>
<field name="model_id"/>
<filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/> <filter string="In Progress" name="progress" domain="[('quality_state', '=', 'none')]"/>
<filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/> <filter string="Passed" name="passed" domain="[('quality_state', '=', 'pass')]"/>
<filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/> <filter string="Failed" name="failed" domain="[('quality_state', '=', 'fail')]"/>

View File

@@ -195,10 +195,18 @@ class PrintingUtils(models.AbstractModel):
text_y = margin + 20 # 文字位置靠近底部 text_y = margin + 20 # 文字位置靠近底部
c.drawString(text_x, text_y, text) c.drawString(text_x, text_y, text)
# 设置字体
if font_found:
c.setFont('SimSun', 12) # 增大字体大小到14pt
else:
# 如果没有找到中文字体,使用默认字体
c.setFont('Helvetica', 120)
logging.warning("未找到中文字体,将使用默认字体")
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", 12) # 准确计算文字宽度
text_x = (page_width - text_width) / 2 # 文字居中对齐 text_x = (page_width - 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)

View File

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

View File

@@ -0,0 +1,32 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
{
'name': '机企猫智能工厂 需求计划',
'version': '1.0',
'summary': '智能工厂计划管理',
'sequence': 1,
'description': """
在本模块,支持齐套检查与下达生产
""",
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_plan', 'jikimo_printing'],
'data': [
'security/ir.model.access.csv',
'views/demand_plan.xml',
'wizard/sf_demand_plan_print_wizard_view.xml',
],
'demo': [
],
'assets': {
'web.assets_qweb': [
],
'web.assets_backend': [
'sf_demand_plan/static/src/scss/style.css',
]
},
'license': 'LGPL-3',
'installable': True,
'application': False,
'auto_install': False,
}

View File

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

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
import logging
import json
from odoo import http, fields, models
from odoo.http import request
from odoo.addons.sf_base.controllers.controllers import MultiInheritController
class SfPlanMrsConnect(http.Controller, MultiInheritController):
@http.route('/api/demand_plan/update_processing_time', type='json', auth='sf_token', methods=['GET', 'POST'],
csrf=False,
cors="*")
def update_processing_time(self, **kw):
"""
根据模型id修改程序工时
:param kw:
:return:
"""
try:
res = {'status': 1, 'message': '成功'}
datas = request.httprequest.data
ret = json.loads(datas)
ret = json.loads(ret['result'])
logging.info('根据模型id修改程序工时:%s' % ret)
demand_plan = request.env['sf.production.demand.plan'].sudo().search(
[('model_id', '=', ret['model_id'])])
if demand_plan:
demand_plan.write(
{'processing_time': ret['total_estimated_time']})
else:
res = {'status': 0, 'message': '未查到该需求计划'}
except Exception as e:
logging.info('update_demand_paln error:%s' % e)
res['status'] = -1
res['message'] = '系统解析错误!'
return json.JSONEncoder().encode(res)

View File

@@ -0,0 +1,4 @@
# -*- coding: utf-8 -*-
from . import sf_production_demand_plan
from . import sale_order

View File

@@ -0,0 +1,23 @@
from odoo import models, fields, api, _
class ReSaleOrder(models.Model):
_inherit = 'sale.order'
def sale_order_create_line(self, product, item):
ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
vals = {
'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id,
}
demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = {
'demand_plan_id': demand_plan.id,
'model_id': demand_plan.model_id,
'filename_url': filename_url,
'type': '1',
}
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
return ret

View File

@@ -0,0 +1,558 @@
# -*- coding: utf-8 -*-
import ast
import json
from odoo import models, fields, api, _
from odoo.exceptions import ValidationError
from odoo.tools import float_compare
from datetime import datetime, timedelta
from odoo.exceptions import UserError
class SfProductionDemandPlan(models.Model):
_name = 'sf.production.demand.plan'
_description = 'sf_production_demand_plan'
def _get_machining_precision(self):
machinings = self.env['sf.machining.accuracy'].sudo().search([])
list = [(m.sync_id, m.name) for m in machinings]
return list
priority = fields.Selection([
('1', '紧急'),
('2', ''),
('3', ''),
('4', ''),
], string='优先级', default='3')
status = fields.Selection([
('10', '草稿'),
('20', '待确认'),
('30', '需求确认'),
('50', '待下达生产'),
('60', '已下达'),
('100', '取消'),
], string='状态', compute='_compute_status', store=True)
sale_order_id = fields.Many2one(comodel_name="sale.order",
string="销售订单", readonly=True)
sale_order_line_id = fields.Many2one(comodel_name="sale.order.line",
string="销售订单行", readonly=True)
company_id = fields.Many2one(
related='sale_order_id.company_id',
store=True, index=True, precompute=True)
partner_id = fields.Many2one(
comodel_name='res.partner',
related='sale_order_line_id.order_partner_id',
string="客户",
store=True, index=True)
order_remark = fields.Text(related='sale_order_id.remark',
string="订单备注", store=True)
glb_url = fields.Char(related='sale_order_line_id.glb_url', string='glb文件地址')
product_id = fields.Many2one(
comodel_name='product.product',
related='sale_order_line_id.product_id',
string='产品', store=True, index=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
part_name = fields.Char('零件名称', related='product_id.part_name')
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', related='sale_order_line_id.supply_method', store=True)
product_uom_qty = fields.Float(
string="需求数量",
related='sale_order_line_id.product_uom_qty', store=True)
deadline_of_delivery = fields.Date('客户交期', related='sale_order_id.deadline_of_delivery', store=True)
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
compute='_compute_inventory_quantity_auto_apply'
)
qty_delivered = fields.Float(
"交货数量", related='sale_order_line_id.qty_delivered')
qty_to_deliver = fields.Float(
"待交货数量", related='sale_order_line_id.qty_to_deliver')
model_long = fields.Char('尺寸', compute='_compute_model_long')
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
related='product_id.model_machining_precision')
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
'plan_process_parameter_rel',
string='表面工艺',
compute='_compute_model_process_parameters_ids'
, store=True
)
product_remark = fields.Char("产品备注", related='product_id.model_remark')
order_code = fields.Char('E-SHOP订单号', related='sale_order_id.order_code')
order_state = fields.Selection(
string='订单状态',
related='sale_order_line_id.state')
route_id = fields.Many2one('stock.route', string='路线', related='sale_order_line_id.route_id', store=True)
contract_date = fields.Date('合同日期')
date_order = fields.Datetime('下单日期', related='sale_order_id.date_order')
contract_code = fields.Char('合同号')
plan_remark = fields.Text("计划备注")
material_check = fields.Selection([
('0', "未齐套"),
('1', "已齐套"),
], string='投料齐套检查', compute='_compute_material_check', store=True)
processing_time = fields.Char('程序工时', readonly=True)
planned_start_date = fields.Date('计划开工日期')
actual_start_date = fields.Date('实际开工日期', compute='_compute_actual_start_date', store=True)
actual_end_date = fields.Date('实际完工日期', compute='_compute_actual_end_date', store=True)
print_count = fields.Char('打印次数', default='T0C0', readonly=True)
sequence = fields.Integer('序号')
hide_action_open_mrp_production = fields.Boolean(
string='显示待工艺确认按钮',
compute='_compute_hid_button',
default=False
)
hide_action_purchase_orders = fields.Boolean(
string='显示采购按钮',
compute='_compute_hide_action_purchase_orders',
default=False
)
hide_action_stock_picking = fields.Boolean(
string='显示调拨单按钮',
compute='_compute_hide_action_stock_picking',
default=False
)
hide_action_outsourcing_stock_picking = fields.Boolean(
string='委外显示调拨单按钮',
compute='_compute_hide_action_stock_picking',
default=False
)
hide_action_view_programming = fields.Boolean(
string='显示编程单按钮',
compute='_compute_hid_button',
default=False
)
outsourcing_purchase_request = fields.Char('委外采购申请单')
@api.depends('sale_order_id.state', 'sale_order_id.mrp_production_ids.schedule_state', 'sale_order_id.order_line',
'sale_order_id.mrp_production_ids.state')
def _compute_status(self):
for record in self:
if record.sale_order_id:
sale_order_state = record.sale_order_id.state
if sale_order_state in ('draft', 'sent', 'supply method'):
record.status = '20' # 待确认
if record.supply_method in ('purchase', 'outsourcing') and sale_order_state in (
'sale', 'processing', 'physical_distribution', 'delivered',
'done') and sale_order_state != 'cancel':
record.status = '60' # 已下达
if record.supply_method in ('automation', 'manual'):
if sale_order_state in (
'sale', 'processing', 'physical_distribution', 'delivered',
'done') and sale_order_state != 'cancel':
record.status = '30' # 需求确认
# 检查所有制造订单的排程单状态,有一个为待排程状态,就为待下达生产
pending_productions = record.sale_order_id.mrp_production_ids.filtered(
lambda p: p.state == 'confirmed' and p.product_id.id == record.product_id.id
)
if pending_productions:
record.status = '50' # 待下达生产
# 检查所有制造订单的排程单状态
if record.sale_order_id.mrp_production_ids and all(
order.product_id == record.product_id and order.schedule_state != '未排' for order in
record.sale_order_id.mrp_production_ids):
record.status = '60' # 已下达
if sale_order_state == 'cancel' or not record.sale_order_line_id:
record.status = '100' # 取消
@api.depends('product_id.part_number', 'product_id.model_name')
def _compute_part_number(self):
for line in self:
if line.product_id:
if line.product_id.part_number:
line.part_number = line.product_id.part_number
else:
if line.product_id.model_name:
line.part_number = line.product_id.model_name.rsplit('.', 1)[0]
else:
line.part_number = None
@api.depends('product_id.length', 'product_id.width', 'product_id.height')
def _compute_model_long(self):
for line in self:
if line.product_id:
line.model_long = f"{line.product_id.length}*{line.product_id.width}*{line.product_id.height}"
else:
line.model_long = None
@api.depends('product_id.materials_id')
def _compute_materials_id(self):
for line in self:
if line.product_id:
line.materials_id = f"{line.product_id.materials_id.name}*{line.product_id.materials_type_id.name}"
else:
line.materials_id = None
@api.depends('product_id.model_process_parameters_ids')
def _compute_model_process_parameters_ids(self):
for line in self:
if line.product_id and line.product_id.model_process_parameters_ids:
line.model_process_parameters_ids = [(6, 0, line.product_id.model_process_parameters_ids.ids)]
else:
line.model_process_parameters_ids = [(5, 0, 0)]
def _compute_inventory_quantity_auto_apply(self):
location_id = self.env['stock.location'].search([('name', '=', '成品存货区')], limit=1).id
product_ids = self.mapped('product_id').ids
if product_ids:
quant_data = self.env['stock.quant'].read_group(
domain=[
('product_id', 'in', product_ids),
('location_id', '=', location_id)
],
fields=['product_id', 'inventory_quantity_auto_apply'],
groupby=['product_id']
)
quantity_map = {item['product_id'][0]: item['inventory_quantity_auto_apply'] for item in quant_data}
else:
quantity_map = {}
for line in self:
if line.product_id:
line.inventory_quantity_auto_apply = quantity_map.get(line.product_id.id, 0.0)
else:
line.inventory_quantity_auto_apply = 0.0
@api.depends('sale_order_id.mrp_production_ids.workorder_ids.date_start')
def _compute_actual_start_date(self):
for record in self:
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
lambda mo: mo.product_id == record.product_id)
if manufacturing_orders:
start_dates = [
workorder.date_start.date() for mo in manufacturing_orders
for workorder in mo.workorder_ids if workorder.date_start
]
record.actual_start_date = min(start_dates) if start_dates else None
else:
record.actual_start_date = None
else:
record.actual_start_date = None
@api.depends('sale_order_id.mrp_production_ids.workorder_ids.state',
'sale_order_id.mrp_production_ids.workorder_ids.date_finished')
def _compute_actual_end_date(self):
for record in self:
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
lambda mo: mo.product_id == record.product_id)
finished_orders = manufacturing_orders.filtered(lambda mo: mo.state == 'done')
if len(finished_orders) >= record.product_uom_qty:
end_dates = [
workorder.date_finished.date() for mo in finished_orders
for workorder in mo.workorder_ids if workorder.date_finished
]
record.actual_end_date = max(end_dates) if end_dates else None
else:
record.actual_end_date = None
else:
record.actual_end_date = None
@api.depends('sale_order_id.mrp_production_ids.move_raw_ids.forecast_availability',
'sale_order_id.mrp_production_ids.move_raw_ids.quantity_done')
def _compute_material_check(self):
for record in self:
if record.sale_order_id and record.sale_order_id.mrp_production_ids:
manufacturing_orders = record.sale_order_id.mrp_production_ids.filtered(
lambda mo: mo.product_id == record.product_id)
if manufacturing_orders and manufacturing_orders.move_raw_ids:
total_forecast_availability = sum(manufacturing_orders.mapped('move_raw_ids.forecast_availability'))
total_quantity_done = sum(manufacturing_orders.mapped('move_raw_ids.quantity_done'))
total_sum = total_forecast_availability + total_quantity_done
if float_compare(total_sum, record.product_uom_qty,
precision_rounding=record.product_id.uom_id.rounding) >= 0:
record.material_check = '1' # 已齐套
else:
record.material_check = '0' # 未齐套
else:
record.material_check = None
else:
record.material_check = None
@api.constrains('planned_start_date')
def _check_planned_start_date(self):
for record in self:
if record.planned_start_date and record.planned_start_date < fields.Date.today():
raise ValidationError("计划开工日期必须大于或等于今天。")
def release_production_order(self):
pro_plan_list = self.env['sf.production.plan'].search(
[('product_id', '=', self.product_id.id), ('state', '=', 'draft')])
sf_production_line = self.env['sf.production.line'].sudo().search(
[('name', '=', '1#CNC自动生产线')], limit=1)
current_datetime = datetime.now() + timedelta(minutes=3)
current_hour = current_datetime.hour + current_datetime.minute / 60
date_planned_start = None
production_lines = sf_production_line.mrp_workcenter_ids.filtered(lambda b: "自动生产线" in b.name)
if production_lines:
if not production_lines.deal_with_workcenter_calendar(current_datetime):
attendance_list = production_lines.resource_calendar_id.attendance_ids
# 获取所有工作日规则并按星期几分组
attendance_by_day = {}
for attendance in attendance_list:
if attendance.dayofweek not in attendance_by_day:
attendance_by_day[attendance.dayofweek] = []
attendance_by_day[attendance.dayofweek].append(attendance)
for day_offset in range(0, 8):
check_date = current_datetime + timedelta(days=day_offset)
# 日期为星期几
check_day = production_lines.get_current_day_of_week(check_date)
if check_day in attendance_by_day:
day_attendances = attendance_by_day[check_day]
if day_offset == 0:
for attendance in day_attendances:
if current_hour < attendance.hour_to:
# 找到下一个有效时间段
if current_hour < attendance.hour_from:
# 使用开始时间
date_planned_start = check_date.replace(
hour=int(attendance.hour_from),
minute=int((attendance.hour_from % 1) * 60),
second=0,
microsecond=0
)
else:
continue
break
else:
# 不是今天,使用第一个工作时间段
attendance = day_attendances[0]
date_planned_start = check_date.replace(
hour=int(attendance.hour_from),
minute=int((attendance.hour_from % 1) * 60),
second=0,
microsecond=0
)
if date_planned_start:
break
else:
date_planned_start = current_datetime
if date_planned_start:
pro_plan_list.production_line_id = sf_production_line.id
pro_plan_list.date_planned_start = date_planned_start
for pro_plan in pro_plan_list:
pro_plan.do_production_schedule()
def button_action_print(self):
return {
'res_model': 'sf.demand.plan.print.wizard',
'type': 'ir.actions.act_window',
'name': _("打印"),
'domain': [('demand_plan_id', 'in', self.ids)],
'views': [[self.env.ref('sf_demand_plan.action_plan_print_tree').id, 'list']],
'target': 'new',
}
@api.depends('sale_order_id.mrp_production_ids.state', 'sale_order_id.mrp_production_ids.programming_state')
def _compute_hid_button(self):
for record in self:
mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == record.product_id.id
)
record.hide_action_open_mrp_production = bool(mrp_production_ids) and record.supply_method in (
'automation', 'manual')
programming_mrp_production_ids = record.sale_order_id.mrp_production_ids.filtered(
lambda p: p.programming_state == '编程中' and p.product_id.id == record.product_id.id
)
record.hide_action_view_programming = bool(programming_mrp_production_ids)
def _compute_hide_action_purchase_orders(self):
for record in self:
record.hide_action_purchase_orders = False
outsourcing_purchase_request = []
if record.supply_method in ('automation',
'manual') and record.material_check == '0' and not record.sale_order_line_id.is_incoming_material:
mrp_production = record.sale_order_id.mrp_production_ids.filtered(
lambda p: p.product_id.id == record.product_id.id
).sorted(key=lambda p: p.id)
if mrp_production:
raw_materials = mrp_production.mapped('move_raw_ids.product_id')
if raw_materials:
purchase_orders = self.env['purchase.order'].sudo().search([
('state', '=', 'purchase'),
('order_line.product_id', 'in', raw_materials.ids)
])
total_purchase_quantity = sum(
sum(
order.order_line.filtered(
lambda line: line.product_id in raw_materials
).mapped('product_qty')
)
for order in purchase_orders
)
if total_purchase_quantity < record.product_uom_qty:
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', mrp_production[0].name), ('state', '!=', 'done')])
outsourcing_purchase_request.extend(pr_ids.ids)
elif record.supply_method in ('purchase', 'outsourcing'):
pr_ids = None
purchase_orders = self.env['purchase.order'].sudo().search([
('state', 'in', ('purchase', 'done')),
('order_line.product_id', '=', record.product_id.id)
])
total_purchase_quantity = sum(
sum(
order.order_line.filtered(
lambda line: line.product_id in record.product_id
).mapped('product_qty')
)
for order in purchase_orders
)
if total_purchase_quantity < record.product_uom_qty:
pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', record.sale_order_id.name), ('state', '!=', 'done')])
outsourcing_purchase_request.extend(pr_ids.ids)
if record.supply_method == 'outsourcing' and not record.sale_order_line_id.is_incoming_material:
bom_line_ids = record.product_id.bom_ids.bom_line_ids
# BOM_数量
total_product_qty = sum(line.product_qty for line in bom_line_ids)
bom_product_ids = bom_line_ids.mapped('product_id')
product_purchase_orders = self.env['purchase.order'].sudo().search([
('state', 'in', ('purchase', 'done')),
('order_line.product_id', 'in', bom_product_ids.ids)
])
# 购订单_数量
total_outsourcing_purchase_quantity = sum(
sum(
order.order_line.filtered(
lambda line: line.product_id in bom_product_ids
).mapped('product_qty')
)
for order in product_purchase_orders
)
if total_outsourcing_purchase_quantity / total_product_qty < record.product_uom_qty:
purchase_request = self.env['purchase.request'].sudo().search(
[('line_ids.product_id', 'in', bom_product_ids.ids),
('line_ids.purchase_state', 'not in', ('purchase', 'done')), ('state', '!=', 'done')])
outsourcing_purchase_request.extend(purchase_request.ids)
record.outsourcing_purchase_request = json.dumps(outsourcing_purchase_request)
if outsourcing_purchase_request:
record.hide_action_purchase_orders = True
@api.depends('sale_order_id.mrp_production_ids.picking_ids.state', 'sale_order_id.picking_ids.state')
def _compute_hide_action_stock_picking(self):
for record in self:
record.hide_action_stock_picking = False
record.hide_action_outsourcing_stock_picking = False
if record.supply_method in ('automation', 'manual'):
manufacturing_orders = record.sale_order_id.mrp_production_ids
record.hide_action_stock_picking = bool(manufacturing_orders.mapped('picking_ids').filtered(
lambda p: p.state == 'assigned'))
elif record.supply_method in ('purchase', 'outsourcing'):
assigned_picking_ids = record.sale_order_id.picking_ids.filtered(
lambda
p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in record.product_id)
if record.supply_method == 'outsourcing':
outsourcing_assigned_picking_ids = record.get_outsourcing_picking_ids()
record.hide_action_outsourcing_stock_picking = outsourcing_assigned_picking_ids
record.hide_action_stock_picking = assigned_picking_ids or outsourcing_assigned_picking_ids
else:
record.hide_action_stock_picking = assigned_picking_ids
def get_outsourcing_picking_ids(self):
order_ids = self.env['purchase.order'].sudo().search(
[('order_line.product_id', 'in', self.product_id.ids),
('purchase_type', '=', 'outsourcing')])
outsourcing_picking_ids = order_ids._get_subcontracting_resupplies()
outsourcing_assigned_picking_ids = outsourcing_picking_ids.filtered(lambda p: p.state == 'assigned')
return outsourcing_assigned_picking_ids
def action_open_sale_order(self):
self.ensure_one()
return {
'type': 'ir.actions.act_window',
'res_model': 'sale.order',
'res_id': self.sale_order_id.id,
'view_mode': 'form',
}
def action_open_mrp_production(self):
self.ensure_one()
mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
lambda p: p.state == 'technology_to_confirmed' and p.product_id.id == self.product_id.id
)
action = {
'res_model': 'mrp.production',
'type': 'ir.actions.act_window',
}
if len(mrp_production_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': mrp_production_ids.id,
})
else:
action.update({
'name': _("制造订单列表"),
'domain': [('id', 'in', mrp_production_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_purchase_request(self):
self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search(
[('id', 'in', ast.literal_eval(self.outsourcing_purchase_request))])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(pr_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': pr_ids[0].id,
})
else:
action.update({
'name': _("采购申请"),
'domain': [('id', 'in', pr_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_stock_picking(self):
self.ensure_one()
action = self.env["ir.actions.actions"]._for_xml_id("stock.action_picking_tree_all")
picking_ids = None
if self.supply_method in ('automation', 'manual'):
picking_ids = self.sale_order_id.mrp_production_ids.mapped('picking_ids').filtered(
lambda p: p.state == 'assigned')
elif self.supply_method in ('purchase', 'outsourcing'):
picking_ids = self.sale_order_id.picking_ids.filtered(
lambda
p: p.state == 'assigned' and p.picking_type_id.name != '发料出库' and p.move_line_ids.product_id in self.product_id)
if self.supply_method == 'outsourcing' and self.hide_action_outsourcing_stock_picking:
picking_ids = picking_ids.union(self.get_outsourcing_picking_ids())
if picking_ids:
if len(picking_ids) > 1:
action['domain'] = [('id', 'in', picking_ids.ids)]
elif picking_ids:
action['res_id'] = picking_ids.id
action['views'] = [(self.env.ref('stock.view_picking_form').id, 'form')]
if 'views' in action:
action['views'] += [(state, view) for state, view in action['views'] if view != 'form']
return action
def action_view_programming(self):
self.ensure_one()
programming_mrp_production_ids = self.sale_order_id.mrp_production_ids.filtered(
lambda p: p.programming_state == '编程中' and p.product_id.id == self.product_id.id
).mapped('programming_no')
if programming_mrp_production_ids:
programming_no = list(set(programming_mrp_production_ids))
numbers_str = "".join(programming_no)
raise ValidationError(f"编程单号:{numbers_str},请去云平台处理")

View File

@@ -0,0 +1,6 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink
access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0
access_sf_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,0,0
access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0
access_sf_demand_plan_print_wizard_for_dispatch,sf.demand.plan.print.wizard for dispatch,model_sf_demand_plan_print_wizard,sf_base.group_plan_dispatch,1,1,0,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_demand_plan sf.production.demand.plan model_sf_production_demand_plan base.group_user 1 0 0 0
3 access_sf_production_demand_plan_for_dispatch sf.production.demand.plan for dispatch model_sf_production_demand_plan sf_base.group_plan_dispatch 1 1 0 0
4 access_sf_demand_plan_print_wizard sf.demand.plan.print.wizard model_sf_demand_plan_print_wizard base.group_user 1 0 0 0
5 access_sf_demand_plan_print_wizard_for_dispatch sf.demand.plan.print.wizard for dispatch model_sf_demand_plan_print_wizard sf_base.group_plan_dispatch 1 1 0 0

View File

@@ -0,0 +1,11 @@
.demand_plan_tree .o_list_table_ungrouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
min-width: 98px !important;
}
.demand_plan_tree .o_list_table_grouped th:not(.o_list_record_selector,.row_no,[data-name=sequence]) {
width: 98px !important;
}
.demand_plan_tree .o_list_table_ungrouped {
min-width: 1900px;
}

View File

@@ -0,0 +1,119 @@
<odoo>
<record id="view_sf_production_demand_plan_tree" model="ir.ui.view">
<field name="name">sf.production.demand.plan.tree</field>
<field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml">
<tree string="需求计划" default_order="create_date desc" editable="bottom" class="demand_plan_tree">
<header>
<button string="打印" name="button_action_print" type="object"
class="btn-primary"/>
</header>
<field name="sequence" widget="handle"/>
<field name="id" optional="hide"/>
<field name="priority"/>
<field name="status"/>
<field name="partner_id"/>
<field name="order_remark"/>
<field name="glb_url" optional="hide"/>
<field name="product_id"/>
<field name="model_id" optional="hide"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="is_incoming_material"/>
<field name="supply_method"/>
<field name="product_uom_qty"/>
<field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply"/>
<field name="qty_delivered"/>
<field name="qty_to_deliver"/>
<field name="model_long"/>
<field name="materials_id"/>
<field name="model_machining_precision"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="product_remark" optional="hide"/>
<field name="order_code" optional="hide"/>
<field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_id" optional="hide"/>
<field name="order_state"/>
<field name="route_id" optional="hide"/>
<field name="contract_date"/>
<field name="date_order"/>
<field name="contract_code"/>
<field name="plan_remark"/>
<field name="processing_time"/>
<field name="material_check"/>
<field name="hide_action_open_mrp_production" invisible="1"/>
<field name="hide_action_purchase_orders" invisible="1"/>
<field name="hide_action_stock_picking" invisible="1"/>
<field name="hide_action_view_programming" invisible="1"/>
<button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"
attrs="{'invisible': [('supply_method', '!=', False)]}"/>
<button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"
attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>
<button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"
attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>
<button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"
attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>
<button name="action_view_programming" type="object" string="编程单" class="btn-secondary"
attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="create_date" optional="hide" string="创建时间"/>
<field name="create_uid" optional="hide" string="创建人"/>
<field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/>
<button name="release_production_order" type="object" string="下达生产" class="btn-primary"
attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/>
</tree>
</field>
</record>
<record id="view_sf_production_demand_plan_search" model="ir.ui.view">
<field name="name">sf.production.demand.plan.search</field>
<field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml">
<search>
<field name="order_remark"/>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="partner_id"/>
<field name="supply_method"/>
<field name="materials_id"/>
<field name="model_process_parameters_ids"/>
<field name="plan_remark"/>
<group expand="0" string="Group By">
<filter name="group_by_priority" string="优先级" domain="[]" context="{'group_by': 'priority'}"/>
<filter name="group_by_status" string="状态" domain="[]" context="{'group_by': 'status'}"/>
<filter name="group_by_partner_id" string="客户" domain="[]" context="{'group_by': 'partner_id'}"/>
<filter name="group_by_is_incoming_material" string="客供料" domain="[]"
context="{'group_by': 'is_incoming_material'}"/>
<filter name="group_by_supply_method" string="供货方式" domain="[]"
context="{'group_by': 'supply_method'}"/>
<filter name="group_by_deadline_of_delivery" string="客户交期" domain="[]"
context="{'group_by': 'deadline_of_delivery'}"/>
<filter name="group_by_materials_id" string="材料" domain="[]"
context="{'group_by': 'materials_id'}"/>
</group>
</search>
</field>
</record>
<record id="sf_production_demand_plan_action" model="ir.actions.act_window">
<field name="name">需求计划</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">sf.production.demand.plan</field>
<field name="view_mode">tree</field>
</record>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
</odoo>

View File

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

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfDemandPlanPrintWizard(models.TransientModel):
_name = 'sf.demand.plan.print.wizard'
_description = u'打印向导'
demand_plan_id = fields.Many2one('sf.production.demand.plan', string='需求计划ID')
product_id = fields.Many2one(
comodel_name='product.product',
related='demand_plan_id.product_id',
string='产品', store=True, index=True)
model_id = fields.Char('模型ID')
filename_url = fields.Char('文件名/URL')
type = fields.Selection([
('1', '图纸'),
('2', '程序单'),
], string='类型')
status = fields.Selection([
('not_start', '未开始'),
('success', '成功'),
('fail', '失败'),
], string='状态', default='not_start')
machining_drawings = fields.Binary('2D加工图纸', related='product_id.machining_drawings', store=True)
workorder_id = fields.Many2one('mrp.workorder', string='工单')
cnc_worksheet = fields.Binary('程序单')
def demand_plan_print(self):
for record in self:
pdf_data = record.machining_drawings if record.type == '1' else record.cnc_worksheet
if pdf_data:
try:
# 执行打印
self.env['jikimo.printing'].sudo().print_pdf(pdf_data)
record.status = 'success'
t_part, c_part = record.demand_plan_id.print_count.split('C')
t_num = int(t_part[1:])
c_num = int(c_part)
if record.type == '1':
t_num += 1
elif record.type == '2':
c_num += 1
record.demand_plan_id.print_count = f"T{t_num}C{c_num}"
except Exception as e:
record.status = 'fail'
_logger.error(f"文件{record.filename_url}打印失败: {str(e)}")
class MrpWorkorder(models.Model):
_inherit = 'mrp.workorder'
def write(self, vals):
res = super(MrpWorkorder, self).write(vals)
for record in self:
if 'cnc_worksheet' in vals:
demand_plan_print = self.env['sf.demand.plan.print.wizard'].sudo().search(
[('workorder_id', '=', record.id)])
if demand_plan_print:
self.env['sf.demand.plan.print.wizard'].sudo().write(
{'cnc_worksheet': res.cnc_worksheet, 'filename_url': record.cnc_worksheet_name})
else:
demand_plan = self.env['sf.production.demand.plan'].sudo().search(
[('product_id', '=', record.product_id.id)])
if demand_plan:
wizard_vals = {
'demand_plan_id': demand_plan.id,
'model_id': demand_plan.model_id,
'type': '2',
'workorder_id': record.id,
'cnc_worksheet': record.cnc_worksheet,
'filename_url': record.cnc_worksheet_name
}
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
return res

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="action_plan_print_tree" model="ir.ui.view">
<field name="name">sf.demand.plan.print.wizard.tree</field>
<field name="model">sf.demand.plan.print.wizard</field>
<field name="arch" type="xml">
<tree string="打印">
<field name="model_id"/>
<field name="filename_url"/>
<field name="type"/>
<field name="machining_drawings" widget="adaptive_viewer"/>
<field name="cnc_worksheet" widget="pdf_viewer"/>
<field name="status"/>
</tree>
</field>
</record>
</odoo>

View File

@@ -17,7 +17,7 @@ class ResProductCategory(models.Model):
class ResProductProduct(models.Model): class ResProductProduct(models.Model):
_inherit = 'product.product' _inherit = 'product.product'
single_manufacturing = fields.Boolean(string="单个制造") # single_manufacturing = fields.Boolean(string="单个制造")
is_bfm = fields.Boolean('业务平台是否自动创建', default=False) is_bfm = fields.Boolean('业务平台是否自动创建', default=False)

View File

@@ -1,16 +1,16 @@
import logging # import logging
from odoo import fields, models, api # from odoo import fields, models, api
from odoo.exceptions import UserError # from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model): # class ResMrpRoutingWorkcenter(models.Model):
_inherit = 'mrp.routing.workcenter' # _inherit = 'mrp.routing.workcenter'
def init(self): # def init(self):
super(ResMrpRoutingWorkcenter, self).init() # super(ResMrpRoutingWorkcenter, self).init()
# 在模块初始化时触发计算字段的更新 # # 在模块初始化时触发计算字段的更新
records = self.search([]) # records = self.search([])
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')): # if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_workcenter',default='False')):
return # return
records.optional_process_parameters_date() # records.optional_process_parameters_date()
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True) # self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_workcenter', True)

View File

@@ -1,85 +1,85 @@
# -*- coding: utf-8 -*- # # -*- coding: utf-8 -*-
import logging # import logging
from odoo import fields, models, api # from odoo import fields, models, api
from odoo.exceptions import UserError # from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class SfProductionProcessParameter(models.Model): # class SfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter' # _inherit = 'sf.production.process.parameter'
@api.model # @api.model
def create(self, vals): # def create(self, vals):
# if vals.get('code', '/') == '/' or vals.get('code', '/') is False: # # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
# vals['code'] = '101'+self.routing_id.code +self.env['ir.sequence'].next_by_code('sf.production.process.parameter') # # vals['code'] = '101'+self.routing_id.code +self.env['ir.sequence'].next_by_code('sf.production.process.parameter')
if vals.get('routing_id'): # if vals.get('routing_id'):
# vals['gain_way'] = '外协' # # vals['gain_way'] = '外协'
routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id')) # routing_id = self.env['mrp.routing.workcenter'].browse(vals.get('routing_id'))
if routing_id.surface_technics_id and not vals.get('process_id'): # if routing_id.surface_technics_id and not vals.get('process_id'):
vals['process_id'] = routing_id.surface_technics_id.id # vals['process_id'] = routing_id.surface_technics_id.id
if vals.get('code', '/') == '/' or vals.get('code', '/') is False: # if vals.get('code', '/') == '/' or vals.get('code', '/') is False:
vals['code'] = '101' + routing_id.code + self.env['ir.sequence'].next_by_code( # vals['code'] = '101' + routing_id.code + self.env['ir.sequence'].next_by_code(
'sf.production.process.parameter') # 'sf.production.process.parameter')
obj = super(SfProductionProcessParameter, self).create(vals) # obj = super(SfProductionProcessParameter, self).create(vals)
return obj # return obj
def create_service_product(self): # def create_service_product(self):
service_categ = self.env.ref( # service_categ = self.env.ref(
'sf_dlm.product_category_surface_technics_sf').sudo() # 'sf_dlm.product_category_surface_technics_sf').sudo()
product_name = f"{self.process_id.name}_{self.name}" # product_name = f"{self.process_id.name}_{self.name}"
product_id = self.env['product.template'].search( # product_id = self.env['product.template'].search(
[("name", '=', product_name)]) # [("name", '=', product_name)])
if product_id: # if product_id:
product_id.server_product_process_parameters_id = self.id # product_id.server_product_process_parameters_id = self.id
else: # else:
res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')]) # res_partner = self.env['res.partner'].search([('name','=','湖南傲派自动化设备有限公司')])
self.env['product.template'].create({ # self.env['product.template'].create({
'detailed_type': 'service', # 'detailed_type': 'service',
'name': product_name, # 'name': product_name,
'invoice_policy': 'delivery', # 'invoice_policy': 'delivery',
'categ_id': service_categ.id, # 'categ_id': service_categ.id,
'description': f"基于{self.name}创建的服务产品", # 'description': f"基于{self.name}创建的服务产品",
'sale_ok': True, # 可销售 # 'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购 # 'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.id, # 'server_product_process_parameters_id': self.id,
'seller_ids': [(0, 0, { # 'seller_ids': [(0, 0, {
# 'delay': 1, # # 'delay': 1,
'partner_id': res_partner.id, # 'partner_id': res_partner.id,
'price': 1, })], # 'price': 1, })],
}) # })
def create_work_center(self): # def create_work_center(self):
production_process_parameter = self # production_process_parameter = self
if not production_process_parameter.process_id: # if not production_process_parameter.process_id:
return # return
if not production_process_parameter.routing_id: # if not production_process_parameter.routing_id:
workcenter_id = self.env['mrp.routing.workcenter'].search( # workcenter_id = self.env['mrp.routing.workcenter'].search(
[("surface_technics_id", '=', production_process_parameter.process_id.id)]) # [("surface_technics_id", '=', production_process_parameter.process_id.id)])
if not workcenter_id: # if not workcenter_id:
outsourcing_work_center = self.env['mrp.workcenter'].search( # outsourcing_work_center = self.env['mrp.workcenter'].search(
[("name", '=', '外协工作中心')]) # [("name", '=', '外协工作中心')])
routing_id = self.env['mrp.routing.workcenter'].create({ # routing_id = self.env['mrp.routing.workcenter'].create({
'workcenter_ids': [(6, 0, outsourcing_work_center.ids)], # 'workcenter_ids': [(6, 0, outsourcing_work_center.ids)],
'routing_tag': 'special', # 'routing_tag': 'special',
'routing_type': '表面工艺', # 'routing_type': '表面工艺',
'is_outsource': True, # 'is_outsource': True,
'surface_technics_id': production_process_parameter.process_id.id, # 'surface_technics_id': production_process_parameter.process_id.id,
'name': production_process_parameter.process_id.name, # 'name': production_process_parameter.process_id.name,
}) # })
production_process_parameter.routing_id = routing_id.id # production_process_parameter.routing_id = routing_id.id
else: # else:
production_process_parameter.routing_id = workcenter_id.id # production_process_parameter.routing_id = workcenter_id.id
def init(self): # def init(self):
super(SfProductionProcessParameter, self).init() # super(SfProductionProcessParameter, self).init()
# 在模块初始化时触发计算字段的更新 # # 在模块初始化时触发计算字段的更新
records = self.search([]) # records = self.search([])
if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process', # if str2bool(self.env['ir.config_parameter'].get_param('sf.production.process.parameter.is_init_process',
default='False')): # default='False')):
return # return
for record in records: # for record in records:
if not record.outsourced_service_products: # if not record.outsourced_service_products:
record.create_service_product() # record.create_service_product()
record.create_work_center() # record.create_work_center()
self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True) # self.env['ir.config_parameter'].set_param('sf.production.process.parameter.is_init_process', True)

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,7 @@
'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', # 'views/stock_warehouse_orderpoint.xml',
], ],
'assets': { 'assets': {

View File

@@ -2,18 +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 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='中控系统') @api_log('获取工单', requester='中控系统')
def get_Work_Info(self, **kw): def get_Work_Info(self, **kw):
@@ -56,7 +54,7 @@ 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='中控系统') @api_log('获取日计划', requester='中控系统')
def get_ShiftPlan(self, **kw): def get_ShiftPlan(self, **kw):
@@ -110,7 +108,7 @@ 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='中控系统') @api_log('工件预调(前置三元检测)', requester='中控系统')
def get_qcCheck(self, **kw): def get_qcCheck(self, **kw):
@@ -706,59 +704,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,4 @@ 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 # from . import stock_warehouse_orderpoint

View File

@@ -279,7 +279,7 @@ class MrpProduction(models.Model):
production_id.part_name = production_id.product_id.part_name production_id.part_name = production_id.product_id.part_name
elif production_id.product_id.categ_id.type == '坯料': elif production_id.product_id.categ_id.type == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name) match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -900,41 +900,41 @@ class MrpProduction(models.Model):
for workorder in production.workorder_ids: for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected() workorder.duration_expected = workorder._get_duration_expected()
def _create_subcontract_purchase_request(self, purchase_request_line): # def _create_subcontract_purchase_request(self, purchase_request_line):
sorted_list = sorted(purchase_request_line, key=itemgetter('name')) # sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
grouped_purchase_request_line = { # grouped_purchase_request_line = {
k: list(g) # k: list(g)
for k, g in groupby(sorted_list, key=itemgetter('name')) # for k, g in groupby(sorted_list, key=itemgetter('name'))
} # }
for name, request_line in grouped_purchase_request_line.items(): # for name, request_line in grouped_purchase_request_line.items():
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id')) # request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
grouped_purchase_request_line_sorted_list = { # grouped_purchase_request_line_sorted_list = {
k: list(g) # k: list(g)
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id')) # for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
} # }
purchase_request_model = self.env["purchase.request"] # purchase_request_model = self.env["purchase.request"]
origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')}) # origin = ", ".join({item['production_name'] for item in request_line_sorted_list if item.get('production_name')})
pr = purchase_request_model.create({ # pr = purchase_request_model.create({
"origin": origin, # "origin": origin,
"company_id": self.company_id.id, # "company_id": self.company_id.id,
"picking_type_id": self.env.ref('stock.picking_type_in').id, # "picking_type_id": self.env.ref('stock.picking_type_in').id,
"group_id": request_line[0].get('group_id'), # "group_id": request_line[0].get('group_id'),
"requested_by": self.env.context.get("uid", self.env.uid), # "requested_by": self.env.context.get("uid", self.env.uid),
"assigned_to": False, # "assigned_to": False,
"bom_id": self[0].bom_id.id, # "bom_id": self[0].bom_id.id,
"is_subcontract":True, # "is_subcontract":True,
}) # })
self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref( # self[0].bom_id.bom_line_ids.product_id.route_ids = [(4,self.env.ref(
'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'] = cur_request_line['product_qty']
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)
cur_request_line.pop('production_name', None) # cur_request_line.pop('production_name', None)
self.env["purchase.request.line"].create(cur_request_line) # self.env["purchase.request.line"].create(cur_request_line)
pr.button_approved() # pr.button_approved()
# 外协出入库单处理 # 外协出入库单处理
def get_subcontract_pick_purchase(self): def get_subcontract_pick_purchase(self):
@@ -962,14 +962,14 @@ class MrpProduction(models.Model):
if not sorted_workorders: if not sorted_workorders:
return return
for workorders in reversed(sorted_workorders): for workorders in reversed(sorted_workorders):
# self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders) self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
# self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names) self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request( # purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(
workorders, production) # workorders, production)
all_workorders += workorders # all_workorders += workorders
self._create_subcontract_purchase_request(purchase_request_line) # self._create_subcontract_purchase_request(purchase_request_line)
for workorder in all_workorders: # for workorder in all_workorders:
workorder._compute_pr_mp_count() # workorder._compute_pr_mp_count()
# 工单排序 # 工单排序
def _reset_work_order_sequence1(self, k): def _reset_work_order_sequence1(self, k):
for rec in self: for rec in self:

View File

@@ -1,7 +1,7 @@
import logging import logging
from odoo import fields, models, api from odoo import fields, models, api
from odoo.exceptions import UserError from odoo.exceptions import UserError
from odoo.tools import str2bool # from odoo.tools import str2bool
class ResMrpRoutingWorkcenter(models.Model): class ResMrpRoutingWorkcenter(models.Model):
@@ -25,20 +25,20 @@ class ResMrpRoutingWorkcenter(models.Model):
workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True) workcenter_ids = fields.Many2many('mrp.workcenter', 'rel_workcenter_route', required=True)
bom_id = fields.Many2one('mrp.bom', required=False) bom_id = fields.Many2one('mrp.bom', required=False)
surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺") surface_technics_id = fields.Many2one('sf.production.process', string="表面工艺")
optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数') # optional_process_parameters = fields.One2many('sf.production.process.parameter','routing_id',string='可选工艺参数')
reserved_duration = fields.Float('预留时长', default=30, tracking=True) reserved_duration = fields.Float('预留时长', default=30, tracking=True)
is_outsource = fields.Boolean('外协', default=False) is_outsource = fields.Boolean('外协', default=False)
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录') individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录')
@api.onchange('surface_technics_id') # @api.onchange('surface_technics_id')
def optional_process_parameters_date(self): # def optional_process_parameters_date(self):
for record in self: # for record in self:
if not record.surface_technics_id: # if not record.surface_technics_id:
continue # continue
parameter_ids = self.env['sf.production.process.parameter'].search([ # parameter_ids = self.env['sf.production.process.parameter'].search([
('process_id', '=', record.surface_technics_id.id), # ('process_id', '=', record.surface_technics_id.id),
]) # ])
record.optional_process_parameters = parameter_ids.ids # record.optional_process_parameters = parameter_ids.ids
# @api.model # @api.model
# def _auto_init(self): # def _auto_init(self):

View File

@@ -21,16 +21,16 @@ class ResWorkcenter(models.Model):
related='equipment_id.production_line_id', store=True) related='equipment_id.production_line_id', store=True)
is_process_outsourcing = fields.Boolean('工艺外协') is_process_outsourcing = fields.Boolean('工艺外协')
users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True) users_ids = fields.Many2many("res.users", 'users_workcenter', tracking=True)
@api.constrains('name') # @api.constrains('name')
def _check_unique_name_code(self): # def _check_unique_name_code(self):
for record in self: # for record in self:
# 检查是否已经存在相同的 name 和 code 组合 # # 检查是否已经存在相同的 name 和 code 组合
existing = self.search([ # existing = self.search([
('name', '=', record.name), # ('name', '=', record.name),
('id', '!=', record.id) # 排除当前记录 # ('id', '!=', record.id) # 排除当前记录
]) # ])
if existing: # if existing:
raise ValueError('记录已存在') # raise ValueError('记录已存在')
def write(self, vals): def write(self, vals):
if 'users_ids' in vals: if 'users_ids' in vals:
old_users = self.users_ids old_users = self.users_ids

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
@@ -71,21 +70,21 @@ class ResMrpWorkOrder(models.Model):
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效', delivery_warning = fields.Selection([('normal', '正常'), ('warning', '告警'), ('overdue', '逾期')], string='时效',
tracking=True) tracking=True)
back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True) back_button_display = fields.Boolean(default=False, compute='_compute_back_button_display', store=True)
pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True) # pr_mp_count = fields.Integer('采购申请单数量', compute='_compute_pr_mp_count', store=True)
@api.depends('state') # @api.depends('state')
def _compute_pr_mp_count(self): # def _compute_pr_mp_count(self):
for item in self: # for item in self:
if not item.is_subcontract: # if not item.is_subcontract:
item.pr_mp_count = 0 # item.pr_mp_count = 0
continue # continue
pr_ids = self.env['purchase.request'].sudo().search( # pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'), # [('origin', 'like', item.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')]) # ('state', '!=', 'rejected')])
if pr_ids: # if pr_ids:
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):
@@ -131,8 +130,14 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False record.back_button_display = False
else: else:
next_workorder = sorted_workorders[position + 1] next_workorder = sorted_workorders[position + 1]
next_state = next_workorder.state # 持续获取下一个工单,直到找到一个不是返工的工单
if (next_state == 'ready' or ( while next_workorder and next_workorder.state == 'rework':
position += 1
if position + 1 < len(sorted_workorders):
next_workorder = sorted_workorders[position + 1]
else:
next_workorder = None
if next_workorder and (next_workorder.state == 'ready' or (
next_workorder.state == 'waiting' and next_workorder.is_subcontract)) and cur_workorder.state == 'done': next_workorder.state == 'waiting' and next_workorder.is_subcontract)) and cur_workorder.state == 'done':
record.back_button_display = True record.back_button_display = True
else: else:
@@ -321,6 +326,7 @@ class ResMrpWorkOrder(models.Model):
tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True) tag_type = fields.Selection([("重新加工", "重新加工")], string="标签", tracking=True)
technology_design_id = fields.Many2one('sf.technology.design') technology_design_id = fields.Many2one('sf.technology.design')
cnc_worksheet_name = fields.Char('工作指令文件名', readonly=True)
def _compute_default_construction_period_status(self): def _compute_default_construction_period_status(self):
need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected', 'done'] need_list = ['pending', 'waiting', 'ready', 'progress', 'to be detected', 'done']
@@ -441,15 +447,15 @@ class ResMrpWorkOrder(models.Model):
action['context'] = dict(self._context, default_origin=self.name) action['context'] = dict(self._context, default_origin=self.name)
return action return action
@api.depends('state', 'production_id.name')
def _compute_surface_technics_purchase_ids(self): def _compute_surface_technics_purchase_ids(self):
for order in self: for order in self:
if order.routing_type == '表面工艺' and order.state not in ['cancel']: if order.routing_type == '表面工艺' and order.state not in ['cancel']:
# domain = [('group_id', '=', self.production_id.procurement_group_id.id),
# ('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
domain = [('purchase_type', '=', 'consignment'), domain = [('purchase_type', '=', 'consignment'),
('origin', 'like', '%' + self.production_id.name + '%'), ('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')] ('state', '!=', 'cancel')]
# domain = [('purchase_type', '=', 'consignment'),
# ('origin', 'like', '%' + self.production_id.name + '%'),
# ('state', '!=', 'cancel')]
purchase = self.env['purchase.order'].search(domain) purchase = self.env['purchase.order'].search(domain)
order.surface_technics_purchase_count = 0 order.surface_technics_purchase_count = 0
if not purchase: if not purchase:
@@ -462,30 +468,30 @@ class ResMrpWorkOrder(models.Model):
else: else:
order.surface_technics_purchase_count = 0 order.surface_technics_purchase_count = 0
def action_view_pr_mrp_workorder(self): # def action_view_pr_mrp_workorder(self):
""" # """
采购请求 # 采购请求
""" # """
self.ensure_one() # self.ensure_one()
pr_ids = self.env['purchase.request'].sudo().search( # pr_ids = self.env['purchase.request'].sudo().search(
[('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'), # [('origin', 'like', self.production_id.name), ('is_subcontract', '=', 'True'),
('state', '!=', 'rejected')]) # ('state', '!=', 'rejected')])
action = { # action = {
'res_model': 'purchase.request', # 'res_model': 'purchase.request',
'type': 'ir.actions.act_window', # 'type': 'ir.actions.act_window',
} # }
if len(pr_ids) == 1: # if len(pr_ids) == 1:
action.update({ # action.update({
'view_mode': 'form', # 'view_mode': 'form',
'res_id': pr_ids[0].id, # 'res_id': pr_ids[0].id,
}) # })
else: # else:
action.update({ # action.update({
'name': _("%s生成采购请求单", self.name), # 'name': _("从 %s生成采购请求单", self.name),
'domain': [('id', 'in', pr_ids)], # 'domain': [('id', 'in', pr_ids)],
'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()
@@ -772,170 +778,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,12 +1252,12 @@ class ResMrpWorkOrder(models.Model):
}] }]
return workorders_values_str return workorders_values_str
def check_lot_exists(self, picking_id, lot_id): # def check_lot_exists(self, picking_id, lot_id):
return bool( # return bool(
picking_id.move_ids.move_line_ids.filtered( # picking_id.move_ids.move_line_ids.filtered(
lambda line: line.lot_id.id == lot_id # 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)
@@ -1317,10 +1299,10 @@ 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( # picking_id = workorder.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_ids
# move_out = workorder.move_subcontract_workorder_ids[1] 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 +1341,11 @@ 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 # picking_id = workorder.production_id.picking_ids.filtered(
wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区') # lambda
move_out = picking_id.move_ids # 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})
@@ -1436,9 +1419,10 @@ 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 == '外协加工区') # picking_id = self.production_id.picking_ids.filtered(
move_out = picking_id.move_ids # 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.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(

View File

@@ -795,7 +795,7 @@ class ResProductMo(models.Model):
for record in self: for record in self:
if record.categ_id.name == '坯料': if record.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.name) match = re.search(r'(S\d{5}-\d+)', record.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

@@ -59,83 +59,83 @@ class PurchaseOrder(models.Model):
production_id = self.env['mrp.production'].search([('origin', 'in', origins)]) production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
purchase.production_count = len(production_id) purchase.production_count = len(production_id)
def process_replenish(self,production,total_qty): # def process_replenish(self,production,total_qty):
record = self # record = self
bom_line_id = production.bom_id.bom_line_ids # bom_line_id = production.bom_id.bom_line_ids
replenish = self.env['stock.warehouse.orderpoint'].search([ # replenish = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', bom_line_id.product_id.id), # ('product_id', '=', bom_line_id.product_id.id),
( # (
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id), # 'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# ('state', 'in', ['draft', 'confirmed']) # # ('state', 'in', ['draft', 'confirmed'])
], limit=1) # ], limit=1)
if not replenish: # if not replenish:
replenish_model = self.env['stock.warehouse.orderpoint'] # replenish_model = self.env['stock.warehouse.orderpoint']
replenish = replenish_model.create({ # replenish = replenish_model.create({
'product_id': bom_line_id.product_id.id, # 'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref( # 'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id, # 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id, # 'group_id': record.group_id.id,
'qty_to_order': total_qty, # 'qty_to_order': total_qty,
'origin': record.name, # 'origin': record.name,
}) # })
else: # else:
replenish.write({ # replenish.write({
'product_id': bom_line_id.product_id.id, # 'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref( # 'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id, # 'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id, # 'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id, # 'group_id': record.group_id.id,
'qty_to_order': total_qty + replenish.qty_to_order, # 'qty_to_order': total_qty + replenish.qty_to_order,
'origin': record.name + ',' + replenish.origin, # 'origin': record.name + ',' + replenish.origin,
}) # })
replenish.action_replenish() # replenish.action_replenish()
def outsourcing_service_replenishment(self): # def outsourcing_service_replenishment(self):
record = self # record = self
if record.purchase_type != 'consignment': # if record.purchase_type != 'consignment':
return # return
grouped_lines = {} # grouped_lines = {}
for line in record.order_line: # for line in record.order_line:
if line.related_product.id not in grouped_lines: # if line.related_product.id not in grouped_lines:
grouped_lines[line.related_product.id] = [] # grouped_lines[line.related_product.id] = []
grouped_lines[line.related_product.id].append(line) # grouped_lines[line.related_product.id].append(line)
for product_id,lines in grouped_lines.items(): # for product_id,lines in grouped_lines.items():
production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) # production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not production: # if not production:
continue # continue
total_qty = sum(line.product_qty for line in lines) # total_qty = sum(line.product_qty for line in lines)
record.process_replenish(production,total_qty) # record.process_replenish(production,total_qty)
for product_id,lines in grouped_lines.items(): # for product_id,lines in grouped_lines.items():
productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) # productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
if not productions: # if not productions:
continue # continue
# 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', '=', productions.bom_id.bom_line_ids.product_id.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('请先完成坯料入库') # raise UserError('请先完成坯料入库')
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'])
if not work_ids: # if not work_ids:
continue # continue
min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence) # min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
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_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})
if not mo.move_line_ids: # if not mo.move_line_ids:
self.env['stock.move.line'].create( # self.env['stock.move.line'].create(
mo.get_move_line(production_id, min_sequence_wk)) # mo.get_move_line(production_id, min_sequence_wk))
# product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1) # product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
# match = re.search(r'(S\d{5}-\d)',product.name) # match = re.search(r'(S\d{5}-\d)',product.name)
# pass # pass
@@ -146,7 +146,7 @@ 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()
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()
@@ -208,7 +208,7 @@ class PurchaseOrderLine(models.Model):
continue continue
if record.product_id.categ_id.name == '坯料': if record.product_id.categ_id.name == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', record.product_id.name) match = re.search(r'(S\d{5}-\d+)', record.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)

View File

@@ -1,30 +1,30 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import base64 # import base64
import datetime # import datetime
import logging # import logging
import json # import json
import os # import os
import re # import re
import traceback # import traceback
from operator import itemgetter # from operator import itemgetter
import requests # import requests
from itertools import groupby # from itertools import groupby
from collections import defaultdict, namedtuple # from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _ # from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError # from odoo.exceptions import UserError, ValidationError
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime # from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
class PurchaseRequestLine(models.Model): # class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request' # _inherit = 'purchase.request'
is_subcontract = fields.Boolean(string='是否外协',default=False) # is_subcontract = fields.Boolean(string='是否外协',default=False)
class PurchaseRequestLine(models.Model): # class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line' # _inherit = 'purchase.request.line'
is_subcontract = fields.Boolean(string='是否外协') # is_subcontract = fields.Boolean(string='是否外协')
class PurchaseRequest(models.Model): # class PurchaseRequest(models.Model):
_inherit = 'purchase.request' # _inherit = 'purchase.request'
bom_id = fields.Many2one('mrp.bom') # bom_id = fields.Many2one('mrp.bom')

View File

@@ -56,7 +56,7 @@ 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 # line.product_id.tracking = product_template_id.tracking
@@ -76,7 +76,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

@@ -7,74 +7,74 @@ from odoo.exceptions import UserError, ValidationError
class SfProductionProcessParameter(models.Model): class SfProductionProcessParameter(models.Model):
_inherit = 'sf.production.process.parameter' _inherit = 'sf.production.process.parameter'
service_products = fields.Many2one( # service_products = fields.Many2one(
'product.template', # 'product.template',
string='外协服务产品',compute='_compute_service_products',inverse='_inverse_service_products', # string='外协服务产品',compute='_compute_service_products',inverse='_inverse_service_products',
store=True # store=True
) # )
outsourced_service_products = fields.One2many( # outsourced_service_products = fields.One2many(
'product.template', # 另一个模型的名称 # 'product.template', # 另一个模型的名称
'server_product_process_parameters_id', # 对应的 Many2one 字段名称 # 'server_product_process_parameters_id', # 对应的 Many2one 字段名称
string='外协服务产品' # string='外协服务产品'
) # )
is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False) # is_product_button = fields.Boolean(compute='_compute_is_product_button',default=False)
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False) # is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序") # routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
@api.depends('outsourced_service_products') # @api.depends('outsourced_service_products')
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.ids 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:
if record.service_products: # if record.service_products:
# 确保关联关系正确 # # 确保关联关系正确
record.outsourced_service_products = record.service_products.ids if record.service_products else False # record.outsourced_service_products = record.service_products.ids if record.service_products else False
else: # else:
record.outsourced_service_products = False # record.outsourced_service_products = False
def name_get(self): # def name_get(self):
result = [] # result = []
for record in self: # for record in self:
name = f"{record.process_id.name} - {record.name}" # 自定义显示格式 # name = f"{record.process_id.name} - {record.name}" # 自定义显示格式
result.append((record.id, name)) # result.append((record.id, name))
return result # return result
@api.constrains('outsourced_service_products') # @api.constrains('outsourced_service_products')
def _validate_partner_limit(self): # def _validate_partner_limit(self):
for record in self: # for record in self:
if len(record.outsourced_service_products) > 1: # if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联") # raise ValidationError("工艺参数不能与多个产品关联")
@api.onchange('outsourced_service_products') # @api.onchange('outsourced_service_products')
def _onchange_validate_partner_limit(self): # def _onchange_validate_partner_limit(self):
for record in self: # for record in self:
if len(record.outsourced_service_products) > 1: # if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联") # raise ValidationError("工艺参数不能与多个产品关联")
@api.depends('outsourced_service_products') # @api.depends('outsourced_service_products')
def _compute_is_product_button(self): # def _compute_is_product_button(self):
for record in self: # for record in self:
if record.outsourced_service_products: # if record.outsourced_service_products:
record.is_product_button = True # record.is_product_button = True
else: # else:
record.is_product_button = False # record.is_product_button = False
def has_wksp_prefix(self): # def has_wksp_prefix(self):
""" # """
判断字符串是否以WKSP开头不区分大小写 # 判断字符串是否以WKSP开头不区分大小写
:param text: 要检查的字符串 # :param text: 要检查的字符串
:return: True/False # :return: True/False
""" # """
return self.code.upper().startswith('101'+self.routing_id.code) # return self.code.upper().startswith('101'+self.routing_id.code)
@api.depends('outsourced_service_products','code') # @api.depends('outsourced_service_products','code')
def _compute_is_delete_button(self): # def _compute_is_delete_button(self):
for record in self: # for record in self:
if record.outsourced_service_products and record.has_wksp_prefix(): # if record.outsourced_service_products and record.has_wksp_prefix():
record.is_delete_button = False # record.is_delete_button = False
elif record.outsourced_service_products: # elif record.outsourced_service_products:
record.is_delete_button = True # record.is_delete_button = True
else: # else:
record.is_delete_button = True # record.is_delete_button = True
@api.model @api.model
def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None): def _name_search(self, name, args=None, operator='ilike', limit=100, name_get_uid=None):
if self._context.get('route_id'): if self._context.get('route_id'):
@@ -90,19 +90,19 @@ class SfProductionProcessParameter(models.Model):
return self._search(domain, limit=limit, access_rights_uid=name_get_uid) return self._search(domain, limit=limit, access_rights_uid=name_get_uid)
return super()._name_search(name, args, operator, limit, name_get_uid) return super()._name_search(name, args, operator, limit, name_get_uid)
def action_create_service_product(self): # def action_create_service_product(self):
if self.id: # 如果是已存在的记录 # if self.id: # 如果是已存在的记录
self.write({}) # 空写入会触发保存 # self.write({}) # 空写入会触发保存
else: # 如果是新记录 # else: # 如果是新记录
self = self.create(self._convert_to_write(self.read()[0])) # self = self.create(self._convert_to_write(self.read()[0]))
return { # return {
'type': 'ir.actions.act_window', # 'type': 'ir.actions.act_window',
'name': '向导名称', # 'name': '向导名称',
'res_model': 'product.creation.wizard', # 'res_model': 'product.creation.wizard',
'view_mode': 'form', # 'view_mode': 'form',
'target': 'new', # 'target': 'new',
'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID # 'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID
} # }
# #
# return { # return {
# 'name': '创建服务产品', # 'name': '创建服务产品',
@@ -116,6 +116,6 @@ class SfProductionProcessParameter(models.Model):
# }, # },
# } # }
def action_hide_service_products(self): # def action_hide_service_products(self):
# self.outsourced_service_products.active = False # # self.outsourced_service_products.active = False
self.active = False # self.active = False

View File

@@ -564,6 +564,13 @@ class StockPicking(models.Model):
part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True) part_numbers = fields.Char(string="零件图号", compute='_compute_part_info', store=True, index=True)
part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True) part_names = fields.Char(string="零件名称", compute='_compute_part_info', store=True, index=True)
model_id = fields.Char('模型ID', compute='_compute_model_id', store=True, index=True)
@api.depends('move_ids_without_package.model_id')
def _compute_model_id(self):
for picking in self:
model_id = picking.move_ids_without_package.mapped('model_id')
picking.model_id = ','.join(filter(None, model_id))
@api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name') @api.depends('move_ids_without_package.part_number', 'move_ids_without_package.part_name')
def _compute_part_info(self): def _compute_part_info(self):
@@ -631,62 +638,84 @@ 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 # lot_ids = None
product_ids = self.move_ids.mapped('product_id') # product_ids = self.move_ids.mapped('product_id')
if not self.move_ids[0].product_id.single_manufacturing and self.move_ids[0].product_id.tracking == 'none': # 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') # 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'] # 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 == '制造前': # if res and self.location_id.name == '外协收料区' and self.location_dest_id.name == '制造前':
# 如果是最后一张外协入库单,则设置库存位置的预留数量 # # 如果是最后一张外协入库单,则设置库存位置的预留数量
for production_id in production_ids: # for production_id in production_ids:
if lot_ids: # if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id # lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered( # # picking_ids = 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 == '制造前')
if lot_id in lot_ids: # if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered( # workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract) # lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
else: # else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract) # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
# lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id # # lot_id = workorder.production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = workorder.production_id.picking_ids.filtered( # # picking_ids = workorder.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 == '制造前')
# if move_in: # # if move_in:
# workorder = move_in.subcontract_workorder_id # # workorder = move_in.subcontract_workorder_id
# workorders = workorder.production_id.workorder_ids # # workorders = workorder.production_id.workorder_ids
# subcontract_workorders = workorders.filtered( # # subcontract_workorders = workorders.filtered(
# lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence') # # lambda wo: wo.is_subcontract == True and wo.state != 'cancel').sorted('sequence')
# # if workorder == subcontract_workorders[-1]: # # # if workorder == subcontract_workorders[-1]:
# # self.env['stock.quant']._update_reserved_quantity( # # # self.env['stock.quant']._update_reserved_quantity(
# # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, # # # move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
# # lot_id=move_in.move_line_ids.lot_id, # # # lot_id=move_in.move_line_ids.lot_id,
# # package_id=False, owner_id=False, strict=False # # # package_id=False, owner_id=False, strict=False
# # ) # # # )
# workorder.button_finish() # # workorder.button_finish()
if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区': # if res and self.location_id.name == '制造前' and self.location_dest_id.name == '外协加工区':
for production_id in production_ids: # for production_id in production_ids:
if lot_ids: # if lot_ids:
lot_id = production_id.move_raw_ids.move_line_ids.lot_id # lot_id = production_id.move_raw_ids.move_line_ids.lot_id
# picking_ids = production_id.picking_ids.filtered( # # picking_ids = 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 == '制造前')
if lot_id in lot_ids: # if lot_id in lot_ids:
workorder_id = production_id.workorder_ids.filtered( # workorder_id = production_id.workorder_ids.filtered(
lambda a: a.state == 'progress' and a.is_subcontract) # lambda a: a.state == 'progress' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_finish() # workorder_id.button_finish()
else: # else:
workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract) # workorder_id = production_id.workorder_ids.filtered(lambda a: a.state == 'ready' and a.is_subcontract)
if not workorder_id: # if not workorder_id:
continue # continue
workorder_id.button_start() # workorder_id.button_start()
picking_type_in = self.env.ref('sf_manufacturing.outcontract_picking_in').id
if res is True and self.picking_type_id.id == picking_type_in:
# 如果是最后一张外协入库单,则设置库存位置的预留数量
move_in = self.move_ids
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()
picking_type_out = self.env.ref('sf_manufacturing.outcontract_picking_out').id
if res and self.picking_type_id.id == picking_type_out:
move_out = self.move_ids
if move_out:
workorder = move_out.subcontract_workorder_id
workorder.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)])
@@ -817,6 +846,7 @@ class ReStockMove(models.Model):
materiel_height = fields.Float(string='物料高度', digits=(16, 4)) materiel_height = fields.Float(string='物料高度', digits=(16, 4))
part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True) part_number = fields.Char(string='零件图号', compute='_compute_part_info', store=True)
part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True) part_name = fields.Char(string='零件名称', compute='_compute_part_info', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
@api.depends('product_id') @api.depends('product_id')
def _compute_part_info(self): def _compute_part_info(self):
@@ -827,7 +857,7 @@ class ReStockMove(models.Model):
move.part_name = move.product_id.part_name move.part_name = move.product_id.part_name
elif move.product_id.categ_id.type == '坯料': elif move.product_id.categ_id.type == '坯料':
product_name = '' product_name = ''
match = re.search(r'(S\d{5}-\d)', move.product_id.name) match = re.search(r'(S\d{5}-\d+)', move.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -859,7 +889,7 @@ class ReStockMove(models.Model):
continue continue
product_name = '' product_name = ''
logging.info('制造订单的产品 %s', production_id.product_id.name) logging.info('制造订单的产品 %s', production_id.product_id.name)
match = re.search(r'(S\d{5}-\d)', production_id.product_id.name) match = re.search(r'(S\d{5}-\d+)', production_id.product_id.name)
# 如果匹配成功,提取结果 # 如果匹配成功,提取结果
if match: if match:
product_name = match.group(0) product_name = match.group(0)
@@ -937,7 +967,7 @@ class ReStockMove(models.Model):
'location_id': self.picking_id.location_id.id, 'location_id': self.picking_id.location_id.id,
'location_dest_id': self.picking_id.location_dest_id.id, 'location_dest_id': self.picking_id.location_dest_id.id,
'picking_id': self.picking_id.id, 'picking_id': self.picking_id.id,
'reserved_uom_qty': 1.0, 'reserved_uom_qty': self.product_uom_qty,
'lot_id': production_id.move_line_raw_ids.lot_id.id, 'lot_id': production_id.move_line_raw_ids.lot_id.id,
'company_id': self.env.company.id, 'company_id': self.env.company.id,
# 'workorder_id': '' if not sorted_workorders else sorted_workorders.id, # 'workorder_id': '' if not sorted_workorders else sorted_workorders.id,

View File

@@ -383,7 +383,7 @@
<field name="process_parameters_id" <field name="process_parameters_id"
attrs="{'readonly': [('id', '!=', False),('routing_tag', '=', 'standard')]}" attrs="{'readonly': [('id', '!=', False),('routing_tag', '=', 'standard')]}"
string="参数" context="{'route_id':route_id,'production_id': production_id}" string="参数" context="{'route_id':route_id,'production_id': production_id}"
options="{'no_create': True}" domain="[('routing_id', '=', 'route_id')]"/> options="{'no_create': True}"/>
<field name="panel" readonly="1"/> <field name="panel" readonly="1"/>
<field name="routing_tag" readonly="1" widget="badge" <field name="routing_tag" readonly="1" widget="badge"
decoration-success="routing_tag == 'standard'" decoration-success="routing_tag == 'standard'"
@@ -602,6 +602,7 @@
<field name="part_number"/> <field name="part_number"/>
<field name="sale_order_id"/> <field name="sale_order_id"/>
<field name="deadline_of_delivery" icon="fa-calendar" enable_counters="1" filter_domain="[('deadline_of_delivery', 'ilike', self)]"/> <field name="deadline_of_delivery" icon="fa-calendar" enable_counters="1" filter_domain="[('deadline_of_delivery', 'ilike', self)]"/>
<field name="model_id"/>
</xpath> </xpath>
<xpath expr="//field[@name='product_variant_attributes']" position="attributes"> <xpath expr="//field[@name='product_variant_attributes']" position="attributes">
<attribute name="invisible">1</attribute> <attribute name="invisible">1</attribute>

View File

@@ -22,7 +22,7 @@
<field name="is_repeat"/> <field name="is_repeat"/>
<field name="reserved_duration"/> <field name="reserved_duration"/>
</field> </field>
<xpath expr="//notebook/page[1]" position="before"> <!-- <xpath expr="//notebook/page[1]" position="before">
<page string="可选工艺参数"> <page string="可选工艺参数">
<field name="optional_process_parameters"> <field name="optional_process_parameters">
<tree editable="bottom"> <tree editable="bottom">
@@ -30,9 +30,9 @@
<field name="is_delete_button" invisible="1"/> <field name="is_delete_button" invisible="1"/>
<field name="code" attrs="{'readonly': True}"/> <field name="code" attrs="{'readonly': True}"/>
<field name="name" required="1"/> <field name="name" required="1"/>
<field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> <field name="service_products" domain="[('detailed_type', '=', 'service'),('server_product_process_parameters_id', '=', False)]"/> -->
<!-- 按钮列 --> <!-- 按钮列 -->
<button name="action_create_service_product" string="创建服务产品" type="object" <!-- <button name="action_create_service_product" string="创建服务产品" type="object"
class="btn-primary" class="btn-primary"
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/> attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
<button name="action_hide_service_products" string="删除" type="object" <button name="action_hide_service_products" string="删除" type="object"
@@ -41,7 +41,7 @@
</tree> </tree>
</field> </field>
</page> </page>
</xpath> </xpath> -->
</field> </field>
</record> </record>
</data> </data>

View File

@@ -144,7 +144,7 @@
statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/> statusbar_visible="pending,waiting,ready,progress,to be detected,done,rework"/>
</xpath> </xpath>
<xpath expr="//div[@name='button_box']" position="inside"> <xpath expr="//div[@name='button_box']" position="inside">
<button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button" <!-- <button type="object" name="action_view_pr_mrp_workorder" class="oe_stat_button"
icon="fa-credit-card" icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user" groups="base.group_user,sf_base.group_sf_order_user"
attrs="{'invisible': [('pr_mp_count', '=', 0)]}"> attrs="{'invisible': [('pr_mp_count', '=', 0)]}">
@@ -154,7 +154,7 @@
</span> </span>
<span class="o_stat_text">采购申请</span> <span class="o_stat_text">采购申请</span>
</div> </div>
</button> </button> -->
<button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button" <button type="object" name="action_view_surface_technics_purchase" class="oe_stat_button"
icon="fa-credit-card" icon="fa-credit-card"
groups="base.group_user,sf_base.group_sf_order_user" groups="base.group_user,sf_base.group_sf_order_user"
@@ -677,8 +677,9 @@
<field name="inherit_id" ref="mrp.view_mrp_production_work_order_search"/> <field name="inherit_id" ref="mrp.view_mrp_production_work_order_search"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<field name="product_id" position="after"> <field name="product_id" position="after">
<field name="part_number" string="成品零件图号"/> <field name="part_number" string="零件图号"/>
<field name="model_id" string="模型id"/> <field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
</field> </field>
<xpath expr="//filter[@name='progress']" position="after"> <xpath expr="//filter[@name='progress']" position="after">
<filter string="待检测" name="state" domain="[('state','=','to be detected')]"/> <filter string="待检测" name="state" domain="[('state','=','to be detected')]"/>

View File

@@ -12,5 +12,18 @@
</xpath> </xpath>
</field> </field>
</record> </record>
<record id="product_template_search_inherit_sf_manufacturing" model="ir.ui.view">
<field name="name">product.template.search</field>
<field name="model">product.template</field>
<field name="inherit_id" ref="product.product_template_search_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='categ_id']" position="after">
<field name="part_number" string="零件图号"/>
<field name="part_name" string="零件名称"/>
<field name="model_id" string="模型ID"/>
</xpath>
</field>
</record>
</data> </data>
</odoo> </odoo>

View File

@@ -73,6 +73,7 @@
<xpath expr="//field[@name='picking_type_id']" position="after"> <xpath expr="//field[@name='picking_type_id']" position="after">
<field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/> <field name="part_numbers" string="零件图号" filter_domain="[('part_numbers', 'ilike', self)]"/>
<field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/> <field name="part_names" string="零件名称" filter_domain="[('part_names', 'ilike', self)]"/>
<field name="model_id" string="模型ID" filter_domain="[('model_id', 'ilike', self)]"/>
</xpath> </xpath>
</field> </field>
</record> </record>

View File

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

View File

@@ -24,6 +24,7 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res = {'status': 1, 'message': '成功'} res = {'status': 1, 'message': '成功'}
datas = request.httprequest.data datas = request.httprequest.data
model_id = None model_id = None
part_number = None
ret = json.loads(datas) ret = json.loads(datas)
ret = json.loads(ret['result']) ret = json.loads(ret['result'])
logging.info('下发编程单:%s' % ret) logging.info('下发编程单:%s' % ret)
@@ -59,7 +60,6 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no']) res['message'] = '编程单号为%s的CNC程序文件从FTP拉取失败' % (ret['programming_no'])
return json.JSONEncoder().encode(res) return json.JSONEncoder().encode(res)
for production in productions: for production in productions:
model_id = production.product_id.model_id # 一个编程单的制造订单对应同一个模型
production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False}) production.write({'programming_state': '已编程', 'work_state': '已编程', 'is_rework': False})
for panel in ret['processing_panel'].split(','): for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单 # 查询状态为进行中且工序类型为CNC加工的工单
@@ -76,6 +76,10 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
cnc_workorder_has.write( cnc_workorder_has.write(
{'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret), {'cnc_ids': cnc_workorder_has.cnc_ids.sudo()._json_cnc_processing(panel, ret),
'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)}) 'cmm_ids': cnc_workorder_has.cmm_ids.sudo()._json_cmm_program(panel, ret)})
# 一个编程单的制造订单对应同一个模型
model_id = productions[0].product_id.model_id
part_number = productions[0].product_id.part_number
for panel in ret['processing_panel'].split(','): for panel in ret['processing_panel'].split(','):
# 查询状态为进行中且工序类型为CNC加工的工单 # 查询状态为进行中且工序类型为CNC加工的工单
cnc_workorder = productions.workorder_ids.filtered( cnc_workorder = productions.workorder_ids.filtered(
@@ -87,16 +91,25 @@ class Sf_Mrs_Connect(http.Controller, MultiInheritController):
program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel) program_path_tmp_panel = os.path.join('/tmp', ret['folder_name'], 'return', panel)
files_panel = os.listdir(program_path_tmp_panel) files_panel = os.listdir(program_path_tmp_panel)
panel_file_path = '' panel_file_path = ''
panel_file_name = ''
if files_panel: if files_panel:
for file in files_panel: for file in files_panel:
file_extension = os.path.splitext(file)[1] file_extension = os.path.splitext(file)[1]
if file_extension.lower() == '.pdf': if file_extension.lower() == '.pdf':
panel_file_path = os.path.join(program_path_tmp_panel, file) panel_file_path = os.path.join(program_path_tmp_panel, file)
panel_file_name = os.path.splitext(file)[0]
logging.info('panel_file_path:%s' % panel_file_path) logging.info('panel_file_path:%s' % panel_file_path)
logging.info('panel_file_name:%s' % panel_file_name)
# 向编程单中添加二维码 # 向编程单中添加二维码
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(
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read())}) panel_file_path,
model_id,
"模型ID%s" % model_id,
"零件图号:%s" % part_number if part_number else None
)
cnc_workorder.write({'cnc_worksheet': base64.b64encode(open(panel_file_path, 'rb').read()),
'cnc_worksheet_name': panel_file_name})
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'
'cancel'] and ap.processing_panel == panel) 'cancel'] and ap.processing_panel == panel)

View File

@@ -1149,7 +1149,7 @@ class sfProductionProcessParameter(models.Model):
'processing_mm': item['processing_mm'], 'processing_mm': item['processing_mm'],
'gain_way':'外协', 'gain_way':'外协',
}) })
production_process_parameter.create_service_product() # production_process_parameter.create_service_product()
else: else:
production_process_parameter.gain_way = '外协' production_process_parameter.gain_way = '外协'
production_process_parameter.name = item['name'] production_process_parameter.name = item['name']
@@ -1161,9 +1161,9 @@ class sfProductionProcessParameter(models.Model):
[('materials_no', 'in', item['materials_model_ids_codes'])]) [('materials_no', 'in', item['materials_model_ids_codes'])])
production_process_parameter.active = item['active'] production_process_parameter.active = item['active']
production_process_parameter.processing_mm = item['processing_mm'] production_process_parameter.processing_mm = item['processing_mm']
if not production_process_parameter.outsourced_service_products: # if not production_process_parameter.outsourced_service_products:
production_process_parameter.create_service_product() # production_process_parameter.create_service_product()
production_process_parameter.create_work_center() # production_process_parameter.create_work_center()
else: else:
raise ValidationError("表面工艺可选参数认证未通过") raise ValidationError("表面工艺可选参数认证未通过")

View File

@@ -15,6 +15,7 @@ class sf_production_plan(models.Model):
# _order = 'state desc, write_date desc' # _order = 'state desc, write_date desc'
part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True) part_name = fields.Char('零件名称', related='product_id.part_name', readonly=True)
part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True) part_number = fields.Char('零件图号', related='product_id.part_number', readonly=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
state = fields.Selection([ state = fields.Selection([
('draft', '待排程'), ('draft', '待排程'),
('done', '已排程'), ('done', '已排程'),

View File

@@ -170,6 +170,7 @@
<field name="part_number"/> <field name="part_number"/>
<field name="order_deadline" filter_domain="[('order_deadline', 'ilike', self)]"/> <field name="order_deadline" filter_domain="[('order_deadline', 'ilike', self)]"/>
<field name="production_line_id"/> <field name="production_line_id"/>
<field name="model_id"/>
<filter string="待排程" name="draft" domain="[('state','=','draft')]"/> <filter string="待排程" name="draft" domain="[('state','=','draft')]"/>
<filter string="已排程" name="done" domain="[('state','=','done')]"/> <filter string="已排程" name="done" domain="[('state','=','done')]"/>
<filter string="加工中" name="processing" domain="[('state','=','processing')]"/> <filter string="加工中" name="processing" domain="[('state','=','processing')]"/>

View File

@@ -196,8 +196,7 @@ class ReSaleOrder(models.Model):
for order in self: for order in self:
order.purchase_order_count = len(order._get_purchase_orders().filtered( order.purchase_order_count = len(order._get_purchase_orders().filtered(
lambda po: po.purchase_type not in ['outsourcing'])) lambda po: po.purchase_type not in ['outsourcing']))
order.consignment_purchase_order_count = len(order._get_purchase_orders().filtered( order.consignment_purchase_order_count = len(order._get_sale_to_purchase('outsourcing'))
lambda po: po.purchase_type in ['outsourcing']))
def action_view_purchase_orders(self): def action_view_purchase_orders(self):
""" """
@@ -228,8 +227,7 @@ class ReSaleOrder(models.Model):
委外加工 委外加工
""" """
self.ensure_one() self.ensure_one()
outsourcing_purchase_order_ids = self._get_purchase_orders().filtered( outsourcing_purchase_order_ids = self._get_sale_to_purchase('outsourcing')
lambda po: po.purchase_type in ['outsourcing']).ids
action = { action = {
'res_model': 'purchase.order', 'res_model': 'purchase.order',
'type': 'ir.actions.act_window', 'type': 'ir.actions.act_window',
@@ -247,6 +245,14 @@ class ReSaleOrder(models.Model):
}) })
return action return action
def _get_sale_to_purchase(self, purchase_type):
"""查询满足条件的采购订单"""
purchase_order_ids = self._get_purchase_orders().filtered(
lambda po: po.purchase_type == purchase_type).ids
order_ids = self.env['purchase.order'].sudo().search(
[('origin', '=', self.name), ('purchase_type', '=', purchase_type)]).ids
return list(set(purchase_order_ids) | set(order_ids))
class ResaleOrderLine(models.Model): class ResaleOrderLine(models.Model):
_inherit = 'sale.order.line' _inherit = 'sale.order.line'
@@ -346,10 +352,10 @@ class RePurchaseOrder(models.Model):
if purchase.order_line[0].product_id.categ_id.name == '坯料': if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协': if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing' 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):
purchase.purchase_type = 'consignment' # purchase.purchase_type = 'consignment'
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')], delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
@@ -384,28 +390,28 @@ class RePurchaseOrder(models.Model):
if not line.taxes_id: if not line.taxes_id:
raise UserError('请对【产品】中的【税】进行选择') raise UserError('请对【产品】中的【税】进行选择')
def get_purchase_request(self, consecutive_process_parameters, production): # def get_purchase_request(self, consecutive_process_parameters, production):
result = [] # result = []
for pp in consecutive_process_parameters: # for pp in consecutive_process_parameters:
server_template = self.env['product.template'].search( # server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id), # [('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')]) # ('detailed_type', '=', 'service')])
# route_ids # # 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": production.product_qty,
"request_id": False, # "request_id": False,
"move_dest_ids": False, # "move_dest_ids": False,
"orderpoint_id": False, # "orderpoint_id": False,
'is_subcontract':True, # 'is_subcontract':True,
'group_id':production.procurement_group_id.id, # 'group_id':production.procurement_group_id.id,
'production_name':pp.production_id.name, # 'production_name':pp.production_id.name,
}) # })
return result # return result
def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names): def get_purchase_order(self, consecutive_process_parameters, production, product_id_to_production_names):
for pp in consecutive_process_parameters: for pp in consecutive_process_parameters: