Merge branch 'develop' into release/release_2.13

This commit is contained in:
胡尧
2025-05-09 14:08:06 +08:00
17 changed files with 249 additions and 24 deletions

View File

@@ -13,6 +13,7 @@
'views/purchase_request_view.xml',
'wizard/purchase_request_line_make_purchase_order_view.xml',
'views/purchase_request_line_view.xml',
'views/stock_picking_views.xml',
],
'assets': {
'web.assets_backend': [

View File

@@ -5,3 +5,4 @@ from . import sale_order
from . import mrp_production
from . import purchase_order
from . import stock_rule
from . import stock_picking

View File

@@ -9,12 +9,20 @@ class MrpProduction(models.Model):
@api.depends('state')
def _compute_pr_mp_count(self):
for item in self:
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# if item.product_id.product_tmpl_id.single_manufacturing == True and not item.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', item.origin), ('product_id', '=', item.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# item.pr_mp_count = len(pr_ids)
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name)])
# item.pr_mp_count = len(pr_ids)
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', item.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
item.pr_mp_count = len(pr_ids)
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', item.name), ('is_subcontract', '!=', 'True')])
if pr_ids:
item.pr_mp_count = len(pr_ids)
else:
item.pr_mp_count = 0
def action_view_pr_mp(self):
"""
@@ -22,7 +30,18 @@ class MrpProduction(models.Model):
"""
self.ensure_one()
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name),('is_subcontract', '!=', True)])
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
# if self.product_id.product_tmpl_id.single_manufacturing == True and not self.is_remanufacture:
# first_order = self.env['mrp.production'].search(
# [('origin', '=', self.origin), ('product_id', '=', self.product_id.id)], limit=1, order='id asc')
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_order.name)])
# else:
# pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', self.name)])
# 由于采购申请合并了所有销售订单行的采购,所以不区分产品
first_mp = self.env['mrp.production'].search(
[('origin', '=', self.origin)], limit=1, order='id asc')
pr_ids = self.env['purchase.request'].sudo().search([('origin', 'like', first_mp.name)])
action = {
'res_model': 'purchase.request',
'type': 'ir.actions.act_window',

View File

@@ -0,0 +1,35 @@
from odoo import fields, api, models, _
class StockPicking(models.Model):
_inherit = "stock.picking"
purchase_request_count = fields.Integer('采购订单数量', compute='_compute_purchase_request')
@api.depends('name')
def _compute_purchase_request(self):
for record in self:
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', record.name)])
record.purchase_request_count = len(purchase_request_ids)
def action_view_purchase_request(self):
self.ensure_one()
purchase_request_ids = self.env['purchase.request'].search([('origin', '=', self.name)])
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': _("%s生成采购请求单", self.name),
'domain': [('id', 'in', purchase_request_ids.ids)],
'view_mode': 'tree,form',
})
return action

View File

@@ -1,4 +1,5 @@
from odoo import api, fields, models
from collections import defaultdict
class StockRule(models.Model):
@@ -44,7 +45,40 @@ class StockRule(models.Model):
purchase_request_line_model.create(request_line_data)
def _run_buy(self, procurements):
res = super(StockRule, self)._run_buy(procurements)
# 如果补货组相同,并且产品相同,则合并
procurements_dict = defaultdict()
for procurement, rule in procurements:
if (procurement.product_id.id, procurement.values['group_id'], rule.id) not in procurements_dict:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)] = {
'product_id': procurement.product_id,
'product_qty': procurement.product_qty,
'product_uom': procurement.product_uom,
'location_id': procurement.location_id,
'name': procurement.name,
'origin': procurement.origin,
'company_id': procurement.company_id,
'values': procurement.values,
'rule': rule
}
else:
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['product_qty'] += procurement.product_qty
procurements_dict[(procurement.product_id.id, procurement.values['group_id'], rule.id)]['values']['move_dest_ids'] |= procurement.values['move_dest_ids']
new_procurements = []
for k, p in procurements_dict.items():
new_procurements.append((
self.env['procurement.group'].Procurement(
product_id=p['product_id'],
product_qty=p['product_qty'],
product_uom=p['product_uom'],
location_id=p['location_id'],
name=p['name'],
origin=p['origin'],
company_id=p['company_id'],
values=p['values']
), p['rule'])
)
res = super(StockRule, self)._run_buy(new_procurements)
# 判断是否根据规则生成新的采购申请单据,如果生成则修改状态为 approved
origins = list(set([procurement[0].origin for procurement in procurements]))
for origin in origins:

