Chưa thành công trong việc gộp nhiều PRs để tạo thành RFQs

This commit is contained in:
mtpc4s9 2025-12-22 10:21:39 +07:00
parent 0570680d58
commit 7555d2ca65
17 changed files with 435 additions and 20 deletions

View File

@ -34,6 +34,7 @@
'views/epr_po_views.xml',
'views/epr_menus.xml',
'wizards/epr_reject_wizard_views.xml',
'wizards/epr_create_rfq_views.xml',
'wizards/epr_create_po_views.xml',
],
'assets': {

View File

@ -0,0 +1,11 @@
id,name,email,phone,is_company,supplier_rank
partner_vendor_1,CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC,contact@abc-office.vn,028 1234 5678,True,1
partner_vendor_2,CÔNG TY CP CÔNG NGHỆ DELTA,sales@deltatech.vn,024 9876 5432,True,1
partner_vendor_3,NHÀ PHÂN PHỐI PHẦN MỀM SIGMA,info@sigmasoft.vn,028 5555 6666,True,1
partner_vendor_4,CÔNG TY VẬT TƯ XÂY DỰNG OMEGA,order@omega-building.vn,028 7777 8888,True,1
partner_vendor_5,ĐẠI LÝ VĂN PHÒNG PHẨM BETA,sales@beta-stationery.vn,028 2222 3333,True,1
partner_vendor_6,CÔNG TY TNHH ĐIỆN TỬ GAMMA,contact@gamma-electronics.vn,024 4444 5555,True,1
partner_vendor_7,NHÀ CUNG CẤP NỘI THẤT EPSILON,info@epsilon-furniture.vn,028 6666 7777,True,1
partner_vendor_8,CÔNG TY DỊCH VỤ IT ZETA,support@zeta-it.vn,1900 1234,True,1
partner_vendor_9,ĐẠI LÝ PHÂN PHỐI LAMBDA,order@lambda-dist.vn,028 8888 9999,True,1
partner_vendor_10,CÔNG TY THƯƠNG MẠI THETA,sales@theta-trading.vn,024 1111 2222,True,1
1 id name email phone is_company supplier_rank
2 partner_vendor_1 CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC contact@abc-office.vn 028 1234 5678 True 1
3 partner_vendor_2 CÔNG TY CP CÔNG NGHỆ DELTA sales@deltatech.vn 024 9876 5432 True 1
4 partner_vendor_3 NHÀ PHÂN PHỐI PHẦN MỀM SIGMA info@sigmasoft.vn 028 5555 6666 True 1
5 partner_vendor_4 CÔNG TY VẬT TƯ XÂY DỰNG OMEGA order@omega-building.vn 028 7777 8888 True 1
6 partner_vendor_5 ĐẠI LÝ VĂN PHÒNG PHẨM BETA sales@beta-stationery.vn 028 2222 3333 True 1
7 partner_vendor_6 CÔNG TY TNHH ĐIỆN TỬ GAMMA contact@gamma-electronics.vn 024 4444 5555 True 1
8 partner_vendor_7 NHÀ CUNG CẤP NỘI THẤT EPSILON info@epsilon-furniture.vn 028 6666 7777 True 1
9 partner_vendor_8 CÔNG TY DỊCH VỤ IT ZETA support@zeta-it.vn 1900 1234 True 1
10 partner_vendor_9 ĐẠI LÝ PHÂN PHỐI LAMBDA order@lambda-dist.vn 028 8888 9999 True 1
11 partner_vendor_10 CÔNG TY THƯƠNG MẠI THETA sales@theta-trading.vn 024 1111 2222 True 1

View File

@ -0,0 +1,21 @@
id,request_id/id,name,product_description,quantity,estimated_price,uom_name,suggested_vendor_name
pr_line_1,pr_demo_1,Máy in HP LaserJet Pro M404dn,Máy in laser đen trắng - in 2 mặt tự động - kết nối mạng LAN,2,8500000,Cái,CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC
pr_line_2,pr_demo_2,Laptop Dell Latitude 5540,Laptop văn phòng - Intel Core i5 - RAM 16GB - SSD 512GB,5,25000000,Bộ,CÔNG TY CP CÔNG NGHỆ DELTA
pr_line_3,pr_demo_3,Bàn làm việc 1m4,Bàn làm việc chữ L - kích thước 1m4 x 0.6m - màu gỗ sồi,10,3500000,Cái,NHÀ CUNG CẤP NỘI THẤT EPSILON
pr_line_4,pr_demo_4,Ghế công thái học Ergohuman,Ghế văn phòng cao cấp - hỗ trợ lưng - tay vịn điều chỉnh,5,12000000,Cái,NHÀ CUNG CẤP NỘI THẤT EPSILON
pr_line_5,pr_demo_5,Máy chiếu Epson EB-X51,Máy chiếu 3800 lumens - độ phân giải XGA - kết nối HDMI,2,15000000,Cái,CÔNG TY TNHH ĐIỆN TỬ GAMMA
pr_line_6,pr_demo_6,Microsoft 365 Business Premium (1 năm),Bản quyền phần mềm Office 365 - bao gồm Teams - OneDrive 1TB,50,3000000,License,NHÀ PHÂN PHỐI PHẦN MỀM SIGMA
pr_line_7,pr_demo_7,Ổ cứng SSD Samsung 870 EVO 1TB,Ổ cứng SSD SATA 2.5 inch - tốc độ đọc 560MB/s,10,2500000,Cái,CÔNG TY CP CÔNG NGHỆ DELTA
pr_line_8,pr_demo_8,Dịch vụ bảo trì Server (6 tháng),Gói bảo trì hệ thống server - bao gồm backup và monitoring 24/7,1,50000000,Gói,CÔNG TY DỊCH VỤ IT ZETA
pr_line_9,pr_demo_9,Router Wifi TP-Link Archer AX73,Router Wifi 6 - băng tần kép - tốc độ 5400Mbps,5,3200000,Cái,CÔNG TY TNHH ĐIỆN TỬ GAMMA
pr_line_10,pr_demo_10,Webcam Logitech C920 HD Pro,Webcam Full HD 1080p - tự động lấy nét - micro kép,10,2800000,Cái,CÔNG TY TNHH ĐIỆN TỬ GAMMA
pr_line_11,pr_demo_11,Giấy A4 Double A 80gsm,Giấy in A4 chất lượng cao - độ trắng 96%,100,85000,Ream,ĐẠI LÝ VĂN PHÒNG PHẨM BETA
pr_line_12,pr_demo_12,Bút bi Thiên Long TL-027,Bút bi mực xanh - nét thanh đẹp,200,5000,Cây,ĐẠI LÝ VĂN PHÒNG PHẨM BETA
pr_line_13,pr_demo_13,Kẹp giấy loại lớn,Kẹp giấy kim loại 51mm - hộp 12 cái,50,25000,Hộp,ĐẠI LÝ VĂN PHÒNG PHẨM BETA
pr_line_14,pr_demo_14,Sổ tay bìa cứng A5,Sổ tay bìa da cao cấp - 200 trang giấy kẻ ngang,30,65000,Quyển,ĐẠI LÝ VĂN PHÒNG PHẨM BETA
pr_line_15,pr_demo_15,Mực in HP 76A Black,Hộp mực chính hãng cho máy in HP LaserJet Pro M404,10,2200000,Hộp,CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC
pr_line_16,pr_demo_16,Điều hòa Daikin Inverter 1HP,Điều hòa tiết kiệm điện - công suất 9000BTU,3,12000000,Bộ,CÔNG TY TNHH ĐIỆN TỬ GAMMA
pr_line_17,pr_demo_17,Bình nước nóng Ariston 30L,Bình nước nóng điện - dung tích 30 lít - có chống giật,2,4500000,Cái,CÔNG TY VẬT TƯ XÂY DỰNG OMEGA
pr_line_18,pr_demo_18,Camera IP Hikvision DS-2CD1047G2,Camera IP 4MP - hồng ngoại 30m - chuẩn IP67,8,1800000,Cái,CÔNG TY TNHH ĐIỆN TỬ GAMMA
pr_line_19,pr_demo_19,Tủ hồ sơ sắt 3 ngăn,Tủ hồ sơ kim loại - có khóa - kích thước 1.2m x 0.5m x 0.4m,5,2800000,Cái,NHÀ CUNG CẤP NỘI THẤT EPSILON
pr_line_20,pr_demo_20,Nước uống đóng chai Aquafina 500ml,Nước tinh khiết chai 500ml - thùng 24 chai,50,95000,Thùng,CÔNG TY THƯƠNG MẠI THETA
1 id request_id/id name product_description quantity estimated_price uom_name suggested_vendor_name
2 pr_line_1 pr_demo_1 Máy in HP LaserJet Pro M404dn Máy in laser đen trắng - in 2 mặt tự động - kết nối mạng LAN 2 8500000 Cái CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC
3 pr_line_2 pr_demo_2 Laptop Dell Latitude 5540 Laptop văn phòng - Intel Core i5 - RAM 16GB - SSD 512GB 5 25000000 Bộ CÔNG TY CP CÔNG NGHỆ DELTA
4 pr_line_3 pr_demo_3 Bàn làm việc 1m4 Bàn làm việc chữ L - kích thước 1m4 x 0.6m - màu gỗ sồi 10 3500000 Cái NHÀ CUNG CẤP NỘI THẤT EPSILON
5 pr_line_4 pr_demo_4 Ghế công thái học Ergohuman Ghế văn phòng cao cấp - hỗ trợ lưng - tay vịn điều chỉnh 5 12000000 Cái NHÀ CUNG CẤP NỘI THẤT EPSILON
6 pr_line_5 pr_demo_5 Máy chiếu Epson EB-X51 Máy chiếu 3800 lumens - độ phân giải XGA - kết nối HDMI 2 15000000 Cái CÔNG TY TNHH ĐIỆN TỬ GAMMA
7 pr_line_6 pr_demo_6 Microsoft 365 Business Premium (1 năm) Bản quyền phần mềm Office 365 - bao gồm Teams - OneDrive 1TB 50 3000000 License NHÀ PHÂN PHỐI PHẦN MỀM SIGMA
8 pr_line_7 pr_demo_7 Ổ cứng SSD Samsung 870 EVO 1TB Ổ cứng SSD SATA 2.5 inch - tốc độ đọc 560MB/s 10 2500000 Cái CÔNG TY CP CÔNG NGHỆ DELTA
9 pr_line_8 pr_demo_8 Dịch vụ bảo trì Server (6 tháng) Gói bảo trì hệ thống server - bao gồm backup và monitoring 24/7 1 50000000 Gói CÔNG TY DỊCH VỤ IT ZETA
10 pr_line_9 pr_demo_9 Router Wifi TP-Link Archer AX73 Router Wifi 6 - băng tần kép - tốc độ 5400Mbps 5 3200000 Cái CÔNG TY TNHH ĐIỆN TỬ GAMMA
11 pr_line_10 pr_demo_10 Webcam Logitech C920 HD Pro Webcam Full HD 1080p - tự động lấy nét - micro kép 10 2800000 Cái CÔNG TY TNHH ĐIỆN TỬ GAMMA
12 pr_line_11 pr_demo_11 Giấy A4 Double A 80gsm Giấy in A4 chất lượng cao - độ trắng 96% 100 85000 Ream ĐẠI LÝ VĂN PHÒNG PHẨM BETA
13 pr_line_12 pr_demo_12 Bút bi Thiên Long TL-027 Bút bi mực xanh - nét thanh đẹp 200 5000 Cây ĐẠI LÝ VĂN PHÒNG PHẨM BETA
14 pr_line_13 pr_demo_13 Kẹp giấy loại lớn Kẹp giấy kim loại 51mm - hộp 12 cái 50 25000 Hộp ĐẠI LÝ VĂN PHÒNG PHẨM BETA
15 pr_line_14 pr_demo_14 Sổ tay bìa cứng A5 Sổ tay bìa da cao cấp - 200 trang giấy kẻ ngang 30 65000 Quyển ĐẠI LÝ VĂN PHÒNG PHẨM BETA
16 pr_line_15 pr_demo_15 Mực in HP 76A Black Hộp mực chính hãng cho máy in HP LaserJet Pro M404 10 2200000 Hộp CÔNG TY TNHH THIẾT BỊ VĂN PHÒNG ABC
17 pr_line_16 pr_demo_16 Điều hòa Daikin Inverter 1HP Điều hòa tiết kiệm điện - công suất 9000BTU 3 12000000 Bộ CÔNG TY TNHH ĐIỆN TỬ GAMMA
18 pr_line_17 pr_demo_17 Bình nước nóng Ariston 30L Bình nước nóng điện - dung tích 30 lít - có chống giật 2 4500000 Cái CÔNG TY VẬT TƯ XÂY DỰNG OMEGA
19 pr_line_18 pr_demo_18 Camera IP Hikvision DS-2CD1047G2 Camera IP 4MP - hồng ngoại 30m - chuẩn IP67 8 1800000 Cái CÔNG TY TNHH ĐIỆN TỬ GAMMA
20 pr_line_19 pr_demo_19 Tủ hồ sơ sắt 3 ngăn Tủ hồ sơ kim loại - có khóa - kích thước 1.2m x 0.5m x 0.4m 5 2800000 Cái NHÀ CUNG CẤP NỘI THẤT EPSILON
21 pr_line_20 pr_demo_20 Nước uống đóng chai Aquafina 500ml Nước tinh khiết chai 500ml - thùng 24 chai 50 95000 Thùng CÔNG TY THƯƠNG MẠI THETA

View File

@ -0,0 +1,21 @@
id,date_required,priority,state
pr_demo_1,2024-12-23,2,draft
pr_demo_2,2024-12-21,3,draft
pr_demo_3,2024-12-26,1,draft
pr_demo_4,2024-12-19,4,draft
pr_demo_5,2024-12-30,2,draft
pr_demo_6,2025-01-15,2,draft
pr_demo_7,2024-12-23,3,draft
pr_demo_8,2024-12-21,4,draft
pr_demo_9,2025-01-06,1,draft
pr_demo_10,2024-12-26,2,draft
pr_demo_11,2024-12-19,1,draft
pr_demo_12,2024-12-21,1,draft
pr_demo_13,2024-12-23,2,draft
pr_demo_14,2024-12-26,1,draft
pr_demo_15,2024-12-21,2,draft
pr_demo_16,2024-12-30,3,draft
pr_demo_17,2025-01-06,2,draft
pr_demo_18,2024-12-23,4,draft
pr_demo_19,2024-12-26,2,draft
pr_demo_20,2025-01-15,1,draft
1 id date_required priority state
2 pr_demo_1 2024-12-23 2 draft
3 pr_demo_2 2024-12-21 3 draft
4 pr_demo_3 2024-12-26 1 draft
5 pr_demo_4 2024-12-19 4 draft
6 pr_demo_5 2024-12-30 2 draft
7 pr_demo_6 2025-01-15 2 draft
8 pr_demo_7 2024-12-23 3 draft
9 pr_demo_8 2024-12-21 4 draft
10 pr_demo_9 2025-01-06 1 draft
11 pr_demo_10 2024-12-26 2 draft
12 pr_demo_11 2024-12-19 1 draft
13 pr_demo_12 2024-12-21 1 draft
14 pr_demo_13 2024-12-23 2 draft
15 pr_demo_14 2024-12-26 1 draft
16 pr_demo_15 2024-12-21 2 draft
17 pr_demo_16 2024-12-30 3 draft
18 pr_demo_17 2025-01-06 2 draft
19 pr_demo_18 2024-12-23 4 draft
20 pr_demo_19 2024-12-26 2 draft
21 pr_demo_20 2025-01-15 1 draft

View File

@ -88,15 +88,15 @@ class EprPurchaseRequest(models.Model):
)
currency_id = fields.Many2one(
'res.currency',
comodel_name='res.currency',
string='Currency',
default=lambda self: self.env.company.currency_id,
required=True
)
line_ids = fields.One2many(
'epr.purchase.request.line',
'request_id',
comodel_name='epr.purchase.request.line',
inverse_name='request_id',
string='Products'
)
@ -397,10 +397,10 @@ class EprPurchaseRequest(models.Model):
'name': _('Request for Quotations'),
'type': 'ir.actions.act_window',
'res_model': 'epr.rfq',
'view_mode': 'list,form', # Odoo 18 ưu tiên dùng 'list' thay vì 'tree'
'view_mode': 'list,form',
'domain': [('id', 'in', self.rfq_ids.ids)],
'context': {
'default_request_ids': [(6, 0, [self.id])], # Tự động link ngược lại PR này nếu tạo mới RFQ
'default_request_ids': [(6, 0, [self.id])], # Tự động link ngược lại PR này nếu tạo mới RFQ
'create': True,
},
}
@ -507,7 +507,7 @@ class EprPurchaseRequestLine(models.Model):
final_vendor_id = fields.Many2one(
comodel_name='res.partner',
string='Final Vendor',
domain="[('supplier_rank', '>', 0)]",
# domain="[('supplier_rank', '>', 0)]",
help="Nhà cung cấp chính thức được bộ phận Mua hàng chốt."
)

