เครื่องมือหาราคาและแก้ราคา (Price engine / price center / modify price)
เมื่อผู้ใช้กรอกสินค้า/จำนวนในตารางสินค้าของเอกสาร backend ต้องหาราคาให้เองตามลำดับเดียวกับโปรแกรม C# เดิม (_findPriceClass.cs, _findPurchasePriceClass.cs) ถ้าบริษัทเปิด erp_option.use_price_center ราคาขายทั้งหมดจะไปถามบริการศูนย์ราคาภายนอกแทน นอกจากนี้มีหน้าจอดูรายการราคา (price info), หน้าแก้ราคาบาร์โค้ดทีละรายการ (check price) และหน้าปรับปรุงราคาทีละหลายแถว (modify price)
ทำงานอย่างไร
1. จุดเรียก (จากตารางสินค้า)
- ตอนแก้จำนวน (
onQtyในtrans_grid.go) ถ้า flag เอกสารอยู่ในsalePriceFlags(30, 34, 36, 44, 46, 48, 76, 144) จำนวน<> 0และไม่ได้ดึงมาจากเอกสารอ้างอิง/ไม่ใช่ของแถม ->g.salePrice->applySalePrice(trans_grid.go:490-495,:564) - flag ซื้อ
purchasePriceFlags(2, 6, 12, 310) เรียกfindPurchasePriceเฉพาะเมื่อ optionGetPurchasePriceเปิด (trans_grid.go:96-97,:497,:529) -- รายละเอียดการคำนวณแถวอยู่ที่ ตารางสินค้า: คำนวณแถว สูตรลัด และ action ของ transgrid (Item grid calc) - ข้อมูลที่ใช้ค้นคือ
priceQuery:ItemCode,UnitCode,Barcode,PartyCode(=custCodeของหัวเอกสาร),DocDate,CurrencyCode,Qty,VatRate,VatType,SaleType(=inquiryTypeของเอกสาร) (trans_grid.go:461)
2. ลำดับหาราคาขาย (findSalePrice, price_engine.go:103)
ทดลองทีละขั้น เจอแล้วหยุด ผลเก็บใน priceResult (Found, Price, Price1, Price2, Type, Mode, Roworder, PriceGuid, DiscountWordFormula, DefaultDiscount, StandPrice)
| ลำดับ | แหล่ง | Type |
เงื่อนไขสำคัญ |
|---|---|---|---|
| 1 | ic_inventory_price ราคาเฉพาะลูกค้า |
1 | price_type = 3 และ cust_code = custCode |
| 2 | ราคากลุ่มลูกค้า | 2 | price_type = 2 จับกลุ่มจาก ar_customer_detail (group_main เทียบ cust_group_1; group_sub_1..4 เทียบ cust_group_2 หรือ cust_group_2 ว่าง) |
| 3 | ราคาทั่วไป (price_mode = 1) |
3 | price_type = 1 |
| 4 | ราคามาตรฐาน (price_mode = 0) |
3 | price_type = 1 |
| 5 | สูตรราคา ic_inventory_price_formula |
6 (mode 6) | ใช้เมื่อสี่ขั้นแรกไม่พบ และมีแถวสูตร |
| 6 | ราคาบาร์โค้ด ic_inventory_barcode.price |
5 (mode 5) | เมื่อส่ง barcode มา |
| 7 | ราคาขายล่าสุด | 7 (mode 5) | เมื่อ option GetLastPriceType = 1 (ใบล่าสุด) หรือ 2 (เฉลี่ย) |
- ทุกขั้นของ
ic_inventory_priceกรองด้วยpriceCond: รหัสสินค้า + หน่วย, วันที่เอกสารอยู่ในช่วงfrom_date..to_date, จำนวนอยู่ในช่วงfrom_qty..to_qty,sale_type IN (0, ชนิดขาย)โดยsaleFilterTypeให้ 2 เมื่อSaleTypeเป็น 0 หรือ 2 ไม่ใช่ก็ให้ 1 (price_engine.go:49,:88) และกรองสกุลเงินด้วยpriceCurrencyCondเมื่อ optionMultiCurrencyเปิด (ไม่เปิด = ส่งสกุลว่าง) - เรียงเลือกแถว:
price_mode DESC, sale_type DESC, transport_type DESC(ขั้นกลุ่มลูกค้าเพิ่มcust_group_2, ขั้นมาตรฐานเพิ่มline_number DESC) แล้วLIMIT 1 - ราคาที่ใช้ (
res.Price):sale_price2(ราคารวมใน) เป็นค่าตั้งต้น แต่ถ้าVatType = 0(แยกนอก) ใช้sale_price1(price_engine.go:118-122);Price1/Price2เก็บทั้งสองไว้ให้หน้าจอเตือนข้ามชนิด - สูตรราคา: เลือกแถว
ic_inventory_price_formulaตามหน่วยsale_type(0 กับชนิดขาย) และtax_type(0 กับ VatType 0->1, 1->2, 2->3) แล้วเลือกคอลัมน์price_0..price_9ตามar_customer.price_level(1-9; นอกนั้นใช้price_0) ส่งให้calcFormulaPriceซึ่งตีความ=ราคา,ส่วนลด/-ส่วนลด/+ส่วนลด/ ตัวเลขล้วน (ตารางสินค้า: คำนวณแถว สูตรลัด และ action ของ transgrid (Item grid calc)) (price_engine.go:133-145,:205) - ราคาล่าสุด: อ่านจาก
ic_trans_detailของtrans_flag = 44(last_status = 0,price_exclude_vat > 0) ของลูกค้าเดียวกัน (ใช้cust_codeของหัวเอกสารถ้าแถวไม่มี); ถ้า VatType เป็นรวมใน (1) บวก VAT เพิ่มจากอัตราของเอกสาร (:157-179) - ถ้าเจอราคาและ option
PriceFormulaControlเปิดและมีสูตร: ใส่StandPrice = price_0ให้ตารางแสดงราคามาตรฐานข้างราคาจริง (:180-182) - ส่วนลดตั้งต้น หาทุกครั้งแม้ไม่พบราคา:
ic_inventory_discountตามลำดับ ลูกค้า (discount_type = 2) -> กลุ่ม (discount_type = 1) -> ทั่วไป (discount_type = 0) ได้ค่าแรกที่เจอเก็บในDefaultDiscount(saleDiscountSteps,:79) และapplySalePriceเขียนทับช่องส่วนลดของแถว
3. ผลที่ตกถึงแถวสินค้า (applySalePrice, trans_grid.go:564)
- เขียน
PriceType,PriceMode,PriceRoworder(แถวic_inventory_priceที่ใช้ — 0 สำหรับสูตร/บาร์โค้ด/ราคาล่าสุด),PriceGuid(ศูนย์ราคาเท่านั้น),Price,SetRefPrice,Price0(เมื่อมีStandPrice) - เขียนราคาลงแถวแม้ไม่พบ (ได้ 0) ตามคอมเมนต์ C#
- คำเตือน (
salePriceWarnings,trans_grid.go:540): optionWarningPrice1เปิดและไม่พบราคา (สินค้าItemTypeไม่ใช่ 3/5) -> "ไม่พบราคาสินค้า"; optionWarningPrice2เปิดและพบราคาแต่เป็น 0 -> ถามยืนยันใช้ราคาอีกชนิด (VatType 0 แต่มีแต่ราคารวมใน, หรือกลับกัน) พร้อมYesPriceที่แปลงด้วย VAT ของเอกสาร
4. ลำดับหาราคาซื้อ (findPurchasePrice, price_engine.go:237)
ic_inventory_purchase_priceเฉพาะผู้ขาย:supplier_code = custCodeและprice_type = 3- ราคาซื้อทั่วไป:
price_type = 1 - ราคาซื้อล่าสุดจาก
ic_trans_detailของtrans_flag IN (12, 315)(last_status = 0,qty <> 0,price_exclude_vat > 0) -- คอลัมน์ราคาเป็นd.priceหรือd.price_2เมื่อMultiCurrencyเปิด
- ใช้วันที่ วันนี้ (ไม่ใช่วันที่เอกสาร) ตามคอมเมนต์ C# cs:10534 (
purchasePriceส่งg.today) - เลือกคอลัมน์ราคา:
sale_price2เมื่อVatType <> 0; ถ้า optionPurchaseZeroVatUsePrice1เปิด ใช้sale_price2เฉพาะVatType = 1(รวมใน) ส่วนที่เหลือใช้sale_price1 - ส่วนลดของแถวราคา (
discount) ถูกคืนในDefaultDiscountและpurchasePriceตั้งเป็นส่วนลดแถวเมื่อเจอ (trans_grid.go:533-535) - แถวของแถม (
IsPermium = 1) ได้ราคา 0 โดยไม่ค้น
5. ศูนย์ราคา (price_center.go)
loadPriceCenterอ่านerp_option.use_price_center,price_list_server,home_currencyครั้งเดียวต่อคำขอตาราง (trans_grid.go:156)- ถ้าเปิด:
gridCtx.salePriceไม่ค้นic_inventory_priceเลย ส่ง GET ไปยังprice_list_serverพารามิเตอร์item_code,unit_code,qty,cust_code(ถ้ามี),doc_date,sale_type,vat_type,branch(+use_currency,currency,home_currencyเมื่อหลายสกุลเงิน) และต่อส่วน?...ที่ตั้งไว้ในprice_list_serverท้ายคำขอ - ตรวจครั้งเดียวต่อคำขอว่าบริการใหม่ (
/SMLPriceCenter/pricelistservice) ตอบ 200 หรือไม่ ถ้าไม่ใช้พาธเก่า (/SMLJavaRESTService/service/pricelist/price) sale_typeที่ส่ง: 0 ถ้าจอไม่ใช่ flag 30/34/36/44; เป็น 2 เมื่อinquiryType0 หรือ 2 และเครดิตเกิน 7 วัน; นอกนั้น 1 (centerSaleType,:95).vat_typeแปลงเป็น 1/2/3 และ "ไม่กระทบภาษี" ส่งว่าง (centerVatType)- ตอบกลับต้องเป็น JSON
{price, price_guid}: ราคา 0 หรือไม่มี = ไม่พบ; พบแล้วType = Mode = 99,price_guidเก็บลงแถว (ยาวเกิน 55 ตัวอักษรถูกปฏิเสธเพราะคอลัมน์ic_trans_detail.price_guidยาว 55) (parseCenterPrice,:210) - ความปลอดภัย:
priceCenterClientไม่ใช้ proxy และตรวจ IP ปลายทางทุกการต่อ (รวมทุก redirect) ปฏิเสธ loopback / link-local / unspecified (priceCenterDialGuard,:47) ส่วน IP ในวง LAN อนุญาต; ตอบกลับอ่านได้ไม่เกิน 1 MB; งบเวลาต่อหนึ่งคำขอตาราง 30 วินาที (priceCenterBudget); ค่าที่ต่อ URL ผ่านurl.QueryEscape - สาขา POS ของศูนย์ราคา (
_findPricePOS) ยังไม่พอร์ต (ระบุไว้ในคอมเมนต์หัวไฟล์ รอลุงจืดตัดสิน) - ศูนย์ราคาล้มเหลว = error "ดึงราคาจากศูนย์ราคา ไม่สำเร็จ" (ไม่ถอยกลับไปราคาในฐานข้อมูล)
6. หน้าต่างดูราคา (POST /process/transgrid/priceinfo)
TransGridPriceInfo(price_info.go:66) รับitemCode,custCodeผลลัพธ์ 3 ชุด:normal(price_type 1,price_mode 1),group(price_type 2,price_mode 1กรองด้วยar_customer_detail.group_mainของลูกค้าถ้ามี),standard(price_type 1,price_mode 0) พร้อมcustGroup- แต่ละแถวมี
unitCode/unitName,salePrice1/2,fromQty/toQty,fromDate/toDate,saleType,status, ชื่อกลุ่ม และroworder(หน้าจอใช้ไฮไลต์แถวที่แถวสินค้าใช้อยู่ ตามคอมเมนต์ cs:78-90) - ตารางเรียงตาม
unit_code, roworder; ตัวเลขผ่านtrimDecimal(ไม่ใช้ float) - สิทธิ์
read
7. แก้ราคาบาร์โค้ดทีละรายการ (POST /process/stocktools/checkpricesave)
CheckPriceSave(check_price.go:48) รับbarcode,newPrice(+StockScopeของ provider/database/user) — พอร์ตจาก_checkPriceUserControl.cs:72-111- เงื่อนไข: ต้องมีบาร์โค้ด (แปลงตัวพิมพ์ใหญ่) และราคาใหม่ต้อง มากกว่า 1 (
priceFloor) ทศนิยมตามstockScales - ใน transaction เดียว: ล็อกแถว
ic_inventory_barcode(FOR UPDATE) อ่านราคาเดิม ->UPDATE ... SET priceทุกแถวที่upper(barcode)ตรงกัน -> เขียนตารางlogsผ่านwriteProcessLog(เมนู "ตรวจสอบราคา", รายละเอียดoldPrice/newPrice); ไม่พบบาร์โค้ด -> "ไม่พบบาร์โค้ด ..." - คืน
{barcode, oldPrice, newPrice}; สิทธิ์write
8. ปรับปรุงราคาหลายแถว (POST /process/modifyprice/{preview|apply})
เมนู 2.1.25 ตามคอมเมนต์ handler (docpicture_handler.go:111) พอร์ตจาก SMLERPIC/_icModifyPrice.cs หน้าเว็บอ่านไฟล์เข้าตาราง ส่งแถวมาเป็น rows (แต่ละแถวเป็น map ชื่อคอลัมน์ -> ข้อความ)
ชนิดราคา (kind) กำหนดที่ modifyPriceKinds (modify_price.go:32)
kind |
ตาราง | price_type / price_mode |
คอลัมน์ราคา |
|---|---|---|---|
standard_1/2/3, general_1/2/3 |
ic_inventory_price |
ประเภท 1 สินค้า / 2 กลุ่มลูกค้า / 3 ลูกค้า; standard = mode 0, general = mode 1 |
sale_price1, sale_price2 |
barcode |
ic_inventory_barcode |
- | price, price_member, price_2..4, price_member_2..4 |
formula |
ic_inventory_price_formula |
- | price_0..price_9 |
- คอลัมน์คีย์เพิ่มของชนิด 2 =
cust_group_1,cust_group_2; ชนิด 3 =cust_code
ขั้นตอน (modifyPrice, :84)
- ตรวจ
kind, ต้องมีแถว, ไม่เกินmodifyPriceMaxRows= 5000 แถว previewเปิด transaction แบบ read-only (ไม่เขียน);applyเปิด transaction เขียน พร้อมล็อกpg_advisory_xact_lock(hashtext('modifyprice'))ให้รันทีละงาน (เหตุผลในคอมเมนต์: ตารางic_inventory_priceไม่มีคีย์ unique ให้พึ่ง)planModifyPrice(:145): ตรวจรหัสสินค้ามีจริงในครั้งเดียว (existingItems, ยกเว้นชนิดbarcode) แล้วทีละแถวparseModifyPriceRow->run- ผลต่อแถว
ModifyPricePlan{row, action, oldPrice, reason}โดยaction=update|insert|skip; สรุปรวมupdated/inserted/skipped applyเขียนmaster_logs1 แถว (function_code 1, menuModifyPrice,data1= JSON ของ kind/zeroOption/rows/plans) (writeModifyPriceLog,:396)
กติกาต่อแถว (parseModifyPriceRow, :193)
- ฟิลด์ข้อความยาวเกิน 50 ตัวอักษร -> skip; ชนิดสินค้าต้องมี
ic_codeและunit_code; ชนิดbarcodeต้องมีbarcode sale_type/tax_typeต้องเป็นจำนวนเต็ม 0-32767;from_date/to_date(ปปปป-ดด-วว) มีผลเมื่อใส่ครบคู่;from_qty/to_qtyมีผลเมื่อทั้งคู่ไม่ใช่ 0 (ใช้กับic_inventory_priceเท่านั้น)- ช่องราคาว่าง = ไม่แก้; ใส่ 0 จะข้ามเว้นแต่ผู้ใช้ติ๊ก
zeroOption; ถ้าไม่มีช่องราคาที่ต้องแก้ -> skip matchค้นแถวเดิมด้วยคีย์ที่กรอก (สินค้า+หน่วย+ประเภท/โหมด และsale_type/คีย์ลูกค้า/tax_type/ช่วงวันที่/ช่วงจำนวนเฉพาะที่กรอก): เจอ ->updateแถวแรกตามroworder; ไม่เจอ ->insert(ชนิดbarcodeไม่เจอ = skip "ไม่พบบาร์โค้ด")- ค่าตั้งต้นตอน
insertของic_inventory_price: จำนวน 1..999999 และวันที่ 1 ม.ค.-31 ธ.ค. ของปีปัจจุบัน ถ้าไม่ได้ระบุ (insertValues,:359) -- โค้ดเก่า C# ทิ้งsale_type/tax_typeตอน insert แล้วเกิดแถวซ้ำทุกรอบ ฝั่ง Go เขียนค่านี้ลงด้วย
สิทธิ์: modifyprice/preview = read, modifyprice/apply = write (permission.go:32,42) ดู การตรวจสิทธิ์เมนูของ route process (Process route rights)
จุดที่ต้องระวัง
- ราคาขายเลือกราคาที่ "เฉพาะเจาะจงที่สุดก่อน" (ลูกค้า > กลุ่ม > ทั่วไป > มาตรฐาน) ไม่ใช่ราคาต่ำสุด; ขั้น 3 กับ 4 ได้
Typeเท่ากัน (3) แยกกันที่Mode(1 หรือ 0) ic_inventory_priceในmodify priceไม่มี unique key: ถ้าไม่กรอกคีย์เสริม (วันที่/จำนวน/ประเภทขาย) การจับคู่จะ update แถวแรกที่ตรงเงื่อนไขเท่านั้น- ศูนย์ราคาเปิดอยู่ = ราคา local ในตาราง
ic_inventory_priceไม่ถูกใช้ตอนกรอกเอกสาร (มีผลเฉพาะ flag ขาย/ที่เรียกsalePrice) price_list_serverที่ชี้ localhost จะถูกปฏิเสธ ต้องใช้ IP ของเครื่องในวง LAN
ที่มาในโค้ด
backend/internal/application/process/price_engine.go:18-priceResult;:49-65priceCond,custGroupCond;:67salePriceSteps;:79saleDiscountSteps;:103findSalePrice;:205priceFormulaRow;:237findPurchasePricebackend/internal/application/process/price_center.go:39-57-priceCenterClientและpriceCenterDialGuard;:71loadPriceCenter;:81gridCtx.salePrice;:95centerSaleType;:129priceCenterURL;:163find;:210parseCenterPricebackend/internal/application/process/price_info.go:50-priceInfoSQL;:66TransGridPriceInfobackend/internal/application/process/check_price.go:31-checkPriceArgs;:48CheckPriceSavebackend/internal/application/process/modify_price.go:22-modifyPriceMaxRows;:32modifyPriceKinds;:84modifyPrice;:145planModifyPrice;:193parseModifyPriceRow;:260match;:303run;:359insertValues;:396writeModifyPriceLogbackend/internal/application/process/trans_grid.go:96-97-salePriceFlags,purchasePriceFlags;:156โหลดศูนย์ราคา;:461priceQuery;:490-497จุดเรียก;:529purchasePrice;:540salePriceWarnings;:564applySalePricebackend/internal/application/process/grid_calc.go:232-calcFormulaPricebackend/internal/application/process/workflow.go:65-writeProcessLogbackend/internal/adapter/inbound/http/process/transgrid_handler.go:54- actionpriceinfo;stocktools_handler.go:168-checkpricesave;docpicture_handler.go:111-135-modifyprice;permission.go:29,32,42,44- สิทธิ์
ดูเพิ่ม
- ตารางสินค้า: คำนวณแถว สูตรลัด และ action ของ transgrid (Item grid calc)
- บาร์โค้ดสินค้า ส่วนลดบาร์โค้ด EAN13 และพิมพ์ป้าย (Barcode menus 2.1.7-2.1.17)
- ข้อมูลหลักแบบหัว-รายการ รหัสอัตโนมัติ และเลย์เอาต์รายการ (Master-detail, code auto-run, DataList)
- ข้อมูลสินค้าและข้อมูลสินค้าเพิ่มเติม (Item master & item detail)
- เครื่องมือสต็อก กลุ่ม route stocktools (Stock Tools)
- ตัวกระจายคำขอของ process (Process dispatcher)
- การตรวจสิทธิ์เมนูของ route process (Process route rights)
- ภาพรวมกลุ่ม process ของ Backend (Process overview)