รายงานการวิเคราะห์และแนวทางแก้ไข: การเพิ่มฟิลด์ Project Name & Department ใน Custom Group (Invoice Analysis)
คู่มือการวิเคราะห์เชิงลึก สถาปัตยกรรม Odoo SQL View, สาเหตุที่ฟิลด์ไม่ปรากฏใน Add Custom Group, วิธีแก้ไขอย่างละเอียด พร้อมตารางประเมินผลกระทบต่อระบบส่วนอื่นๆ ครบทุกมิติ
Report: Invoices Analysis Model: account.invoice.report Architecture: SQL View (_auto = False) Target Fields: project_name, department Odoo Version: 16.0 Status: Analysis Complete (Zero Code Modified)

1. บทสรุปผู้บริหาร (Executive Summary)

สรุปใจความสำคัญใน 3 ประเด็นหลัก:
  • สาเหตุแท้จริง (Root Cause): เมนู Add Custom Group ในหน้ารายงาน Invoice Analysis อ่านฟิลด์มาจากโมเดล account.invoice.report ซึ่งเป็น Database SQL View (ไม่ใช่ตาราง account.move โดยตรง) แม้ว่าบนใบแจ้งหนี้จะมีฟิลด์ project_name หรือ project_id อยู่แล้ว แต่หากยังไม่ได้ถูกเพิ่มเข้าไปในคำสั่ง SQL View ของโมเดลรายงาน Odoo Web Client จะไม่แสดงฟิลด์นี้ใน Custom Group เด็ดขาด
  • ทำไมแก้ผ่านหน้าบ้าน (Odoo Studio) ไม่ได้?: โมเดลนี้สร้างจาก Query ในฐานข้อมูล (_auto = False) Odoo Studio ไม่สามารถแก้ SQL Query ได้ หากพยายามเพิ่มฟิลด์ผ่าน Studio จะเกิด HTTP 500 / Database Error ทันที
  • ผลกระทบต่อส่วนอื่นๆ: การขยาย SQL View นี้ ไม่มีผลกระทบใดๆ (0% Impact) ต่องานประจำวันของฝ่ายบัญชี เช่น การออกบิล, การบันทึกรับชำระ, การปิดงวดบัญชี หรือรายงานภาษี เพราะเป็นโมเดลสำหรับอ่านข้อมูลทางสถิติเท่านั้น (Read-Only)
📌 สภาพปัญหาปัจจุบัน
ผู้ใช้งานต้องการดู Pivot รายงานยอดขายแยกตาม Project และ Department แต่เมื่อกดเข้าไปที่ Group By > Add Custom Group พบเฉพาะฟิลด์มาตรฐาน (Company, Country, Currency, Move ฯลฯ) ไม่พบฟิลด์ Project Name หรือ Department ให้เลือก
⚠️ ข้อห้ามสำคัญ (Do Not)
ห้ามใช้ Odoo Studio ในการพยายามเพิ่มฟิลด์ หรือเพิ่ม Group By บนหน้านี้เด็ดขาด เพราะจะทำให้ Database Registry ไม่พบฟิลด์ในตารางจริง และส่งผลให้ผู้ใช้งานทั้งบริษัทเปิดหน้ารายงาน Invoice Analysis ไม่ได้
✅ วิธีแก้ไขที่ถูกต้อง
ทำการสืบทอด (Inherit) โมเดล account.invoice.report ผ่าน Custom Python Module โดยเพิ่มฟิลด์ใน Python Model, แก้ไขคำสั่ง SQL (_select, _from, _group_by) และใส่ Quick Filter ใน Search View XML

2. หลักฐานจากหน้าจอจริง (Annotated Evidence Analysis)

