Compare commits

..

6 Commits

Author SHA1 Message Date
liaodanlong
9f67dbe9aa 工序外协工单没有自动触发工单开始、结束的优化需求处理 2024-12-12 16:06:55 +08:00
liaodanlong
ab32f881c2 制造订单退回调整删除表面工艺再次确认后-没被删除的表面工艺的外协出入库单变成了草稿状态问题处理 2024-12-11 17:09:51 +08:00
liaodanlong
f32ca13dff 修改日志的使用库 2024-12-11 11:05:25 +08:00
liaodanlong
d1ba2cc3d4 Merge branch 'refs/heads/develop' into feature/delivery_status
# Conflicts:
#	sf_manufacturing/models/mrp_production.py
2024-12-11 10:07:34 +08:00
liaodanlong
4738b03a45 销售订单与制造订单关联 2024-12-11 10:03:20 +08:00
liaodanlong
73ab43f16a 人工线下加工 2024-11-28 10:05:17 +08:00
14 changed files with 131 additions and 247 deletions

View File

@@ -24,7 +24,6 @@
'wizard/production_wizard_views.xml',
'wizard/production_technology_wizard_views.xml',
'wizard/production_technology_re_adjust_wizard_views.xml',
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',
@@ -39,7 +38,6 @@
'views/sf_maintenance_equipment.xml',
'views/res_config_settings_views.xml',
'views/sale_order_views.xml',
'views/mrp_workorder_batch_replan.xml',
],
'assets': {

View File

@@ -15,3 +15,4 @@ from . import sf_technology_design
from . import sf_production_common
from . import sale_order
from . import quick_easy_order
from . import purchase_order

View File

@@ -8,6 +8,7 @@ import re
import requests
from itertools import groupby
from collections import defaultdict, namedtuple
from odoo import api, fields, models, SUPERUSER_ID, _
from odoo.exceptions import UserError, ValidationError
from odoo.addons.sf_base.commons.common import Common
@@ -18,6 +19,7 @@ class MrpProduction(models.Model):
_inherit = 'mrp.production'
_description = "制造订单"
_order = 'create_date desc'
sale_order_id = fields.Many2one('sale.order', string='销售订单', compute='_compute_sale_order_id', store=True)
deadline_of_delivery = fields.Date('订单交期', tracking=True, compute='_compute_deadline_of_delivery')
# tray_ids = fields.One2many('sf.tray', 'production_id', string="托盘")
maintenance_count = fields.Integer(compute='_compute_maintenance_count', string="Number of maintenance requests")
@@ -34,6 +36,29 @@ class MrpProduction(models.Model):
tool_state_remark = fields.Text(string='功能刀具状态备注(缺刀)', compute='_compute_tool_state_remark', store=True)
tool_state_remark2 = fields.Text(string='功能刀具状态备注(无效刀)', readonly=True)
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
def _compute_sale_order_id(self):
for production in self:
# 初始化 sale_order_id 为 False
sale_order_id = False
# 使用正则表达式查找产品名称中的 'S' 开头的字母数字字符串
match = re.search(r'S\d+', production.product_id.with_context(lang='zh_CN').name) # 从字符串开始匹配
if match:
result = match.group(0)
try:
# 查找与匹配的字符串相符的销售订单
sale_order = self.env['sale.order'].search(
[('name', '=', result)], limit=1, order='id asc'
)
if sale_order:
production.sale_order_id = sale_order.id
else:
logging.warning("No sale order found for production {} with product {} (name match: {})".format(
production.id, production.product_id.name, result))
except Exception as e:
logging.error("Error while fetching sale order for production {}: {}".format(production.id, str(e)))
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
def _compute_deadline_of_delivery(self):
for production in self:
@@ -378,37 +403,16 @@ class MrpProduction(models.Model):
if process_parameters:
raise UserError(_("【工艺设计】-【参数】为%s的在【产品】中不存在,请先创建", ", ".join(process_parameters)))
if production_confirmed:
production_count = self.env['mrp.production'].search_count([
('origin', '=', self.origin),
('product_id', '=', self.product_id.id),
('state', '=', 'confirmed')
])
if production_count > 1:
return {
'name': _('退回调整'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_manufacturing.sf_production_technology_re_adjust_wizard_form_view').id,
'form')],
'res_model': 'sf.production.technology.re_adjust.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
else:
return {
'name': _('退回调整'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_manufacturing.sf_production_technology_re_adjust_wizard_confirm_form_view').id,
'form')],
'res_model': 'sf.production.technology.re_adjust.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
return {
'name': _('退回调整'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.production.technology.re_adjust.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
# 工艺确认
def technology_confirm(self):
@@ -462,37 +466,16 @@ class MrpProduction(models.Model):
error_panel.append(design.panel)
else:
if not error_panel and not process_parameters:
production_count = self.env['mrp.production'].search_count([
('origin', '=', self.origin),
('product_id', '=', self.product_id.id),
('state', '=', 'technology_to_confirmed')
])
if production_count > 1:
return {
'name': _('工艺确认'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_manufacturing.sf_production_technology_wizard_form_view').id,
'form')],
'res_model': 'sf.production.technology.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
else:
return {
'name': _('工艺确认'),
'type': 'ir.actions.act_window',
'views': [(self.env.ref(
'sf_manufacturing.sf_production_technology_wizard_confirm_form_view').id,
'form')],
'res_model': 'sf.production.technology.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
return {
'name': _('工艺确认'),
'type': 'ir.actions.act_window',
'view_mode': 'form',
'res_model': 'sf.production.technology.wizard',
'target': 'new',
'context': {
'default_production_id': self.id,
'default_origin': self.origin,
}}
if error_panel:
raise UserError(_("【加工面】为%s的标准工序顺序有误,请调整后重试", ", ".join(error_panel)))
return True
@@ -1478,6 +1461,26 @@ class MrpProduction(models.Model):
for production in self:
production.production_type = '自动化产线加工' if not production.product_id.is_manual_processing else '人工线下加工'
@api.depends('procurement_group_id.mrp_production_ids.move_dest_ids.group_id.sale_id')
def _compute_sale_order_count(self):
for production in self:
if production.sale_order_id:
production.sale_order_count = 1
else:
production.sale_order_count = 0
def action_view_sale_orders(self):
if self.sale_order_id:
action = {
'res_model': 'sale.order',
'type': 'ir.actions.act_window',
}
action.update({
'view_mode': 'form',
'res_id': self.sale_order_id.id,
})
return action
@api.model_create_multi
def create(self, vals_list):
"""
@@ -1538,9 +1541,7 @@ class MrpProduction(models.Model):
def action_view_purchase_orders(self):
self.ensure_one()
if self.is_remanufacture:
production = self
elif self.product_id.product_tmpl_id.single_manufacturing == True:
if self.product_id.product_tmpl_id.single_manufacturing == True:
production = self.env['mrp.production'].search(
[('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
else:

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
# Part of Odoo. See LICENSE file for full copyright and licensing details.
from collections import defaultdict
from odoo import api, fields, models, _
from odoo.tools import OrderedSet
# _get_surface_technics_purchase_ids
class PurchaseOrder(models.Model):
_inherit = 'purchase.order'
def button_confirm(self):
super().button_confirm()
workorders = self.env['mrp.workorder'].search([('purchase_id', '=', self.id)])
for workorder in workorders:
if workorder.routing_type == '表面工艺' and workorder.is_subcontract is True:
move_out = workorder.move_subcontract_workorder_ids[1]
# move_out = self.env['stock.move'].search(
# [('location_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'WH-PREPRODUCTION')]).id),
# ('location_dest_id', '=', self.env['stock.location'].search(
# [('barcode', 'ilike', 'VL-SPOC')]).id),
# ('origin', '=', self.production_id.name), ('state', 'not in', ['cancel', 'done'])])
for mo in move_out:
if mo.state != 'done':
mo.write({'state': 'assigned', 'production_id': False})
if not mo.move_line_ids:
self.env['stock.move.line'].create(mo.get_move_line(workorder.production_id, workorder))
return True

View File

@@ -644,20 +644,27 @@ class StockPicking(models.Model):
workorder = move_in.subcontract_workorder_id
workorders = workorder.production_id.workorder_ids
subcontract_workorders = workorders.filtered(lambda wo: wo.is_subcontract == True).sorted('sequence')
if workorder == subcontract_workorders[-1]:
if workorder == subcontract_workorders[-1]:
self.env['stock.quant']._update_reserved_quantity(
move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty, lot_id=move_in.move_line_ids.lot_id,
move_in.product_id, move_in.location_dest_id, move_in.product_uom_qty,
lot_id=move_in.move_line_ids.lot_id,
package_id=False, owner_id=False, strict=False
)
workorder.button_finish()
picking_type_out = self.env.ref('sf_manufacturing.outcontract_picking_out').id
if res and self.picking_type_id.id == picking_type_out:
move_out = self.move_ids
if move_out:
workorder = move_out.subcontract_workorder_id
workorder.button_start()
return res
# 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders):
for workorder in workorders:
if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'draft'})
workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'draft'})
workorder.move_subcontract_workorder_ids.write({'state': 'waiting'})
workorder.move_subcontract_workorder_ids.picking_id.write({'state': 'waiting'})
else:
# 创建一个新的补货组
procurement_group_id = self.env['procurement.group'].create({
@@ -671,14 +678,16 @@ class StockPicking(models.Model):
else:
# 从sorted_workorders中找到上一工单的move
if sorted_workorders.index(workorder) > 0:
move_dest_id = sorted_workorders[sorted_workorders.index(workorder) - 1].move_subcontract_workorder_ids[1].id
move_dest_id = \
sorted_workorders[sorted_workorders.index(workorder) - 1].move_subcontract_workorder_ids[1].id
new_picking = True
outcontract_picking_type_in = self.env.ref(
'sf_manufacturing.outcontract_picking_in').id,
outcontract_picking_type_out = self.env.ref(
'sf_manufacturing.outcontract_picking_out').id,
moves_in = self.env['stock.move'].sudo().create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in, procurement_group_id.id, move_dest_id))
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
procurement_group_id.id, move_dest_id))
picking_in = self.create(
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
# pick_ids.append(picking_in.id)
@@ -686,7 +695,8 @@ class StockPicking(models.Model):
{'picking_id': picking_in.id, 'state': 'waiting'})
moves_in._assign_picking_post_process(new=new_picking)
moves_out = self.env['stock.move'].sudo().create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out, procurement_group_id.id, moves_in.id))
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
procurement_group_id.id, moves_in.id))
workorder.write({'move_subcontract_workorder_ids': [(6, 0, [moves_in.id, moves_out.id])]})
picking_out = self.create(
moves_out._get_new_picking_values_Res(item, workorder, 'WH/OCOUT/'))
@@ -694,8 +704,7 @@ class StockPicking(models.Model):
moves_out.write(
{'picking_id': picking_out.id, 'state': 'waiting'})
moves_out._assign_picking_post_process(new=new_picking)
@api.depends('move_type', 'immediate_transfer', 'move_ids.state', 'move_ids.picking_id')
def _compute_state(self):
super(StockPicking, self)._compute_state()
@@ -706,7 +715,9 @@ class StockPicking(models.Model):
if picking.move_ids:
workorder = picking.move_ids[0].subcontract_workorder_id
if picking.state == 'assigned':
if workorder.state in ['pending', 'waiting'] or workorder._get_surface_technics_purchase_ids().state in ['draft', 'sent']:
if workorder.state in ['pending',
'waiting'] or workorder._get_surface_technics_purchase_ids().state in [
'draft', 'sent']:
picking.state = 'waiting'
@@ -719,7 +730,8 @@ class ReStockMove(models.Model):
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False):
route_id = self.env.ref('sf_manufacturing.route_surface_technology_outsourcing').id
stock_rule = self.env['stock.rule'].sudo().search([('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)])
stock_rule = self.env['stock.rule'].sudo().search(
[('route_id', '=', route_id), ('picking_type_id', '=', picking_type_id)])
move_values = {
'name': '',
'company_id': item.company_id.id,
@@ -997,8 +1009,9 @@ class ReStockMove(models.Model):
res['origin'] = ','.join(productions.mapped('name'))
res['retrospect_ref'] = production.product_id.name
return res
subcontract_workorder_id = fields.Many2one('mrp.workorder', '外协工单组件', check_company=True, index='btree_not_null')
subcontract_workorder_id = fields.Many2one('mrp.workorder', '外协工单组件', check_company=True,
index='btree_not_null')
class ReStockQuant(models.Model):

View File

@@ -182,5 +182,3 @@ access_sf_manual_product_model_type_routing_sort_group_sf_mrp_user,sf_manual_pro
access_sf_manual_product_model_type_routing_sort_manager,sf_manual_product_model_type_routing_sort,model_sf_manual_product_model_type_routing_sort,sf_base.group_sf_mrp_manager,1,1,1,1
access_sf_manual_product_model_type_routing_sort_group_plan_dispatch,sf_manual_product_model_type_routing_sort_group_plan_dispatch,model_sf_manual_product_model_type_routing_sort,sf_base.group_plan_dispatch,1,0,0,0
access_sf_detection_result_manager,sf_detection_result_manager,model_sf_detection_result,,1,1,1,1
access_mrp_workorder_batch_replan_wizard_group_plan_dispatch,mrp_workorder_batch_replan_wizard_group_plan_dispatch,model_mrp_workorder_batch_replan_wizard,sf_base.group_plan_dispatch,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
182
183
184

View File

@@ -43,6 +43,7 @@
<field name="activity_ids" string="下一个活动" widget="list_activity" optional="hide"/>
</xpath>
<xpath expr="//field[@name='origin']" position="replace">
<field name="sale_order_id" optional="show"/>
<field name="origin" optional="hide"/>
</xpath>
<xpath expr="//field[@name='components_availability']" position="replace">
@@ -123,6 +124,7 @@
<field name="tool_state"
attrs="{'invisible': [('production_type', 'not in', ['自动化产线加工'])]}"/>
<field name="tool_state_remark" string="备注" attrs="{'invisible': [('tool_state', '!=', '1')]}"/>
<field name="sale_order_id" readonly="1"/>
<field name="deadline_of_delivery" readonly="1"/>
<field name="tool_state_remark2" invisible="1"/>
</xpath>
@@ -584,6 +586,7 @@
<searchpanel>
<field name="state" icon="fa-filter" enable_counters="1"/>
<field name="delivery_status" icon="fa-filter" enable_counters="1"/>
<field name="production_type" icon="fa-filter" enable_counters="1"/>
</searchpanel>
</xpath>
<filter name='todo' position="replace"/>

View File

@@ -1,18 +0,0 @@
<odoo>
<data>
<record id="mrp_production_workorder_tree_editable_view_inherit" model="ir.ui.view">
<field name="name">>mrp.workorder.tree.editable.inherit</field>
<field name="model">mrp.workorder</field>
<field name="inherit_id" ref="mrp.mrp_production_workorder_tree_editable_view"/>
<field name="arch" type="xml">
<xpath expr="//tree/field[1]" position="before">
<header>
<button string="重新安排" name="%(sf_manufacturing.mrp_workorder_batch_replan_wizard)d" type="action"
class="treeHeaderBtn"
invisible="context.get('workorder_type') not in ('工件装夹中心','1#自动生产线','工件拆卸中心')"/>
</header>
</xpath>
</field>
</record>
</data>
</odoo>

View File

@@ -3,4 +3,3 @@ from . import rework_wizard
from . import production_wizard
from . import production_technology_wizard
from . import production_technology_re_adjust_wizard
from . import mrp_workorder_batch_replan_wizard

View File

@@ -1,70 +0,0 @@
# -*- coding: utf-8 -*-
import logging
from datetime import datetime, timedelta
from odoo import models, api, fields, _
class MrpWorkorderBatchReplanWizard(models.TransientModel):
_name = 'mrp.workorder.batch.replan.wizard'
_description = '制造订单批量重新安排向导'
def _get_date_planned_start(self):
planned_start_date = datetime.now() + timedelta(hours=2)
logging.info('计划开始加工时间: %s', planned_start_date)
return planned_start_date
def _get_default_workorder_count(self):
active_ids = self.env.context.get('active_ids', [])
return len(active_ids)
def _get_default_workorder_type(self):
active_ids = self.env.context.get('active_ids', [])
if active_ids:
workorders = self.env['mrp.workorder'].browse(active_ids)
if workorders:
routing_type = set(workorders.mapped('routing_type'))
return '/'.join(sorted(routing_type)) if routing_type else None
return None
workorder_type = fields.Char(string='工单类型', default=_get_default_workorder_type, readonly=True)
workorder_count = fields.Integer(string='工单数量',
default=_get_default_workorder_count,
readonly=True)
workorder_id = fields.Many2many('mrp.workorder', string=u'工单')
def confirm(self):
routing_type = set(self.workorder_id.mapped('routing_type'))
if len(routing_type) > 1:
raise models.ValidationError("批量重新安排工单类型必须一致。")
show_json_popover = self.workorder_id.mapped('show_json_popover')
if any(not value for value in show_json_popover):
raise models.ValidationError("所选工单必须都为逾期状态")
failed_workorders = {}
for workorder_info in self.workorder_id:
try:
workorder_info.action_replan()
except Exception as e:
reason = str(e)
if reason in failed_workorders:
failed_workorders[reason].append(
workorder_info.production_id.name)
else:
failed_workorders[reason] = [workorder_info.production_id.name]
if failed_workorders:
error_messages = "\n".join(
[f"制造订单: {', '.join(workorder_names)}, 原因: {reason}" for reason, workorder_names in
failed_workorders.items()])
return {
'type': 'ir.actions.client',
'tag': 'display_notification',
'params': {
'message': f"以下工单重新安排失败:\n{error_messages}",
'sticky': False,
'color': 'red',
'next': {
'type': 'ir.actions.act_window_close'
}
},
}

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="utf-8" ?>
<odoo>
<record id="mrp_workorder_batch_replan_wizard_form" model="ir.ui.view">
<field name="name">mrp.workorder.batch.replan.wizard.form.view</field>
<field name="model">mrp.workorder.batch.replan.wizard</field>
<field name="arch" type="xml">
<form>
<group>
<field name="workorder_type"/>
<field name="workorder_count"/>
</group>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="取消" class="btn-primary" special="cancel"/>
</footer>
</form>
</field>
</record>
<record id="mrp_workorder_batch_replan_wizard" model="ir.actions.act_window">
<field name="name">重新安排工单</field>
<field name="type">ir.actions.act_window</field>
<field name="res_model">mrp.workorder.batch.replan.wizard</field>
<field name="view_mode">form</field>
<field name="view_id" ref="mrp_workorder_batch_replan_wizard_form"/>
<field name="target">new</field>
<field name="context">{'default_workorder_id': active_ids}</field>
</record>
</odoo>

View File

@@ -21,26 +21,6 @@
</field>
</record>
<record model="ir.ui.view" id="sf_production_technology_re_adjust_wizard_confirm_form_view">
<field name="name">sf.production.technology.re_adjust.wizard.form.view</field>
<field name="model">sf.production.technology.re_adjust.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="production_id" invisible="1"/>
<field name="origin" invisible="1"/>
<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>
<record id="action_sf_production_technology_re_adjust_wizard" model="ir.actions.act_window">
<field name="name">工艺退回调整</field>
<field name="res_model">sf.production.technology.re_adjust.wizard</field>

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<record model="ir.ui.view" id="sf_production_technology_wizard_form_view">
<record model="ir.ui.view" id="sf_production_technology_wizard_form_view">
<field name="name">sf.production.technology.wizard.form.view</field>
<field name="model">sf.production.technology.wizard</field>
<field name="arch" type="xml">
@@ -13,28 +13,7 @@
对当前制造订单,同一销售订单相同产品所生成的制造订单统一进行工艺调整与确认
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"
confirm="是否确认工艺调整"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>
</form>
</field>
</record>
<record model="ir.ui.view" id="sf_production_technology_wizard_confirm_form_view">
<field name="name">sf.production.technology.wizard.form.view</field>
<field name="model">sf.production.technology.wizard</field>
<field name="arch" type="xml">
<form>
<sheet>
<field name="production_id" invisible="1"/>
<field name="origin" invisible="1"/>
<div>
是否确认工艺调整
</div>
<footer>
<button string="确认" name="confirm" type="object" class="oe_highlight"/>
<button string="确认" name="confirm" type="object" class="oe_highlight" confirm="是否确认工艺调整"/>
<button string="取消" class="btn btn-secondary" special="cancel"/>
</footer>
</sheet>

View File

@@ -23,7 +23,7 @@
<t t-if="record.workorder_count.raw_value &gt; 0">
<button class="btn btn-primary" name="action_work_order" type="object"
attrs="{'invisible': [('name', '=', '功能刀具组装中心')]}"
context="{'search_default_ready': 1, 'search_default_progress': 1, 'workorder_type': name}">
context="{'search_default_ready': 1, 'search_default_progress': 1}">
<span>工单</span>
</button>
</t>