Đã thành công trong việc gom PRs thành RFQ:
+ Lưu ý cực kỳ quan trong: Lý do trường "Source Requests" (request_ids) bị NULL chính là do vấn đề định nghĩa trong View XML của Wizard.
Đây là một "bẫy" kinh điển (Common Pitfall) trong Odoo khi làm việc với Wizard (TransientModel):
Nguyên tắc: Trong Wizard, nếu một trường có dữ liệu (được set từ default_get) nhưng:
1. Không xuất hiện trong file XML.
2. Hoặc có xuất hiện nhưng là readonly="1" và thiếu force_save="1".
=> Thì khi bạn bấm nút "Create RFQ", Odoo Web Client sẽ không gửi giá trị của trường đó về server. Kết quả là code Python nhận được giá trị False hoặc Null.
This commit is contained in:
parent
7555d2ca65
commit
fc8141542a
Binary file not shown.
@ -54,7 +54,7 @@ class EprRfq(models.Model):
|
||||
# Link ngược lại PR gốc (01 RFQ có thể gom nhiều PR)
|
||||
request_ids = fields.Many2many(
|
||||
comodel_name='epr.purchase.request',
|
||||
relation='epr_rfq_purchase_request_rel', # Tên bảng trung gian
|
||||
relation='epr_rfq_purchase_request_rel', # Tên bảng trung gian
|
||||
column1='rfq_id',
|
||||
column2='request_id',
|
||||
string='Source Requests',
|
||||
@ -363,29 +363,48 @@ class EprRfqLine(models.Model):
|
||||
comodel_name='epr.rfq',
|
||||
string='RFQ Reference',
|
||||
required=True,
|
||||
ondelete='cascade'
|
||||
ondelete='cascade',
|
||||
index=True
|
||||
)
|
||||
|
||||
# Sản phẩm (Odoo Product)
|
||||
# === LIÊN KẾT VỚI PR (QUAN TRỌNG) ===
|
||||
# Link về dòng chi tiết của PR gốc
|
||||
pr_line_id = fields.Many2one(
|
||||
'epr.purchase.request.line',
|
||||
string='Source PR Line',
|
||||
ondelete='set null',
|
||||
index=True,
|
||||
help="Dòng yêu cầu mua hàng gốc sinh ra dòng báo giá này."
|
||||
)
|
||||
|
||||
# Link về PR Header (Tiện ích để group/filter)
|
||||
purchase_request_id = fields.Many2one(
|
||||
related='pr_line_id.request_id',
|
||||
string='Purchase Request',
|
||||
store=True,
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# === SẢN PHẨM & CHI TIẾT ===
|
||||
product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
string='Product'
|
||||
string='Product',
|
||||
required=True
|
||||
)
|
||||
|
||||
description = fields.Text(
|
||||
string='Description'
|
||||
)
|
||||
description = fields.Text(string='Description')
|
||||
|
||||
# Số lượng & Đơn giá (Có thể khác với PR ban đầu do đàm phán)
|
||||
quantity = fields.Float(
|
||||
string='Quantity',
|
||||
required=True,
|
||||
default=1.0
|
||||
default=1.0,
|
||||
digits='Product Unit of Measure'
|
||||
)
|
||||
|
||||
uom_id = fields.Many2one(
|
||||
comodel_name='uom.uom',
|
||||
string='UoM'
|
||||
string='UoM',
|
||||
required=True
|
||||
)
|
||||
|
||||
price_unit = fields.Float(
|
||||
@ -398,30 +417,40 @@ class EprRfqLine(models.Model):
|
||||
relation='epr_rfq_line_taxes_rel',
|
||||
column1='line_id',
|
||||
column2='tax_id',
|
||||
string='Taxes'
|
||||
string='Taxes',
|
||||
context={'active_test': False}
|
||||
)
|
||||
|
||||
# === TÍNH TOÁN TIỀN TỆ ===
|
||||
currency_id = fields.Many2one(
|
||||
related='rfq_id.currency_id',
|
||||
store=True,
|
||||
string='Currency',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# Tổng tiền trên RFQ line
|
||||
subtotal = fields.Monetary(
|
||||
compute='_compute_subtotal',
|
||||
string='Subtotal',
|
||||
store=True
|
||||
store=True,
|
||||
currency_field='currency_id'
|
||||
)
|
||||
|
||||
currency_id = fields.Many2one(
|
||||
related='rfq_id.currency_id'
|
||||
)
|
||||
|
||||
@api.depends('quantity', 'price_unit')
|
||||
@api.depends('quantity', 'price_unit', 'taxes_id')
|
||||
def _compute_subtotal(self):
|
||||
"""Tính tổng tiền (chưa bao gồm thuế)"""
|
||||
for line in self:
|
||||
line.subtotal = line.quantity * line.price_unit
|
||||
|
||||
# === ONCHANGE PRODUCT (GỢI Ý) ===
|
||||
@api.onchange('product_id')
|
||||
def _onchange_product_id(self):
|
||||
"""Tự động điền UoM và tên khi chọn sản phẩm"""
|
||||
if self.product_id:
|
||||
self.description = self.product_id.display_name
|
||||
self.uom_id = self.product_id.uom_po_id or self.product_id.uom_id
|
||||
self.description = self.product_id.display_name
|
||||
# Tự động lấy thuế mua hàng mặc định của sản phẩm
|
||||
self.taxes_id = self.product_id.supplier_taxes_id
|
||||
|
||||
# ==============================================================================
|
||||
# LINE-LEVEL LINKING LOGIC (From RFQs to POs)
|
||||
|
||||
@ -151,7 +151,7 @@
|
||||
<!-- Khu vực tổng tiền bên phải -->
|
||||
<group name="note_group" col="6" class="mt-2 mt-md-0">
|
||||
<group class="oe_subtotal_footer oe_right" colspan="2" name="sale_total">
|
||||
<!-- Lưu ý: Cần thêm field amount_total compute trong python nếu muốn hiện tổng ở đây -->
|
||||
<field name="amount_total" widget="monetary" options="{'currency_field': 'currency_id'}"/>
|
||||
</group>
|
||||
<div class="oe_clear"/>
|
||||
</group>
|
||||
|
||||
Binary file not shown.
@ -14,65 +14,78 @@ class EprCreateRfqWizard(models.TransientModel):
|
||||
string='PR Lines to Process'
|
||||
)
|
||||
|
||||
# -------------------------------------------------------------------------
|
||||
# 1. LOAD DATA (BẮT BUỘC CÓ)
|
||||
# -------------------------------------------------------------------------
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
"""
|
||||
Khi mở Wizard, tự động load các PR đã chọn từ màn hình danh sách.
|
||||
Lấy dữ liệu từ các PR được chọn (active_ids) và điền vào dòng Wizard.
|
||||
"""
|
||||
res = super().default_get(fields_list)
|
||||
|
||||
# Lấy ID của các PR đang được chọn ở màn hình danh sách
|
||||
active_ids = self.env.context.get('active_ids', [])
|
||||
|
||||
if not active_ids:
|
||||
return res
|
||||
|
||||
# Lấy danh sách PR gốc
|
||||
# Đọc dữ liệu PR
|
||||
requests = self.env['epr.purchase.request'].browse(active_ids)
|
||||
|
||||
# Prepare dữ liệu cho dòng Wizard
|
||||
# Kiểm tra trạng thái (Optional: chỉ cho phép gộp PR đã duyệt)
|
||||
if any(pr.state != 'approved' for pr in requests):
|
||||
raise UserError(_("Bạn chỉ có thể tạo RFQ từ các PR đã được phê duyệt."))
|
||||
|
||||
lines_vals = []
|
||||
for pr in requests:
|
||||
# Chỉ xử lý các PR đã được duyệt (Ví dụ trạng thái 'approved')
|
||||
# Bạn có thể bỏ comment dòng dưới nếu có field state
|
||||
if pr.state != 'approved':
|
||||
raise UserError(_("PR %s chưa được duyệt.", pr.name))
|
||||
|
||||
for line in pr.line_ids:
|
||||
# Loop qua từng dòng sản phẩm của PR để đưa vào Wizard
|
||||
# Giả sử PR Line có model là 'epr.purchase.request.line'
|
||||
for pr_line in pr.line_ids:
|
||||
lines_vals.append(Command.create({
|
||||
'pr_line_id': line.id, # Link tới PR Line
|
||||
# Link dữ liệu để truy vết sau này
|
||||
'request_id': pr.id,
|
||||
'final_vendor_id': line.final_vendor_id.id if line.final_vendor_id else False,
|
||||
'suggested_vendor_name': line.suggested_vendor_name,
|
||||
'pr_line_id': pr_line.id,
|
||||
|
||||
# Dữ liệu hiển thị/chỉnh sửa trên wizard
|
||||
'suggested_vendor_name': pr_line.suggested_vendor_name,
|
||||
'final_vendor_id': pr_line.final_vendor_id.id,
|
||||
|
||||
'final_product_id': pr_line.product_id.id,
|
||||
'product_description': pr_line.name or pr_line.product_id.name,
|
||||
'quantity': pr_line.quantity,
|
||||
'uom_id': (
|
||||
pr_line.product_id.uom_po_id.id or
|
||||
pr_line.product_id.uom_id.id
|
||||
),
|
||||
}))
|
||||
|
||||
# Gán danh sách lệnh tạo dòng vào field line_ids
|
||||
res['line_ids'] = lines_vals
|
||||
return res
|
||||
|
||||
def action_create_rfqs(self):
|
||||
"""
|
||||
Logic hardened with .sudo():
|
||||
1. Access PR data using sudo to bypass security rules.
|
||||
2. Group by Vendor.
|
||||
3. Create RFQ Header and Lines using sudo() to ensure data persistence.
|
||||
4. Redirect to the newly created RFQ(s).
|
||||
Gộp PR thành RFQ:
|
||||
1. Validate: Chọn đầy đủ Vendor & Product.
|
||||
2. Gom nhóm theo Vendor.
|
||||
3. Tạo RFQ Header & Lines (Dùng sudo để bypass quyền truy cập PR).
|
||||
"""
|
||||
self.ensure_one()
|
||||
|
||||
# 1. Validation
|
||||
missing_vendor_lines = self.line_ids.filtered(lambda l: not l.final_vendor_id)
|
||||
if missing_vendor_lines:
|
||||
raise UserError(_("Please select a Final Vendor for all lines."))
|
||||
# 1. Validate & Sync Vendor
|
||||
for line in self.line_ids:
|
||||
if not line.final_vendor_id:
|
||||
raise UserError(_("Vui lòng chọn Vendor cho sản phẩm: %s", line.product_description))
|
||||
|
||||
missing_product_lines = self.line_ids.filtered(lambda l: not l.final_product_id)
|
||||
if missing_product_lines:
|
||||
raise UserError(_("Please select a Final Product for all lines."))
|
||||
if line.pr_line_id.final_vendor_id != line.final_vendor_id:
|
||||
line.pr_line_id.sudo().write({
|
||||
'final_vendor_id': line.final_vendor_id.id
|
||||
})
|
||||
|
||||
# 2. Grouping
|
||||
grouped_lines = {}
|
||||
for wiz_line in self.line_ids:
|
||||
# Sync back to PR line (Sudo to ensure write permission if needed)
|
||||
if wiz_line.pr_line_id.final_vendor_id != wiz_line.final_vendor_id:
|
||||
wiz_line.pr_line_id.sudo().write({'final_vendor_id': wiz_line.final_vendor_id.id})
|
||||
|
||||
vendor = wiz_line.final_vendor_id
|
||||
if vendor not in grouped_lines:
|
||||
grouped_lines[vendor] = self.env['epr.create.rfq.line']
|
||||
@ -82,64 +95,52 @@ class EprCreateRfqWizard(models.TransientModel):
|
||||
|
||||
# 3. RFQ Creation
|
||||
for vendor, wiz_lines in grouped_lines.items():
|
||||
# A. Lấy danh sách PR unique cho field Many2many
|
||||
source_requests = wiz_lines.mapped('request_id')
|
||||
|
||||
# B. Chuẩn bị dữ liệu lines (One2many)
|
||||
rfq_line_commands = []
|
||||
source_pr_ids = []
|
||||
|
||||
for wiz_line in wiz_lines:
|
||||
# DÙNG SUDO: Để chắc chắn lấy được dữ liệu PR line
|
||||
pr_line_sudo = wiz_line.pr_line_id.sudo()
|
||||
request_sudo = wiz_line.request_id.sudo()
|
||||
|
||||
# Collect unique source PR ids
|
||||
if request_sudo and request_sudo.id not in source_pr_ids:
|
||||
source_pr_ids.append(request_sudo.id)
|
||||
|
||||
# Determine UoM (Directly from Product or PR Line)
|
||||
uom_id = False
|
||||
if wiz_line.final_product_id:
|
||||
uom_id = wiz_line.final_product_id.uom_po_id.id or wiz_line.final_product_id.uom_id.id
|
||||
|
||||
if not uom_id and pr_line_sudo.product_id:
|
||||
uom_id = pr_line_sudo.product_id.uom_po_id.id or pr_line_sudo.product_id.uom_id.id
|
||||
|
||||
# Create line command - DÙNG GIÁ TRỊ TỪ SUDO RECORD
|
||||
rfq_line_commands.append(Command.create({
|
||||
'product_id': wiz_line.final_product_id.id,
|
||||
'description': pr_line_sudo.name or '',
|
||||
'quantity': pr_line_sudo.quantity or 1.0,
|
||||
'uom_id': uom_id,
|
||||
'price_unit': 0.0,
|
||||
'description': wiz_line.product_description,
|
||||
'quantity': wiz_line.quantity,
|
||||
'uom_id': wiz_line.uom_id.id,
|
||||
# Link ngược lại dòng PR gốc để truy vết
|
||||
'pr_line_id': wiz_line.pr_line_id.id
|
||||
}))
|
||||
|
||||
# Create RFQ header - SỬ DỤNG SUDO ĐỂ TẠO
|
||||
rfq = self.env['epr.rfq'].sudo().create({
|
||||
# Tạo RFQ Header
|
||||
rfq_vals = {
|
||||
'partner_id': vendor.id,
|
||||
'state': 'draft',
|
||||
'date_order': fields.Datetime.now(),
|
||||
'request_ids': [Command.set(source_pr_ids)],
|
||||
'request_ids': [Command.set(source_requests.ids)],
|
||||
'line_ids': rfq_line_commands,
|
||||
})
|
||||
}
|
||||
|
||||
rfq = self.env['epr.rfq'].create(rfq_vals)
|
||||
created_rfqs |= rfq
|
||||
|
||||
# 4. Result Action
|
||||
# 4. Redirect
|
||||
if not created_rfqs:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
action = {
|
||||
'name': _('Generated RFQs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'epr.rfq',
|
||||
'context': {'create': False},
|
||||
}
|
||||
|
||||
if len(created_rfqs) == 1:
|
||||
return {
|
||||
'name': _('Request for Quotation'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'epr.rfq',
|
||||
'view_mode': 'form',
|
||||
'res_id': created_rfqs.id,
|
||||
'target': 'current',
|
||||
}
|
||||
action['view_mode'] = 'form'
|
||||
action['res_id'] = created_rfqs.id
|
||||
else:
|
||||
return {
|
||||
'name': _('Requests for Quotation'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'epr.rfq',
|
||||
'view_mode': 'list,form',
|
||||
'domain': [('id', 'in', created_rfqs.ids)],
|
||||
'target': 'current',
|
||||
}
|
||||
action['view_mode'] = 'list,form' # Odoo 18 dùng 'list'
|
||||
action['domain'] = [('id', 'in', created_rfqs.ids)]
|
||||
|
||||
return action
|
||||
|
||||
|
||||
class EprCreateRfqLine(models.TransientModel):
|
||||
@ -148,62 +149,52 @@ class EprCreateRfqLine(models.TransientModel):
|
||||
|
||||
wizard_id = fields.Many2one('epr.create.rfq.wizard', string='Wizard')
|
||||
|
||||
# Link tới PR gốc (Readonly)
|
||||
# Dữ liệu PR gốc
|
||||
request_id = fields.Many2one(
|
||||
comodel_name='epr.purchase.request',
|
||||
string='Purchase Request',
|
||||
'epr.purchase.request',
|
||||
string='PR',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# Link tới PR Line gốc
|
||||
pr_line_id = fields.Many2one(
|
||||
comodel_name='epr.purchase.request.line',
|
||||
'epr.purchase.request.line',
|
||||
string='PR Line',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# Cột hiển thị text gợi ý (Readonly) -> Giúp Officer tham chiếu
|
||||
suggested_vendor_name = fields.Char(
|
||||
string='Suggested Vendor (Text)',
|
||||
readonly=True,
|
||||
help="Tên nhà cung cấp do người yêu cầu nhập tay (tham khảo)."
|
||||
)
|
||||
suggested_vendor_name = fields.Char(string='Suggested Vendor', readonly=True)
|
||||
|
||||
# Cột Final Vendor (Editable) -> Đây là nơi Officer thao tác chính
|
||||
# Cho phép User chọn/sửa trong Wizard
|
||||
final_vendor_id = fields.Many2one(
|
||||
comodel_name='res.partner',
|
||||
'res.partner',
|
||||
string='Final Vendor',
|
||||
required=True,
|
||||
# domain="[('supplier_rank', '>', 0)]", # Chỉ lấy nhà cung cấp
|
||||
help="Chọn nhà cung cấp chính thức trong hệ thống để tạo RFQ."
|
||||
required=True
|
||||
)
|
||||
|
||||
# Lấy thông tin sản phẩm cần mua
|
||||
product_description = fields.Char(
|
||||
related='pr_line_id.name',
|
||||
string='Product / Description',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# Purchasing Officer chọn sản phẩm tương ứng với mô tả của User
|
||||
final_product_id = fields.Many2one(
|
||||
comodel_name='product.product',
|
||||
'product.product',
|
||||
string='Final Product',
|
||||
required=True,
|
||||
# domain="[('purchase_ok', '=', True)]",
|
||||
help="Chọn sản phẩm tương ứng với mô tả của người yêu cầu."
|
||||
required=True
|
||||
)
|
||||
|
||||
# Lấy số lượng
|
||||
product_description = fields.Char(string='Description')
|
||||
quantity = fields.Float(
|
||||
related='pr_line_id.quantity',
|
||||
string='Qty',
|
||||
digits='Product Unit of Measure'
|
||||
)
|
||||
|
||||
uom_id = fields.Many2one(
|
||||
'uom.uom',
|
||||
string='UoM'
|
||||
)
|
||||
|
||||
uom_name = fields.Char(
|
||||
string='PR UoM',
|
||||
readonly=True
|
||||
)
|
||||
|
||||
# Lấy đơn vị tính
|
||||
uom_name = fields.Char(
|
||||
related='pr_line_id.uom_name',
|
||||
string='UoM',
|
||||
readonly=True
|
||||
)
|
||||
@api.onchange('final_product_id')
|
||||
def _onchange_final_product_id(self):
|
||||
if self.final_product_id:
|
||||
product = self.final_product_id
|
||||
self.uom_id = product.uom_po_id or product.uom_id
|
||||
|
||||
@ -21,10 +21,14 @@
|
||||
<list editable="bottom" create="0" delete="0"
|
||||
decoration-danger="not final_vendor_id">
|
||||
<!-- PR Reference (readonly) -->
|
||||
<field name="request_id" string="PR Ref"/>
|
||||
<field name="request_id"
|
||||
string="Source PR"
|
||||
readonly="1"
|
||||
force_save="1"
|
||||
options="{'no_open': True}"/>
|
||||
|
||||
<!-- Product Description -->
|
||||
<field name="product_description" readonly="1"/>
|
||||
<!-- Product Description (now editable as requested) -->
|
||||
<field name="product_description"/>
|
||||
|
||||
<!-- Final Product (editable - main action field) -->
|
||||
<field name="final_product_id"
|
||||
@ -37,7 +41,8 @@
|
||||
<field name="quantity"/>
|
||||
|
||||
<!-- UOM -->
|
||||
<field name="uom_name" optional="show"/>
|
||||
<field name="uom_name" optional="show" string="PR UoM"/>
|
||||
<field name="uom_id" groups="uom.group_uom" optional="show"/>
|
||||
|
||||
<!-- Suggested Vendor Text (readonly - for reference) -->
|
||||
<field name="suggested_vendor_name"
|
||||
|
||||
138
addons/epr/wizards/new_epr_create_rfq.py
Normal file
138
addons/epr/wizards/new_epr_create_rfq.py
Normal file
@ -0,0 +1,138 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from odoo import models, fields, api, Command, _
|
||||
from odoo.exceptions import UserError
|
||||
|
||||
class EprCreateRfqWizard(models.TransientModel):
|
||||
_name = 'epr.create.rfq.wizard'
|
||||
_description = 'Wizard: Merge PRs to RFQ'
|
||||
|
||||
line_ids = fields.One2many('epr.create.rfq.line', 'wizard_id', string='Lines')
|
||||
|
||||
@api.model
|
||||
def default_get(self, fields_list):
|
||||
"""
|
||||
Load dữ liệu từ PRs được chọn vào Wizard để User review trước khi tạo RFQ.
|
||||
"""
|
||||
res = super().default_get(fields_list)
|
||||
active_ids = self.env.context.get('active_ids', [])
|
||||
|
||||
if not active_ids:
|
||||
return res
|
||||
|
||||
# Lấy các Purchase Requests được chọn
|
||||
requests = self.env['epr.purchase.request'].browse(active_ids)
|
||||
|
||||
lines_vals = []
|
||||
for pr in requests:
|
||||
# Giả định PR có field line_ids chứa sản phẩm chi tiết
|
||||
# Ta cần loop qua từng dòng PR để đưa vào Wizard
|
||||
for pr_line in pr.line_ids:
|
||||
lines_vals.append(Command.create({
|
||||
'request_id': pr.id, # Link để truy vết PR gốc
|
||||
'pr_line_id': pr_line.id, # Link dòng gốc
|
||||
'suggested_vendor_name': pr.suggested_vendor_name, # Hoặc pr_line.suggested_vendor
|
||||
'final_vendor_id': pr.final_vendor_id.id,
|
||||
'final_product_id': pr_line.product_id.id,
|
||||
'product_description': pr_line.name or pr_line.product_id.name,
|
||||
'quantity': pr_line.quantity, # <--- QUAN TRỌNG: Lấy số lượng từ PR
|
||||
'uom_name': pr_line.uom_id.name, # Chỉ để hiển thị
|
||||
}))
|
||||
|
||||
res['line_ids'] = lines_vals
|
||||
return res
|
||||
|
||||
def action_create_rfqs(self):
|
||||
self.ensure_one()
|
||||
|
||||
# 1. Validate: Kiểm tra xem đã chọn Vendor chưa
|
||||
if any(not l.final_vendor_id for l in self.line_ids):
|
||||
raise UserError(_("Vui lòng chọn 'Final Vendor' cho tất cả các dòng trước khi tạo RFQ."))
|
||||
|
||||
# 2. Gom nhóm theo Vendor (Dictionary: {vendor_id: [lines]})
|
||||
grouped_by_vendor = {}
|
||||
for line in self.line_ids:
|
||||
vendor = line.final_vendor_id
|
||||
if vendor not in grouped_by_vendor:
|
||||
grouped_by_vendor[vendor] = []
|
||||
grouped_by_vendor[vendor].append(line)
|
||||
|
||||
new_rfqs = self.env['epr.rfq']
|
||||
|
||||
# 3. Tạo RFQ cho từng Vendor
|
||||
for vendor, w_lines in grouped_by_vendor.items():
|
||||
|
||||
# A. Thu thập tất cả Source Requests (Many2many) cho RFQ Header
|
||||
# Dùng set() để loại bỏ các ID trùng lặp nếu nhiều dòng cùng thuộc 1 PR
|
||||
source_request_ids = list(set(l.request_id.id for l in w_lines))
|
||||
|
||||
# B. Chuẩn bị dữ liệu line cho RFQ (One2many)
|
||||
rfq_lines_commands = []
|
||||
for w_line in w_lines:
|
||||
rfq_lines_commands.append(Command.create({
|
||||
'product_id': w_line.final_product_id.id,
|
||||
'description': w_line.product_description,
|
||||
'quantity': w_line.quantity, # <--- FIX LỖI: Map đúng số lượng
|
||||
# 'uom_id': ..., # Nếu bạn có field uom_id
|
||||
# 'price_unit': 0.0, # RFQ thường để giá 0 để xin báo giá
|
||||
}))
|
||||
|
||||
# C. Tạo RFQ (Header + Lines)
|
||||
# Dùng .sudo() nếu user hiện tại không có quyền tạo RFQ nhưng được phép chạy wizard này
|
||||
rfq_vals = {
|
||||
'vendor_id': vendor.id,
|
||||
'state': 'draft',
|
||||
'date_order': fields.Datetime.now(),
|
||||
|
||||
# FIX LỖI: Dùng Command.set() để gán danh sách IDs vào Many2many
|
||||
'request_ids': [Command.set(source_request_ids)],
|
||||
|
||||
# Tạo các dòng con
|
||||
'line_ids': rfq_lines_commands,
|
||||
}
|
||||
|
||||
new_rfq = self.env['epr.rfq'].sudo().create(rfq_vals)
|
||||
new_rfqs += new_rfq
|
||||
|
||||
# 4. Redirect người dùng đến (các) RFQ vừa tạo
|
||||
if not new_rfqs:
|
||||
return {'type': 'ir.actions.act_window_close'}
|
||||
|
||||
action = {
|
||||
'name': _('Generated RFQs'),
|
||||
'type': 'ir.actions.act_window',
|
||||
'res_model': 'epr.rfq',
|
||||
'domain': [('id', 'in', new_rfqs.ids)],
|
||||
'context': {'create': False},
|
||||
}
|
||||
if len(new_rfqs) == 1:
|
||||
action.update({
|
||||
'view_mode': 'form',
|
||||
'res_id': new_rfqs.id,
|
||||
})
|
||||
else:
|
||||
action.update({
|
||||
'view_mode': 'list,form', # Odoo 18 dùng 'list'
|
||||
})
|
||||
|
||||
return action
|
||||
|
||||
|
||||
class EprCreateRfqLine(models.TransientModel):
|
||||
_name = 'epr.create.rfq.line'
|
||||
_description = 'Wizard Line: PR Details'
|
||||
|
||||
wizard_id = fields.Many2one('epr.create.rfq.wizard')
|
||||
|
||||
# Các field dữ liệu để chuyển từ PR -> RFQ
|
||||
request_id = fields.Many2one('epr.purchase.request', readonly=True)
|
||||
pr_line_id = fields.Many2one('epr.purchase.request.line', readonly=True) # Lưu tham chiếu dòng gốc
|
||||
|
||||
suggested_vendor_name = fields.Char(readonly=True)
|
||||
final_vendor_id = fields.Many2one('res.partner', string='Final Vendor', required=True)
|
||||
|
||||
final_product_id = fields.Many2one('product.product', string='Product', required=True)
|
||||
product_description = fields.Char(string='Description')
|
||||
|
||||
# Field Quantity cần editable trong wizard để user có thể điều chỉnh nếu muốn
|
||||
quantity = fields.Float(string='Quantity', digits='Product Unit of Measure')
|
||||
uom_name = fields.Char(readonly=True)
|
||||
Loading…
Reference in New Issue
Block a user