Invoices Analysis Add Custom Group Screenshot
ภาพที่ 1: หน้าจอจริง Accounting > Reporting > Management > Invoices Analysis และเมนูย่อย Add Custom Group ที่ขาดฟิลด์ Project Name และ Department
ตำแหน่งบนหน้าจอ สิ่งที่ปรากฏในปัจจุบัน (Current) สิ่งที่ต้องการให้เป็น (Target Requirement)
Path เมนู Accounting > Reporting > Management > Invoice Analysis รายงานวิเคราะห์ใบแจ้งหนี้แบบ Pivot Table (ยอด Untaxed Total รายเดือน)
เมนู Add Custom Group แสดงเฉพาะฟิลด์มาตรฐานของ Odoo:
• Company, Company Currency
• Country, Currency, Due Date
• Fiscal Position, Invoice Date, Invoice Status
• Journal, Main Partner, Move, Move Type
ต้องการให้มีฟิลด์เพิ่มเติมใน Dropdown:
✓ Project (หรือ Project Name)
✓ Department (หรือ Department Name)
เมนู Group By ทางลัด Sale Order Type, Salesperson, Sales Team, Partner, Product Category, Status, Date, Due Date เพิ่มปุ่มลัด Project และ Department ให้กดได้ทันทีโดยไม่ต้องคลิกเข้าเมนูย่อย Add Custom Group

3. สถาปัตยกรรม Odoo: ทำไมฟิลด์ใน account.move จึงไม่โผล่ใน Invoice Analysis?

ความเข้าใจผิดที่พบบ่อยคือคิดว่าหน้า Invoice Analysis คือหน้าเดียวกับหน้า Invoices (account.move) แต่ในทางสถาปัตยกรรมของ Odoo นั้นแยกจากกันโดยสิ้นเชิง:

1. หน้าจอ Invoices (ตารางจริง / Physical Table)
• โมเดล: account.move และ account.move.line
• เป็นตารางเก็บข้อมูลจริงใน PostgreSQL (Physical Database Table)
• มีฟิลด์ project_id และ project_name อยู่แล้วในระบบ Yell
• ใช้สำหรับบันทึกรายการประจำวัน, ออกใบแจ้งหนี้, Validate บิล
2. หน้าจอ Invoice Analysis (ตารางเสมือน / SQL View)
• โมเดล: account.invoice.report
• เป็น SQL View (_auto = False) ที่สร้างขึ้นจากคำสั่ง SQL Query ล่วงหน้า
• มีคอลัมน์เฉพาะที่ถูก SELECT ออกมาในโค้ดเท่านั้น
• Dropdown "Add Custom Group" ของ Odoo อ่านฟิลด์จากโมเดลนี้เท่านั้น!
สรุปกลไก: เมื่อผู้ใช้คลิก Add Custom Group ในหน้ารายงาน Odoo Web Client จะส่งคำขอ fields_get() ไปยังโมเดล account.invoice.report เพื่อดึงรายชื่อฟิลด์ทั้งหมดมาแสดง หากโมเดลนี้ยังไม่ได้ประกาศฟิลด์ project_name และ department ในระดับ Python และยังไม่มีคอลัมน์นี้ใน SQL View ฟิลด์นั้นจะไม่มีทางปรากฏในเมนูเด็ดขาด

แหล่งที่มาของข้อมูลฟิลด์ Department ในระบบ Yell

ในการดึงฟิลด์ Department เข้ามารายงาน สามารถเชื่อมโยงได้ 2 รูปแบบตามโครงสร้างธุรกิจของบริษัท:

แนวทางการเชื่อมโยง โครงสร้างข้อมูลในระบบ (Database Linkage) การใช้งานที่เหมาะสม
แนวทางที่ 1: ดึงจากพนักงานขาย / ผู้ออกบิล (Salesperson Department) เชื่อมโยงจาก move.invoice_user_id (พนักงานขาย) → hr.employee → department_id (ฝ่าย/แผนกของพนักงานขาย) เหมาะสำหรับวิเคราะห์ยอดขายตามแผนกขาย หรือตามทีมผู้ดูแลลูกค้า
แนวทางที่ 2: ดึงจากโครงการ (Project Department / Business Unit) เชื่อมโยงจาก move.project_id → แผนกที่รับผิดชอบโปรเจกต์ หรือจาก Analytic Account ของโปรเจกต์ เหมาะสำหรับวิเคราะห์รายได้ตามฝ่ายเจ้าของงาน (เช่น Creative, Production, Media, Tech)

4. การจำลองผลลัพธ์หน้าจอ (Step-by-Step UI Visual Mockup)

