Compare commits

...

10 Commits

Author SHA1 Message Date
liaodanlong
186ac391ea 并发创建制造订单的工单 2025-01-21 15:53:09 +08:00
黄焱
53ceed4649 Accept Merge Request #1782: (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/1782?initial=true
2025-01-17 14:22:56 +08:00
hyyy
e39e9d8812 放弃保存 -> 不保存 2025-01-17 14:17:46 +08:00
胡尧
0a01afc863 Accept Merge Request #1781: (feature/增加质检模块 -> develop)
Merge Request: 修复采购单没有接收产品的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1781?initial=true
2025-01-17 12:55:39 +08:00
胡尧
3c12b05b94 Accept Merge Request #1780: (feature/增加质检模块 -> develop)
Merge Request: 修复采购单没有接收产品的问题

Created By: @胡尧
Accepted By: @胡尧
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1780?initial=true
2025-01-17 11:27:40 +08:00
禹翔辉
bccdd93884 Accept Merge Request #1779: (feature/制造订单状态优化_1 -> 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/1779?initial=true
2025-01-17 11:07:54 +08:00
yuxianghui
41cdeb7fd9 制造订单状态优化 2025-01-17 11:06:17 +08:00
马广威
57ab276c37 Accept Merge Request #1778: (feature/制造功能优化 -> develop)
Merge Request: 调整扫码字段位置

Created By: @马广威
Accepted By: @马广威
URL: https://jikimo-hn.coding.net/p/jikimo_sfs/d/jikimo_sf/git/merge/1778?initial=true
2025-01-17 10:01:16 +08:00
mgw
f3e64b007e 调整扫码字段位置 2025-01-17 09:59:46 +08:00
mgw
eed89836e1 调整扫码字段位置 2025-01-17 09:36:37 +08:00
5 changed files with 234 additions and 162 deletions

View File

@@ -62,7 +62,7 @@ patch(FormStatusIndicator.prototype, 'jikimo_frontend.FormStatusIndicator', {
const dom1 = buttonsDom.children('.o_form_button_save') const dom1 = buttonsDom.children('.o_form_button_save')
const dom2 = buttonsDom.children('.o_form_button_cancel') const dom2 = buttonsDom.children('.o_form_button_cancel')
dom1.append('保存') dom1.append('保存')
dom2.append('放弃保存') dom2.append('保存')
} }
} catch (e) { } catch (e) {
console.log(e) console.log(e)

View File

@@ -7,6 +7,7 @@
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/> <field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//notebook/page[last()]" position="after"> <xpath expr="//notebook/page[last()]" position="after">
<field name="routing_type" invisible="1"/>
<page string="异常记录" name="workorder_exception" attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}"> <page string="异常记录" name="workorder_exception" attrs="{'invisible': [('routing_type', '!=', 'CNC加工')]}">
<field name="exception_ids" nolabel="1" readonly="1"> <field name="exception_ids" nolabel="1" readonly="1">
<tree create="false" delete="false" edit="false"> <tree create="false" delete="false" edit="false">

View File

