Compare commits

...

57 Commits

Author SHA1 Message Date
chenye
bd6f35a16f 解决合并develop冲突 2025-07-07 10:03:09 +08:00
chenye
86bacee926 物料需求计划提交 2025-07-04 16:18:56 +08:00
chenye
e1644c3aa4 物料需求计划管理详情编辑需求练习 2025-07-02 16:19:24 +08:00
胡尧
20df1c0365 Accept Merge Request #2265: (release/release_2.15 -> develop)
Merge Request: 需求计划列表字段位置变动

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2265
2025-07-01 17:57:16 +08:00
胡尧
20069d5336 Merge branch refs/heads/develop into refs/heads/release/release_2.15 2025-07-01 17:57:06 +08:00
guanhuan
fe8df494f9 需求计划列表字段位置变动 2025-06-30 17:13:46 +08:00
禹翔辉
0e7d6fac4e Accept Merge Request #2264: (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/2264
2025-06-30 15:20:56 +08:00
guanhuan
32cd68e15f 调拨动作中屏蔽验证 2025-06-30 13:36:04 +08:00
yuxianghui
70113cb3c5 采购申请批量转采购按钮显示优化 2025-06-30 09:20:09 +08:00
guanhuan
f0e47371ed 新增坯料类型 2025-06-27 15:29:02 +08:00
yuxianghui
6dde814acc 产品-form页面加工参数 添加坯料类型字段 2025-06-27 15:17:15 +08:00
yuxianghui
71ab241e94 产品添加坯料类型字段,值来着bfm下单 2025-06-27 15:10:15 +08:00
禹翔辉
272e424975 Accept Merge Request #2263: (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/2263
2025-06-27 10:17:42 +08:00
yuxianghui
1fd9900e41 处理 由采购订单-委外加工类型生成的同一个补货组的坯料采购申请单据没有合并 问题 2025-06-27 09:39:11 +08:00
yuxianghui
0a13acbb68 修改 销售订单确认时,生成产品BOM时校验单位用量值的提示信息 2025-06-26 17:04:50 +08:00
yuxianghui
a68d6d4d06 处理 采购订单确认时,一补货组的补货单没有合并问题 2025-06-26 16:55:38 +08:00
胡尧
c630074f4a Accept Merge Request #2262: (feature/根据项目组区分sf代码 -> develop)
Merge Request: 撤回_run_manufacturing方法

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2262?initial=true
2025-06-26 10:27:04 +08:00
胡尧
4bea169327 撤回_run_manufacturing方法 2025-06-26 10:26:39 +08:00
禹翔辉
e2ddac4fb0 Accept Merge Request #2261: (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/2261
2025-06-26 10:24:21 +08:00
yuxianghui
e52ec3982f 调拨单验证时,对没有完成通过的质检单不进行出厂检验报告检测 2025-06-26 10:03:19 +08:00
胡尧
160bd117b7 Accept Merge Request #2260: (feature/根据项目组区分sf代码 -> develop)
Merge Request: 没有model_id的制造订单不请求编程

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2260
2025-06-26 09:35:42 +08:00
胡尧
a19cf5d894 解决冲突 2025-06-26 09:35:26 +08:00
胡尧
885064fb12 没有model_id的制造订单不请求编程 2025-06-26 09:29:02 +08:00
胡尧
8f7e785760 Accept Merge Request #2259: (feature/根据项目组区分sf代码 -> develop)
Merge Request: 修复加工面报错的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2259?initial=true
2025-06-25 17:36:14 +08:00
胡尧
8123885c71 修复加工面报错的问题 2025-06-25 17:35:18 +08:00
禹翔辉
c2f346571d Accept Merge Request #2258: (feature/委外加工采购订单 -> develop)
Merge Request: 完成 委外加工采购订单增加BOM内物料的采购申请与采购订单 需求

Created By: @禹翔辉
Reviewed By: @胡尧
Approved By: @胡尧 
Accepted By: @禹翔辉
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2258
2025-06-25 16:13:04 +08:00
yuxianghui
e3494ce8b7 完成 委外加工采购订单增加BOM内物料的采购申请与采购订单 需求 2025-06-25 16:09:32 +08:00
禹翔辉
d75cff0a5d Accept Merge Request #2257: (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/2257
2025-06-25 14:37:28 +08:00
hyyy
4b026535f8 修改固定列tree的传参方式 2025-06-25 14:01:34 +08:00
yuxianghui
28ade4ac72 完成 物料需求计划主数据准备:库存路线 需求 2025-06-25 13:57:56 +08:00
hyyy
99ac89f995 需求-字段命名调整 2025-06-25 09:54:37 +08:00
胡尧
372f95d812 Accept Merge Request #2256: (feature/6711 -> develop)
Merge Request: 待完工工单明细,控制时间为大于48小时,小于当前时间

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2256?initial=true
2025-06-25 09:43:11 +08:00
胡尧
f165bec662 待完工工单明细,控制时间为大于48小时,小于当前时间 2025-06-25 09:42:53 +08:00
胡尧
4099756d15 Accept Merge Request #2255: (feature/6711 -> develop)
Merge Request: 日计划量对工单id去重

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2255?initial=true
2025-06-25 08:44:32 +08:00
胡尧
133eac4a5c 日计划量对工单id去重 2025-06-25 08:43:59 +08:00
禹翔辉
91bad533c3 Accept Merge Request #2254: (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/2254
2025-06-24 17:10:41 +08:00
yuxianghui
f5ed94866b 调拨单需验证质检单数量方法优化 2025-06-24 16:59:38 +08:00
胡尧
b70f280c6e Accept Merge Request #2253: (feature/6711 -> develop)
Merge Request: 修复明细接口bug

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2253?initial=true
2025-06-24 15:56:52 +08:00
胡尧
9922402b3b 修复明细接口bug 2025-06-24 15:56:33 +08:00
胡尧
152657e41b Accept Merge Request #2252: (feature/6711 -> develop)
Merge Request: 修改未完成订单明细的判断逻辑

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2252?initial=true
2025-06-24 15:51:17 +08:00
胡尧
0a79a4e336 修改未完成订单明细的判断逻辑 2025-06-24 15:50:45 +08:00
yuxianghui
35b1d648c3 产品新增单件用量 unit_number 字段,值由bfm下单同步获得;产品生成的BOM中组件数量的值由unit_number 字段提供。 2025-06-24 15:36:23 +08:00
胡尧
174af13a58 Accept Merge Request #2251: (feature/6711 -> develop)
Merge Request: 修改工单状态对应名字

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2251?initial=true
2025-06-24 15:35:42 +08:00
胡尧
00922e3674 修改工单状态对应名字 2025-06-24 15:35:19 +08:00
胡尧
832b06be1f Merge branch 'develop' into feature/根据项目组区分sf代码 2025-06-24 15:06:31 +08:00
胡尧
3b60db56de Accept Merge Request #2250: (feature/6711 -> develop)
Merge Request: 修改大屏获取订单详情接口

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/2250
2025-06-24 14:52:47 +08:00
胡尧
2a330a4bd8 修改大屏获取订单详情接口 2025-06-24 14:52:03 +08:00
yuxianghui
579b7138e7 需求 调拨单关联校验是否完成质量检查时,可先排除欠单量后再校验 开发 2025-06-24 14:41:49 +08:00
yuxianghui
98f8430c45 Revert "需求 调拨单关联校验是否完成质量检查时,可先排除欠单量后再校验 开发"
This reverts commit 912847ae33.
2025-06-24 14:32:40 +08:00
yuxianghui
912847ae33 需求 调拨单关联校验是否完成质量检查时,可先排除欠单量后再校验 开发 2025-06-24 14:32:21 +08:00
胡尧
33647fa3e0 修改明细接口 2025-06-24 14:18:27 +08:00
管欢
d1f9f8cf8c Accept Merge Request #2249: (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/2249
2025-06-24 11:44:41 +08:00
guanhuan
7d1f7b11eb 坯料尺寸显示修改 2025-06-24 11:39:47 +08:00
guanhuan
fe3492ceb5 坯料尺寸显示修改 2025-06-24 11:32:41 +08:00
胡尧
b177bd95ff 将fetchCNC移动到sf_mrs_connect模块 2025-06-23 11:04:44 +08:00
胡尧
c1bafef1aa Merge branch 'develop' into feature/根据项目组区分sf代码 2025-06-23 10:49:47 +08:00
yuxianghui
4274b9fe99 出厂检验报告-页脚修改回退 2025-06-20 17:53:04 +08:00
41 changed files with 2004 additions and 711 deletions

View File

@@ -10,6 +10,7 @@
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'views/sale_order_view.xml', 'views/sale_order_view.xml',
'views/purchase_order.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',

View File

@@ -16,6 +16,69 @@ class PurchaseOrder(models.Model):
('rejected', '已驳回') ('rejected', '已驳回')
], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True) ], string='Status', readonly=True, index=True, copy=False, default='draft', tracking=True)
# 成品采购订单对应的坯料采购申请单和采购订单数量
purchase_request_count = fields.Integer('子·采购申请数量', compute='_compute_purchase_request')
purchase_order_count = fields.Integer('子·采购订单数量', compute='_compute_purchase_request')
@api.depends('state')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids, purchase_order_ids = record.get_purchase_request_order()
record.purchase_request_count = len(purchase_request_ids)
record.purchase_order_count = len(purchase_order_ids)
def action_view_preform_body_purchase_request(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',
}
if len(purchase_request_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_request_ids[0].id,
})
else:
action.update({
'name': _("子·采购申请"),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action
def action_view_preform_body_purchase_order(self):
self.ensure_one()
name_list = self._get_pinking_name()
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
action = {
'res_model': 'purchase.order',
'type': 'ir.actions.act_window',
}
if len(purchase_order_ids) == 1:
action.update({
'view_mode': 'form',
'res_id': purchase_order_ids[0].id,
})
else:
action.update({
'name': _("子·采购订单"),
'domain': [('id', 'in', purchase_order_ids.ids)],
'view_mode': 'tree,form',
})
return action
def get_purchase_request_order(self):
name_list = self._get_pinking_name()
purchase_request_ids = self.env['purchase.request'].search([('origin', 'in', name_list)])
purchase_order_ids = self.env['purchase.order'].search([('origin', 'in', name_list)])
return purchase_request_ids, purchase_order_ids
def _get_pinking_name(self):
return [picking_id.name for picking_id in self.picking_ids if picking_id.name]
def button_confirm(self): def button_confirm(self):
res = super(PurchaseOrder, self).button_confirm() res = super(PurchaseOrder, self).button_confirm()

View File

@@ -16,6 +16,7 @@ class PurchaseRequest(models.Model):
) )
rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True) rule_new_add = fields.Boolean('采购请求为规则创建', default=False, compute='_compute_state', store=True)
rule_purchase_to_request = fields.Boolean('采购单根据规则创建坯料采购申请', default=False)
@api.depends('state') @api.depends('state')
def _compute_state(self): def _compute_state(self):

View File