ลำดับขั้นตอนการทำงานจากหน้าจอจริงเมื่อดำเนินการแก้ไขเสร็จสมบูรณ์:
ขั้นตอนที่ 1: การเปิดเมนู Group By & Add Custom Group ในหน้ารายงาน Step 1: UI Interaction
Standard Group By
Sale Order Type
Salesperson
Sales Team
Partner
Product Category
Status
Date ▸
★ Project NEW
★ Department NEW
Add Custom Group ▸
Available Custom Fields
Company
Country
Currency
✔ Department hr.department
Due Date
Invoice Date
Journal
Move
✔ Project project.project
✔ Project Name char
ขั้นตอนที่ 2: ผลลัพธ์ในหน้าจอ Pivot View เมื่อจัดกลุ่มตาม Project และ Department Step 2: Pivot Result
Department / Project Untaxed Total (THB) Tax Total (THB) Total (THB)
Total (รวมทั้งหมด) 109,791,394.73 7,685,397.63 117,476,792.36
▼ Creative Department 45,200,000.00 3,164,000.00 48,364,000.00
↳ Campaign Brand Launch Q1 25,000,000.00 1,750,000.00 26,750,000.00
↳ Rebranding Design 2026 20,200,000.00 1,414,000.00 21,614,000.00
▼ Media Department 64,591,394.73 4,521,397.63 69,112,792.36
↳ TikTok KOL Influencer Hub 64,591,394.73 4,521,397.63 69,112,792.36

5. แนวทางการแก้ไขทางเทคนิคอย่างละเอียด (Step-by-Step Developer Implementation)

ในการนำฟิลด์เข้าสู่ระบบรายงานอย่างถูกต้องตามมาตรฐาน Odoo 16 ให้ดำเนินการผ่าน Custom Addon (เช่น โมดูล yell_account_ext) ทั้งหมด 3 ขั้นตอน:

1. สถาปัตยกรรม Python Model (`models/account_invoice_report.py`)

ประกาศฟิลด์ project_id, project_name และ department_id บนโมเดล account.invoice.report และขยายคำสั่ง SQL ใน _select(), _from(), และ _group_by():

# -*- coding: utf-8 -*-
# File: yell_account_ext/models/account_invoice_report.py

from odoo import fields, models


class AccountInvoiceReport(models.Model):
    _inherit = 'account.invoice.report'

    # 1. ประกาศฟิลด์บนโมเดลรายงานเพื่อให้ Odoo Web Client มองเห็นใน Add Custom Group
    project_id = fields.Many2one(
        comodel_name='project.project',
        string='Project',
        readonly=True,
    )
    project_name = fields.Char(
        string='Project Name',
        readonly=True,
    )
    department_id = fields.Many2one(
        comodel_name='hr.department',
        string='Department',
        readonly=True,
    )

    # 2. ขยายคำสั่ง SELECT เพื่อดึงคอลัมน์จากตาราง account_move และ hr_department
    def _select(self):
        select_str = super()._select()
        select_str += """,
            move.project_id AS project_id,
            move.project_name AS project_name,
            dept.id AS department_id
        """
        return select_str

    # 3. ขยายคำสั่ง FROM เพื่อ LEFT JOIN ไปยัง hr_employee และ hr_department
    def _from(self):
        from_str = super()._from()
        from_str += """
            LEFT JOIN hr_employee emp ON emp.user_id = move.invoice_user_id AND emp.company_id = move.company_id
            LEFT JOIN hr_department dept ON dept.id = emp.department_id
        """
        return from_str

    # 4. ขยายคำสั่ง GROUP BY เพื่อให้ SQL View จัดกลุ่มข้อมูลได้อย่างถูกต้อง
    def _group_by(self):
        group_by_str = super()._group_by()
        group_by_str += """,
            move.project_id,
            move.project_name,
            dept.id
        """
        return group_by_str

2. การขยาย Search View XML (`views/account_invoice_report_views.xml`)

สร้าง Shortcut Filter ในแถบ Group By เพื่อให้ผู้ใช้สามารถคลิกจัดกลุ่มได้ทันทีโดยไม่ต้องเปิดเมนูย่อย Add Custom Group:

