外协流程更改

This commit is contained in:
liaodanlong
2025-04-17 14:29:06 +08:00
parent 0db31f7936
commit 962931bc9d
17 changed files with 281 additions and 58 deletions

View File

@@ -11,7 +11,7 @@
'category': 'sf',
'website': 'https://www.sf.jikimo.com',
'depends': ['sf_base', 'sf_maintenance', 'web_widget_model_viewer', 'sf_warehouse', 'jikimo_attachment_viewer',
'jikimo_sale_multiple_supply_methods','product'],
'jikimo_sale_multiple_supply_methods', 'product'],
'data': [
'data/cron_data.xml',
'data/stock_data.xml',
@@ -30,6 +30,7 @@
'wizard/mrp_workorder_batch_replan_wizard_views.xml',
'wizard/sf_programming_reason_views.xml',
'wizard/sale_order_cancel_views.xml',
'wizard/process_outsourcing.xml',
'views/mrp_views_menus.xml',
'views/agv_scheduling_views.xml',
'views/stock_lot_views.xml',

View File

@@ -16,4 +16,5 @@ from . import sf_production_common
from . import sale_order
from . import quick_easy_order
from . import purchase_order
from . import quality_check
from . import quality_check
from . import purchase_request_line

View File

@@ -6,6 +6,7 @@ import json
import os
import re
import traceback
from operator import itemgetter
import requests
from itertools import groupby
@@ -889,11 +890,40 @@ class MrpProduction(models.Model):
workorders_values.append(
self.env[
'mrp.workorder']._json_workorder_surface_process_str(
production, route, product_production_process.seller_ids[0].partner_id.id))
production, route, product_production_process.seller_ids[0].partner_id.id if product_production_process.seller_ids else False))
production.workorder_ids = workorders_values
for workorder in production.workorder_ids:
workorder.duration_expected = workorder._get_duration_expected()
def _create_subcontract_purchase_request(self,purchase_request_line):
sorted_list = sorted(purchase_request_line, key=itemgetter('name'))
grouped_purchase_request_line = {
k: list(g)
for k, g in groupby(sorted_list, key=itemgetter('name'))
}
for name, request_line in grouped_purchase_request_line.items():
request_line_sorted_list = sorted(request_line, key=itemgetter('product_id'))
grouped_purchase_request_line_sorted_list = {
k: list(g)
for k, g in groupby(request_line_sorted_list, key=itemgetter('product_id'))
}
purchase_request_model = self.env["purchase.request"]
pr = purchase_request_model.create({
"origin": name,
"company_id": self.company_id.id,
"picking_type_id": self.env.ref('stock.picking_type_in').id,
"group_id": request_line[0].get('group_id'),
"requested_by": self.env.context.get("uid", self.env.uid),
"assigned_to": False,
"bom_id":self[0].bom_id.id,
})
for product_id, request_line_list in grouped_purchase_request_line_sorted_list.items():
cur_request_line = request_line_list[0]
cur_request_line['product_qty'] = len(request_line_list)
cur_request_line['request_id'] = pr.id
cur_request_line['origin'] = name
cur_request_line.pop('group_id', None)
self.env["purchase.request.line"].create(cur_request_line)
# 外协出入库单处理
def get_subcontract_pick_purchase(self):
production_all = self.sorted(lambda x: x.id)
@@ -903,6 +933,7 @@ class MrpProduction(models.Model):
for product_id, pd in grouped_product_ids.items():
product_id_to_production_names[product_id] = [p.name for p in pd]
sorted_workorders = None
purchase_request_line = []
for production in production_all:
proc_workorders = []
process_parameter_workorder = self.env['mrp.workorder'].search(
@@ -917,11 +948,11 @@ class MrpProduction(models.Model):
sorted_workorders = sorted(process_parameter_workorder, key=lambda w: w.sequence)
if not sorted_workorders:
return
purchase_request_line = []
for workorders in reversed(sorted_workorders):
self.env['stock.picking'].create_outcontract_picking(workorders, production, sorted_workorders)
self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
# self.env['purchase.order'].get_purchase_order(workorders, production, product_id_to_production_names)
purchase_request_line = purchase_request_line + self.env['purchase.order'].get_purchase_request(workorders, production)
self._create_subcontract_purchase_request(purchase_request_line)
# 工单排序
def _reset_work_order_sequence1(self, k):

View File

@@ -106,11 +106,11 @@ class ResMrpWorkOrder(models.Model):
record.back_button_display = False
# tag_type
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type !='重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type != '重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
else:
@@ -122,11 +122,11 @@ class ResMrpWorkOrder(models.Model):
else:
record.back_button_display = False
if cur_workorder.is_subcontract or cur_workorder.routing_type == '解除装夹' or cur_workorder.routing_type == '切割' or any(
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type !='重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
detection_result.processing_panel == cur_workorder.processing_panel and
detection_result.routing_type == cur_workorder.routing_type and
cur_workorder.tag_type != '重新加工' and
detection_result.test_results != '合格'
for detection_result in cur_workorder.production_id.detection_result_ids
):
record.back_button_display = False
@@ -429,9 +429,11 @@ class ResMrpWorkOrder(models.Model):
def _compute_surface_technics_purchase_ids(self):
for order in self:
if order.routing_type == '表面工艺' and order.state not in ['cancel']:
domain = [('purchase_type', '=', 'consignment'),
('origin', 'like', '%' + self.production_id.name + '%'),
('state', '!=', 'cancel')]
domain = [('group_id', '=', self.production_id.procurement_group_id.id),
('purchase_type', '=', 'consignment'), ('state', '!=', 'cancel')]
# domain = [('purchase_type', '=', 'consignment'),
# ('origin', 'like', '%' + self.production_id.name + '%'),
# ('state', '!=', 'cancel')]
purchase = self.env['purchase.order'].search(domain)
order.surface_technics_purchase_count = 0
if not purchase:
@@ -472,14 +474,15 @@ class ResMrpWorkOrder(models.Model):
return result
def _get_surface_technics_purchase_ids(self):
domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain)
# domain = [('origin', 'like', '%' + self.production_id.name + '%'), ('purchase_type', '=', 'consignment')]
domain = [('group_id', '=', self.production_id.procurement_group_id.id), ('purchase_type', '=', 'consignment')]
purchase_orders = self.env['purchase.order'].search(domain, order='id desc', # 按创建时间降序(最新的在前)
limit=1)
purchase_orders_id = self.env['purchase.order']
for po in purchase_orders:
for line in po.order_line:
if line.product_id.server_product_process_parameters_id == self.surface_technics_parameters_id:
if line.product_qty == 1:
purchase_orders_id = line.order_id
purchase_orders_id = line.order_id
return purchase_orders_id
supplier_id = fields.Many2one('res.partner', string='外协供应商')
@@ -1200,6 +1203,7 @@ class ResMrpWorkOrder(models.Model):
'cmm_ids': production.workorder_ids.filtered(lambda t: t.routing_type == 'CNC加工').cmm_ids,
}]
return workorders_values_str
def _process_compute_state(self):
for workorder in self:
# 如果工单的工序没有进行排序则跳出循环
@@ -1224,7 +1228,7 @@ class ResMrpWorkOrder(models.Model):
and workorder.production_id.schedule_state == '已排'
and len(workorder.production_id.picking_ids.filtered(
lambda w: w.state not in ['done', 'cancel'])) == 0):
# and workorder.production_id.programming_state == '已编程'
# and workorder.production_id.programming_state == '已编程'
if workorder.is_subcontract is True:
if workorder.production_id.state == 'rework':
workorder.state = 'waiting'
@@ -1287,6 +1291,7 @@ class ResMrpWorkOrder(models.Model):
mo.get_move_line(workorder.production_id, workorder))
else:
workorder.state = 'waiting'
@api.depends('production_availability', 'blocked_by_workorder_ids', 'blocked_by_workorder_ids.state',
'production_id.tool_state', 'production_id.schedule_state', 'sequence',
'production_id.programming_state')
@@ -1301,6 +1306,7 @@ class ResMrpWorkOrder(models.Model):
for check_id in workorder.check_ids:
if not check_id.is_inspect:
check_id.quality_state = 'none'
# 重写工单开始按钮方法
def button_start(self):
# 判断工单状态是否为等待组件
@@ -1534,7 +1540,7 @@ class ResMrpWorkOrder(models.Model):
# workorder.rfid_code_old = rfid_code
# workorder.rfid_code = False
logging.info('workorder.rfid_code:%s' % workorder.rfid_code)
if is_production_id is True:
logging.info('product_qty:%s' % record.production_id.product_qty)
for move_raw_id in record.production_id.move_raw_ids:
@@ -1552,7 +1558,8 @@ class ResMrpWorkOrder(models.Model):
# 如果工单包含了外协工序,需要预留数量
if self.move_raw_ids.move_orig_ids.subcontract_workorder_id:
location_id = self.move_raw_ids.location_id
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(lambda q: q.location_id.id == location_id.id)
quant = self.move_raw_ids.lot_ids.quant_ids.filtered(
lambda q: q.location_id.id == location_id.id)
if quant.reserved_quantity == 0:
self.env['stock.quant']._update_reserved_quantity(
self.move_raw_ids.product_id,
@@ -1706,7 +1713,8 @@ class ResMrpWorkOrder(models.Model):
store=True, string='工序作业')
individuation_page_ids = fields.Many2many('sf.work.individuation.page', string='个性化记录',
related='routing_work_center_id.individuation_page_ids')
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids', store=True)
individuation_page_list = fields.Char('个性化记录', default='', compute='_compute_individuation_page_ids',
store=True)
@api.depends('name')
def _compute_routing_work_center_id(self):
@@ -1727,6 +1735,7 @@ class ResMrpWorkOrder(models.Model):
individuation_page_list = [item.code for item in mw.individuation_page_ids]
if individuation_page_list:
mw.individuation_page_list = list(set(individuation_page_list))
# =============================================================================================
is_inspect = fields.Boolean('需送检', compute='_compute_is_inspect', store=True, default=False)
@@ -2040,6 +2049,7 @@ class WorkPieceDelivery(models.Model):
def _get_agv_route_type_selection(self):
return self.env['sf.agv.task.route'].fields_get(['route_type'])['route_type']['selection']
type = fields.Selection(selection=_get_agv_route_type_selection, string='类型')
delivery_duration = fields.Float('配送时长', compute='_compute_delivery_duration')
status = fields.Selection(

View File

@@ -66,6 +66,37 @@ class PurchaseOrder(models.Model):
raise UserError('请对【产品】中的【数量】进行输入')
if line.price_unit <= 0:
raise UserError('请对【产品】中的【单价】进行输入')
if record.purchase_type == 'consignment':
bom_line_id = record.order_line[0].purchase_request_lines.request_id.bom_id.bom_line_ids
replenish = self.env['stock.warehouse.orderpoint'].search([
('product_id', '=', bom_line_id.product_id.id),
(
'location_id', '=', self.env.ref('sf_stock.stock_location_outsourcing_material_receiving_area').id),
# ('state', 'in', ['draft', 'confirmed'])
], limit=1)
if not replenish:
replenish_model = self.env['stock.warehouse.orderpoint']
replenish = replenish_model.create({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': 1,
'origin': record.name,
})
else:
replenish.write({
'product_id': bom_line_id.product_id.id,
'location_id': self.env.ref(
'sf_stock.stock_location_outsourcing_material_receiving_area').id,
'route_id': self.env.ref('sf_stock.stock_route_process_outsourcing').id,
'group_id': record.group_id.id,
'qty_to_order': 1 + replenish.qty_to_order,
'origin': record.name + ',' + replenish.origin,
})
replenish.action_replenish()
return super(PurchaseOrder, self).button_confirm()

View File

@@ -0,0 +1,27 @@
# -*- coding: utf-8 -*-
import base64
import datetime
import logging
import json
import os
import re
import traceback
from operator import itemgetter
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.tools import float_compare, float_round, float_is_zero, format_datetime
class PurchaseRequestLine(models.Model):
_inherit = 'purchase.request.line'
is_subcontract = fields.Boolean(string='是否外协')
class PurchaseRequest(models.Model):
_inherit = 'purchase.request'
bom_id = fields.Many2one('mrp.bom')

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
import logging
from odoo import fields, models, api
from odoo.exceptions import UserError
from odoo.exceptions import UserError, ValidationError
from odoo.tools import str2bool
@@ -16,6 +16,11 @@ class SfProductionProcessParameter(models.Model):
is_delete_button = fields.Boolean(compute='_compute_is_delete_button', default=False)
routing_id = fields.Many2one('mrp.routing.workcenter', string="工序")
@api.constrains('outsourced_service_products')
def _validate_partner_limit(self):
for record in self:
if len(record.outsourced_service_products) > 1:
raise ValidationError("工艺参数不能与多个产品关联")
@api.depends('outsourced_service_products')
def _compute_is_product_button(self):
for record in self:
@@ -56,32 +61,31 @@ class SfProductionProcessParameter(models.Model):
return super()._name_search(name, args, operator, limit, name_get_uid)
def action_create_service_product(self):
# 获取产品分类(服务)
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
default_values = {
'detailed_type': 'service',
'name': f"{self.process_id.name}{self.name}",
'invoice_policy': 'delivery',
'categ_id': service_categ.id,
'description': f"基于{self.name}创建的服务产品",
'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.id,
}
# 创建服务产品
# 返回打开新创建产品的表单视图
if self.id: # 如果是已存在的记录
self.write({}) # 空写入会触发保存
else: # 如果是新记录
self = self.create(self._convert_to_write(self.read()[0]))
return {
'name': '创建服务产品',
'type': 'ir.actions.act_window',
'res_model': 'product.product',
'name': '向导名称',
'res_model': 'product.creation.wizard',
'view_mode': 'form',
'view_id': self.env.ref('product.product_normal_form_view').id,
'target': 'new', # 关键参数,使窗口以弹窗形式打开
'context': {
'default_' + k: v for k, v in default_values.items()
},
'target': 'new',
'context': {'default_process_parameter_id': self.id}, # 传递当前记录ID
}
#
# return {
# 'name': '创建服务产品',
# 'type': 'ir.actions.act_window',
# 'res_model': 'product.product',
# 'view_mode': 'form',
# 'view_id': self.env.ref('product.product_normal_form_view').id,
# 'target': 'new', # 关键参数,使窗口以弹窗形式打开
# 'context': {
# 'default_' + k: v for k, v in default_values.items()
# },
# }
def action_hide_service_products(self):
self.outsourced_service_products.active = False
# self.outsourced_service_products.active = False
self.active = False

View File

@@ -193,4 +193,6 @@ access_sf_programming_record,sf_programming_record,model_sf_programming_record,b
access_sf_work_individuation_page,sf_work_individuation_page,model_sf_work_individuation_page,sf_base.group_sf_mrp_user,1,1,1,0
access_sf_work_individuation_page_group_plan_dispatch,sf_work_individuation_page_group_plan_dispatch,model_sf_work_individuation_page,sf_base.group_plan_dispatch,1,1,0,0
access_sf_sale_order_cancel_wizard,sf_sale_order_cancel_wizard,model_sf_sale_order_cancel_wizard,sf_base.group_sf_order_user,1,1,1,0
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
access_sf_sale_order_cancel_line,sf_sale_order_cancel_line,model_sf_sale_order_cancel_line,sf_base.group_sf_order_user,1,0,1,1
access_product_creation_wizard,product_creation_wizard,model_product_creation_wizard,base.group_user,1,1,1,0
1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
193
194
195
196
197
198

View File

@@ -34,7 +34,7 @@
<!-- 按钮列 -->
<button name="action_create_service_product" string="创建服务产品" type="object"
class="btn-primary"
attrs="{'invisible': [('is_product_button', '=', False)]}"/>
attrs="{'invisible': [('is_product_button', '=', True)]}" context="{'default_process_parameter_id':id}"/>
<button name="action_hide_service_products" string="删除" type="object"
class="oe_highlight"
attrs="{'invisible': [('is_delete_button', '=', True)]}"/>

View File

@@ -6,3 +6,4 @@ from . import production_technology_re_adjust_wizard
from . import mrp_workorder_batch_replan_wizard
from . import sf_programming_reason
from . import sale_order_cancel
from . import process_outsourcing

View File

@@ -0,0 +1,42 @@
from odoo import models, fields, api
class ProductCreationWizard(models.TransientModel):
_name = 'product.creation.wizard'
_description = '产品创建向导'
# 唯一需要用户输入的字段:产品类别
categ_id = fields.Many2one(
'product.category',
string='产品类别',
required=True,default=lambda self: self.env.ref(
'sf_manufacturing.product_category_outsource_other_process',
raise_if_not_found=False
).sudo(),
)
process_parameter_id = fields.Many2one('sf.production.process.parameter')
def action_create_service_product(self):
# 获取产品分类(服务)
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
default_values = {
'detailed_type': 'service',
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}",
'invoice_policy': 'delivery',
'categ_id': service_categ.id,
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.process_parameter_id.id,
}
def action_create_product(self):
service_categ = self.env.ref('sf_manufacturing.product_category_outsource_other_process').sudo()
default_values = {
'detailed_type': 'service',
'name': f"{self.process_parameter_id.process_id.name}{self.process_parameter_id.name}",
'invoice_policy': 'delivery',
'categ_id': self.categ_id.id,
'description': f"基于{self.process_parameter_id.name}创建的服务产品",
'sale_ok': True, # 可销售
'purchase_ok': True, # 可采购
'server_product_process_parameters_id': self.process_parameter_id.id,
}
self.env['product.template'].create(default_values)