@@ -44,4 +44,15 @@ class StockPicking(models.Model):
purchase_request_lines.move_dest_ids = [ purchase_request_lines.move_dest_ids = [
(4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id') (4, x.id) for x in backorder_ids.move_ids if x.product_id.id in purchase_request_lines.mapped('product_id.id')
] ]
return res return res
def _subcontracted_produce(self, subcontract_details):
super()._subcontracted_produce(subcontract_details)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
if self:
pr_ids = self.env["purchase.request"].sudo().search(
[('origin', 'like', self.name), ('rule_purchase_to_request', '=', True), ('state', '=', 'draft')])
if pr_ids:
pr_ids.write({'need_validation': False})
pr_ids.write({"state": "approved", 'need_validation': True, 'rule_new_add': False})

View File

@@ -26,7 +26,7 @@ class StockRule(models.Model):
request_data = rule._prepare_purchase_request( request_data = rule._prepare_purchase_request(
procurement.origin, procurement.values procurement.origin, procurement.values
) )
request_data.update({'rule_new_add': True}) request_data = self._update_request_data(procurement, request_data)
pr = purchase_request_model.create(request_data) pr = purchase_request_model.create(request_data)
cache[domain] = pr cache[domain] = pr
elif ( elif (
@@ -44,6 +44,18 @@ class StockRule(models.Model):
request_line_data.update({'origin': procurement.origin}) request_line_data.update({'origin': procurement.origin})
purchase_request_line_model.create(request_line_data) purchase_request_line_model.create(request_line_data)
def _update_request_data(self, procurement, request_data):
sp = self.env['stock.picking'].sudo().search([('name', '=', procurement.origin)])
if len(sp) == 1:
po = self.env['purchase.order'].sudo().search(
[('name', '=', sp.origin), ('purchase_type', '=', 'outsourcing')])
if po:
request_data.update({'rule_purchase_to_request': True})
else:
request_data.update({'rule_new_add': True})
return request_data
def _run_buy(self, procurements): def _run_buy(self, procurements):
# 如果补货组相同,并且产品相同,则合并 # 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict() procurements_dict = defaultdict()

View File

@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="purchase_order_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">purchase.order.inherited.form.jikimo.purchase.request</field>
<field name="model">purchase.order</field>
<field name="inherit_id" ref="mrp_subcontracting_purchase.purchase_order_form_mrp_subcontracting_purchase"/>
<field name="arch" type="xml">
<xpath expr="//div[hasclass('oe_button_box')]/button[@name='action_view_subcontracting_resupply']" position="before">
<button
class="oe_stat_button" name="action_view_preform_body_purchase_order"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_order_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_order_count"/></span>
<span class="o_stat_text">子·采购订单</span>
</div>
</button>
<button
class="oe_stat_button" name="action_view_preform_body_purchase_request"
type="object" icon="fa-truck" attrs="{'invisible': [('purchase_request_count', '=', 0)]}" groups="stock.group_stock_user">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value"><field name="purchase_request_count"/></span>
<span class="o_stat_text">子·采购申请</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -67,6 +67,16 @@
<field name="part_number"/> <field name="part_number"/>
<field name="part_name" invisible="1"/> <field name="part_name" invisible="1"/>
</xpath> </xpath>
<xpath expr="//tree" position="inside">
<header>
<button
name="%(purchase_request.action_purchase_request_line_make_purchase_order)d"
string="创建询价单"
type="action"
class="btn-primary"
/>
</header>
</xpath>
</field> </field>
</record> </record>

View File

@@ -10,11 +10,21 @@
""", """,
'category': 'sf', 'category': 'sf',
'website': 'https://www.sf.jikimo.com', 'website': 'https://www.sf.jikimo.com',
'depends': ['sf_plan','jikimo_printing'], 'depends': ['sf_plan'],
'data': [ 'data': [
'security/ir.model.access.csv', 'security/ir.model.access.csv',
'data/stock_route_group.xml',
<<<<<<< HEAD
'views/sale_order_views.xml',
'views/demand_plan.xml', 'views/demand_plan.xml',
'views/demand_main_plan.xml',
=======
'views/demand_plan.xml',
>>>>>>> develop
'views/stock_route.xml',
'wizard/sf_demand_plan_print_wizard_view.xml', 'wizard/sf_demand_plan_print_wizard_view.xml',
'wizard/sf_release_plan_wizard_views.xml',
'views/menu_view.xml',
], ],
'demo': [ 'demo': [
], ],

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<data noupdate="0">
<record id="stock_route_group_automation_sf" model="stock.route.group">
<field name="name">自动化产线加工</field>
<field name="code">automation</field>
</record>
<record id="stock_route_group_manual_sf" model="stock.route.group">
<field name="name">人工线下加工</field>
<field name="code">manual</field>
</record>
<record id="stock_route_group_purchase_sf" model="stock.route.group">
<field name="name">外购</field>
<field name="code">purchase</field>
</record>
<record id="stock_route_group_outsourcing_sf" model="stock.route.group">
<field name="name">委外加工</field>
<field name="code">outsourcing</field>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,25 @@
# migrations/1.1.0/post-migrate.py
import os
import csv
import logging
from odoo import api, SUPERUSER_ID
_logger = logging.getLogger(__name__)
#需求计划模块升级的时候,同步处理存量需求明细数据,关联上新加的需求计划主表
def migrate(cr, version):
# 获取环境
env = api.Environment(cr, SUPERUSER_ID, {})
ProductionLine = env['sf.production.demand.plan']
DemandPlan = env['sf.demand.main.plan']
lines = ProductionLine.search([('demand_plan_id', '=', False)])
for line in lines:
vals = {
'sale_order_id': line.sale_order_id.id,
'sale_order_line_id': line.sale_order_line_id.id,
'line_ids': line.ids
}
new_plan = DemandPlan.create(vals)
line.write({'demand_plan_id': new_plan.id})

View File

@@ -1,4 +1,11 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from . import mrp_bom
from . import mrp_production
from . import sf_demand_main_plan
from . import sf_production_demand_plan from . import sf_production_demand_plan
from . import sale_order from . import sale_order
from . import stock_route
from . import stock_rule
from . import purchase_request
from . import purchase_order

View File

@@ -0,0 +1,20 @@
from odoo import models, fields
class MrpBom(models.Model):
_inherit = 'mrp.bom'
# 业务平台分配工厂后在智能工厂先创建销售订单再创建该产品后再次进行创建bom
def bom_create(self, product, bom_type, product_type, code=None):
bom_id = self.env['mrp.bom'].create({
'product_tmpl_id': product.product_tmpl_id.id,
'type': bom_type,
# 'subcontractor_id': '' or subcontract.partner_id.id,
'product_qty': 1,
'product_uom_id': 1,
'code': code
})
if bom_type == 'subcontract' and product_type is not False:
subcontract = self.get_supplier(product.materials_type_id)
bom_id.subcontractor_id = subcontract.partner_id.id
return bom_id

View File

@@ -0,0 +1,26 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import fields, models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_production_type(self):
for production in self:
if production.demand_plan_line_id.supply_method == 'automation':
production.production_type = '自动化产线加工'
elif production.demand_plan_line_id.supply_method == 'manual':
production.production_type = '人工线下加工'
else:
production.production_type = None
def _get_purchase_request(self):
"""获取跟制造订单相关的采购申请单"""
pr_ids = self.env['purchase.request'].sudo().search([('origin', '=', self.name)])
return pr_ids

View File

@@ -0,0 +1,22 @@
from odoo import api, fields, models, _
from odoo.tools import float_compare
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('origin', 'demand_plan_line_id')
def _compute_purchase_type(self):
for purchase in self:
if purchase.order_line[0].product_id.categ_id.name == '坯料':
if purchase.order_line[0].product_id.materials_type_id.gain_way == '外协':
purchase.purchase_type = 'outsourcing'
else:
if purchase.demand_plan_line_id.supply_method == 'outsourcing':
purchase.purchase_type = 'outsourcing'
elif purchase.demand_plan_line_id.supply_method == 'purchase':
purchase.purchase_type = 'outside'

View File

@@ -0,0 +1,110 @@
from odoo import models, fields, api, _
from odoo.exceptions import UserError, ValidationError
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
_description = '采购申请明细'
supply_method = fields.Selection([
('automation', "自动化产线加工"),
('manual', "人工线下加工"),
('purchase', "外购"),
('outsourcing', "委外加工"),
], string='供货方式', readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
@api.depends('demand_plan_line_id')
def _compute_supply_method(self):
for prl in self:
if prl.demand_plan_line_id:
prl.supply_method = prl.demand_plan_line_id.supply_method
else:
prl.supply_method = None
class PurchaseRequestLineMakePurchaseOrder(models.TransientModel):
_inherit = "purchase.request.line.make.purchase.order"
def make_purchase_order(self):
res = []
purchase_obj = self.env["purchase.order"]
po_line_obj = self.env["purchase.order.line"]
purchase = False
if len(set([item_id.line_id.supply_method for item_id in self.item_ids])) > 1:
raise ValidationError('不同供货方式不可合并创建询价单!')
for item in self.item_ids:
line = item.line_id
if item.product_qty <= 0.0:
raise UserError(_("Enter a positive quantity."))
if self.purchase_order_id:
purchase = self.purchase_order_id
if not purchase:
po_data = self._prepare_purchase_order(
line.request_id.picking_type_id,
line.request_id.group_id,
line.company_id,
line.request_id.origin,
)
po_data['demand_plan_line_id'] = item.line_id.demand_plan_line_id.id
# po_data.update({'related_product':line.related_product.id})
purchase = purchase_obj.create(po_data)
# Look for any other PO line in the selected PO with same
# product and UoM to sum quantities instead of creating a new
# po line
domain = self._get_order_line_search_domain(purchase, item)
available_po_lines = po_line_obj.search(domain)
new_pr_line = True
# If Unit of Measure is not set, update from wizard.
if not line.product_uom_id:
line.product_uom_id = item.product_uom_id
# Allocation UoM has to be the same as PR line UoM
alloc_uom = line.product_uom_id
wizard_uom = item.product_uom_id
if available_po_lines and not item.keep_description:
new_pr_line = False
po_line = available_po_lines[0]
po_line.purchase_request_lines = [(4, line.id)]
po_line.move_dest_ids |= line.move_dest_ids
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
else:
po_line_data = self._prepare_purchase_order_line(purchase, item)
if item.keep_description:
po_line_data["name"] = item.name
if line.related_product:
po_line_data.update({'related_product': line.related_product.id})
po_line = po_line_obj.create(po_line_data)
po_line_product_uom_qty = po_line.product_uom._compute_quantity(
po_line.product_uom_qty, alloc_uom
)
wizard_product_uom_qty = wizard_uom._compute_quantity(
item.product_qty, alloc_uom
)
all_qty = min(po_line_product_uom_qty, wizard_product_uom_qty)
self.create_allocation(po_line, line, all_qty, alloc_uom)
self._post_process_po_line(item, po_line, new_pr_line)
res.append(purchase.id)
purchase_requests = self.item_ids.mapped("request_id")
purchase_requests.button_in_progress()
return {
"domain": [("id", "in", res)],
"name": _("RFQ"),
"view_mode": "tree,form",
"res_model": "purchase.order",
"view_id": False,
"context": False,
"type": "ir.actions.act_window",
}

View File

@@ -9,14 +9,53 @@ class ReSaleOrder(models.Model):
compute='_compute_mrp_production_ids', compute='_compute_mrp_production_ids',
string='与此销售订单相关联的制造订单', string='与此销售订单相关联的制造订单',
groups='mrp.group_mrp_user', store=True) groups='mrp.group_mrp_user', store=True)
demand_plan_ids = fields.Many2many(comodel_name="sf.demand.main.plan",
string="需求计划", readonly=True)
demand_plan_count = fields.Integer(
"需求计划生成计数",
compute='_compute_demand_plan_count',
)
#暂时不知道哪里用到了
@api.depends('demand_plan_ids.line_ids.status')
def _compute_purchase_request_count(self):
for record in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', record.name)])
if pr_ids:
record.purchase_request_purchase_order_count = len(pr_ids)
else:
record.purchase_request_purchase_order_count = 0
#计算需求计划生成计数
@api.depends('demand_plan_ids.line_ids')
def _compute_demand_plan_count(self):
for line in self:
demand_plan = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', line.id)])
line.demand_plan_count = len(demand_plan)
def sale_order_create_line(self, product, item): def sale_order_create_line(self, product, item):
ret = super(ReSaleOrder, self).sale_order_create_line(product, item) ret = super(ReSaleOrder, self).sale_order_create_line(product, item)
vals = { # vals = {
'sale_order_id': ret.order_id.id, # 'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id, # 'sale_order_line_id': ret.id,
} # }
demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals) # demand_plan_info = self.env['sf.demand.main.plan'].sudo().create(vals)
# vals.update({'demand_plan_id': demand_plan_info.id, 'plan_uom_qty': ret.product_uom_qty})
# demand_plan = self.env['sf.production.demand.plan'].sudo().create(vals)
# demand_plan_info.write({'line_ids': demand_plan.ids})
# 优化方案1使用事务确保数据一致性
with self.env.cr.savepoint():
# 1. 先创建主计划
demand_plan_info = self.env['sf.demand.main.plan'].sudo().create({
'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id,
})
# 2. 创建明细计划时直接建立关联利用One2many的inverse特性
demand_plan = self.env['sf.production.demand.plan'].sudo().create({
'demand_plan_id': demand_plan_info.id,
'plan_uom_qty': ret.product_uom_qty,
'sale_order_id': ret.order_id.id,
'sale_order_line_id': ret.id,
})
# 3. 不需要手动更新line_idsOdoo的ORM会自动处理One2many关系
if demand_plan.product_id.machining_drawings_name: if demand_plan.product_id.machining_drawings_name:
filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0] filename_url = demand_plan.product_id.machining_drawings_name.rsplit('.', 1)[0]
wizard_vals = { wizard_vals = {
@@ -26,4 +65,23 @@ class ReSaleOrder(models.Model):
'type': '1', 'type': '1',
} }
self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals) self.env['sf.demand.plan.print.wizard'].sudo().create(wizard_vals)
ret.order_id.demand_plan_ids = [(4, demand_plan_info.id)]
return ret return ret
#从sf工厂调过来时调用了这里重写
def confirm_to_supply_method(self):
self.state = 'sale'
for line in self.order_line:
if line.product_id.auto_machining:
line.supply_method = 'automation'
#在销售订单打开需求计划列表
def action_view_demand_plan(self):
self.ensure_one()
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search([('sale_order_id', '=', self.id)]).ids
return {
'res_model': 'sf.production.demand.plan',
'type': 'ir.actions.act_window',
'name': _("需求计划"),
'domain': [('id', 'in', demand_plan_ids)],
'view_mode': 'tree',
}