View File

@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8" ?>
<odoo>
<record id="stock_pikcing_inherited_form_jikimo_purchase_request" model="ir.ui.view">
<field name="name">stock.pikcing.inherited.form.jikimo.purchase.request</field>
<field name="model">stock.picking</field>
<field name="inherit_id" ref="stock.view_picking_form"/>
<field name="arch" type="xml">
<xpath expr="//div[@name='button_box']/button" position="before">
<button class="oe_stat_button" name="action_view_purchase_request" type="object" icon="fa-credit-card"
attrs="{'invisible': [('purchase_request_count', '=', 0)]}">
<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

@@ -141,7 +141,7 @@ class QualityCheck(models.Model):
# # 出厂检验报告编号
# report_number = fields.Char('出厂检验报告编号', compute='_compute_report_number', readonly=True)
# 总数量值为调拨单_产品明细_数量
total_qty = fields.Char('总数量', compute='_compute_total_qty')
total_qty = fields.Char('总数量', compute='_compute_total_qty', store=True)
column_nums = fields.Integer('测量值列数', default=1)
@@ -153,9 +153,9 @@ class QualityCheck(models.Model):
for move in record.picking_id.move_ids_without_package:
if move.product_id == record.product_id:
total_qty = int(move.product_uom_qty)
record.total_qty = total_qty if total_qty > 0 else ''
record.total_qty = total_qty if total_qty > 0 else 0
else:
record.total_qty = ''
record.total_qty = 0
# 检验数
check_qty = fields.Integer('检验数', default=lambda self: self._get_default_check_qty())
@@ -735,8 +735,9 @@ class QualityCheck(models.Model):
def _compute_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.qty_to_test = float_round(qc.qty_line * qc.testing_percentage_within_lot / 100,
precision_rounding=self.product_id.uom_id.rounding, rounding_method="UP")
precision_rounding=rounding, rounding_method="UP")
else:
qc.qty_to_test = qc.qty_line

View File

@@ -23,8 +23,8 @@ class QualityCheckWizard(models.TransientModel):
lot_name = fields.Char(related='current_check_id.lot_name')
lot_line_id = fields.Many2one(related='current_check_id.lot_line_id')
qty_line = fields.Float(related='current_check_id.qty_line')
qty_to_test = fields.Float(related='current_check_id.qty_to_test')
qty_tested = fields.Float(related='current_check_id.qty_tested', readonly=False)
qty_to_test = fields.Float(related='current_check_id.qty_to_test', string='待检')
qty_tested = fields.Float(related='current_check_id.qty_tested', string='已检', readonly=False)
measure = fields.Float(related='current_check_id.measure', readonly=False)
measure_on = fields.Selection(related='current_check_id.measure_on')
quality_state = fields.Selection(related='current_check_id.quality_state')

View File

@@ -1357,11 +1357,10 @@ class ResMrpWorkOrder(models.Model):
# 判断是否有坯料的序列号信息
boolean = False
if self.production_id.move_raw_ids:
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料':
if self.production_id.move_raw_ids[0].product_id.categ_type == '坯料' and self.production_id.move_raw_ids[0].product_id.tracking == 'serial':
if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids:
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
if self.production_id.move_raw_ids[0].move_line_ids[0].lot_id.name:
boolean = True
else:
boolean = True
if not boolean:

View File

@@ -1030,6 +1030,7 @@ class ResProductMo(models.Model):
'single_manufacturing': product_id.single_manufacturing,
'is_bfm': True,
'active': True,
'tracking': finish_product.tracking, # 坯料的跟踪方式跟随成品
}
# 外协和采购生成的坯料需要根据材料型号绑定供应商
if route_type == 'subcontract' or route_type == 'purchase':

View File

@@ -96,8 +96,12 @@ class PurchaseOrder(models.Model):
# 'origin': record.name + ',' + replenish.origin,
# })
# replenish.action_replenish()
return super(PurchaseOrder, self).button_confirm()
res = super(PurchaseOrder, self).button_confirm()
for line in self.order_line:
# 将产品不追踪序列号的行项目设置qty_done
if line.move_ids[0].product_id.tracking == 'none':
line.move_ids[0].quantity_done = line.move_ids[0].product_qty
return res
origin_sale_id = fields.Many2one('sale.order', string='销售订单号', store=True, compute='_compute_origin_sale_id')