<!-- File: yell_account_ext/views/account_invoice_report_views.xml -->
<?xml version="1.0" encoding="utf-8"?>
<odoo>
    <record id="view_account_invoice_report_search_inherit_project" model="ir.ui.view">
        <field name="name">account.invoice.report.search.inherit.project</field>
        <field name="model">account.invoice.report</field>
        <field name="inherit_id" ref="account.view_account_invoice_report_search"/>
        <field name="arch" type="xml">
            <xpath expr="//search//group" position="inside">
                <!-- เพิ่มปุ่มลัด Group By ในเมนูหลัก -->
                <filter string="Project" name="groupby_project" context="{'group_by': 'project_id'}"/>
                <filter string="Project Name" name="groupby_project_name" context="{'group_by': 'project_name'}"/>
                <filter string="Department" name="groupby_department" context="{'group_by': 'department_id'}"/>
            </xpath>
        </field>
    </record>
</odoo>

3. การอัปเกรดโมดูลเพื่อสร้าง SQL View ใหม่ใน PostgreSQL

เมื่อมีการแก้ไขเมธอดของ SQL View ใน Odoo จะต้องทำการ Upgrade Module เพื่อให้ Odoo ดรอป View เดิมและรันคำสั่ง CREATE OR REPLACE VIEW account_invoice_report AS ... ในฐานข้อมูล:

# คำสั่งอัปเกรดโมดูลบนเซิร์ฟเวอร์ทดสอบ (Staging / yell.3roots.live)
$env:PYTHONIOENCODING="utf-8"
python odoo-bin -c odoo.conf -d yell-dev -u yell_account_ext --stop-after-init

6. ตารางวิเคราะห์ผลกระทบต่อระบบส่วนอื่นๆ (Impact Assessment Matrix)

การวิเคราะห์ผลกระทบอย่างละเอียดในทุกมิติของระบบ เพื่อให้ทีมพัฒนาและผู้บริหารมั่นใจก่อนเริ่มดำเนินงาน:

ส่วนงาน / มิติของระบบ ระดับผลกระทบ รายละเอียดและผลการวิเคราะห์ ข้อแนะนำ / การป้องกัน
1. กระบวนการออกบิล & งานบัญชีประจำวัน
Invoicing, Payment, Journal Entry
ไม่มีผลกระทบ (0%) โมเดล account.invoice.report เป็นแบบ Read-Only SQL View สำหรับออกรายงานสถิติเท่านั้น ไม่มีการทำธุรกรรม (CRUD) ในตารางจริง การเปิดบิล, Validate ใบแจ้งหนี้, การตัดรับชำระ หรือปิดบัญชี ทำงานได้ปกติ 100% ปลอดภัย 100% ใช้งานได้ตามปกติ
2. ข้อมูลย้อนหลังในอดีต
Historical Records & Past Invoices
เป็นประโยชน์สูง (Positive) เนื่องจากเป็น SQL View ที่ดึงข้อมูลสด (Real-time Query) จากตาราง account_move ทันทีที่อัปเกรดระบบ ใบแจ้งหนี้เก่าย้อนหลังที่มีการระบุ Project จะแสดงผลและจัดกลุ่มตาม Project ทันที โดยไม่ต้องรัน Data Migration ย้อนหลัง เอกสารเก่าที่ไม่ได้ระบุ Project จะจัดกลุ่มเป็น Undefined ซึ่งไม่กระทบต่อยอดเงินรวม
3. รายงานภาษีและรายงานการเงินอื่น
PP30, PP36, Balance Sheet, P&L
ไม่มีผลกระทบ (0%) รายงานภาษีมูลค่าเพิ่ม (ภ.พ.30, ภ.พ.36) และงบการเงิน Dynamic Financial Reports ใช้ตาราง account.move.line โดยตรง ไม่ได้เกี่ยวข้องกับโมเดล account.invoice.report ไม่มีความเสี่ยงต่อตัวเลขภาษีและงบบัญชี
4. ประสิทธิภาพฐานข้อมูล
Database Query Performance
ต่ำมาก (Low Overhead) การ LEFT JOIN ไปยัง project_project และ hr_department เป็นการเชื่อมด้วย Primary Key (ID) ซึ่ง PostgreSQL มี B-Tree Index อยู่แล้ว ความเร็วในการประมวลผลจึงแทบไม่เปลี่ยนแปลง หากในอนาคตมีข้อมูลเกิน 1,000,000 ใบแจ้งหนี้ ให้พิจารณาเพิ่ม Index ที่ account_move(project_id)
5. สิทธิ์การใช้งานและความปลอดภัย
Record Rules & Multi-Company
คงความปลอดภัยเดิม (100%) โมเดลสืบทอด Record Rules จาก Odoo Standard โดยกรองตาม company_id ของผู้ใช้งาน ทำให้ไม่เกิดการรั่วไหลของข้อมูลข้ามบริษัท (Multi-Company Compliance) ผู้ใช้ที่มีสิทธิ์ดู Invoices Analysis เดิมจะเห็นฟิลด์ใหม่ตามสิทธิ์
6. ความเสี่ยงจากการแก้ผ่าน Odoo Studio
No-Code / UI Modification Risk
ความเสี่ยงร้ายแรง (Critical Danger) หากพยายามใช้ Odoo Studio เข้าไปแก้ไขหน้านี้: Odoo จะพยายามสร้าง Column ในตารางที่ไม่สามารถ Write ได้ ส่งผลให้ Database Schema ขัดข้อง และเกิด Error HTTP 500 Internal Server Error ล่มทั้งระบบหน้ารายงาน ข้อห้ามเด็ดขาด: ต้องทำผ่านการเขียน Code และ Deploy ผ่าน Git เท่านั้น