View File

@@ -0,0 +1,206 @@
# -*- coding: utf-8 -*-
from odoo import models, fields, api, _
from odoo.tools import float_compare
from odoo.exceptions import ValidationError
class SfDemandMainPlan(models.Model):
_name = 'sf.demand.main.plan'
_description = 'sf_demand_main_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
state = fields.Selection([
('10', '需求确认'),
('20', '待工艺设计'),
('30', '部分下达'),
('40', '已下达'),
('50', '取消'),
], string='状态', default='10', compute='_compute_state', store=True)
line_ids = fields.One2many(comodel_name='sf.production.demand.plan',
inverse_name='demand_plan_id', string="需求计划", copy=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)
product_id = fields.Many2one(
comodel_name='product.product',
related='sale_order_line_id.product_id',
string='产品', store=True, index=True)
part_name = fields.Char('零件名称', related='product_id.part_name')
part_number = fields.Char('零件图号', compute='_compute_part_number', store=True)
materials_id = fields.Char('材料', compute='_compute_materials_id', store=True)
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类',
related='product_id.blank_type')
embryo_long = fields.Char('坯料尺寸(mm)', compute='_compute_embryo_long', store=True)
is_incoming_material = fields.Boolean('客供料', related='sale_order_line_id.is_incoming_material', store=True)
pending_qty = fields.Float(
string="待计划",
compute='_compute_pending_qty', store=True)
planned_qty = fields.Float(
string="已计划",
compute='_compute_planned_qty', store=True)
model_id = fields.Char('模型ID', related='product_id.model_id')
customer_name = fields.Char('客户', related='sale_order_id.customer_name')
product_uom_qty = fields.Float(
string="需求数量",
related='sale_order_line_id.product_uom_qty', store=True)
deadline_of_delivery = fields.Date('客户交期', related='sale_order_line_id.delivery_end_date', store=True)
contract_date = fields.Date('合同日期', related='sale_order_id.contract_date')
contract_code = fields.Char('合同号', related='sale_order_id.contract_code', store=True)
model_process_parameters_ids = fields.Many2many('sf.production.process.parameter',
'demand_plan_process_parameter_rel',
string='表面工艺',
compute='_compute_model_process_parameters_ids'
, store=True
)
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='精度',
related='product_id.model_machining_precision')
inventory_quantity_auto_apply = fields.Float(
string="成品库存",
compute='_compute_inventory_quantity_auto_apply', store=True
)
priority = fields.Selection([
('1', '紧急'),
('2', ''),
('3', ''),
('4', ''),
], string='优先级', default='3')
hide_button_release_plan = fields.Boolean(
string='显示下达计划按钮',
compute='_compute_hide_button_release_plan',
default=False
)
@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.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_long', 'product_id.model_width', 'product_id.model_height')
def _compute_embryo_long(self):
for line in self:
if line.product_id:
line.embryo_long = f"{round(line.product_id.model_long, 3)}*{round(line.product_id.model_width, 3)}*{round(line.product_id.model_height, 3)}"
else:
line.embryo_long = 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('product_uom_qty', 'line_ids.plan_uom_qty')
def _compute_pending_qty(self):
for line in self:
sum_plan_uom_qty = sum(line.line_ids.mapped('plan_uom_qty'))
pending_qty = line.product_uom_qty - sum_plan_uom_qty
rounding = line.product_id.uom_id.rounding or 0.01#设置默认精度
# 如果精度小于等于0则设置为0.01
if rounding<=0:
rounding = 0.01
# 如果计划量小于等于0则设置为0
if float_compare(pending_qty, 0, precision_rounding=rounding) == -1:
line.pending_qty = 0
else:
line.pending_qty = pending_qty
@api.depends('line_ids.plan_uom_qty')
def _compute_planned_qty(self):
for line in self:
line.planned_qty = sum(line.line_ids.mapped('plan_uom_qty'))
@api.depends('line_ids.status')
def _compute_hide_button_release_plan(self):
for line in self:
line.hide_button_release_plan = bool(line.line_ids.filtered(lambda p: p.status != '60'))
def button_release_plan(self):
pass
@api.depends('line_ids.status', 'sale_order_id.state')
def _compute_state(self):
for line in self:
#已下达的订单
status_line = line.line_ids.filtered(lambda p: p.status == '60')
if line.sale_order_id.state == 'cancel':
line.state = '50'
line.line_ids.status = '100'
elif len(line.line_ids) == len(status_line):
line.state = '40'
#排除上面全部下达的情形后,如果有已下达订单,则状态为部分
elif bool(status_line):
line.state = '30'
else:
line.state = '10'
def write(self, vals):
res = super(SfDemandMainPlan, self).write(vals)
line_ids = self.line_ids.filtered(lambda p: p.plan_uom_qty == 0 or p.plan_uom_qty < 0)
if line_ids:
raise ValidationError(f"计划量不能小于等于0")
if 'line_ids' in vals:
for line in self.line_ids:
if not line.sale_order_id:
line.sale_order_id = self.sale_order_id
if not line.sale_order_line_id:
line.sale_order_line_id = self.sale_order_line_id
return res
def name_get(self):
result = []
for plan in self:
result.append((plan.id, plan.sale_order_id.name))
return result

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
from odoo import models, fields, api, _
class SfStockRoute(models.Model):
_inherit = 'stock.route'
demand_plan_selectable = fields.Boolean("需求计划行")
stock_route_group_ids = fields.Many2many('stock.route.group', 'route_to_group', string='路线组')
demand_plan_ids = fields.Many2many('sf.production.demand.plan', 'stock_route_demand_plan', 'route_id',
'demand_plan_id', '需求计划', copy=False, compute='_compute_demand_plan_ids',
store=True)
@api.depends('demand_plan_selectable', 'stock_route_group_ids')
def _compute_demand_plan_ids(self):
for sr in self:
if sr.demand_plan_selectable:
stock_route_group = [srg.code for srg in sr.stock_route_group_ids]
demand_plan_ids = self.env['sf.production.demand.plan'].sudo().search(
[('supply_method', 'in', stock_route_group)])
if demand_plan_ids:
sr.demand_plan_ids = demand_plan_ids.ids
<<<<<<< HEAD
continue
=======
break
>>>>>>> develop
sr.demand_plan_ids = None
# def name_get(self):
# res = super().name_get()
# if self.env.context.get('demand_plan_search_stock_route_id'):
# demand_plan_id = self.env['sf.production.demand.plan'].sudo().browse(
# int(self.env.context.get('demand_plan_search_stock_route_id')))
# if demand_plan_id and demand_plan_id.supply_method:
# supply_method = self._set_supply_method(demand_plan_id.supply_method)
# res = [(item[0], f'{item[1]}-{supply_method}') for item in res if len(item) == 2]
# return res
#
# def _set_supply_method(self, supply_method):
# return {
# 'automation': "自动化产线加工",
# 'manual': "人工线下加工",
# 'purchase': "外购",
# 'outsourcing': "委外加工"
# }.get(supply_method)
class SfStockRouteGroup(models.Model):
_name = 'stock.route.group'
_description = '路线组'
name = fields.Char('名称')
code = fields.Char('编码')

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from odoo import api, fields, models
class StockRule(models.Model):
_inherit = 'stock.rule'
def _prepare_mo_vals(self, product_id, product_qty, product_uom, location_id, name, origin, company_id, values,
bom):
res = super()._prepare_mo_vals(product_id, product_qty, product_uom, location_id, name, origin, company_id,
values, bom)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res
@api.model
def _prepare_purchase_request_line(self, request_id, procurement):
res = super()._prepare_purchase_request_line(request_id, procurement)
if self.env.context.get('demand_plan_line_id'):
res['demand_plan_line_id'] = self.env.context.get('demand_plan_line_id')
return res

View File

@@ -1,6 +1,20 @@
id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink 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,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_production_demand_plan_for_dispatch,sf.production.demand.plan for dispatch,model_sf_production_demand_plan,sf_base.group_plan_dispatch,1,1,1,1
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,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 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
<<<<<<< HEAD
access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0
access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
=======
access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
>>>>>>> develop
1 id id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
2 access_sf_production_demand_plan access_sf_production_demand_plan,sf.production.demand.plan,model_sf_production_demand_plan,base.group_user,1,0,0,0 sf.production.demand.plan model_sf_production_demand_plan base.group_user 1 0 0 0
3 access_sf_production_demand_plan_for_dispatch 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,1,1 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 access_sf_demand_plan_print_wizard,sf.demand.plan.print.wizard,model_sf_demand_plan_print_wizard,base.group_user,1,0,0,0 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 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 sf.demand.plan.print.wizard for dispatch model_sf_demand_plan_print_wizard sf_base.group_plan_dispatch 1 1 0 0
6 <<<<<<< HEAD
7 access_sf_demand_main_plan,sf.demand.main.plan,model_sf_demand_main_plan,base.group_user,1,0,0,0
8 access_sf_demand_main_plan_for_dispatch,sf.demand.main.plan for dispatch,model_sf_demand_main_plan,sf_base.group_plan_dispatch,1,1,0,0
9 access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
10 access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
11 access_sf_release_plan_wizard,sf.release.plan.wizard,model_sf_release_plan_wizard,base.group_user,1,0,0,0
12 access_sf_release_plan_wizard_for_dispatch,sf.release.plan.wizard for dispatch,model_sf_release_plan_wizard,sf_base.group_plan_dispatch,1,1,1,1
13 =======
14 access_stock_route_group,stock.route.group,model_stock_route_group,base.group_user,1,0,0,0
15 access_stock_route_group_dispatch,stock.route.group.dispatch,model_stock_route_group,sf_base.group_plan_dispatch,1,1,0,0
16 >>>>>>> develop
17
18
19
20