@@ -1,20 +1,27 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
import asyncio
import base64 import base64
import cProfile
import concurrent
import datetime import datetime
import io
import logging import logging
import json import json
import os import os
import pstats
import re import re
import threading
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
import requests import requests
from itertools import groupby from itertools import groupby
from collections import defaultdict, namedtuple from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _ from odoo import api, fields, models, SUPERUSER_ID, _, tools
from odoo.exceptions import UserError, ValidationError from odoo.exceptions import UserError, ValidationError
from odoo.addons.sf_base.commons.common import Common from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_compare, float_round, float_is_zero, format_datetime from odoo.tools import float_compare, float_round, float_is_zero, format_datetime
class MrpProduction(models.Model): class MrpProduction(models.Model):
_inherit = 'mrp.production' _inherit = 'mrp.production'
_description = "制造订单" _description = "制造订单"
@@ -350,7 +357,7 @@ class MrpProduction(models.Model):
if any((wo.test_results == '返工' and wo.state == 'done' and if any((wo.test_results == '返工' and wo.state == 'done' and
(production.programming_state in ['已编程'] or wo.individuation_page_PTD is True)) (production.programming_state in ['已编程'] or wo.individuation_page_PTD is True))
or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程']) or (wo.is_rework is True and wo.state == 'done' and production.programming_state in ['编程中', '已编程'])
for wo in production.workorder_ids): for wo in production.workorder_ids) or production.is_rework is True:
production.state = 'rework' production.state = 'rework'
if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids): if any(wo.test_results == '报废' and wo.state == 'done' for wo in production.workorder_ids):
production.state = 'scrap' production.state = 'scrap'
@@ -759,24 +766,21 @@ class MrpProduction(models.Model):
# if self.product_id.tracking == 'serial': # if self.product_id.tracking == 'serial':
# self._set_qty_producing() # self._set_qty_producing()
# 重载根据工序生成工单的程序如果产品BOM中没有工序时 # 重载根据工序生成工单的程序如果产品BOM中没有工序时
# 根据产品对应的模板类型中工序,去生成工单; # 根据产品对应的模板类型中工序,去生成工单;
# CNC加工工序的选取规则 # CNC加工工序的选取规则
# 如果自动报价有带过来预分配的机床, # 如果自动报价有带过来预分配的机床,
# 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制; # 则根据设备找到工作中心;否则采用前面描述的工作中心分配机制;
# 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心; # 其他规则限制: 默认只分配给工作中心状态为非故障的工作中心;
def process_production(self):
def _create_workorder3(self, item):
for production in self:
if not production.bom_id or not production.product_id:
continue
workorders_values = [] workorders_values = []
product_qty = production.product_uom_id._compute_quantity(production.product_qty, # production = self.env['mrp.production'].browse(production_id)
production.bom_id.product_uom_id) product_qty = self.product_uom_id._compute_quantity(self.product_qty,
exploded_boms, dummy = production.bom_id.explode(production.product_id, self.bom_id.product_uom_id)
product_qty / production.bom_id.product_qty, exploded_boms, dummy = self.bom_id.explode(self.product_id,
picking_type=production.bom_id.picking_type_id) product_qty / self.bom_id.product_qty,
picking_type=self.bom_id.picking_type_id)
for bom, bom_data in exploded_boms: for bom, bom_data in exploded_boms:
# If the operations of the parent BoM and phantom BoM are the same, don't recreate work orders. # If the operations of the parent BoM and phantom BoM are the same, don't recreate work orders.
if not (bom.operation_ids and (not bom_data['parent_line'] or bom_data[ if not (bom.operation_ids and (not bom_data['parent_line'] or bom_data[
@@ -787,36 +791,80 @@ class MrpProduction(models.Model):
continue continue
workorders_values += [{ workorders_values += [{
'name': operation.name, 'name': operation.name,
'production_id': production.id, 'production_id': self.id,
'workcenter_id': operation.workcenter_id.id, 'workcenter_id': operation.workcenter_id.id,
'product_uom_id': production.product_uom_id.id, 'product_uom_id': self.product_uom_id.id,
'operation_id': operation.id, 'operation_id': operation.id,
'state': 'pending', 'state': 'pending',
}] }]
if production.product_id.categ_id.type in ['成品', '坯料']: if self.product_id.categ_id.type in ['成品', '坯料']:
# # 根据工序设计生成工单 # # 根据工序设计生成工单
technology_design_ids = sorted(production.technology_design_ids, key=lambda x: x.sequence) technology_design_ids = sorted(self.technology_design_ids, key=lambda x: x.sequence)
for route in technology_design_ids: for route in technology_design_ids:
workorder_has = self.env['mrp.workorder'].search( workorder_has = self.env['mrp.workorder'].search(
[('technology_design_id', '=', route.id), ('production_id', '=', production.id)]) [('technology_design_id', '=', route.id), ('production_id', '=', self.id)])
if not workorder_has: if not workorder_has:
if route.route_id.routing_type not in ['表面工艺']: if route.route_id.routing_type not in ['表面工艺']:
workorders_values.append( workorders_values.append(
self.env['mrp.workorder'].json_workorder_str(production, route)) self.env['mrp.workorder'].json_workorder_str(self, route))
else: else:
product_production_process = self.env['product.template'].search( product_production_process = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', route.process_parameters_id.id)]) [('server_product_process_parameters_id', '=', route.process_parameters_id.id)])
workorders_values.append( workorders_values.append(
self.env[ self.env[
'mrp.workorder']._json_workorder_surface_process_str( 'mrp.workorder']._json_workorder_surface_process_str(
production, route, product_production_process.seller_ids[0].partner_id.id)) self, route, product_production_process.seller_ids[0].partner_id.id))
production.workorder_ids = workorders_values return workorders_values
def _set_workorder_duration_expected(self):
try:
# 在每个线程中创建独立的 ORM 环境
with api.Environment.manage():
new_cr = self.pool.cursor()
self = self.with_env(self.env(cr=new_cr))
# program_ids = self.sudo().env['loyalty.program'].browse(program_ids.ids)
# 使用独立的环境来处理数据库事务
# 在独立的环境中对 workorder 进行操作
production = self.sudo().env['mrp.production'].browse(self.id)
workorders_values = production.process_production()
production.write({'workorder_ids': workorders_values})
for workorder in production.workorder_ids: for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected() workorder.duration_expected = workorder._get_duration_expected()
# 可以进行其他与工作单相关的操作
# workorder_env.write({'...'})
return production
except Exception as e:
logging.error(f"Error processing workorder {workorder.id}: {e}")
# workorders_values = self.process_production(self)
# print('_set_workorder_duration_expected wqio ', self)
# self.write({'workorder_ids': workorders_values})
# for workorder in self.workorder_ids:
# workorder.duration_expected = workorder._get_duration_expected()
def _create_workorder3(self, item):
with ThreadPoolExecutor(max_workers=os.cpu_count()) as executor:
futures = []
for production in self:
if not production.bom_id or not production.product_id:
continue
# 提交每个生产任务到线程池
futures.append(executor.submit(production._set_workorder_duration_expected))
# 等待所有线程完成任务
results = []
for future in futures:
try:
result = future.result()
if result:
results.append(result)
except Exception as e:
logging.error(f"Error processing production: {e}")
return results
# 外协出入库单处理 # 外协出入库单处理
def get_subcontract_pick_purchase(self): def get_subcontract_pick_purchase(self,productions):
production_all = self.sorted(lambda x: x.id) production_all = productions.sorted(lambda x: x.id)
product_id_to_production_names = {} product_id_to_production_names = {}
grouped_product_ids = {k: list(g) for k, g in grouped_product_ids = {k: list(g) for k, g in
groupby(production_all, key=lambda x: x.product_id.id)} groupby(production_all, key=lambda x: x.product_id.id)}
@@ -825,9 +873,10 @@ class MrpProduction(models.Model):
sorted_workorders = None sorted_workorders = None
for production in production_all: for production in production_all:
proc_workorders = [] proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search( process_parameter_workorder=production.workorder_ids.filtered(lambda w: w.surface_technics_parameters_id and w.is_subcontract and w.state!='cancel')
[('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id), # process_parameter_workorder = self.env['mrp.workorder'].search(
('is_subcontract', '=', True), ('state', '!=', 'cancel')], order='sequence asc') # [('surface_technics_parameters_id', '!=', False), ('production_id', '=', production.id),
# ('is_subcontract', '=', True), ('state', '!=', 'cancel')], order='sequence asc')
if process_parameter_workorder: if process_parameter_workorder:
# 将这些特殊表面工艺工单的采购单与调拨单置为失效 # 将这些特殊表面工艺工单的采购单与调拨单置为失效
for workorder in process_parameter_workorder: for workorder in process_parameter_workorder:
@@ -942,11 +991,7 @@ class MrpProduction(models.Model):
if purchase_order_line: if purchase_order_line:
line.unlink() line.unlink()
def _reset_work_order_sequence(self): def _process_reset_work_order_sequence(self,rec):
"""
工单工序排序方法(新)
"""
for rec in self:
workorder_ids = rec.workorder_ids workorder_ids = rec.workorder_ids
technology_design_ids = rec.technology_design_ids technology_design_ids = rec.technology_design_ids
if workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')): if workorder_ids.filtered(lambda item: item.state in ('返工', 'rework')):
@@ -983,7 +1028,12 @@ class MrpProduction(models.Model):
key=lambda w: w.sequence).sequence key=lambda w: w.sequence).sequence
for cw in cancel_work_ids: for cw in cancel_work_ids:
cw.sequence = sequence + 1 cw.sequence = sequence + 1
def _reset_work_order_sequence(self,productions):
"""
工单工序排序方法(新)
"""
for rec in productions:
self._process_reset_work_order_sequence(rec)
def _reset_work_order_sequence_1(self): def _reset_work_order_sequence_1(self):
""" """
工单工序排序方法(旧) 工单工序排序方法(旧)
@@ -1079,9 +1129,9 @@ class MrpProduction(models.Model):
# 创建工单并进行排序 # 创建工单并进行排序
def _create_workorder(self, item): def _create_workorder(self, item):
self._create_workorder3(item) productions = self._create_workorder3(item)
self._reset_work_order_sequence() self._reset_work_order_sequence(productions)
return True return productions
def production_process(self, pro_plan): def production_process(self, pro_plan):
type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False} type_map = {'装夹预调': False, 'CNC加工': False, '解除装夹': False}
@@ -1756,3 +1806,5 @@ class sf_processing_panel(models.Model):
name = fields.Char('加工面') name = fields.Char('加工面')
active = fields.Boolean('有效', default=True) active = fields.Boolean('有效', default=True)

View File

@@ -126,9 +126,9 @@
<field name="model">mrp.workorder</field> <field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/> <field name="inherit_id" ref="mrp.mrp_production_workorder_form_view_inherit"/>
<field name="arch" type="xml"> <field name="arch" type="xml">
<xpath expr="//sheet" position="inside"> <xpath expr="//form" position="inside">
<!-- 其他可见字段 --> <!-- 其他可见字段 -->
<field name="_barcode_scanned" widget="barcode_handler" invisible="1"/> <field name="_barcode_scanned" widget="barcode_handler"/>
</xpath> </xpath>
<!-- <xpath expr="//form" position="inside"> --> <!-- <xpath expr="//form" position="inside"> -->
<!-- <script src="sf_manufacturing/static/src/js/customRFID.js"/> --> <!-- <script src="sf_manufacturing/static/src/js/customRFID.js"/> -->
@@ -312,7 +312,7 @@
<xpath expr="//page[1]" position="before"> <xpath expr="//page[1]" position="before">
<page string="工件装夹" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'> <page string="工件装夹" attrs='{"invisible": [("routing_type","!=","装夹预调")]}'>
<group> <group>
<field name="_barcode_scanned" widget="barcode_handler"/> <!-- <field name="_barcode_scanned" widget="barcode_handler"/> -->
<group string="托盘"> <group string="托盘">
<field name="tray_serial_number" readonly="1" string="序列号"/> <field name="tray_serial_number" readonly="1" string="序列号"/>
</group> </group>

View File

@@ -1,7 +1,9 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Part of YiZuo. See LICENSE file for full copyright and licensing details. # Part of YiZuo. See LICENSE file for full copyright and licensing details.
import logging import cProfile
from itertools import groupby import io
import pstats
from concurrent.futures import ThreadPoolExecutor
from odoo import models, api, fields, _ from odoo import models, api, fields, _
@@ -13,17 +15,12 @@ class ProductionTechnologyWizard(models.TransientModel):
origin = fields.Char(string='源单据') origin = fields.Char(string='源单据')
is_technology_confirm = fields.Boolean(default=True) is_technology_confirm = fields.Boolean(default=True)
def confirm(self): def _process_production(self,productions,technology_designs):
if self.is_technology_confirm is True and self.production_id.product_id.categ_id.type in ['成品', '坯料']:
domain = [('origin', '=', self.origin), ('state', '=', 'technology_to_confirmed'),
('product_id', '=', self.production_id.product_id.id)]
else:
domain = [('id', '=', self.production_id.id)]
technology_designs = self.env['sf.technology.design'].sudo().search(
[('production_id', '=', self.production_id.id), ('active', 'in', [True, False])])
# technology_designs = self.production_id.technology_design_ids
productions = self.env['mrp.production'].search(domain)
for production in productions: for production in productions:
# self._process_production_special_design(production,technology_designs)
with ThreadPoolExecutor(max_workers=4) as executor:
executor.submit(self._process_production_special_design, production,technology_designs)
def _process_production_special_design(self,production,technology_designs):
if production != self.production_id: if production != self.production_id:
self.env['sf.technology.design'].sudo().unified_procedure_multiple_work_orders(technology_designs, self.env['sf.technology.design'].sudo().unified_procedure_multiple_work_orders(technology_designs,
production) production)
@@ -38,7 +35,8 @@ class ProductionTechnologyWizard(models.TransientModel):
# 工单采购单外协出入库单皆需取消 # 工单采购单外协出入库单皆需取消
domain = [('production_id', '=', special.production_id.id)] domain = [('production_id', '=', special.production_id.id)]
if special.process_parameters_id: if special.process_parameters_id:
domain += [('surface_technics_parameters_id', '=', special.process_parameters_id.id), ('state', '!=', 'cancel')] domain += [('surface_technics_parameters_id', '=', special.process_parameters_id.id),
('state', '!=', 'cancel')]
else: else:
domain += [('technology_design_id', '=', special.id), ('state', '!=', 'cancel')] domain += [('technology_design_id', '=', special.id), ('state', '!=', 'cancel')]
workorder = self.env['mrp.workorder'].search(domain) workorder = self.env['mrp.workorder'].search(domain)
@@ -60,7 +58,8 @@ class ProductionTechnologyWizard(models.TransientModel):
else: else:
if special.production_id.workorder_ids: if special.production_id.workorder_ids:
workorder = self.env['mrp.workorder'].search( workorder = self.env['mrp.workorder'].search(
[('technology_design_id', '=', special.id), ('production_id', '=', special.production_id.id), ('state', '!=', 'cancel')]) [('technology_design_id', '=', special.id), ('production_id', '=', special.production_id.id),
('state', '!=', 'cancel')])
if not workorder: if not workorder:
if special.route_id.routing_type == '表面工艺': if special.route_id.routing_type == '表面工艺':
product_production_process = self.env['product.template'].search( product_production_process = self.env['product.template'].search(
@@ -81,14 +80,34 @@ class ProductionTechnologyWizard(models.TransientModel):
else: else:
if workorder.blocked_by_workorder_ids: if workorder.blocked_by_workorder_ids:
workorder.blocked_by_workorder_ids = blocked_by_workorder_ids[0] workorder.blocked_by_workorder_ids = blocked_by_workorder_ids[0]
productions._create_workorder(False) def confirm(self):
if self.is_technology_confirm is True and self.production_id.product_id.categ_id.type in ['成品', '坯料']:
domain = [('origin', '=', self.origin), ('state', '=', 'technology_to_confirmed'),
('product_id', '=', self.production_id.product_id.id)]
else:
domain = [('id', '=', self.production_id.id)]
technology_designs = self.env['sf.technology.design'].sudo().search(
[('production_id', '=', self.production_id.id), ('active', 'in', [True, False])])
# technology_designs = self.production_id.technology_design_ids
productions = self.env['mrp.production'].search(domain)
pr = cProfile.Profile()
pr.enable()
self._process_production(productions,technology_designs)
productions = productions._create_workorder(False)
if self.production_id.product_id.categ_id.type == '成品': if self.production_id.product_id.categ_id.type == '成品':
productions.get_subcontract_pick_purchase() self.production_id.get_subcontract_pick_purchase(productions)
productions.is_adjust = False
for item in productions: for item in productions:
item.is_adjust = False
workorder = item.workorder_ids.filtered(lambda wo: wo.state not in ('cancel')).sorted( workorder = item.workorder_ids.filtered(lambda wo: wo.state not in ('cancel')).sorted(
key=lambda a: a.sequence) key=lambda a: a.sequence)
if workorder[0].state in ['pending']: if workorder[0].state in ['pending']:
if workorder[0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程': if workorder[0].production_id.product_id.categ_id.type == '成品' and item.programming_state != '已编程':
workorder[0].state = 'waiting' workorder[0].state = 'waiting'
pr.disable() # 停止性能分析
# 将结果输出到 StringIO
s = io.StringIO()
ps = pstats.Stats(pr, stream=s)
ps.strip_dirs().sort_stats('cumulative').print_stats()
analysis_output = s.getvalue()
return productions return productions