View File

@@ -59,6 +59,7 @@ class SaleOrder(models.Model):
line.product_id.product_tmpl_id.copy_template(product_template_id)
# 将模板上的single_manufacturing属性复制到成品上
line.product_id.single_manufacturing = product_template_id.single_manufacturing
line.product_id.tracking = product_template_id.tracking
order_id = self
product = line.product_id

View File

@@ -675,6 +675,7 @@ class StockPicking(models.Model):
# 创建 外协出库入单
def create_outcontract_picking(self, workorders, item, sorted_workorders):
production = workorders[0].production_id
for workorder in workorders:
if workorder.move_subcontract_workorder_ids:
workorder.move_subcontract_workorder_ids.write({'state': 'cancel'})
@@ -706,7 +707,7 @@ class StockPicking(models.Model):
})
moves_in = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_in,
procurement_group_id.id, move_dest_id))
procurement_group_id.id, move_dest_id, production.product_uom_qty))
picking_in = self.create(
moves_in._get_new_picking_values_Res(item, workorder, 'WH/OCIN/'))
# pick_ids.append(picking_in.id)
@@ -716,7 +717,7 @@ class StockPicking(models.Model):
# self.env.context.get('default_production_id')
moves_out = self.env['stock.move'].sudo().with_context(context).create(
self.env['stock.move']._get_stock_move_values_Res(item, outcontract_picking_type_out,
procurement_group_id.id, moves_in.id))
procurement_group_id.id, moves_in.id, production.product_uom_qty))
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/'))
@@ -848,7 +849,7 @@ class ReStockMove(models.Model):
traceback_error = traceback.format_exc()
logging.error("零件图号 零件名称获取失败:%s" % traceback_error)
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False):
def _get_stock_move_values_Res(self, item, picking_type_id, group_id, move_dest_ids=False, product_uom_qty=1.0):
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)])
@@ -857,7 +858,7 @@ class ReStockMove(models.Model):
'company_id': item.company_id.id,
'product_id': item.bom_id.bom_line_ids.product_id.id,
'product_uom': item.bom_id.bom_line_ids.product_uom_id.id,
'product_uom_qty': 1.0,
'product_uom_qty': product_uom_qty,
'location_id': stock_rule.location_src_id.id,
'location_dest_id': stock_rule.location_dest_id.id,
'origin': item.name,

View File