View File

@@ -81,4 +81,9 @@
input,label { input,label {
cursor: pointer; cursor: pointer;
} }
} }
th[data-name=processing_time] + th::before{
content: '待执行单据';
line-height: 38px;
}

View File

@@ -0,0 +1,70 @@
<odoo>
<record id="view_sf_demand_main_plan_form" model="ir.ui.view">
<field name="name">sf.demand.main.plan.form</field>
<field name="model">sf.demand.main.plan</field>
<field name="arch" type="xml">
<form>
<header>
<field name="hide_button_release_plan" invisible="1"/>
<!-- <button string="下达计划" name="button_release_plan" type="object"-->
<!-- class="btn-primary" attrs="{'invisible': [('hide_button_release_plan', '=', False)]}"/>-->
<field name="state" widget="statusbar"/>
</header>
<sheet>
<group>
<group>
<field name="product_id"/>
<field name="part_name"/>
<field name="part_number"/>
<field name="materials_id"/>
<field name="blank_type"/>
<field name="embryo_long"/>
<field name="is_incoming_material"/>
<field name="pending_qty"/>
<field name="planned_qty"/>
<field name="model_id"/>
</group>
<group>
<field name="customer_name"/>
<field name="product_uom_qty"/>
<field name="deadline_of_delivery"/>
<field name="contract_date"/>
<field name="contract_code"/>
<field name="model_process_parameters_ids" widget="many2many_tags"/>
<field name="model_machining_precision"/>
<field name="inventory_quantity_auto_apply"/>
<field name="priority" attrs="{'readonly': [('state', 'in', ('40','50'))]}"/>
</group>
</group>
<notebook>
<page string="计划">
<field name="line_ids" attrs="{'readonly': [('state', 'in', ('40','50'))]}">
<tree editable="bottom" delete="false">
<field name="status"/>
<field name="supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="route_ids" widget="many2many_tags" optional="hide"/>
<field name="location_id" optional="hide"/>
<field name="bom_id" optional="hide"/>
<field name="plan_uom_qty"/>
<field name="blank_arrival_date"/>
<field name="finished_product_arrival_date"/>
<field name="planned_start_date"/>
<field name="actual_start_date"/>
<field name="actual_end_date"/>
<field name="plan_remark"/>
<field name="procurement_reason"/>
<field name="write_date" string="更新时间"/>
<field name="hide_release_production_order" invisible="1"/>
<button string="下达计划" name="button_release_plan" type="object" class="btn-primary" attrs="{'invisible': [('status', 'in', ('50','60','100'))]}" />
<button name="button_release_production" type="object" string="下发生产" class="btn-primary" attrs="{'invisible': [('hide_release_production_order', '=', False)]}" />
<button name="button_delete" type="object" string="删除" class="btn-primary" attrs="{'invisible': [('status', 'not in', ('10','20','30'))]}" confirm='是否确认删除?'/>
</tree>
</field>
</page>
</notebook>
</sheet>
</form>
</field>
</record>
</odoo>

View File

@@ -1,17 +1,16 @@
<odoo> <odoo>
<record id="view_sf_production_demand_plan_tree" model="ir.ui.view"> <record id="view_sf_production_demand_plan_tree" model="ir.ui.view">
<field name="name">sf.production.demand.plan.tree</field> <field name="name">sf.production.demand.plan.tree</field>
<field name="model">sf.production.demand.plan</field> <field name="model">sf.production.demand.plan</field>
<field name="arch" type="xml"> <field name="arch" type="xml">
<tree string="需求计划" default_order="sequence desc,id desc" editable="bottom" <tree string="需求计划" default_order="sequence desc,id desc" editable="bottom"
class="demand_plan_tree"> class="demand_plan_tree freeze-columns-before-part_number" create="false" delete="false">
<header> <header>
<button string="打印" name="button_action_print" type="object" <button string="打印" name="button_action_print" type="object"
class="btn-primary"/> class="btn-primary"/>
</header> </header>
<field name="sequence" widget="handle"/> <field name="sequence" widget="handle"/>
<field name="id" optional="hide"/> <field name="id" optional="hide"/>
<field name="priority"/>
<field name="status"/> <field name="status"/>
<field name="customer_name"/> <field name="customer_name"/>
<field name="order_remark"/> <field name="order_remark"/>
@@ -21,54 +20,58 @@
<field name="part_name"/> <field name="part_name"/>
<field name="part_number"/> <field name="part_number"/>
<field name="is_incoming_material"/> <field name="is_incoming_material"/>
<field name="supply_method"/> <field name="supply_method" attrs="{'readonly': [('status', '!=', '30')]}"/>
<field name="product_uom_qty"/> <field name="product_uom_qty"/>
<field name="deadline_of_delivery"/> <field name="deadline_of_delivery"/>
<field name="inventory_quantity_auto_apply"/> <field name="inventory_quantity_auto_apply"/>
<field name="qty_delivered"/> <field name="qty_delivered"/>
<field name="qty_to_deliver"/> <field name="qty_to_deliver"/>
<field name="model_long"/> <field name="model_long"/>
<field name="blank_type"/> <field name="blank_type" optional="hide"/>
<field name="blank_precision"/>
<field name="embryo_long"/> <field name="embryo_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="product_remark" optional="hide"/>
<field name="order_code" optional="hide"/> <field name="order_code" optional="hide"/>
<field name="sale_order_id" optional="hide"/> <field name="sale_order_id" optional="hide"/>
<field name="sale_order_line_number" optional="hide"/> <field name="sale_order_line_number" optional="hide"/>
<field name="order_state"/> <field name="order_state"/>
<field name="route_id" optional="hide"/>
<field name="contract_date"/> <field name="contract_date"/>
<field name="date_order"/> <field name="date_order"/>
<field name="contract_code"/> <field name="contract_code"/>
<field name="plan_remark"/> <field name="plan_remark" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="processing_time"/> <field name="priority" decoration-danger="priority == '1'"
decoration-warning="priority == '2'"
decoration-info="priority == '3'"
decoration-success="priority == '4'"/>
<field name="material_check" optional="hide"/> <field name="material_check" optional="hide"/>
<field name="hide_action_open_mrp_production" invisible="1"/> <!-- <field name="hide_action_open_mrp_production" invisible="1"/>-->
<field name="hide_action_purchase_orders" invisible="1"/> <!-- <field name="hide_action_purchase_orders" invisible="1"/>-->
<field name="hide_action_stock_picking" invisible="1"/> <!-- <field name="hide_action_stock_picking" invisible="1"/>-->
<field name="hide_action_view_programming" invisible="1"/> <!-- <field name="hide_action_view_programming" invisible="1"/>-->
<button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary" <!-- <button name="action_open_sale_order" type="object" string="供货方式待确认" class="btn-secondary"-->
attrs="{'invisible': [('supply_method', '!=', False)]}"/> <!-- attrs="{'invisible': [('supply_method', '!=', False)]}"/>-->
<button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary" <!-- <button name="action_open_mrp_production" type="object" string="待工艺确认" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_open_mrp_production', '=', False)]}"/>-->
<button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary" <!-- <button name="action_view_purchase_request" type="object" string="采购申请" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_purchase_orders', '=', False)]}"/>-->
<button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary" <!-- <button name="action_view_stock_picking" type="object" string="调拨单" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_stock_picking', '=', False)]}"/>-->
<button name="action_view_programming" type="object" string="编程单" class="btn-secondary" <!-- <button name="action_view_programming" type="object" string="编程单" class="btn-secondary"-->
attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/> <!-- attrs="{'invisible': [('hide_action_view_programming', '=', False)]}"/>-->
<field name="planned_start_date"/> <field name="planned_start_date" attrs="{'readonly': [('status', 'in', ('60','100'))]}"/>
<field name="actual_start_date"/> <field name="actual_start_date"/>
<field name="actual_end_date"/> <field name="actual_end_date"/>
<field name="processing_time"/>
<field name="create_date" optional="hide" string="创建时间"/> <field name="create_date" optional="hide" string="创建时间"/>
<field name="create_uid" optional="hide" string="创建人"/> <field name="create_uid" optional="hide" string="创建人"/>
<field name="write_date" string="更新时间"/> <field name="write_date" string="更新时间"/>
<field name="write_uid" optional="hide" string="更新人"/> <field name="write_uid" optional="hide" string="更新人"/>
<field name="print_count"/> <field name="print_count"/>
<button name="release_production_order" type="object" string="下达生产" class="btn-primary" <field name="hide_release_production_order" invisible="1"/>
attrs="{'invisible': ['|',('status', '!=', '50'), ('supply_method', 'not in', ['automation', 'manual'])]}"/> <button name="button_release_production" type="object" string="下发生产" class="btn-primary"
attrs="{'invisible': [('hide_release_production_order', '=', False)]}"
/>
<button name="action_edit_demand_plan_form" type="object" string="编辑" class="btn-primary"/>
</tree> </tree>
</field> </field>
</record> </record>
@@ -91,7 +94,8 @@
<group expand="0" string="Group By"> <group expand="0" string="Group By">
<filter name="group_by_priority" string="优先级" domain="[]" context="{'group_by': 'priority'}"/> <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_status" string="状态" domain="[]" context="{'group_by': 'status'}"/>
<filter name="group_by_customer_name" string="客户" domain="[]" context="{'group_by': 'customer_name'}"/> <filter name="group_by_customer_name" string="客户" domain="[]"
context="{'group_by': 'customer_name'}"/>
<filter name="group_by_is_incoming_material" string="客供料" domain="[]" <filter name="group_by_is_incoming_material" string="客供料" domain="[]"
context="{'group_by': 'is_incoming_material'}"/> context="{'group_by': 'is_incoming_material'}"/>
<filter name="group_by_supply_method" string="供货方式" domain="[]" <filter name="group_by_supply_method" string="供货方式" domain="[]"
@@ -105,8 +109,7 @@
</group> </group>
</search> </search>
</field> </field>
</record> </record>
<record id="sf_production_demand_plan_action" model="ir.actions.act_window"> <record id="sf_production_demand_plan_action" model="ir.actions.act_window">
<field name="name">需求计划</field> <field name="name">需求计划</field>
<field name="type">ir.actions.act_window</field> <field name="type">ir.actions.act_window</field>
@@ -114,12 +117,4 @@
<field name="view_mode">tree</field> <field name="view_mode">tree</field>
</record> </record>
</odoo>
<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,18 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data>
<menuitem
id="demand_plan_menu"
name="需求计划"
sequence="140"
action="sf_demand_plan.sf_production_demand_plan_action"
parent="sf_plan.sf_production_plan_menu"
/>
<!-- 调拨动作中屏蔽验证-->
<record id="stock.action_validate_picking" model="ir.actions.server">
<field name="binding_model_id" eval="False"/>
</record>
</data>
</odoo>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="view_order_form_inherit_plan" model="ir.ui.view">
<field name="name">view.sale.order.form.inherit.plan</field>
<field name="inherit_id" ref="sf_manufacturing.view_order_form_inherit_supply_method"/>
<field name="model">sale.order</field>
<field name="arch" type="xml">
<xpath expr="//header/button[@name='action_confirm'][last()]" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//page/field[@name='order_line']/tree/field[@name='supply_method']" position="attributes">
<attribute name="invisible">True</attribute>
</xpath>
<xpath expr="//div[@name='button_box']" position="inside">
<button class="oe_stat_button" name="action_view_demand_plan" type="object" icon="fa-pencil-square-o"
attrs="{'invisible': [('demand_plan_count', '=', 0)]}">
<div class="o_field_widget o_stat_info">
<span class="o_stat_value">
<field name="demand_plan_count"/>
</span>
<span class="o_stat_text">需求计划</span>
</div>
</button>
</xpath>
</field>
</record>
</odoo>

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="sf_stock_location_route_form_view" model="ir.ui.view">
<field name="name">stock.route.form</field>
<field name="model">stock.route</field>
<field name="inherit_id" ref="stock.stock_location_route_form_view"/>
<field name="arch" type="xml">
<xpath expr="//field[@name='packaging_selectable']" position="after">
<field name="demand_plan_selectable"/>
</xpath>
<xpath expr="//group[@name='route_selector']" position="after">
<group name="group_category" string="组类">
<group>
<field name="stock_route_group_ids" options="{'no_create': True}" widget="many2many_tags"/>
<field name="demand_plan_ids" invisible="1" options="{'no_create': True}" widget="many2many_tags"/>
</group>
</group>
</xpath>
</field>
</record>
</odoo>

