945 Technical Manual
945 Development Concept
ออกแบบระบบโดยเน้นการคงการทำงานด้านบัญชีตามมาตรฐาน Odoo ให้มากที่สุดโดยอิงหลักการ Odoo + OCA + Custom
ส่วนโมดูลเพิ่มเติม เน้นการใช้งาน OCA modules ก่อนหลังจากนั้นจึงใช้ Customer Codes ดูคำบรรยายของแต่ละโมดูลเสริมได้จาก Modulesx
การยิง API มีการยิงเข้า 2 จุดหลัก คือเข้าที่หน้าต่าง Sales Order และเข้าที่หน้าต่าง 945 Operations โดยการยิง Reference API สามารถดูได้ที่ APIs
การยิง API เข้ามาในระบบจะเป็นการยิงเข้า Job Queue เสมอเพื่อให้ระบบมีความเร็วและรับโหลดได้มาก
กรณียิง API เข้า Sales Order ในบางประเภทจะมีการ Compute Action เพื่อทำการปรับปรุงรายการให้เข้ากับแบบแผนการคำนวนของ 945 และใช้ Sale Automatic Workflow เพื่อทำการวิ่งเอกสารต่อไปยัง Account Invoice เพื่อทำการบันทึกบัญชี (ตามหลัก Odoo ปกติ)
กรณียิง API เข้า 945 Operations (ซึ่งมีหลาย windows/model ในการรับข้อมูล) จะมีการสร้าง Journal ด้วย Account Move Template
ในกรณีมีการคำนวนที่่มีความซับซ้อน ระบบจะเรียกใช้งาน Compute Helper
มี Unit Test สำหรับ process ที่สำคัญ
Odoo + OCA + 945
Odoo Core
Odoo Enterprise version 13
Odoo Community Association (OCA)
Customer Codes (945)
> Modules
OCA Modules
- l10n_th_xxx (โมดูลที่เริ่มต้นด้วย)
เกี่ยวกับภาษีไทย - WHT, VAT, Undue VAT, Tax Reports.
- queue_job
Base module สำหรับ Queue Job ถูกเรียกใช้โดย sunteen_api ในการเปลี่ยน function ให้เป็นแบบ queue
@job_auto_delay
- sale_automatic_workflow, sale_automatic_workflow_job
โมดูลเสริมเพื่อให้ workflow จาก Sales Order -> Invoice เกิดขึ้นโดยอัตโนมัติ
- account_mass_reconcile
โมดูลเสริมเพื่อให้ผู้ใช้งานทางบัญชีสามารถจับคู่เคลียร์ได้่ง่ายขึ้น โดยตั้งค่าการเคลียร์แบบอัตโนมัติได้
- account_move_template, account_move_template_enhanced
ช่วยในการสร้าง Template สำหรับการบันทึกบัญชี เรียกใช้โดย Operations ต่างๆ
- account_payment_term_extension
เพิ่ม Payment Term ตามโจทย์ของ 945 เช่นเอกสารในอาทิตย์นี้ทั้งหมดให้ไปกองวันครบกำหนดที่ศูกร์หน้า เป็นต้น
945 Modules
- sunteen
โมดูลหลักสำหรับ Business Process ของ 945 ทั้งหมด เช่น eCommerce, Express.
- sunteen_install
ทำหน้าที่่เรียกการติดตั้งโมดูลทั่งหมดที่ต้องใช้ใน 945 โดยตัวเองไม่ได้มี logic ใดๆ
- sunteen_coa, sunteen_data, sunteen_form
ทำหน้าที่โหลดข้อมูลที่ต้องใช้งาน เช่น Chart of Account, Move Templates, การตั้งค่าต่างๆ และแบบฟอร์ม
- sunteen_mass_reconcile
เสริมการทำงานของ account_mass_reconcile เพื่อให้จับคู่เคลียร์ผ่าน Parcel Number ได้
- sunteen_test
โมดูลเสริมสำหรับการทำ Unit Test
> 945 APIs
ตัวอย่างการใช้งาน APIs ระบบ 945
eCommerce
eCommerce - Standard
eCommerce - Consignment Fix GP
eCommerce - Consignment VAR GP
eCommerce - Standard
API 1 (sale.order)
{
"payload": {
"status": "out of delivery", # Status: out of delivery
"partner_id": 1, # Customer (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Standard", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
API2 (sunteen.delivery.complete):
{
"payload": {
"status": "completed", # Status : completed/return
"partner_id": 1, # Customer (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Standard", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
eCommerce - Consignment Fix GP
API1 (sunteen.record.transportation.cost):
{
"payload": {
"status": "out of delivery", # Status: out of delivery
"partner_id": 1, # Customer=Consignor (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Consignment Fix GP", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
API2 (sale.order):
{
"payload": {
"status": "completed", # Status: completed/return
"partner_id": 1, # Customer=Consignor (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Consignment Fix GP", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
eCommerce - Consignment Var GP
API1 (sunteen.record.transportation.cost):
{
"payload": {
"status": "out of delivery", # Status: out of delivery
"partner_id": 1, # Customer=Consignor (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Consignment Var GP", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
API2 (sale.order):
{
"payload": {
"status": "completed", # Status: completed/return
"partner_id": 1, # Customer=Consignor (res.partner)
"sunteen_dealer_id": 2, # Dealer (res.partner)
"workflow_process_id": "eCommerce Consignment Var GP", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"sunteen_payment_method_id": 3, # Payment Method (sunteen.payment.method)
"sunteen_payment_provider_id": 34, # Payment Provider (res.partner)
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
},
{
"sunteen_parcel_number": "TDZ001",
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
},
{
"sunteen_parcel_number": "TDZ001", # Dealer Amount Difference Line
"sunteen_partner_id": 14,
"sunteen_customer": "CUSTOMER1",
"product_id": 18,
"product_uom_qty": 1,
"dealer_price_unit": 20.0, # API sent +, odoo change to -
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
Express
Express - Standard
Express - Standard
API1 (sale.order):
{
"payload": {
"status": "out of delivery", # Status: out of delivery
"partner_id": 1, # Customer (res.partner)
"sunteen_merchant_id": 33, # Merchant (res.partner)
"sunteen_merchant_level": "ecom", # Merchant Level: ecom/agent/franchise
"sunteen_merchant_ownership": "my945", # Merchant Ownership: my945/others
"workflow_process_id": "Express Standard", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
"type": "normal", # Type: normal, cod
"cod_amount": 0, # COD Amount
},
{
"sunteen_parcel_number": "TDZ002",
"sunteen_partner_id": 15,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
"type": "cod",
"cod_amount": 20,
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
API2 (sunteen.express.delivery.complete):
{
"payload": {
"status": "completed", # Status : completed/return
"partner_id": 1, # Customer (res.partner)
"sunteen_merchant_id": 33, # Merchant (res.partner)
"sunteen_merchant_level": "ecom", # Merchant Level: ecom/agent/franchise
"sunteen_merchant_ownership": "my945", # Merchant Ownership: my945/others
"workflow_process_id": "Express Standard", # Automatic Workflow (sale.workflow.process)
"date_order": "2020-01-31", # Order Date
"transaction_date": "", # Transaction Date
"total": 500.0, # Total
"order_line": [ # Sale Order Line
{
"sunteen_parcel_number": "TDZ001", # Parcel Number
"sunteen_partner_id": 14, # Transporter (res.partner)
"sunteen_customer": "CUSTOMER1", # Customer
"product_id": 16, # Product (product.product)
"product_uom_qty": 1, # Quantity
"dealer_price_unit": 100.0, # Dealer Unit Price
"type": "normal", # Type: normal, cod
"cod_amount": 0, # COD Amount
"status": "completed", # Status: completed/return
},
{
"sunteen_parcel_number": "TDZ002",
"sunteen_partner_id": 15,
"sunteen_customer": "CUSTOMER1",
"product_id": 17,
"product_uom_qty": 2,
"dealer_price_unit": 200.0,
"type": "cod",
"cod_amount": 20,
"status": "completed",
},
]
},
"auto_create": { # Optional for auto create master data
"partner_id": [
{"name": "Customer 1", "ref": "CUSTOMER1"}
],
"product_id": [
{"name": "Product 1", "default_code": "PRODUCT1"}
]
}
}
Note
สำหรับ eCommerce การส่งข้อมูลจะเป็น 1 API ต่อ 1 Parcel Number ส่วน Express จะเป็น 1 API ต่อ หลาย Parcel Number
> Job Queue
Job Queue คืออะไร
Job Queue คือการหน่วงการทำงานของ process โดยแทนที่จะทำทันที ก็จะหน่วงและทำในภายหลัง
เช่น หากระบบต้นทางต้องการยิง API เข้ามา 1,000 รายการ ถ้าไม่มี Job Queue ระบบต้นทางจะต้องรอจนกว่ารายการก่อนหน้าจะทำงานเสร็จ จึงจะยิงรายการถัดมาได้ ถ้า server ไม่สามารถทำงานได้เร็วพอก็จะเกิดปัญหาได้ ถ้าใช้ Job Queue ระบบต้นทางสามารถยิงข้อมูลเข้ามาได้ทั้งหมดอย่างรวดเร็วโดยไม่โหลด server (เพราะยังไม่เกิดการทำงานจริง)
ข้อมูลการทำงานทั้งหมดจะเข้ามาพักที่ table queue.job
ของ Odoo ก่อน แล้วจึงทยอยทำงานพร้อมกันตามจำนวน workers
ข้อดีอีกประการหนึ่ง จากการที่ข้อมูลการทำงานได้เข้ามาพักไว้ก่อน ไม่ว่าการทำงานจะสำเร็จหรือไม่ ก็จะสามารถตรวจเช็คได้ในภายหลังที่ Odoo
ข้อดีของ Job Queue
เก็บรายการที่ยิงเข้ามาใน database table queue.job (ตั้งเวลาลบทิ้งได้หลังจากทำงานเสร็จและเวลาผ่านไปนาน)
Jobrunner: ทำงานกับ queue ที่เข้ามาได้อย่างทันที
Channels: แบ่งการทำงานของ workers ตาม channel, sub-channel ได้ เพื่อจำกัด worker บางตัวให้ทำงานบางอย่างไม่ปนกัน (ไม่แย่ง CPU)
Retries: มีการ retry งานที่ failed (บางครั้ง faile เพราะ update รายการเดียวกันพร้อมกัน ซึ่งทำอีกครั้งก็จะผ่านไปได้)
Retry Pattern: เช่น ให้ retry 3 ครั้งแรกห่างกัน 10 วินาที retry 5 ครั้งต่อไป ทุกๆ 1 นาที
Note
สำหรับ 945 เราได้ตั้งค่าการทำงานแบบทั่วๆไป อนาคตสามารถเปลี่ยนแปลงได้ตามความเหมาะสม
Configurations
เพื่อให้ Job Queue ทำงานได้ ต้องมีการตั้งค่าที่ odoo config file.
Using the Odoo configuration file:
[options]
(...)
workers = 6
server_wide_modules = web,queue_job
(...)
[queue_job]
channels = root:2
Note
ตัวอย่างนี้ให้ใช้ worker 2 ตัวจากทั้งหมด 6 ตัว ทำงานกับ channel = root
Usage
Job Menu: แต่ละ API ที่ถูกยิงเข้ามาจะเข้ามาสร้าง Job โดยจะเริ่มต้นที่ status = Pending
Job Status: Pending = รอ, Enqueue = เข้าคิว, Started = เริ่มทำงาน, Done = เสร็จ, Failed = ไม่สำเร็จ
Task: รายละเอียดของ function call ที่จะทำ โดยมี input data ทั้งหมดที่ต้องใช้
Function / Channel: Function นี้ทำงานผ่าน Channel ใหน เช่น root.sunteen_api ซึ่งเป็น sub-channel ของ root (จะใช้ร worker ่วมกับ root หากไม่ประกาศแยกไว้)
Retry: จำนวนครั้งที่ retry
Result: ผลลัพธ์ที่ได้ หากเกิด Error จะแสดงเป็นข้อความ exception และเปลี่ยนสถานะเป็น status = Failed (และ retry)
> Sale Automatic Workflow
Sale Automatic Workflow คืออะไร
Menu: Sales > Configuration > Automatic Workflow > Automatic Workflow
Sale Automatic Workflow คือ กระบวนการการดำเนินงานโดยอัตโนมัติที่เกี่ยวข้องกับการขาย ตัวอย่างกระบวนการการดำเนินงานที่เกี่ยวข้องกับการขาย เช่น การ Confirm Sale Order, การสร้าง Invoice จาก Sale Order และการ Validate Invoice ของ Sale Order เป็นต้น โดยกระบวนการการดำเนินงานโดยอัตโนมัติที่เกี่ยวข้องกับการขาย สามารถทำงานได้ใช้โมดูล sale_automatic_workflow และ sale_automatic_workflow_job ของ OCA
โมดูล sale_automatic_workflow เป็นโมดูลที่ใช้ในการสร้างและตั้งค่าการทำงานของ Automatic Workflow ที่จะนำมาใช้ในกระบวนการขาย ซึ่งเหมาะสำหรับระบบที่มีการทำงานโดยการ Interface ผ่าน API เข้ามาที่ Sale Order ใน Odoo
โมดูล sale_automatic_workflow_job เป็นโมดูลที่รวมการทำงานของ sale_automatic_workflow และ job_queue เข้าด้วยกัน ทำให้ระบบสามารถจัดลำดับการทำงานของ Automatic Workflow ได้
Configurations
การตั้งค่า Sale Automatic Workflow แบ่งออกเป็น 3 ส่วน ดังนี้
Order Configuration
Workflow Options
945 Workflow Options
Order Configuration
- Shipping Policy คือ วิธีการจัดส่งสินค้า ซึ่งแบ่งได้ดังนี้
Deliver all products at once คือ ส่งสินค้าทั้งหมดที่ลูกค้าต้องการในครั้งเดียว
Deliver each product when available คือ ส่งสินค้าเท่าที่มีในคลัง(ตามที่ระบุในระบบ)
Sales Team คือ ทีมที่ขายสินค้าได้
Workflow Options
Validate Order คือ ต้องการให้ Confirm Sale Oder หรือไม่ โดยสามารถจำกัด Domain ของ Sale Order ที่ต้องการ Confirm ได้ที่ Order Filter
Confirm and Transfer Picking คือ ต้องการให้ส่งของและ Validate Delivery Order หรือไม่
Create Invoice คือ ต้องการให้สร้าง Invoice หรือไม่ โดยสามารถจำกัด Domain ของ Sale Order ที่ต้องการสร้าง Invoice ได้ที่ Create Invoice Filter
Validate Invoice คือ ต้องการให้ Validate Invoice หรือไม่ โดยสามารถจำกัด Domain ของ Sale Order ที่ต้องการ Validate ได้ที่ Validate Invoice Filter
Sale Done คือ ต้องการ Locked Sale Order หลังจาก Confirm Sale Oder หรือไม่
Force Invoice Date คือ ต้องการให้ Invoice Date ใน Invoice เป็นวันเดียวกับ Order Date ใน Sale Order หรือไม่
Invoice Service on delivery คือ ต้องการให้บันทึกจำนวน Deliveried ของสินค้าประเภทบริการใน Sale Order หลังจากสร้าง Invoice หรือไม่
Sales Journal คือ Journal ที่จะใช้ใน Invoice
Warning Message คือ ข้อความที่จะขึ้นแจ้งเตือน เมื่อเลือก Automatic Workflow นี้ ใน Sale Order
945 Workflow Options
Allow Compute Sale Order Line คือ ต้องการให้คำนวณรายได้อื่นๆ เช่น รายได้ค่าขนส่ง ใน Sale Order หรือไม่
Record Transportation Cost คือ ต้องการสร้าง Transportation Cost (TC) หรือไม่
Record Delivery Complete คือ ต้องการสร้าง Delivery Complete (DC) หรือไม่
- Consignment Payable คือ วิธีการคำนวณเจ้าหนี้การค้าฝากขาย ซึ่งจะเท่ากับ Account Payable Transfer (APT) โดยสามารถเลือกได้ ดังนี้
None คือ ไม่คำนวณ Consignment Payable
Fixed Cost คือ คำนวณโดยที่ทราบต้นทุนที่แน่นอน เช่น เมื่อขายสินค้า A ได้ จะต้องจ่ายเงินให้ผู้ฝากขาย 10 บาท/หน่วย
Variance Cost คือ คำนวณโดยที่ไม่ทราบต้นทุนที่แน่นอน เช่น เมื่อขายสินค้า B ได้ ผู้ฝากขายจะได้รับเงินตามรายได้หลังหักค่าใช้จ่ายต่างๆแล้ว
945’s Configurations
ปัจจุบัน 945 มีการใช้ Sale Automatic Workflow ทั้งหมด 4 แบบ ดังนี้
eCommerce Standard
eCommerce Consignment Fix GP
eCommerce Consignment Var GP
Express Standard
eCommerce Standard
eCommerce Consignment Fix GP
eCommerce Consignment Var GP
Express Standard
> Account Move Template
Account Move Template คืออะไร
Menu: Accounting > Configuration > Accounting > Journal Entry Templates
Account Move Template หรือ Journal Entry Template คือ Template ของการบันทึกบัญชี มีประโยชน์ในการบันทึกบัญชีที่มีรูปแบบของคู่บัญชีเหมือนๆกัน แต่มีจำนวนมาก
Configurations
Name คือ ชื่อของ Template
Journal คือ สมุดบัญชี
Reference คือ ข้อความที่ต้องการให้แสดงใน Reference ของ Journal Entry
Sequence คือ ลำดับของ line และใช้อ้างอิงในการคำนวณด้วย
Label คือ ข้อความที่ใช้อธิบายการบันทึกบัญชีของ line นั้นๆ
Account คือ บัญชีที่ต้องการบันทึก
Account Opt. คือ บัญชีที่ต้องการบันทึก เมื่อ Amount ติดลบ
Partner คือ คู่ค้า
Payment Terms คือ ระยะเวลาที่กำหนดในการจ่ายเงิน
Amount
Direction คือ ทิศทางในการบันทึกบัญชี ได้แก่ Credit และ Debit
Type คือ วิธีการรับข้อมูล Amount มี 2 แบบ คือ Computed และ User Input
Note คือ ข้อความที่ต้องการบันทึกเพิ่มเติม
Note
Compute Formula คือ ส่วนที่ไว้ใส่สูตรที่ใช้ในการคำนวณ Amount ของ line นั้น จะปรากฏเมื่อเลือก Type เป็น Computed
Taxes
Is a refund? คือ เป็นการคืนเงินหรือไม่
Originator Tax คือ ภาษีตั้งต้น
Taxes คือ ภาษี
ตัวอย่าง Account Move Template
วิธีใช้ Account Move Template
1. เรียกใช้งานผ่าน Code
สิ่งที่จำเป็นในการเรียกใช้งาน Account Move Template คือ
สร้าง Record ของ account.move.template.run
เรียกใช้ function generate_move()
ตัวอย่างการเรียกใช้งานผ่าน Code
def process_generate_move(self):
wiz = self.create_move()
wizard = self.env[wiz["res_model"]].browse(wiz["res_id"])
wizard.with_context(wiz["context"]).generate_move()
2. เรียกใช้งานผ่าน UI
วิธีที่ 1
เข้าไปที่ Accounting > Accounting > Miscellaneous > Create Entry from Template
เลือก Template ที่ต้องการใช้เป็นแบบในการบันทึกบัญชี
ใส่ข้อมูลที่ต้องการให้แสดงใน Journal Entry
หลังจากใส่ข้อมูลและตรวจสอบข้อมูลเรียบร้อยแล้ว ให้กดที่ปุ่ม CREATE JOURNAL ENTRY เพื่อสร้าง Journal Entry
Note
สามารถ overwrite ข้อมูลใน line ที่เราต้องการได้ โดยการเพิ่มข้อมูลที่ต้องการ overwrite ไปที่ฟิลด์ Overwrite ในรูปแบบของ Dictionary เช่น {“L0”: {“partner_id”: 1, “date_maturity”: “2020-02-02”}}
วิธีที่ 2
เข้าไปที่ Accounting > Configuration > Accounting > Journal Entry Templates
เลือก Template ที่ต้องการใช้เป็นแบบในการบันทึกบัญชี
กดที่ปุ่ม GENERATE JOURNAL ENTRY
ใส่ข้อมูลที่ต้องการให้แสดงใน Journal Entry
หลังจากใส่ข้อมูลและตรวจสอบข้อมูลเรียบร้อยแล้ว ให้กดที่ปุ่ม CREATE JOURNAL ENTRY เพื่อสร้าง Journal Entry
> Compute helper
Compute Helper คืออะไร
Menu: 945 > Configurations > Compute > Compute Type / Helper
Compute Helper เกิดขึ้นเนื่องจากการคำนวนเพื่อให้ได้ค่าต่างๆที่เกิดขึ้นในระบบเช่น Service Cost ซึ่งหลังจากที่เราพัฒนาระบบไปได้ซักระยะ พบว่าระบบมีความซับซ้อนเพราะสูตรการคำนวนขึ้นอยู่กับหลายตัวแปร เช่น Partner, Product, Payment Method การฝังกฏการคำนวนไว้ในโค้ด (hard code) จะทำให้การดูแลระบบเป็นไปอย่างยากลำบาก
จึงได้มีการสร้างตาราง Mapping ไว้แยกต่างหาก (Compute Helper) เพื่อให้เห็นว่าในแต่ละประเภทของการคำนวน (Compute Type) มีสูตรการคำนวน (Server Action) อย่างไร
ตัวอย่างตามรูปคือตัวช่วยการคำนวนสำหรับฟังก์ชั่น _get_service_cost()
ตัวอย่างสำหรับ _get_service_cost() จะถูกเรียกใช้งานผ่าน compute helper ตามตัวอย่างด้านล่าง
def _get_service_cost(self, payment_method_id, payment_provider_id):
res = self.env["sunteen.compute.helper"].run(
self, "_get_service_cost", partner_id=payment_provider_id.id or False,
payment_method_id=payment_method_id.id or False, input_dict={}
)
return res
Note
- Compute Helper ทั้งหมดปัจจุบันจะถูกโหลดเข้าระบบด้วยไฟล์ data จาก
sunteen/data/sunteen_compute_helper_data.xml
sunteen_data/data/sunteen.compute.helper.csv
Server Actions
Menu: Settings > Technical > Actions > Server Actions
ด้วย Compute Helper จะมาเรียก Server Action ต่อ แล้ว Server Action คืออะไร?
Server Action เป็นฟังก์ชั่นมาตรฐานหนึ่งของ Odoo ใช้สำหรับการเขียน scriptlet ด้วย Python สั้นๆได้ โดยใน scriptlet สามารถรับการผ่านค่าต่างๆผ่านตัวแปร Context (เช่น model, active_id และอื่นๆ) โดยหลังจากที่สร้าง scriptlet นี้แล้วสามารถนำมันไปผูกใช้งาน กับโมเดลที่สนใจได้ ในกรณีของ 945 เราสร้าง scriptlet โดยไม่ได้นำไปผูกกับโมเดลใดๆ แต่เรียกใช้โดยตรงผ่านฟัังชั่นดังตัวอย่าง _get_service_cost()
รูปนี้เป็นตัวอย่างการเขียน Server Action ที่ใช้ในการคำนวน Service Cost
Note
รายละเอียดเพิ่มเติมเกี่ยวกับ Server Action
> Unit Test
What is unit test?
Unit Test คือ script ในการจำลองให้ระบบทำงานผ่านโค้ด มีประโยชน์ในการตรวจสอบระบบที่มีการแก้ไขต่อเนื่องให้แน่ใจว่าสิ่งที่แก้ไม่ไปขัดแย้งกับส่วนเดิม หรือถ้าแย้งกันเราจะได้รู้ว่าระบบทำงานถูกต้องหรือไม่ หรือต้องปรับโค้ดและ test script ให้ถูกต้อง
ในระบบ 945 เรามีการเขียน Unit Test เฉพาะกับการทำงานกับ API และซึ่งมีการบันทึกบัญชีอัตโนมัติ โดยปกติเราจะรัน unit test บน development machine เท่านั้นเพื่อตรวจสอบการทำงานของโค้ดก่อนการ deploy
Test script จะถูกเก็บไว้ที่โมดูล sunteen_test โดยเมื่อจะรัน test สิ่งที่ต้องทำคือการ start Odoo ผ่าน Command Line ด้วย -d <database> -i sunteen_test –test-enable เช่น
> ./odoo-bin -c odoo.conf -d api_odoo -i sunteen_test --test-enable
คำสั่งนี้จะบอกให้ Odoo ทำงานกับ database api_odoo และทำการรันการทดสอบของโมดูล sunteen_test
โดย log ของ command line จะมีผลดังตัวอย่างนี้หากสำเร็จ
2020-11-19 05:02:50,837 23016 INFO sunteen odoo.addons.sunteen_test.tests.test_ecom_standard_1: Starting TestEcomStandard.test_2_transportation_cost ...
2020-11-19 05:02:50,958 23016 WARNING sunteen odoo.addons.base.models.res_company: The method '_company_default_get' on res.company is deprecated and shouldn't be used anymore
2020-11-19 05:02:51,428 23016 INFO sunteen odoo.addons.sunteen_test.tests.test_ecom_standard_1: Starting TestEcomStandard.test_3_invoice_entry ...
2020-11-19 05:02:51,432 23016 INFO sunteen odoo.modules.module: Ran 3 tests in 1.024s
และอาจมีผลลัพธ์แบบนี้หากไม่สำเร็จ
2020-11-19 04:49:11,697 21813 INFO sunteen odoo.addons.sunteen_test.tests.test_ecom_standard_1: Starting TestEcomStandard.test_4_delivery_complete ...
2020-11-19 04:49:11,698 21813 INFO sunteen odoo.addons.sunteen_api.models.utils: [sunteen.delivery.complete].create_data(), input: {'payload': {'status': 'out for delivery', 'partner_id': 4960, 'sunteen_dealer_id': 4961, 'name': '55-CP20053042686x', 'workflow_process_id': 'eCommerce Standard', 'date_order': '2020-06-01', 'transation_date': '2020-06-01', 'sunteen_payment_method_id': 1, 'sunteen_payment_provider_id': 10, 'total': 3200.0, 'order_line': [{'product_id': 1323, 'product_uom_qty': 1, 'dealer_price_unit': 1600.0, 'sunteen_parcel_number': 'TDZ20982676x', 'sunteen_partner_id': 10, 'sunteen_customer': 'คันธรส จงอร่ามเรือง(Kantarost)', 'name': 'Colla Tab 10 กระปุก'}, {'product_id': 1324, 'product_uom_qty': 1, 'dealer_price_unit': 1600.0, 'sunteen_parcel_number': 'TDZ20982676x', 'sunteen_partner_id': 10, 'sunteen_customer': 'คันธรส จงอร่ามเรือง(Kantarost)', 'name': 'Aqua S คอลลาเจน 10 แพ็ค'}, {'product_id': 1, 'product_uom_qty': 1, 'dealer_price_unit': 50.0, 'sunteen_parcel_number': 'TDZ20982676x', 'sunteen_partner_id': 10, 'sunteen_customer': 'sudtinee jaithip', 'name': 'Dealer Amount Difference'}]}, 'status': 'completed'}
2020-11-19 04:49:11,701 21813 INFO sunteen odoo.addons.sunteen_test.tests.test_ecom_standard_1: ======================================================================
2020-11-19 04:49:11,701 21813 ERROR sunteen odoo.addons.sunteen_test.tests.test_ecom_standard_1: ERROR: TestEcomStandard.test_4_delivery_complete
Traceback (most recent call last):
File "/home/kittiu/PycharmProjects/odoo-945-erp/sunteen_test/tests/test_ecom_standard_1.py", line 131, in test_4_delivery_complete
res = self.DC.create_data(self.ecom_standard_api2_data)
...
ValueError: Wrong value for sunteen.delivery.complete.delivery_status: 'out for delivery'
การเขียน Unit Test
การเขียน Unit Test มีหลักการคือให้มองการทำงานเป็น blackbox เราจะใส่ input data เรียกการทำงาน และประเมินผลที่ได้ และเป้าหมายคือพยายามเขียน script ให้รันผ่านโค้ดให้มากที่สุด โดยเมื่อมีการ start Odoo ด้วย –test-enable ระบบจะเข้าไปหาโฟลเดอร์ tests และไฟล์ที่ชื่อเริ่มต้นด้วย test_xxx.py และจะไปรัน method ที่เริ่มต้นด้วย def test_yyy()
ระบบจะมองหาโฟลเดอร์ tests และหารไฟล์ที่เริ่มด้วย test_xxx.py
Inherit Test Class
Setup() คือส่วนที่เอาไว้ initialize ข้อมูลที่ต้องการใช้สำหรับแต่ละการทดสอบ
Test Cases, 1, 2, 3, 4… แล้วแต่เรื่องที่ต้องการ
Note
รายละเอียดเพิ่มเติมเกี่ยวกับ Unit Testing
> SQL Views Editor
หน้าต่าง SQL Views
Menu: Settings > Technical > Database Structure > SQL Views
แทนที่จะเขียน SQL ดิบๆ เพื่อสร้าง Database View โดยตรงที่ระบบฐานข้อมูล เรามีโมดูลช่วยให้การสร้างและดูแล View ง่ายและมีระเบียบขึ้นด้วยหน้าต่าง SQL Views
สร้าง SQL View
เราสามารถสร้างใหม่และปรับปรุง SQL View ได้จากหน้าต่างนี้
ตั้งชื่อ Name (จะเป็นชื่อเมนู) และชื่อ Technical Name (จะเป็นชื่อ database view)
ชื่อของ database view ที่จะสร้างขึ้น สามารถเรียกใช้งานได้จากระบบ BI อื่นๆ
ลำดับของ UI View ที่จะสร้างขึ้น เป็น View ที่ใช้เพื่อดูข้อมูล, export excel และอื่นๆ
Is Materialized View, สร้าง view แบบ static จาก dynamic sql (ข้อดีคือเร็วเหมือน data table แต่จะไม่ real time เพราะถูก refresh เป็นระยะ)
SQL Query ที่ใช้ในการสร้าง View
Activate View
หลังจากทดสอบ View เสร็จ (Preview SQL Expression) เราสามารถเริ่มใช้งานได้โดย กดปุ่มเหล่านี้
[Validate SQL Expression] -> [Create SQL View, Indexes And Model] > [Create UI]
Open View
คลิกหที่ปุ่ม Open View จากหน้าต่าง SQL View Edit เพื่อเริ่มใช้งานได้ทันที หรือจะเข้ามาทางเมนู Dashboard จากหน้าต่าง Desktop ก็ได้เช่นกัน
Export Excel จาก Tree View โดยการเลือก Column
Refresh Materialized View
หากต้องการ Refresh Data ของ view ที่สร้างขี้นสามารถทำได้โดย
กดปุ่ม Refresh โดยตรง
ดูการตั้งค่าการ Refresh ของ Scheduled Job ของ View นี้
Note
ปกติตั้งไว้ 1 Month สามารถปรับให้ถี่ขึ้นตามต้องการ เช่นวันละ 1 ครั้ง ไม่ควรถี่เกินไปเพราะถ้าข้อมูลเยอะจะใช้เวลานาน