View File

@@ -0,0 +1,37 @@
<odoo>
<record id="view_product_creation_wizard_form" model="ir.ui.view">
<field name="name">product.creation.wizard.form</field>
<field name="model">product.creation.wizard</field>
<field name="arch" type="xml">
<form string="创建产品">
<sheet>
<group>
<field name="categ_id" required="1"/>
</group>
</sheet>
<footer>
<button
name="action_create_product"
string="创建"
type="object"
class="btn-primary"
/>
<button
string="取消"
special="cancel"
class="btn-secondary"
/>
</footer>
</form>
</field>
</record>
<!-- 向导动作 -->
<record id="action_product_creation_wizard" model="ir.actions.act_window">
<field name="name">快速创建产品</field>
<field name="res_model">product.creation.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<field name="binding_model_id" ref="product.model_product_product"/>
</record>
</odoo>

View File

@@ -343,6 +343,11 @@ class RePurchaseOrder(models.Model):
if order_line.product_id.id in product_list:
purchase.purchase_type = 'outsourcing'
break
request_lines = self.order_line.mapped('purchase_request_lines')
# 检查是否存在 is_subcontract 为 True 的行
if any(line.is_subcontract for line in request_lines):
purchase.purchase_type = 'consignment'
delivery_warning = fields.Selection([('normal', '正常'), ('warning', '预警'), ('overdue', '已逾期')],
string='交期状态', default='normal',
@@ -379,15 +384,20 @@ class RePurchaseOrder(models.Model):
def get_purchase_request(self, consecutive_process_parameters, production):
result = []
for pp in consecutive_process_parameters:
server_template = self.env['product.template'].search(
[('server_product_process_parameters_id', '=', pp.surface_technics_parameters_id.id),
('detailed_type', '=', 'service')])
result.append({
"product_id": pp.product_id.id,
"name": production.name,
"product_id": server_template.product_variant_id.id,
"name": production.procurement_group_id.name,
"date_required": fields.Datetime.now(),
"product_uom_id":pp.product_id.uom_po_id.id,
"product_uom_id":server_template.uom_id.id,
"product_qty": 1,
"request_id": False,
"move_dest_ids": False,
"orderpoint_id": False,
'is_subcontract':True,
'group_id':production.procurement_group_id.id,
})
return result
@@ -411,14 +421,14 @@ class RePurchaseOrder(models.Model):
'manual_part_name': pp.part_name,
}))
# 获取服务商品最后一个供应商的采购员
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id
purchase_user_id = server_template.seller_ids[-1].partner_id.purchase_user_id if server_template.seller_ids else False
purchase_order = self.env['purchase.order'].sudo().create({
'partner_id': server_template.seller_ids[0].partner_id.id,
'partner_id': server_template.seller_ids[0].partner_id.id if server_template.seller_ids else False,
'origin': production.name,
'state': 'draft',
'purchase_type': 'consignment',
'order_line': server_product_process,
'user_id': purchase_user_id.id
'user_id': purchase_user_id.id if purchase_user_id else False,
})
purchase_order.order_line._compute_part_number()
pp.purchase_id = [(6, 0, [purchase_order.id])]

View File

@@ -24,6 +24,7 @@
# always loaded
'data': [
# 'security/ir.model.access.csv',
'data/stock_location_data.xml',
'views/stock_picking.xml',
'views/stock_product_template.xml',
],

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<data noupdate="1">
<record id="stock_location_outsourcing_material_receiving_area" model="stock.location">
<field name="name">外协收料区</field>
<field name="usage">internal</field>
</record>
<record id="stock_route_process_outsourcing" model='stock.route'>
<field name="name">工序外协</field>
<field name="company_id"></field>
</record>
</data>
</odoo>

View File

@@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from . import stock_picking
from . import stock_picking
from . import stock_warehouse_orderpoint

View File

@@ -0,0 +1,11 @@
import logging
from odoo import models, fields
_logger = logging.getLogger(__name__)
class StockWarehouseOrderpoint(models.Model):
_inherit = 'stock.warehouse.orderpoint'
origin = fields.Char('补货来源')