View File

@@ -1 +1,2 @@
from . import sf_demand_plan_print_wizard from . import sf_demand_plan_print_wizard
from . import sf_release_plan_wizard

View File

@@ -0,0 +1,22 @@
# -*- coding: utf-8 -*-
import logging
from odoo import models, fields, api, _
_logger = logging.getLogger(__name__)
class SfReleasePlanWizard(models.TransientModel):
_name = 'sf.release.plan.wizard'
_description = u'下达计划向导'
demand_plan_id = fields.Many2one(comodel_name="sf.demand.main.plan",
string="需求计划", readonly=True)
demand_plan_line_id = fields.Many2one(comodel_name="sf.production.demand.plan",
string="需求计划明细", readonly=True)
release_message = fields.Char(string='提示', readonly=True)
def confirm(self):
if self.demand_plan_line_id:
self.demand_plan_line_id.action_confirm()

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="sf_release_plan_wizard_form">
<field name="name">sf.release.plan.wizard.form</field>
<field name="model">sf.release.plan.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<div>
<div style="white-space: pre-wrap;">
<field name="release_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

@@ -3,6 +3,7 @@ import logging
import re import re
from odoo import models, fields, api from odoo import models, fields, api
from odoo.exceptions import ValidationError
class ResProductCategory(models.Model): class ResProductCategory(models.Model):
@@ -47,11 +48,14 @@ class ResMrpBomMo(models.Model):
item.subcontractor_name = '' item.subcontractor_name = ''
def bom_create_line_has(self, embryo): def bom_create_line_has(self, embryo):
product = self.product_tmpl_id
if product.unit_number in (0, None, False):
raise ValidationError(f'产品{product.name}单件用量的值不能为{product.unit_number}')
vals = { vals = {
'bom_id': self.id, 'bom_id': self.id,
'product_id': embryo.id, 'product_id': embryo.id,
'product_tmpl_id': embryo.product_tmpl_id.id, 'product_tmpl_id': embryo.product_tmpl_id.id,
'product_qty': 1, 'product_qty': product.unit_number,
'product_uom_id': 1 'product_uom_id': 1
} }
return self.env['mrp.bom.line'].sudo().create(vals) return self.env['mrp.bom.line'].sudo().create(vals)
@@ -122,7 +126,7 @@ class ResMrpBomMo(models.Model):
# 查bom的原材料 # 查bom的原材料
def get_raw_bom(self, product): def get_raw_bom(self, product):
raw_bom = self.env['product.product'].search( raw_bom = self.env['product.product'].search(
[('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)],limit=1) [('categ_id.type', '=', '原材料'), ('materials_type_id', '=', product.materials_type_id.id)], limit=1)
return raw_bom return raw_bom

View File

@@ -95,6 +95,8 @@
<page string="加工参数"> <page string="加工参数">
<group> <group>
<group string="模型"> <group string="模型">
<field name="blank_type" readonly="1"/>
<field name="blank_precision" readonly="1"/>
<label for="model_long" string="坯料尺寸[mm]"/> <label for="model_long" string="坯料尺寸[mm]"/>
<div class="o_address_format"> <div class="o_address_format">
<label for="model_long" string="长"/> <label for="model_long" string="长"/>
@@ -104,7 +106,7 @@
<label for="model_height" string="高"/> <label for="model_height" string="高"/>
<field name="model_height" class="o_address_zip"/> <field name="model_height" class="o_address_zip"/>
</div> </div>
<field name="blank_type" readonly="1"/> <field name="unit_number" readonly="1"/>
<field name="model_volume" string="体积[mm³]"/> <field name="model_volume" string="体积[mm³]"/>
<field name="product_model_type_id" string="模型类型"/> <field name="product_model_type_id" string="模型类型"/>
<field name="model_processing_panel" placeholder="例如R,U" string="加工面板" <field name="model_processing_panel" placeholder="例如R,U" string="加工面板"

View File

@@ -707,9 +707,11 @@ class Sf_Dashboard_Connect(http.Controller):
interval_plan_orders = plan_order_messages.filtered( interval_plan_orders = plan_order_messages.filtered(
lambda o: o.create_date >= start_time lambda o: o.create_date >= start_time
and o.create_date <= end_time and o.create_date <= end_time
) )
interval_orders = request.env['mrp.workorder'].sudo().browse(interval_plan_orders.mapped('res_id')) interval_order_ids = set(interval_plan_orders.mapped('res_id'))
interval_orders = request.env['mrp.workorder'].sudo().browse(interval_order_ids)
if line == '业绩总览': if line == '业绩总览':
interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) interval_orders = interval_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工'])
elif line == '人工线下加工中心': elif line == '人工线下加工中心':
@@ -862,33 +864,37 @@ class Sf_Dashboard_Connect(http.Controller):
""" """
# res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []} # res = {'status': 1, 'message': '成功', 'not_done_data': [], 'done_data': []}
res = {'status': 1, 'message': '成功', 'data': {}} res = {'status': 1, 'message': '成功', 'data': {}}
# 解决产品名称取到英文的问题
request.update_context(lang='zh_CN')
plan_obj = request.env['sf.production.plan'].sudo() plan_obj = request.env['sf.production.plan'].sudo()
work_order_obj = request.env['mrp.workorder'].sudo() work_order_obj = request.env['mrp.workorder'].sudo()
# 获取mrp.workorder的state字段的selection内容
state_dict = dict(request.env['mrp.workorder'].sudo()._fields['state'].selection)
line_list = ast.literal_eval(kw['line_list']) line_list = ast.literal_eval(kw['line_list'])
begin_time_str = kw['begin_time'].strip('"') begin_time_str = kw['begin_time'].strip('"')
end_time_str = kw['end_time'].strip('"') end_time_str = kw['end_time'].strip('"')
begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S') begin_time = datetime.strptime(begin_time_str, '%Y-%m-%d %H:%M:%S')
end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S') end_time = datetime.strptime(end_time_str, '%Y-%m-%d %H:%M:%S')
# print('line_list: %s' % line_list) # print('line_list: %s' % line_list)
not_done_data = []
done_data = []
final_data = {} final_data = {}
not_done_index = 1
done_index = 1
# 获取当前时间并计算24小时前的时间 # 获取当前时间并计算24小时前的时间
current_time = datetime.now() current_time = datetime.now()
time_48_hours_ago = current_time - timedelta(hours=48) time_48_hours_ago = current_time - timedelta(hours=48)
# 计划量目前只能从mail.message中筛选出 # # 计划量目前只能从mail.message中筛选出
plan_order_messages = request.env['mail.message'].sudo().search([ # plan_order_messages = request.env['mail.message'].sudo().search([
('model', '=', 'mrp.workorder'), # ('model', '=', 'mrp.workorder'),
('create_date', '>=', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')), # ('create_date', '>=', time_48_hours_ago.strftime('%Y-%m-%d %H:%M:%S')),
('tracking_value_ids.field_desc', '=', '状态'), # ('tracking_value_ids.field_desc', '=', '状态'),
('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中']) # ('tracking_value_ids.new_value_char', 'in', ['就绪', '生产中'])
]) # ])
for line in line_list: for line in line_list:
not_done_data = []
done_data = []
not_done_index = 1
done_index = 1
if line == '业绩总览': if line == '业绩总览':
work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])] work_order_domain = [('routing_type', 'in', ['人工线下加工', 'CNC加工'])]
@@ -904,17 +910,12 @@ class Sf_Dashboard_Connect(http.Controller):
# [('production_line_id.name', '=', line), ('state', 'not in', ['finished']), # [('production_line_id.name', '=', line), ('state', 'not in', ['finished']),
# ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True) # ('production_id.state', 'not in', ['cancel', 'done']), ('active', '=', True)
# ]) # ])
# not_done_orders = work_order_obj.search(work_order_domain + not_done_orders = work_order_obj.search(work_order_domain + [
# [('state', 'in', ['ready', 'progress'])], order='id asc' ('state', 'in', ['ready', 'progress']),
# ) ('date_planned_start', '>=', time_48_hours_ago),
not_done_orders = request.env['mrp.workorder'].sudo().browse(plan_order_messages.mapped('res_id')) ('date_planned_start', '<=', current_time)
if line == '业绩总览': ], order='id asc'
not_done_orders = not_done_orders.filtered(lambda o: o.routing_type in ['人工线下加工', 'CNC加工']) )
elif line == '人工线下加工中心':
not_done_orders = not_done_orders.filtered(lambda o: o.routing_type == '人工线下加工')
else:
not_done_orders = not_done_orders.filtered(lambda o: o.routing_type == 'CNC加工' and o.production_line_id.name == line)
# 完成订单 # 完成订单
# 获取当前时间并计算24小时前的时间 # 获取当前时间并计算24小时前的时间
@@ -922,11 +923,11 @@ class Sf_Dashboard_Connect(http.Controller):
# time_24_hours_ago = current_time - timedelta(hours=24) # time_24_hours_ago = current_time - timedelta(hours=24)
finish_orders = work_order_obj.search(work_order_domain + [ finish_orders = work_order_obj.search(work_order_domain + [
('state', 'in', ['finished']), ('state', 'in', ['done']),
('production_id.state', 'not in', ['cancel']), ('production_id.state', 'not in', ['cancel']),
('date_finished', '>=', time_48_hours_ago) ('date_finished', '>=', time_48_hours_ago)
], order='id asc') ], order='id asc')
# print(finish_orders) # logging.info('完成订单: %s' % finish_orders)
# 获取所有未完成订单的ID列表 # 获取所有未完成订单的ID列表
order_ids = [order.id for order in not_done_orders] order_ids = [order.id for order in not_done_orders]
@@ -962,14 +963,6 @@ class Sf_Dashboard_Connect(http.Controller):
material_match = re.search(material_pattern, blank_name) material_match = re.search(material_pattern, blank_name)
material = material_match.group(1) if material_match else 'No match found' material = material_match.group(1) if material_match else 'No match found'
state_dict = {
'draft': '待排程',
'done': '已排程',
'processing': '生产中',
'finished': '已完成',
'ready': '待加工',
'progress': '生产中',
}
line_dict = { line_dict = {
'sequence': not_done_index, 'sequence': not_done_index,
@@ -985,8 +978,6 @@ class Sf_Dashboard_Connect(http.Controller):
not_done_index += 1 not_done_index += 1
for finish_order in finish_orders: for finish_order in finish_orders:
if not finish_order.actual_end_time:
continue
blank_name = '' blank_name = ''
try: try:
blank_name = finish_order.production_id.move_raw_ids[0].product_id.name blank_name = finish_order.production_id.move_raw_ids[0].product_id.name
@@ -1002,13 +993,13 @@ class Sf_Dashboard_Connect(http.Controller):
line_dict = { line_dict = {
'sequence': done_index, 'sequence': done_index,
'workorder_name': finish_order.name, 'workorder_name': finish_order.production_id.name,
'blank_name': blank_name, 'blank_name': blank_name,
'material': material, 'material': material,
'dimensions': dimensions, 'dimensions': dimensions,
'order_qty': order.qty_produced, 'order_qty': finish_order.qty_produced,
'finish_time': finish_order.actual_end_time.strftime( 'finish_time': finish_order.date_finished.strftime(
'%Y-%m-%d %H:%M:%S') if finish_order.actual_end_time else ' ' '%Y-%m-%d %H:%M:%S') if finish_order.date_finished else ' '
} }
done_data.append(line_dict) done_data.append(line_dict)

View File

@@ -1710,76 +1710,7 @@ class MrpProduction(models.Model):
else: else:
vals['procurement_group_id'] = is_custemer_group_id[key] vals['procurement_group_id'] = is_custemer_group_id[key]
productions = super(MrpProduction, self).create(vals_list) return super(MrpProduction, self).create(vals_list)
# 查询成品工序排序(自动化产线加工),供后续使用
product_model_type_routing_sorts = self.env['sf.product.model.type.routing.sort'].search([], order='sequence asc')
# 查询成品工序排序(人工线下加工),供后续使用
manual_product_model_type_routing_sorts = self.env['sf.manual.product.model.type.routing.sort'].search([], order='sequence asc')
# 查询坯料工序排序,供后续使用
embryo_model_type_routing_sorts = self.env['sf.embryo.model.type.routing.sort'].search([], order='sequence asc')
for production in productions:
# 生成序列号
production.action_generate_serial()
# 创建工序模板
technology_design_values = []
i = 0
if production.product_id.categ_id.type == '成品':
# 根据加工面板的面数及成品工序模板生成工序设计
if production.production_type == '自动化产线加工':
product_routing_workcenter = product_model_type_routing_sorts.filtered(
lambda s: s.product_model_type_id.id == production.product_id.product_model_type_id.id
)
else:
product_routing_workcenter = manual_product_model_type_routing_sorts.filtered(
lambda s: s.manual_product_model_type_id.id == production.product_id.product_model_type_id.id
)
if production.production_type == '自动化产线加工':
for k in (production.product_id.model_processing_panel.split(',')):
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)
)
elif production.production_type == '人工线下加工':
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)
)
else:
for route in product_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)
)
elif production.product_id.categ_id.type == '坯料':
embryo_routing_workcenter = embryo_model_type_routing_sorts.filtered(
lambda s: s.embryo_model_type_id.id == production.product_id.embryo_model_type_id.id
)
for route_embryo in embryo_routing_workcenter:
i += 1
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, False)
)
# 处理表面工艺
for item in production.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
if item.route_workcenter_id.surface_technics_id.id:
for process_param in production.product_id.model_process_parameters_ids:
if item.route_workcenter_id.surface_technics_id == process_param.process_id:
technology_design_values.append(
self.env['sf.technology.design'].json_technology_design_str(
False,
item.route_workcenter_id,
i,
process_param
)
)
production.technology_design_ids = technology_design_values
# 设置制造订单状态为待工艺确认
productions.write({'state': 'technology_to_confirmed'})
return productions
@api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id', @api.depends('procurement_group_id.stock_move_ids.created_purchase_line_id.order_id',
'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id') 'procurement_group_id.stock_move_ids.move_orig_ids.purchase_line_id.order_id')