@@ -268,6 +268,8 @@ class ReworkWizard(models.TransientModel):
'cmm_ids': new_cnc_workorder.cmm_ids.sudo()._json_cmm_program(
cnc_work.processing_panel, ret),
'cnc_worksheet': old_cnc_rework.cnc_worksheet})
# 复制装夹图纸
new_cnc_workorder.processing_drawing = old_cnc_rework.processing_drawing
# ========== 处理装夹预调 【装夹图纸】 数据 ================
for new_pre_work in new_pre_workorder_ids:
pre_rework = max(self.production_id.workorder_ids.filtered(

View File

@@ -5,6 +5,7 @@ from odoo import fields, models, api
from odoo.exceptions import ValidationError
from datetime import datetime
from odoo.addons.sf_base.commons.common import Common
from odoo.tools import float_round
class QualityCheck(models.Model):
@@ -132,3 +133,71 @@ class QualityCheck(models.Model):
return "零件特采发送成功"
else:
raise ValidationError("零件特采发送失败")
@api.model_create_multi
def create(self, vals_list):
for val in vals_list:
if 'point_id' in val and 'measure_on' not in val:
# 如果没有控制方式字段,则从检查点读取质量方式
point_id = self.env['quality.point'].browse(val['point_id'])
val.update({'measure_on': point_id.measure_on})
return super(QualityCheck, self).create(vals_list)
@api.depends('total_qty','testing_percentage_within_lot', 'is_lot_tested_fractionally')
def _compute_workorder_qty_to_test(self):
for qc in self:
if qc.is_lot_tested_fractionally:
rounding = qc.product_id.uom_id.rounding if qc.product_id.uom_id else 0.01
qc.workorder_qty_to_test = float_round(float(qc.total_qty) * qc.testing_percentage_within_lot / 100,
precision_rounding=rounding, rounding_method="UP")
else:
qc.workorder_qty_to_test = qc.total_qty
@api.depends('picking_id', 'workorder_id')
def _compute_total_qty(self):
super(QualityCheck, self)._compute_total_qty()
for qc in self:
if not qc.picking_id and qc.workorder_id:
qc.total_qty = qc.workorder_id.production_id.product_qty
@api.depends('workorder_qty_to_test')
def _compute_workorder_qty_tested(self):
for qc in self:
qc.workorder_qty_tested = qc.workorder_qty_to_test
workorder_qty_to_test = fields.Float('应检', compute='_compute_workorder_qty_to_test', store=True)
workorder_qty_tested = fields.Float('已检', compute='_compute_workorder_qty_tested', store=True)
workorder_qty_test_failed = fields.Float('不合格数')
@api.onchange('total_qty', 'workorder_qty_test_failed', 'workorder_qty_to_test', 'workorder_qty_tested')
def _onchage_qty(self):
for record in self:
if record.total_qty and record.workorder_qty_to_test and record.workorder_qty_to_test > float(record.total_qty):
record.workorder_qty_to_test = float(record.total_qty)
return {
'warning': {
'title': '警告',
'message': '待检数量不能超过总数量'
}
}
if record.workorder_qty_to_test and record.workorder_qty_tested and record.workorder_qty_tested > record.workorder_qty_to_test:
record.workorder_qty_tested = record.workorder_qty_to_test
return {
'warning': {
'title': '警告',
'message': '已检数量不能超过待检数量'
}
}
if record.workorder_qty_tested and record.workorder_qty_test_failed and record.workorder_qty_test_failed > record.workorder_qty_tested:
record.workorder_qty_test_failed = record.workorder_qty_tested
return {
'warning': {
'title': '警告',
'message': '不合格数量不能超过已检数量'
}
}

View File

@@ -88,6 +88,42 @@
<button name="do_cancel_publish" string="取消发布" type="object" class="btn-primary" confirm="确定取消发布吗?" attrs="{'invisible': ['|',('is_out_check', '=', False), ('publish_status', '!=', 'published')]}"/>
<button name="do_re_publish" string="重新发布" type="object" class="btn-primary" attrs="{'invisible': ['|', ('is_out_check', '=', False), ('publish_status', '!=', 'canceled')]}"/>
</xpath>
<xpath expr="//field[@name='total_qty']" position="attributes">
<attribute name="attrs">{
'invisible': ['&amp;', '|', ('measure_on', '!=', 'product'), ('is_out_check', '=', False), '|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'readonly': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)],
'on_change': ['|', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False)]
}</attribute>
</xpath>
<xpath expr="//field[@name='total_qty']" position="after">
<label for="workorder_qty_to_test"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_to_test" attrs="{'readonly': 0, 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_tested"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_tested" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
<label for="workorder_qty_test_failed"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}"/>
<div class="o_row"
attrs="{'invisible': ['|', '&amp;', ('measure_on', '!=', 'move_line'), ('workorder_id', '=', False), ('is_lot_tested_fractionally', '=', False)]}">
<field name="workorder_qty_test_failed" attrs="{'readonly': [('quality_state', '!=', 'none')], 'on_chnage': 1}"/>
<field name="uom_id"/>
</div>
</xpath>
<xpath expr="//label[@for='qty_tested']" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
<xpath expr="//div[@class='o_row'][.//field[@name='qty_tested']]" position="attributes">
<attribute name="attrs">{'invisible': ['|', '|', ('measure_on', '!=', 'move_line'), ('is_lot_tested_fractionally', '=', False), '&amp;', ('measure_on', '=', 'move_line'), ('workorder_id', '!=', False)]}</attribute>
</xpath>
</field>
</record>

View File

@@ -419,7 +419,7 @@ class RePurchaseOrder(models.Model):
('detailed_type', '=', 'service')])
server_product_process.append((0, 0, {
'product_id': server_template.product_variant_id.id,
'product_qty': 1,
'product_qty': production.product_uom_qty,
'product_uom': server_template.uom_id.id,
'related_product': production.product_id.id,
'manual_part_number': pp.part_number,