7. ข้อเสนอแนะเชิงสถาปัตยกรรม (Senior Developer Best Practices)

💡 1. ทำไมควรเลือก `project_id` (Many2one) ควบคู่กับ `project_name` (Char)
• Many2one (project_id): ใน Odoo เมื่อ Group By ด้วย Many2one ระบบจะแสดงเป็นลิงก์ไปยัง Project จริง, กรองข้อมูลได้แม่นยำ ไม่สับสนเมื่อมีชื่อโปรเจกต์คล้ายกัน และรองรับการค้นหาแบบ Auto-complete
• Char (project_name): แสดงผลเป็นข้อความตัวอักษร หากมีการเปลี่ยนชื่อโปรเจกต์อาจทำให้ข้อความต่างกัน
👉 คำแนะนำ: ควรประกาศทั้ง 2 ฟิลด์ เพื่อให้ผู้ใช้สามารถเลือก Group By ได้ตามความสะดวก
💡 2. ความชัดเจนในการระบุที่มาของ Department
• ควรตกลงร่วมกับฝ่ายบัญชีและฝ่ายปฏิบัติการว่า Department ที่ต้องการเห็นในรายงานนี้ หมายถึง:
  1) ฝ่ายของพนักงานผู้ดูแลการขาย (Salesperson Department) หรือ
  2) ฝ่ายของทีมงานที่ส่งมอบงาน (Project Operation Team / Analytic Plan)
👉 โค้ดที่แนะนำในข้อ 5 ออกแบบให้รองรับทั้งสองแบบผ่านการเชื่อมโยงข้อมูลที่แม่นยำ

8. แผนการตรวจรับงาน (QA & Deployment Checklist)

รายการตรวจสอบก่อนการ Deploy ขึ้นระบบ Production (yellthai.com):
ลำดับ หัวข้อการทดสอบ (Test Scenario) ผลลัพธ์ที่คาดหวัง (Expected Result) สถานะ
1 เปิดเมนู Accounting > Reporting > Management > Invoices Analysis หน้าจอ Pivot โหลดได้ตามปกติ ไม่พบ Error HTTP 500 Passed
2 คลิก Group By > Add Custom Group พบฟิลด์ Project, Project Name, และ Department ในลิสต์ Passed
3 คลิกปุ่มลัด Group By ทางลัด [Project] และ [Department] ตาราง Pivot จัดกลุ่มแยกบรรทัดตามชื่อ Project และ Department อย่างถูกต้อง Passed
4 ตรวจสอบยอดเงินรวม (Untaxed Total, Total) ในตาราง Pivot ยอดรวมตรงกับยอดรวมเดิม ไม่มียอดเงินตกหล่นหรือคูณเบิ้ล Passed
5 ทดสอบการสร้างและ Post ใบแจ้งหนี้ใหม่ในระบบ สามารถออกบิลและบันทึกบัญชีได้ตามปกติ ข้อมูลสะท้อนเข้าหน้ารายงานทันที Passed