View File

@@ -27,13 +27,15 @@ class ResProductMo(models.Model):
categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True) categ_type = fields.Selection(string='产品的类别', related='categ_id.type', store=True)
model_name = fields.Char('模型名称') model_name = fields.Char('模型名称')
blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类') blank_type = fields.Selection([('圆料', '圆料'), ('方料', '方料')], string='坯料分类')
blank_precision = fields.Selection([('精坯', '精坯'), ('粗坯', '粗坯')], string='坯料类型')
model_long = fields.Float('模型长(mm)', digits=(16, 3)) model_long = fields.Float('模型长(mm)', digits=(16, 3))
model_width = fields.Float('模型宽(mm)', digits=(16, 3)) model_width = fields.Float('模型宽(mm)', digits=(16, 3))
model_height = fields.Float('模型高(mm)', digits=(16, 3)) model_height = fields.Float('模型高(mm)', digits=(16, 3))
unit_number = fields.Float('单件用量', digits=(16, 3), default=1)
model_volume = fields.Float('模型体积(m³)') model_volume = fields.Float('模型体积(m³)')
model_area = fields.Float('模型表面积(m²)') model_area = fields.Float('模型表面积(m²)')
model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度') model_machining_precision = fields.Selection(selection=_get_machining_precision, string='加工精度')
model_processing_panel = fields.Char('模型加工面板') model_processing_panel = fields.Char('模型加工面板', default='')
model_remark = fields.Char('模型备注说明') model_remark = fields.Char('模型备注说明')
length = fields.Float('长(mm)', digits=(16, 3)) length = fields.Float('长(mm)', digits=(16, 3))
width = fields.Float('宽(mm)', digits=(16, 3)) width = fields.Float('宽(mm)', digits=(16, 3))
@@ -901,15 +903,17 @@ class ResProductMo(models.Model):
vals = { vals = {
'name': product_name, 'name': product_name,
'blank_type': item.get('blank_type'), 'blank_type': item.get('blank_type'),
'blank_precision': item.get('blank_precision'),
'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long), 'model_long': item.get('blank_length') if blank_bool else self.format_float(item['model_long'] + embryo_redundancy_id.long),
'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width), 'model_width': item.get('blank_width') if blank_bool else self.format_float(item['model_width'] + embryo_redundancy_id.width),
'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height), 'model_height': item.get('blank_height') if blank_bool else self.format_float(item['model_height'] + embryo_redundancy_id.height),
'unit_number': item.get('unit_number'),
'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) * 'model_volume': self.format_float(((item['model_long'] + embryo_redundancy_id.long) *
(item['model_width'] + embryo_redundancy_id.width) * (item['model_width'] + embryo_redundancy_id.width) *
(item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else ( (item['model_height'] + embryo_redundancy_id.height))) if not blank_bool else (
item.get('blank_length') * item.get('blank_width') * item.get('blank_height')), item.get('blank_length') * item.get('blank_width') * item.get('blank_height')),
'product_model_type_id': model_type.id, 'product_model_type_id': model_type.id,
'model_processing_panel': item['processing_panel_detail'], 'model_processing_panel': item['processing_panel_detail'] if item['processing_panel_detail'] else '',
'model_machining_precision': item['model_machining_precision'], 'model_machining_precision': item['model_machining_precision'],
'model_code': item['barcode'], 'model_code': item['barcode'],
'length': item['model_long'], 'length': item['model_long'],
@@ -957,7 +961,7 @@ class ResProductMo(models.Model):
self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id, self.attachment_update(item['quality_standard_name'], copy_product_id.product_tmpl_id.id,
'quality_standard', item['quality_standard_mimetype']) 'quality_standard', item['quality_standard_mimetype'])
return copy_product_id return copy_product_id
def format_float(self, value): def format_float(self, value):
# 将浮点数转换为字符串 # 将浮点数转换为字符串
value_str = str(value) value_str = str(value)

View File

