256 lines
12 KiB
Python
256 lines
12 KiB
Python
# -*- coding: utf-8 -*-
|
||
# Part of Odoo. See LICENSE file for full copyright and licensing details.
|
||
import re
|
||
from collections import defaultdict
|
||
|
||
from odoo import api, fields, models, _
|
||
from odoo.exceptions import UserError
|
||
from odoo.tools import OrderedSet
|
||
|
||
|
||
# _get_surface_technics_purchase_ids
|
||
class PurchaseOrder(models.Model):
|
||
_inherit = 'purchase.order'
|
||
|
||
production_count = fields.Integer(
|
||
"关联制造订单",
|
||
compute='_compute_workorder_count',
|
||
)
|
||
|
||
def button_cancel(self):
|
||
account_moves = set() # 使用集合以避免重复,并提高查找速度
|
||
accounts = self.env['account.move'].search(
|
||
[('id', 'in', self.invoice_ids.ids), ('state', 'not in', ['cancel', False])])
|
||
|
||
# 直接筛选掉状态为'cancel'或False的记录,避免多次迭代
|
||
for account in accounts:
|
||
account_moves.add(account.name) # 使用set的add方法避免重复添加
|
||
|
||
# 如果你需要list形式的结果,可以将set转换为list
|
||
account_moves = list(account_moves)
|
||
if account_moves:
|
||
raise UserError(_("请联系工厂生产经理对该采购单的供应商账单进行取消"))
|
||
return super(PurchaseOrder, self).button_cancel()
|
||
def action_view_production(self):
|
||
origins = [order.name for order in self.picking_ids]
|
||
production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
|
||
if not production_id:
|
||
return
|
||
action = {
|
||
'res_model': 'mrp.production',
|
||
'type': 'ir.actions.act_window',
|
||
}
|
||
if len(production_id) == 1:
|
||
action.update({
|
||
'view_mode': 'form',
|
||
'res_id': production_id.id,
|
||
})
|
||
else:
|
||
action.update({
|
||
'name': _("制造订单列表"),
|
||
'domain': [('id', 'in', production_id.ids)],
|
||
'view_mode': 'tree,form',
|
||
})
|
||
return action
|
||
|
||
def _compute_workorder_count(self):
|
||
for purchase in self:
|
||
origins = [order.name for order in purchase.picking_ids]
|
||
production_id = self.env['mrp.production'].search([('origin', 'in', origins)])
|
||
purchase.production_count = len(production_id)
|
||
|
||
def process_replenish(self,production,total_qty):
|
||
record = self
|
||
bom_line_id = production.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': total_qty,
|
||
'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': total_qty + replenish.qty_to_order,
|
||
'origin': record.name + ',' + replenish.origin,
|
||
})
|
||
replenish.action_replenish()
|
||
|
||
def outsourcing_service_replenishment(self):
|
||
record = self
|
||
if record.purchase_type != 'consignment':
|
||
return
|
||
grouped_lines = {}
|
||
for line in record.order_line:
|
||
if line.related_product.id not in grouped_lines:
|
||
grouped_lines[line.related_product.id] = []
|
||
grouped_lines[line.related_product.id].append(line)
|
||
for product_id,lines in grouped_lines.items():
|
||
production = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
|
||
if not production:
|
||
continue
|
||
total_qty = sum(line.product_qty for line in lines)
|
||
record.process_replenish(production,total_qty)
|
||
for product_id,lines in grouped_lines.items():
|
||
productions = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
|
||
if not productions:
|
||
continue
|
||
# production.bom_id.bom_line_ids.product_id
|
||
location_id = self.env['stock.location'].search([('name', '=', '制造前')])
|
||
quants = self.env['stock.quant'].search([
|
||
('product_id', '=', productions.bom_id.bom_line_ids.product_id.id),
|
||
('location_id', '=', location_id.id)
|
||
])
|
||
total_qty = sum(quants.mapped('quantity')) # 计算该位置的总库存量
|
||
is_available = total_qty > 0
|
||
if not is_available:
|
||
raise UserError('请先完成坯料入库')
|
||
for production_id in productions:
|
||
work_ids = production_id.workorder_ids.filtered(
|
||
lambda wk: wk.state not in ['done', 'rework', 'cancel'])
|
||
if not work_ids:
|
||
continue
|
||
min_sequence_wk = min(work_ids, key=lambda wk: wk.sequence)
|
||
if min_sequence_wk.is_subcontract and min_sequence_wk.state == 'ready':
|
||
picking_id = production_id.picking_ids.filtered(
|
||
lambda wk: wk.location_id.name == '制造前' and wk.location_dest_id.name == '外协加工区')
|
||
move_out = picking_id.move_ids
|
||
for mo in move_out:
|
||
if production_id.bom_id.bom_line_ids.product_id.id != mo.product_id.id:
|
||
continue
|
||
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(production_id, min_sequence_wk))
|
||
# product = self.env['mrp.production'].search([('product_id', '=', product_id)], limit=1)
|
||
# match = re.search(r'(S\d{5}-\d)',product.name)
|
||
# pass
|
||
def button_confirm(self):
|
||
for record in self:
|
||
for line in record.order_line:
|
||
if line.product_qty <= 0:
|
||
raise UserError('请对【产品】中的【数量】进行输入')
|
||
if line.price_unit <= 0:
|
||
raise UserError('请对【产品】中的【单价】进行输入')
|
||
record.outsourcing_service_replenishment()
|
||
|
||
res = super(PurchaseOrder, self).button_confirm()
|
||
|
||
for line in self.order_line:
|
||
# 将产品不追踪序列号的行项目设置qty_done
|
||
if not line.move_ids:
|
||
continue
|
||
if line.move_ids and 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')
|
||
origin_sale_ids = fields.Many2many('sale.order', string='销售订单号(多个)', store=True,
|
||
compute='_compute_origin_sale_id')
|
||
|
||
@api.depends('origin')
|
||
def _compute_origin_sale_id(self):
|
||
for purchase in self:
|
||
if not purchase.origin:
|
||
continue
|
||
elif 'MO' in purchase.origin:
|
||
mp_name_list = [name.strip() for name in purchase['origin'].split(',')]
|
||
os_ids = list({mp_id.sale_order_id.id for mp_id in self.env['mrp.production'].sudo().search([
|
||
('name', 'in', mp_name_list)])})
|
||
if len(os_ids) == 1:
|
||
purchase.origin_sale_id = os_ids[0]
|
||
elif len(os_ids) >= 2:
|
||
purchase.origin_sale_ids = os_ids
|
||
elif 'S' in purchase.origin:
|
||
os_name_list = [name.strip() for name in purchase['origin'].split(',')]
|
||
os_ids = self.env['sale.order'].sudo().search([('name', 'in', os_name_list)])
|
||
if len(os_ids) == 1:
|
||
purchase.origin_sale_id = os_ids.id
|
||
elif len(os_ids) >= 2:
|
||
purchase.origin_sale_ids = os_ids.ids
|
||
elif 'IN' in purchase.origin:
|
||
sp_name_list = [name.strip() for name in purchase['origin'].split(',')]
|
||
os_ids = list({sp_id.sale_order_id.id for sp_id in self.env['stock.picking'].sudo().search([
|
||
('name', 'in', sp_name_list)])})
|
||
if len(os_ids) == 1:
|
||
purchase.origin_sale_id = os_ids[0]
|
||
elif len(os_ids) >= 2:
|
||
purchase.origin_sale_ids = os_ids
|
||
|
||
|
||
class PurchaseOrderLine(models.Model):
|
||
_inherit = 'purchase.order.line'
|
||
|
||
part_number = fields.Char('零件图号', store=True, compute='_compute_part_number')
|
||
part_name = fields.Char('零件名称', store=True, compute='_compute_part_number')
|
||
related_product = fields.Many2one('product.product', string='关联产品',
|
||
help='经此产品工艺加工成的成品')
|
||
manual_part_number = fields.Char()
|
||
manual_part_name = fields.Char()
|
||
|
||
@api.depends('product_id')
|
||
def _compute_part_number(self):
|
||
for record in self:
|
||
if record.part_number and record.part_name:
|
||
continue
|
||
if record.product_id.categ_id.name == '坯料':
|
||
product_name = ''
|
||
match = re.search(r'(S\d{5}-\d)', record.product_id.name)
|
||
# 如果匹配成功,提取结果
|
||
if match:
|
||
product_name = match.group(0)
|
||
sale_order_name = ''
|
||
match_sale = re.search(r'S(\d+)', record.product_id.name)
|
||
if match_sale:
|
||
sale_order_name = match_sale.group(0)
|
||
sale_order = self.env['sale.order'].sudo().search(
|
||
[('name', '=', sale_order_name)])
|
||
if sale_order:
|
||
filtered_order_line = sale_order.order_line.filtered(
|
||
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
|
||
)
|
||
record.part_number = filtered_order_line.product_id.part_number
|
||
record.part_name = filtered_order_line.product_id.part_name
|
||
elif record.order_id.purchase_type == 'consignment':
|
||
product_name = ''
|
||
match = re.search(r'(S\d{5}-\d)', record.related_product.name)
|
||
# 如果匹配成功,提取结果
|
||
if match:
|
||
product_name = match.group(0)
|
||
sale_order_name = ''
|
||
match_sale = re.search(r'S(\d+)', record.related_product.name)
|
||
if match_sale:
|
||
sale_order_name = match_sale.group(0)
|
||
sale_order = self.env['sale.order'].sudo().search(
|
||
[('name', '=', sale_order_name)])
|
||
if sale_order:
|
||
filtered_order_line = sale_order.order_line.filtered(
|
||
lambda order_line: re.search(f'{product_name}$', order_line.product_id.name)
|
||
)
|
||
record.part_number = filtered_order_line.product_id.part_number
|
||
record.part_name = filtered_order_line.product_id.part_name
|
||
else:
|
||
record.part_number = record.product_id.part_number
|
||
record.part_name = record.product_id.part_name
|
||
if record.manual_part_name:
|
||
# 如果手动设置了 part_name,使用手动设置的值
|
||
record.part_name = record.manual_part_name
|
||
if record.manual_part_number:
|
||
record.part_number = record.manual_part_number |