View File

@ -59,17 +59,15 @@ class EprRfq(models.Model):
column2='request_id',
string='Source Requests',
# Chỉ lấy các PR đã duyệt để tạo RFQ
domain="[('state', '=', 'approved')]",
readonly=True
domain="[('state', '=', 'approved')]"
)
partner_id = fields.Many2one(
comodel_name='res.partner',
string='Vendor',
required=True,
tracking=True,
tracking=True
# domain="[('supplier_rank', '>', 0)]", # Chỉ chọn đã từng được chọn qua ít nhất 01 lần
readonly=True
)
company_id = fields.Many2one(
@ -105,8 +103,7 @@ class EprRfq(models.Model):
comodel_name='epr.rfq.line',
inverse_name='rfq_id',
string='Products',
copy=True,
readonly=True
copy=True
)
# Link sang Purchase Order gốc của Odoo
@ -121,12 +118,23 @@ class EprRfq(models.Model):
string='PO Count'
)
request_count = fields.Integer(
compute='_compute_request_count',
string='PR Count'
)
# === 5. COMPUTE METHODS ===
@api.depends('purchase_ids')
def _compute_purchase_count(self):
for rfq in self:
rfq.purchase_count = len(rfq.purchase_ids)
# Link ngược về PR gốc
@api.depends('request_ids')
def _compute_request_count(self):
for rfq in self:
rfq.request_count = len(rfq.request_ids)
# === 6. CRUD OVERRIDES ===
@api.model_create_multi
def create(self, vals_list):
@ -225,9 +233,37 @@ class EprRfq(models.Model):
raise UserError(_("Chỉ có thể reset khi ở trạng thái Sent, To Approve hoặc Cancel."))
rfq.write({'state': 'draft'})
# Mở danh sách các PO được tạo từ RFQ này
def action_view_purchase_orders(self):
"""Mở danh sách các Purchase Orders (PO) được tạo từ RFQ này"""
self.ensure_one()
return {
'name': _('Purchase Orders'),
'type': 'ir.actions.act_window',
'res_model': 'purchase.order',
'view_mode': 'list,form',
# Filter các PO có field epr_rfq_id khớp với ID hiện tại
'domain': [('epr_rfq_id', '=', self.id)],
'context': {'default_epr_rfq_id': self.id},
'target': 'current',
}
# Mở danh sách các PR gốc của RFQ này
def action_view_source_requests(self):
"""Mở danh sách các PR gốc của RFQ này"""
self.ensure_one()
return {
'name': _('Source Purchase Requests'),
'type': 'ir.actions.act_window',
'res_model': 'epr.purchase.request',
'view_mode': 'list,form',
'domain': [('id', 'in', self.request_ids.ids)],
'target': 'current',
}
# -------------------------------------------------------------------------
# RFQ APPROVAL PROCESS
# -------------------------------------------------------------------------
def action_submit_approval(self):
"""Nút bấm Submit for Approval"""
self.ensure_one()
@ -333,8 +369,7 @@ class EprRfqLine(models.Model):
# Sản phẩm (Odoo Product)
product_id = fields.Many2one(
comodel_name='product.product',
string='Product',
required=True
string='Product'
)
description = fields.Text(

View File

@ -25,3 +25,7 @@ access_epr_create_po_wizard_officer,ePR Create PO Wizard Officer,model_epr_creat
access_epr_create_po_wizard_admin,ePR Create PO Wizard Admin,model_epr_create_po_wizard,group_epr_admin,1,1,1,1
access_epr_create_po_line_wizard_officer,ePR Create PO Line Wizard Officer,model_epr_create_po_line_wizard,group_epr_purchasing_officer,1,1,1,1
access_epr_create_po_line_wizard_admin,ePR Create PO Line Wizard Admin,model_epr_create_po_line_wizard,group_epr_admin,1,1,1,1
access_epr_create_rfq_wizard_officer,ePR Create RFQ Wizard Officer,model_epr_create_rfq_wizard,group_epr_purchasing_officer,1,1,1,1
access_epr_create_rfq_wizard_admin,ePR Create RFQ Wizard Admin,model_epr_create_rfq_wizard,group_epr_admin,1,1,1,1
access_epr_create_rfq_line_officer,ePR Create RFQ Line Officer,model_epr_create_rfq_line,group_epr_purchasing_officer,1,1,1,1
access_epr_create_rfq_line_admin,ePR Create RFQ Line Admin,model_epr_create_rfq_line,group_epr_admin,1,1,1,1

1 id name model_id:id group_id:id perm_read perm_write perm_create perm_unlink
25 access_epr_create_po_wizard_admin ePR Create PO Wizard Admin model_epr_create_po_wizard group_epr_admin 1 1 1 1
26 access_epr_create_po_line_wizard_officer ePR Create PO Line Wizard Officer model_epr_create_po_line_wizard group_epr_purchasing_officer 1 1 1 1
27 access_epr_create_po_line_wizard_admin ePR Create PO Line Wizard Admin model_epr_create_po_line_wizard group_epr_admin 1 1 1 1
28 access_epr_create_rfq_wizard_officer ePR Create RFQ Wizard Officer model_epr_create_rfq_wizard group_epr_purchasing_officer 1 1 1 1
29 access_epr_create_rfq_wizard_admin ePR Create RFQ Wizard Admin model_epr_create_rfq_wizard group_epr_admin 1 1 1 1
30 access_epr_create_rfq_line_officer ePR Create RFQ Line Officer model_epr_create_rfq_line group_epr_purchasing_officer 1 1 1 1
31 access_epr_create_rfq_line_admin ePR Create RFQ Line Admin model_epr_create_rfq_line group_epr_admin 1 1 1 1

View File

@ -82,15 +82,25 @@
<sheet>
<!-- SMART BUTTON AREA -->
<!-- <div class="oe_button_box" name="button_box">
<!-- Button POs: Hiển thị số lượng POs liên quan -->
<div class="oe_button_box" name="button_box">
<button name="action_view_purchase_orders"
type="object"
class="oe_stat_button"
icon="fa-shopping-cart"
invisible="purchase_count == 0">
<field name="purchase_count" widget="statinfo" string="Purchase Orders"/>
<field name="purchase_count" widget="statinfo" string="POs"/>
</button>
</div> -->
<!-- Button PRs: Hiển thị số lượng PRs liên quan -->
<button name="action_view_source_requests"
type="object"
class="oe_stat_button"
icon="fa-file-text-o"
invisible="request_count == 0">
<field name="request_count" widget="statinfo" string="PRs"/>
</button>
</div>
<!-- TITLE: Số phiếu RFQ -->
<div class="oe_title">
@ -105,7 +115,7 @@
<group>
<!-- Readonly logic: Chỉ cho sửa khi nháp -->
<field name="partner_id" widget="res_partner_many2one" context="{'show_address': 1}" readonly="state != 'draft'"/>
<field name="request_ids" widget="many2many_tags" options="{'color_field': 'color'}" readonly="state != 'draft'"/>
<field name="request_ids" widget="many2many_tags" readonly="state != 'draft'"/>
<field name="currency_id" groups="base.group_multi_currency" readonly="state != 'draft'"/>
</group>
<group>
@ -160,6 +170,17 @@
context="{'default_rfq_id': id}"/>
</page>
<page string="Source Requests" name="source_requests">
<field name="request_ids" readonly="1">
<list>
<field name="name"/>
<field name="employee_id"/>
<field name="date_required"/>
<field name="state" widget="badge"/>
</list>
</field>
</page>
</notebook>
</sheet>

View File

@ -1,2 +1,3 @@
from . import epr_reject_wizard
from . import epr_create_rfq
from . import epr_create_po

View File

@ -0,0 +1,209 @@
# -*- 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'
# Danh sách các dòng PR đang được xử lý trong Wizard
line_ids = fields.One2many(
comodel_name='epr.create.rfq.line',
inverse_name='wizard_id',
string='PR Lines to Process'
)
@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.
"""
res = super().default_get(fields_list)
active_ids = self.env.context.get('active_ids', [])
if not active_ids:
return res
# Lấy danh sách PR gốc
requests = self.env['epr.purchase.request'].browse(active_ids)
# Prepare dữ liệu cho dòng Wizard
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:
lines_vals.append(Command.create({
'pr_line_id': line.id, # Link tới PR Line
'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,
}))
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).
"""
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."))
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."))
# 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']
grouped_lines[vendor] |= wiz_line
created_rfqs = self.env['epr.rfq'].sudo()
# 3. RFQ Creation
for vendor, wiz_lines in grouped_lines.items():
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,
}))
# Create RFQ header - SỬ DỤNG SUDO ĐỂ TẠO
rfq = self.env['epr.rfq'].sudo().create({
'partner_id': vendor.id,
'state': 'draft',
'date_order': fields.Datetime.now(),
'request_ids': [Command.set(source_pr_ids)],
'line_ids': rfq_line_commands,
})
created_rfqs |= rfq
# 4. Result Action
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',
}
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',
}
class EprCreateRfqLine(models.TransientModel):
_name = 'epr.create.rfq.line'
_description = 'Wizard Line: PR Details'
wizard_id = fields.Many2one('epr.create.rfq.wizard', string='Wizard')
# Link tới PR gốc (Readonly)
request_id = fields.Many2one(
comodel_name='epr.purchase.request',
string='Purchase Request',
readonly=True
)
# Link tới PR Line gốc
pr_line_id = fields.Many2one(
comodel_name='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)."
)
# Cột Final Vendor (Editable) -> Đây là nơi Officer thao tác chính
final_vendor_id = fields.Many2one(
comodel_name='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."
)
# 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',
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."
)
# Lấy số lượng
quantity = fields.Float(
related='pr_line_id.quantity',
string='Qty',
readonly=True
)
# Lấy đơn vị tính
uom_name = fields.Char(
related='pr_line_id.uom_name',
string='UoM',
readonly=True
)

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<odoo>
<!-- ============================================================ -->
<!-- WIZARD FORM VIEW: Create RFQ from PRs -->
<!-- ============================================================ -->
<record id="view_epr_create_rfq_wizard_form" model="ir.ui.view">
<field name="name">epr.create.rfq.wizard.form</field>
<field name="model">epr.create.rfq.wizard</field>
<field name="arch" type="xml">
<form string="Create RFQ from Purchase Requests">
<sheet>
<div class="oe_title">
<h2>Batch Create RFQs</h2>
<p class="text-muted">
Review lines below. Assign a <b>Final Vendor</b> to group them.
Lines with the same Final Vendor will be merged into one RFQ.
</p>
</div>
<field name="line_ids" nolabel="1">
<list editable="bottom" create="0" delete="0"
decoration-danger="not final_vendor_id">
<!-- PR Reference (readonly) -->
<field name="request_id" string="PR Ref"/>
<!-- Product Description -->
<field name="product_description" readonly="1"/>
<!-- Final Product (editable - main action field) -->
<field name="final_product_id"
string="Final Product"
options="{'no_create': True}"
placeholder="Select Product to Group..."
decoration-bf="final_product_id"/>
<!-- Quantity -->
<field name="quantity"/>
<!-- UOM -->
<field name="uom_name" optional="show"/>
<!-- Suggested Vendor Text (readonly - for reference) -->
<field name="suggested_vendor_name"
string="Suggested Vendor"
decoration-muted="1"
optional="show"/>
<!-- Final Vendor (editable - main action field) -->
<field name="final_vendor_id"
string="Final Vendor"
options="{'no_create': True}"
placeholder="Select Vendor to Group..."
decoration-bf="final_vendor_id"/>
<!-- Hidden fields -->
<field name="pr_line_id" column_invisible="True"/>
</list>
</field>
</sheet>
<footer>
<button name="action_create_rfqs"
string="Create RFQ(s)"
type="object"
class="btn-primary"
data-hotkey="q"/>
<button string="Cancel"
class="btn-secondary"
special="cancel"
data-hotkey="z"/>
</footer>
</form>
</field>
</record>
<!-- ============================================================ -->
<!-- ACTION: Binding to PR List View -->
<!-- ============================================================ -->
<record id="action_epr_pr_create_rfq" model="ir.actions.act_window">
<field name="name">Create RFQ</field>
<field name="res_model">epr.create.rfq.wizard</field>
<field name="view_mode">form</field>
<field name="target">new</field>
<!-- Binding to epr.purchase.request list view -->
<field name="binding_model_id" ref="model_epr_purchase_request"/>
<field name="binding_view_types">list</field>
<!-- Only Purchasing Officer and Admin can see this action -->
<field name="groups_id" eval="[(4, ref('epr.group_epr_purchasing_officer')), (4, ref('epr.group_epr_admin'))]"/>
</record>
</odoo>

View File

@ -1,4 +1,4 @@
version: '3.1'
# version: '3.1'
services:
db:
image: postgres:17
@ -32,4 +32,4 @@ services:
- ./addons:/mnt/extra-addons
- ./etc:/etc/odoo
restart: always # run as a service