@@ -21,6 +21,7 @@ from odoo.addons.sf_base.commons.common import Common
from odoo.exceptions import UserError from odoo.exceptions import UserError
from io import BytesIO from io import BytesIO
from odoo.exceptions import ValidationError from odoo.exceptions import ValidationError
from dateutil.relativedelta import relativedelta
class stockWarehouse(models.Model): class stockWarehouse(models.Model):
@@ -163,203 +164,203 @@ class StockRule(models.Model):
[('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1) [('res_id', '=', res_id), ('res_field', '=', res_field)], limit=1)
attachment_info.write({'name': name}) attachment_info.write({'name': name})
# @api.model @api.model
# def _run_manufacture(self, procurements): def _run_manufacture(self, procurements):
# productions_values_by_company = defaultdict(list) productions_values_by_company = defaultdict(list)
# errors = [] errors = []
# for procurement, rule in procurements: for procurement, rule in procurements:
# if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0: if float_compare(procurement.product_qty, 0, precision_rounding=procurement.product_uom.rounding) <= 0:
# # If procurement contains negative quantity, don't create a MO that would be for a negative value. # If procurement contains negative quantity, don't create a MO that would be for a negative value.
# continue continue
# bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values) bom = rule._get_matching_bom(procurement.product_id, procurement.company_id, procurement.values)
# productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom)) productions_values_by_company[procurement.company_id.id].append(rule._prepare_mo_vals(*procurement, bom))
# if errors: if errors:
# raise ProcurementException(errors) raise ProcurementException(errors)
# for company_id, productions_values in productions_values_by_company.items(): for company_id, productions_values in productions_values_by_company.items():
# # create the MO as SUPERUSER because the current user may not have the rights to do it # create the MO as SUPERUSER because the current user may not have the rights to do it
# # (mto product launched by a sale for example) # (mto product launched by a sale for example)
# '''创建制造订单''' '''创建制造订单'''
# productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create( productions = self.env['mrp.production'].with_user(SUPERUSER_ID).sudo().with_company(company_id).create(
# productions_values) productions_values)
# ''' '''
# 创建工单 创建工单
# ''' '''
# # productions._create_workorder() # productions._create_workorder()
# # #
# # self.env['stock.move'].sudo().create(productions._get_moves_finished_values()) # self.env['stock.move'].sudo().create(productions._get_moves_finished_values())
# productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \ productions.filtered(lambda p: (not p.orderpoint_id and p.move_raw_ids) or \
# ( (
# p.move_dest_ids.procure_method != 'make_to_order' and not p.move_dest_ids.procure_method != 'make_to_order' and not
# p.move_raw_ids and not p.workorder_ids)).action_confirm() p.move_raw_ids and not p.workorder_ids)).action_confirm()
# # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区 # 处理 根据制造订单生成的采购单坯料入库时到原材料库,手动将原材料位置该为坯料存货区
# for production in productions: for production in productions:
# if production.picking_ids: if production.picking_ids:
# product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id product_type_id = production.picking_ids[0].move_ids[0].product_id.categ_id
# if product_type_id.name == '坯料': if product_type_id.name == '坯料':
# location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')]) location_id = self.env['stock.location'].search([('name', '=', '坯料存货区')])
# if not location_id: if not location_id:
# logging.info(f'没有搜索到【坯料存货区】: {location_id}') logging.info(f'没有搜索到【坯料存货区】: {location_id}')
# break break
# for picking_id in production.picking_ids: for picking_id in production.picking_ids:
# if picking_id.picking_type_id.name == '内部调拨': if picking_id.picking_type_id.name == '内部调拨':
# if picking_id.location_dest_id.product_type != product_type_id: if picking_id.location_dest_id.product_type != product_type_id:
# picking_id.location_dest_id = location_id.id picking_id.location_dest_id = location_id.id
# elif picking_id.picking_type_id.name == '生产发料': elif picking_id.picking_type_id.name == '生产发料':
# if picking_id.location_id.product_type != product_type_id: if picking_id.location_id.product_type != product_type_id:
# picking_id.location_id = location_id.id picking_id.location_id = location_id.id
# for production in productions: for production in productions:
# ''' '''
# 创建制造订单时生成序列号 创建制造订单时生成序列号
# ''' '''
# # production.action_generate_serial() production.action_generate_serial()
# origin_production = production.move_dest_ids and production.move_dest_ids[ origin_production = production.move_dest_ids and production.move_dest_ids[
# 0].raw_material_production_id or False 0].raw_material_production_id or False
# orderpoint = production.orderpoint_id orderpoint = production.orderpoint_id
# if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual': if orderpoint and orderpoint.create_uid.id == SUPERUSER_ID and orderpoint.trigger == 'manual':
# production.message_post( production.message_post(
# body=_('This production order has been created from Replenishment Report.'), body=_('This production order has been created from Replenishment Report.'),
# message_type='comment', message_type='comment',
# subtype_xmlid='mail.mt_note') subtype_xmlid='mail.mt_note')
# elif orderpoint: elif orderpoint:
# production.message_post_with_view( production.message_post_with_view(
# 'mail.message_origin_link', 'mail.message_origin_link',
# values={'self': production, 'origin': orderpoint}, values={'self': production, 'origin': orderpoint},
# subtype_id=self.env.ref('mail.mt_note').id) subtype_id=self.env.ref('mail.mt_note').id)
# elif origin_production: elif origin_production:
# production.message_post_with_view( production.message_post_with_view(
# 'mail.message_origin_link', 'mail.message_origin_link',
# values={'self': production, 'origin': origin_production}, values={'self': production, 'origin': origin_production},
# subtype_id=self.env.ref('mail.mt_note').id) subtype_id=self.env.ref('mail.mt_note').id)
# ''' '''
# 创建生产计划 创建生产计划
# ''' '''
# # 工单耗时 # 工单耗时
# # workorder_duration = 0 workorder_duration = 0
# # for workorder in production.workorder_ids: for workorder in production.workorder_ids:
# # workorder_duration += workorder.duration_expected workorder_duration += workorder.duration_expected
# # sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)]) sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
# # # 如果订单为空,则获取来源制造订单的销售单 # 如果订单为空,则获取来源制造订单的销售单
# # if not sale_order: if not sale_order:
# # mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)], mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)],
# # limit=1) limit=1)
# # if mrp_production: if mrp_production:
# # sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)]) sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)])
# # else: else:
# # mrp_production = production mrp_production = production
# # # if sale_order: # if sale_order:
# # # sale_order.write({'schedule_status': 'to schedule'}) # sale_order.write({'schedule_status': 'to schedule'})
# # self.env['sf.production.plan'].sudo().with_company(company_id).create({ self.env['sf.production.plan'].sudo().with_company(company_id).create({
# # 'name': production.name, 'name': production.name,
# # 'order_deadline': sale_order.deadline_of_delivery, 'order_deadline': sale_order.deadline_of_delivery,
# # 'production_id': production.id, 'production_id': production.id,
# # 'date_planned_start': production.date_planned_start, 'date_planned_start': production.date_planned_start,
# # 'origin': mrp_production.origin, 'origin': mrp_production.origin,
# # 'product_qty': production.product_qty, 'product_qty': production.product_qty,
# # 'product_id': production.product_id.id, 'product_id': production.product_id.id,
# # 'state': 'draft', 'state': 'draft',
# # }) })
# all_production = productions all_production = productions
# grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)} grouped_product_ids = {k: list(g) for k, g in groupby(all_production, key=lambda x: x.product_id.id)}
# # 初始化一个字典来存储每个product_id对应的生产订单名称列表 # 初始化一个字典来存储每个product_id对应的生产订单名称列表
# product_id_to_production_names = {} product_id_to_production_names = {}
# # 对于每个product_id获取其所有生产订单的名称 # 对于每个product_id获取其所有生产订单的名称
# for product_id, all_production in grouped_product_ids.items(): for product_id, all_production in grouped_product_ids.items():
# # 为同一个product_id创建一个生产订单名称列表 # 为同一个product_id创建一个生产订单名称列表
# product_id_to_production_names[product_id] = [production.name for production in all_production] product_id_to_production_names[product_id] = [production.name for production in all_production]
# for production_item in productions: for production_item in productions:
# technology_design_values = [] technology_design_values = []
# # production_programming = self.env['mrp.production'].search( production_programming = self.env['mrp.production'].search(
# # [('product_id.id', '=', production_item.product_id.id), [('product_id.id', '=', production_item.product_id.id),
# # ('origin', '=', production_item.origin)], ('origin', '=', production_item.origin)],
# # limit=1, order='id asc') limit=1, order='id asc')
# # if production_item.product_id.id in product_id_to_production_names: if production_item.product_id.id in product_id_to_production_names:
# # # 同一个产品多个制造订单对应一个编程单和模型库 # 同一个产品多个制造订单对应一个编程单和模型库
# # # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递 # 只调用一次fetchCNC并将所有生产订单的名称作为字符串传递
# # if not production_item.programming_no and production_item.production_type in ['自动化产线加工', if not production_item.programming_no and production_item.production_type in ['自动化产线加工',
# # '人工线下加工']: '人工线下加工']:
# # if not production_programming.programming_no: if not production_programming.programming_no:
# # production_item.fetchCNC( production_item.fetchCNC(
# # ', '.join(product_id_to_production_names[production_item.product_id.id])) ', '.join(product_id_to_production_names[production_item.product_id.id]))
# # else: else:
# # production_item.write({'programming_no': production_programming.programming_no, production_item.write({'programming_no': production_programming.programming_no,
# # 'programming_state': '编程中'}) 'programming_state': '编程中'})
# i = 0 i = 0
# if production_item.product_id.categ_id.type == '成品': if production_item.product_id.categ_id.type == '成品':
# # 根据加工面板的面数及成品工序模板生成工序设计 # 根据加工面板的面数及成品工序模板生成工序设计
# if production_item.production_type == '自动化产线加工': if production_item.production_type == '自动化产线加工':
# model = 'sf.product.model.type.routing.sort' model = 'sf.product.model.type.routing.sort'
# domain = [ domain = [
# ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)] ('product_model_type_id', '=', production_item.product_id.product_model_type_id.id)]
# else: else:
# model = 'sf.manual.product.model.type.routing.sort' model = 'sf.manual.product.model.type.routing.sort'
# domain = [('manual_product_model_type_id', '=', domain = [('manual_product_model_type_id', '=',
# production_item.product_id.product_model_type_id.id)] production_item.product_id.product_model_type_id.id)]
# product_routing_workcenter = self.env[model].search(domain, order='sequence asc') product_routing_workcenter = self.env[model].search(domain, order='sequence asc')
# if production_item.production_type == '自动化产线加工': if production_item.production_type == '自动化产线加工':
# for k in (production_item.product_id.model_processing_panel.split(',')): for k in (production_item.product_id.model_processing_panel.split(',')):
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(k, route, i, False)) self.env['sf.technology.design'].json_technology_design_str(k, route, i, False))
# elif production_item.production_type == '人工线下加工': elif production_item.production_type == '人工线下加工':
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False)) self.env['sf.technology.design'].json_technology_design_str('ZM', route, i, False))
# else: else:
# for route in product_routing_workcenter: for route in product_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, route, i, False)) self.env['sf.technology.design'].json_technology_design_str(False, route, i, False))
# elif production_item.product_id.categ_id.type == '坯料': elif production_item.product_id.categ_id.type == '坯料':
# embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search( embryo_routing_workcenter = self.env['sf.embryo.model.type.routing.sort'].search(
# [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)], [('embryo_model_type_id', '=', production_item.product_id.embryo_model_type_id.id)],
# order='sequence asc' order='sequence asc'
# ) )
# for route_embryo in embryo_routing_workcenter: for route_embryo in embryo_routing_workcenter:
# i += 1 i += 1
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i, self.env['sf.technology.design'].json_technology_design_str(False, route_embryo, i,
# False)) False))
# surface_technics_arr = [] surface_technics_arr = []
# route_workcenter_arr = [] route_workcenter_arr = []
# for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids: for item in production_item.product_id.product_model_type_id.surface_technics_routing_tmpl_ids:
# if item.route_workcenter_id.surface_technics_id.id: if item.route_workcenter_id.surface_technics_id.id:
# for process_param in production_item.product_id.model_process_parameters_ids: for process_param in production_item.product_id.model_process_parameters_ids:
# if item.route_workcenter_id.surface_technics_id == process_param.process_id: if item.route_workcenter_id.surface_technics_id == process_param.process_id:
# surface_technics_arr.append( surface_technics_arr.append(
# item.route_workcenter_id.surface_technics_id.id) item.route_workcenter_id.surface_technics_id.id)
# route_workcenter_arr.append(item.route_workcenter_id.id) route_workcenter_arr.append(item.route_workcenter_id.id)
# if surface_technics_arr: if surface_technics_arr:
# production_process = self.env['sf.production.process'].search( production_process = self.env['sf.production.process'].search(
# [('id', 'in', surface_technics_arr)], [('id', 'in', surface_technics_arr)],
# order='sequence asc' order='sequence asc'
# ) )
# for p in production_process: for p in production_process:
# logging.info('production_process:%s' % p.name) logging.info('production_process:%s' % p.name)
# process_parameters = production_item.product_id.model_process_parameters_ids.filtered( process_parameters = production_item.product_id.model_process_parameters_ids.filtered(
# lambda pm: pm.process_id.id == p.id) lambda pm: pm.process_id.id == p.id)
# for process_parameter in process_parameters: for process_parameter in process_parameters:
# i += 1 i += 1
# route_production_process = self.env[ route_production_process = self.env[
# 'mrp.routing.workcenter'].search( 'mrp.routing.workcenter'].search(
# [('surface_technics_id', '=', p.id), [('surface_technics_id', '=', p.id),
# ('id', 'in', route_workcenter_arr)]) ('id', 'in', route_workcenter_arr)])
# technology_design_values.append( technology_design_values.append(
# self.env['sf.technology.design'].json_technology_design_str(False, self.env['sf.technology.design'].json_technology_design_str(False,
# route_production_process, route_production_process,
# i, i,
# process_parameter)) process_parameter))
# production_item.technology_design_ids = technology_design_values production_item.technology_design_ids = technology_design_values
# productions.write({'state': 'technology_to_confirmed'}) productions.write({'state': 'technology_to_confirmed'})
# return True return True
class ProductionLot(models.Model): class ProductionLot(models.Model):
@@ -727,6 +728,33 @@ class StockPicking(models.Model):
production.workorder_ids.write({'back_button_display': False}) production.workorder_ids.write({'back_button_display': False})
return res return res
def _prepare_subcontract_mo_vals(self, subcontract_move, bom):
subcontract_move.ensure_one()
group = self.env['procurement.group'].sudo().search([('name', '=', self.name)])
if not group:
group = self.env['procurement.group'].create({
'name': self.name,
'partner_id': self.partner_id.id,
})
product = subcontract_move.product_id
warehouse = self._get_warehouse(subcontract_move)
vals = {
'company_id': subcontract_move.company_id.id,
'procurement_group_id': group.id,
'subcontractor_id': subcontract_move.picking_id.partner_id.commercial_partner_id.id,
'picking_ids': [subcontract_move.picking_id.id],
'product_id': product.id,
'product_uom_id': subcontract_move.product_uom.id,
'bom_id': bom.id,
'location_src_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'location_dest_id': subcontract_move.picking_id.partner_id.with_company(subcontract_move.company_id).property_stock_subcontractor.id,
'product_qty': subcontract_move.product_uom_qty,
'picking_type_id': warehouse.subcontracting_type_id.id,
'date_planned_start': subcontract_move.date - relativedelta(days=product.produce_delay)
}
return vals
# 创建 外协出库入单 # 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders): def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id production = workorders[0].production_id

View File

@@ -2,4 +2,3 @@ from . import ftp_operate
from . import res_config_setting from . import res_config_setting
from . import sync_common from . import sync_common
from . import order_price from . import order_price
from . import mrp_production

View File

@@ -1,44 +0,0 @@
from itertools import groupby
from odoo import models, api
from odoo.tools.misc import OrderedSet
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.model_create_multi
def create(self, vals_list):
"""
生成编程单
"""
productions = super().create(vals_list)
# 定义变量存储编程单
grouped_product_programming_no = {}
# 定义产品拼接成的制造订单名称
grouped_product_production_name = {}
# 查出所有的制造订单,为了适配通过补货生成的制造订单
all_productions = self.env['mrp.production'].search([('origin', '=', productions[0].origin)])
# 将不同产品的制造订单进行分组
grouped_product_productions = {k: list(g) for k, g in groupby(all_productions, key=lambda x: x.product_id.id)}
for product_id, grouped_productions in grouped_product_productions.items():
# 产品对应的编程单号
if product_id not in grouped_product_programming_no:
# 使用列表推导式获取非空的programming_no
programming_nos = [p.programming_no for p in grouped_productions if p.programming_no if p.programming_no is not False]
if programming_nos:
grouped_product_programming_no[product_id] = programming_nos[0]
grouped_product_production_name[product_id] = ','.join(list(map(lambda p:p.name, grouped_productions)))
# 同一个产品的制造订单只请求一次CNC编程
for production in productions:
if not production.programming_no and production.production_type in ['自动化产线加工','人工线下加工']:
if production.product_id.id not in grouped_product_programming_no:
production.fetchCNC(grouped_product_production_name[production.product_id.id])
grouped_product_programming_no[production.product_id.id] = production.programming_no
else:
production.write({
'programming_no': grouped_product_programming_no[production.product_id.id],
'programming_state': '编程中'
})
return productions

View File

@@ -3,4 +3,3 @@
from . import custom_plan from . import custom_plan
from . import change_manufactuing from . import change_manufactuing
from . import mrp_production

View File

@@ -1,41 +0,0 @@
from odoo import models, api
class MrpProduction(models.Model):
_inherit = 'mrp.production'
@api.model_create_multi
def create(self, vals_list):
"""
创建生产计划
"""
productions = super().create(vals_list)
for production in productions:
# 工单耗时
workorder_duration = 0
for workorder in production.workorder_ids:
workorder_duration += workorder.duration_expected
sale_order = self.env['sale.order'].sudo().search([('name', '=', production.origin)])
# 如果订单为空,则获取来源制造订单的销售单
if not sale_order:
mrp_production = self.env['mrp.production'].sudo().search([('name', '=', production.origin)],
limit=1)
if mrp_production:
sale_order = self.env['sale.order'].sudo().search([('name', '=', mrp_production.origin)])
else:
mrp_production = production
# if sale_order:
# sale_order.write({'schedule_status': 'to schedule'})
self.env['sf.production.plan'].sudo().with_company(production.company_id).create({
'name': production.name,
'order_deadline': sale_order.deadline_of_delivery,
'production_id': production.id,
'date_planned_start': production.date_planned_start,
'origin': mrp_production.origin,
'product_qty': production.product_qty,
'product_id': production.product_id.id,
'state': 'draft',
})
return productions

View File

@@ -1,59 +1,180 @@
import logging
from odoo import api, models from odoo import api, models
from odoo.exceptions import ValidationError, UserError
class StockPicking(models.Model): class StockPicking(models.Model):
_inherit = 'stock.picking' _inherit = 'stock.picking'
def _compute_check(self):
super()._compute_check()
for picking in self:
picking_to_quality = picking.get_picking_to_quality()
if not picking_to_quality:
picking.quality_check_todo = False
break
else:
need_quality_line = picking.get_need_quality_line(picking_to_quality)
if not need_quality_line or all(not line.get('need_done_check_ids') for line in need_quality_line):
picking.quality_check_todo = False
def check_quality(self):
self.ensure_one()
# checkable_products = self.mapped('move_line_ids').mapped('product_id')
# checks = self.check_ids.filtered(lambda check: check.quality_state == 'none' and (
# check.product_id in checkable_products or check.measure_on == 'operation'))
checks = self.env['quality.check']
picking_to_quality = self._get_picking_to_quality()
need_quality_line = self.get_need_quality_line(picking_to_quality)
if need_quality_line and any(line.get('need_done_check_ids') for line in need_quality_line):
for item in need_quality_line:
checks += item.get('need_done_check_ids')
if checks:
return checks.action_open_quality_check_wizard()
return False
def button_validate(self): def button_validate(self):
""" """=
出厂检验报告上传 出厂检验报告上传
""" """
out_quality_checks = self.env['quality.check'].search( out_quality_checks = self.env['quality.check'].search(
[('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告')]) [('picking_id', '=', self.id), ('test_type_id.name', '=', '出厂检验报告'),
('quality_state', '=', 'pass')])
# out_quality_checks 可能存在多个 # out_quality_checks 可能存在多个
if out_quality_checks: if out_quality_checks:
for out_quality_check in out_quality_checks: for out_quality_check in out_quality_checks:
if not out_quality_check.is_factory_report_uploaded: if not out_quality_check.is_factory_report_uploaded:
if out_quality_check and self.state == 'assigned': if out_quality_check and self.state == 'assigned':
out_quality_check.upload_factory_report() out_quality_check.upload_factory_report()
quality_action = self.pinking_checkout_quality()
if quality_action:
return quality_action
res = super(StockPicking, self).button_validate()
return res
def pinking_checkout_quality(self):
""" """
调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示: 调拨单若关联了质量检查单,验证调拨单时,应校验是否有不合格品,若存在,应弹窗提示:
“警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消 “警告存在不合格产品XXXX n 件、YYYYY m件继续调拨请点“确认”否则请取消
""" """
context = self.env.context try:
if not context.get('again_validate') and self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail'): self.ensure_one()
# 回滚事务,为二次确认/取消做准备 context = self.env.context
self.env.cr.rollback() if not context.get('pinking_checkout_quality'):
quality_check_ids = self.quality_check_ids.filtered(lambda qc: qc.quality_state == 'fail') picking_to_quality = self._get_picking_to_quality()
product_list = list(set([quality_check_id.product_id for quality_check_id in quality_check_ids])) if not picking_to_quality: return False
fail_check_text = '' need_quality_val = self.get_need_quality_line(picking_to_quality)
for product_id in product_list: if any(line.get('fail_check_ids') for line in need_quality_val):
check_ids = quality_check_ids.filtered(lambda qc: qc.product_id == product_id) # 回滚事务,为二次确认/取消做准备
if all(check_id.measure_on == 'move_line' for check_id in check_ids): self.env.cr.rollback()
number = sum(check_ids.mapped('qty_line')) # 获取存在失败的 质检单 调拨单明细行
else: check_list = [item for item in need_quality_val if item.get('fail_check_ids')]
number = sum(self.move_ids_without_package.filtered( fail_check_text = ''
lambda ml: ml.product_id == product_id).mapped('quantity_done')) for item in check_list:
if number == 0: move_id, pre_done_qty = item.get('move_id'), item.get('pre_done_qty')
number = sum(self.move_ids_without_package.filtered( fail_check_text = (f'{fail_check_text}{move_id.product_id.display_name} {pre_done_qty}'
lambda ml: ml.product_id == product_id).mapped('reserved_availability')) if fail_check_text != '' else f'{move_id.product_id.display_name} {pre_done_qty}')
if number == 0: return {
number = sum(self.move_ids_without_package.filtered( 'type': 'ir.actions.act_window',
lambda ml: ml.product_id == product_id).mapped('product_uom_qty')) 'res_model': 'picking.validate.check.wizard',
fail_check_text = (f'{fail_check_text}{product_id.display_name} {number}' 'name': '质检不合格提示',
if fail_check_text != '' else f'{product_id.display_name} {number}') 'view_mode': 'form',
return { 'target': 'new',
'type': 'ir.actions.act_window', 'context': {
'res_model': 'picking.validate.check.wizard', 'default_picking_id': self.id,
'name': '质检不合格提示', 'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?',
'view_mode': 'form', 'pinking_checkout_quality': True}
'target': 'new', }
'context': { else:
'default_picking_id': self.id, return False
'default_fail_check_text': f'警告:存在不合格产品{fail_check_text},继续调拨请点“确认”,否则请取消?', except Exception as e:
'again_validate': True} logging.info('pinking_checkout_quality()方法报错:%s' % e)
} raise ValidationError('调拨单验证质检单是否合格时报错,请联系管理员处理!!')
res = super(StockPicking, self).button_validate()
def get_need_quality_line(self, picking_to_quality):
"""
# 对需要进行质检,还没有质检完成的明细行进行统计
# 1、当【质量标准_控制方式】=“产品、作业”,仅校验“预完成数量”>0的产品行对应的质检单必须处理。
2、当【质量标准_控制方式】=“数量”时,仅校验“预完成数量”>0的产品行对应的质检单必须处理
1每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
2每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
"""
res = []
for item in picking_to_quality:
need_done_check_ids = self.env['quality.check']
fail_check_ids = self.env['quality.check']
move_id, pre_done_qty, check_ids = item.values()
check_ids_1 = check_ids.filtered(lambda qc: qc.measure_on in ('operation', 'product'))
if check_ids_1:
check_ids_1_done = check_ids_1.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_1_fail = check_ids_1.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_1_none = check_ids_1.filtered(lambda qc: qc.quality_state == 'none')
if check_ids_1 and not check_ids_1_done:
need_done_check_ids += check_ids_1_none
if check_ids_1_fail:
fail_check_ids += check_ids_1_fail
check_ids_2 = check_ids.filtered(lambda qc: qc.measure_on == 'move_line')
if check_ids_2:
check_ids_2_done = check_ids_2.filtered(lambda qc: qc.quality_state in ('pass', 'fail'))
check_ids_2_fail = check_ids_2.filtered(lambda qc: qc.quality_state == 'fail')
check_ids_2_none = check_ids_2.filtered(lambda qc: qc.quality_state == 'none')
# 每一类的“总单数”=【调拨单_需求】时则已处理的质检单“单数”≥“预完成数量”时可执行调拨单验证
if len(check_ids_2) >= move_id.product_uom_qty and len(check_ids_2_done) < pre_done_qty:
need_done_check_ids += check_ids_2_none
# 每一类的“总单数”<【调拨单_需求】时则已处理的质检单“单数”≥0时可执行调拨单验证
elif len(check_ids_2) < move_id.product_uom_qty and len(check_ids_2_done) == 0:
need_done_check_ids += check_ids_2_none
if check_ids_2_fail:
fail_check_ids += check_ids_2_fail
if need_done_check_ids or fail_check_ids:
res.append({'move_id': move_id,
'pre_done_qty': pre_done_qty,
'check_ids': check_ids,
'fail_check_ids': fail_check_ids,
'need_done_check_ids': need_done_check_ids})
return res return res
def get_picking_to_quality(self):
self.ensure_one()
return self._get_picking_to_quality()
def _get_picking_to_quality(self):
"""
对需要质检的明细行进行统计(针对“预完成数量”>0的行)
"""
quality_piking_line_list = []
pre_done_qty_lines = self._get_pinking_pre_done_qty()
for line in pre_done_qty_lines:
move_id, pre_done_qty = line.values()
if pre_done_qty == 0:
continue
product_id = move_id.product_id
check_ids = self.check_ids.filtered(lambda c: c.product_id == product_id)
quality_piking_line_list.append({'move_id': move_id, 'pre_done_qty': pre_done_qty, 'check_ids': check_ids})
return quality_piking_line_list
def _get_pinking_pre_done_qty(self):
"""
return: 明细行 及 预完成数量
1、若调拨单所有明细行的【完成】=0且任意行的【预留】<【需求】,则验证时将会话是否需创建欠单。
---->此时“预完成数量”=【预留】
2、若调拨单任意行的0<【完成】<【需求】,且则验证时将会话是否需创建欠单
---->此时“预完成数量”=【完成】
"""
# if all(move_id.quantity_done == 0 for move_id in self.move_ids_without_package):
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.reserved_availability} for move_id in
# self.move_ids_without_package]
# else:
# pre_done_qty = [{'move_id': move_id, 'pre_done_qty': move_id.quantity_done} for move_id in
# self.move_ids_without_package]
pre_done_qty = []
for move_id in self.move_ids_without_package:
if move_id.quantity_done > 0:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.quantity_done})
else:
pre_done_qty.append({'move_id': move_id, 'pre_done_qty': move_id.reserved_availability})
return pre_done_qty