สินค้าและคลังสินค้า
ฐานข้อมูลสินค้าที่อยู่เบื้องหลังรายการในเอกสาร พร้อมรายการคลังสินค้าที่สต็อกเคลื่อนไหว
รายการในเอกสารจะเป็นข้อความอิสระก็ได้ แต่การผูกกับสินค้าเป็นสิ่งที่ทำให้สต็อกเคลื่อนไหว ต้นทุนไหลเข้าบัญชี และบัญชีรายได้ถูกต้อง response ของสินค้าห่อด้วย data
ค้นหาสินค้า
GET /products → แบ่งหน้า อยู่ใน data
| พารามิเตอร์ | หมายเหตุ |
|---|---|
search | ค้นจากรหัส ชื่อ และบาร์โค้ด |
category | ใช้ ชื่อ หมวด ไม่ใช่ id |
productType | inventory, nonInventory หรือ service |
warehouseId | จำกัดตัวเลขสต็อกเฉพาะคลังเดียว |
isActive | true / false |
isDeleted | true เพื่อรวมแถวที่ลบแบบ soft delete |
page / limit | ดูการแบ่งหน้า |
curl -X GET "$ABSERP_API_URL/openapi/v1/products?productType=inventory&isActive=true&limit=100" \
-H "Authorization: Bearer $ABSERP_API_KEY"
GET /products/barcode/{code}และGET /products/low-stockไม่เปิดให้ใช้ผ่าน Open API (ใช้ได้ในเว็บแอปเท่านั้น) ทั้งสองอย่างทำได้ด้วย endpoint รายการ เพราะsearchค้นบาร์โค้ดด้วย จึงใช้GET /products?search=8850000000017ที่จุดชำระเงินได้ และรายงานสินค้าใกล้หมดคือการเทียบstockQtyกับreorderPointจากสินค้าที่ดึงมาแล้ว
ชนิดของสินค้า
| ชนิด | ตัดสต็อก | ใช้กับ |
|---|---|---|
inventory | ใช่ | สินค้าที่นับจำนวนจริง |
nonInventory | ไม่ | สินค้าที่ไม่นับจำนวน เช่น วัสดุบรรจุภัณฑ์ |
service | ไม่ | ค่าแรง ค่าขนส่ง ค่าบริการรายเดือน |
มีเพียง inventory เท่านั้นที่ทำให้สต็อกและต้นทุนเฉลี่ยเคลื่อนไหว การตั้งค่านี้ผิดคือสาเหตุที่พบบ่อยของอาการ "ทำไมรายงานสต็อกว่างเปล่า"
ชนิด VAT
vatType (ฝั่งขาย) และ purchaseVatType (ฝั่งซื้อ) จำเป็นทั้งคู่ตอนสร้าง
| ค่า | ความหมาย |
|---|---|
vatExclusive | ราคายังไม่รวม VAT ระบบจะบวกเพิ่ม |
vatInclusive | ราคารวม VAT แล้ว |
zeroVat | อัตราศูนย์ |
noVat | ไม่อยู่ในระบบ VAT |
ค่าเหล่านี้บอกว่าราคาของสินค้าตั้งไว้แบบใด ส่วน VAT ที่ใช้กับเอกสารขายจริงมาจาก vatRate ของเอกสารนั้น
สร้างสินค้า
POST /products → 201 คืนสินค้าใน data เป็น JSON ต่างจากผู้ติดต่อ
ต้องระบุ code, name, productType, vatType, purchaseVatType
curl -X POST "$ABSERP_API_URL/openapi/v1/products" \
-H "Authorization: Bearer $ABSERP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"code": "WID-001",
"name": "Widget",
"description": "วิดเจ็ตมาตรฐาน สีน้ำเงิน",
"category": "ฮาร์ดแวร์",
"unit": "ชิ้น",
"barcode": "8850000000017",
"productType": "inventory",
"vatType": "vatExclusive",
"purchaseVatType": "vatExclusive",
"costPrice": 180,
"salePrice": 300,
"taxRate": 7,
"reorderPoint": 20,
"initialStock": {
"date": "2026-09-08",
"quantity": 50,
"unitCost": 180
},
"altUnits": [
{ "unit": "กล่อง", "ratio": 12, "salePrice": 3400, "purchasePrice": 2100 }
]
}'| ฟิลด์ | ชนิด | จำเป็น | หมายเหตุ |
|---|---|---|---|
code | string | ใช่ | ไม่เกิน 50 ตัวอักษร ไม่ซ้ำในองค์กร |
name | string | ใช่ | ไม่เกิน 255 ตัวอักษร |
productType | string | ใช่ | ดูด้านบน |
vatType / purchaseVatType | string | ใช่ | ดูด้านบน |
description | string | ไม่ | แสดงบนเอกสารขาย |
purchaseDescription | string | ไม่ | แสดงบนเอกสารซื้อ |
category | string | ไม่ | ไม่เกิน 100 ตัวอักษร |
unit | string | ไม่ | หน่วยหลัก เช่น ชิ้น |
barcode | string | ไม่ | ไม่เกิน 100 ตัวอักษร |
costPrice / salePrice | number | ไม่ | 0 หรือมากกว่า |
taxRate | number | ไม่ | 0–100 |
incomeAccount / expenseAccount | string | ไม่ | รหัสบัญชีจากผังบัญชี |
reorderPoint | number | ไม่ | ใช้กับรายการสินค้าใกล้หมด |
initialStock | object | ไม่ | date (YYYY-MM-DD), quantity, unitCost — ยอดยกมา |
altUnits | array | ไม่ | unit, ratio, salePrice, purchasePrice, barcode |
code ซ้ำจะได้ 409 — ไม่ใช่ 400 — พร้อม errorCode: "BadRequest" และ errorDescription: "product code already exists" ให้แยกกรณีนี้ด้วยสถานะ HTTP เพราะรหัสข้อผิดพลาดอย่างเดียวแยกไม่ออกจากความผิดพลาดในการตรวจสอบข้อมูลทั่วไป
หน่วยทางเลือก
ratio คือจำนวนหน่วยหลักที่อยู่ในหน่วยทางเลือกหนึ่งหน่วย ดังนั้น { "unit": "กล่อง", "ratio": 12 } บนสินค้าที่นับเป็น ชิ้น หมายถึงหนึ่งกล่องมี 12 ชิ้น หากต้องการขายเป็นกล่อง ให้ใส่ id ของหน่วยทางเลือกที่ระบบคืนมาไว้ใน productAltUnitId ของรายการในเอกสาร ABSERP จะแปลงการตัดสต็อกกลับเป็นหน่วยหลักให้เอง
อ่านสินค้ารายรายการ
GET /products/{id} → คืนสินค้าใน data พร้อมภาพรวมสต็อก
{
"success": true,
"data": {
"id": 5,
"code": "WID-001",
"name": "Widget",
"productType": "inventory",
"costPrice": 180,
"salePrice": 300,
"vatType": "vatExclusive",
"purchaseVatType": "vatExclusive",
"taxRate": 7,
"reorderPoint": 20,
"isActive": true,
"stockQty": 50,
"avgCost": 180,
"stockValue": 9000,
"stockBalances": [
{
"warehouseId": 1,
"warehouseName": "คลังหลัก",
"quantity": 50,
"averageCost": 180,
"totalValue": 9000
}
],
"altUnits": [
{ "id": 2, "unit": "กล่อง", "ratio": 12, "salePrice": 3400, "purchasePrice": 2100 }
],
"images": [],
"stockMovements": [],
"createdAt": 1735689600
}
}stockQty,avgCostและstockValueเป็นยอดรวมทั้งองค์กรstockBalancesแยกยอดตามคลัง ซึ่งเป็นข้อมูลที่ต้องใช้เมื่อหน้าร้านและคลังกลางเก็บสินค้าตัวเดียวกันavgCostเป็นต้นทุนเฉลี่ยถ่วงน้ำหนักที่ ABSERP ปรับให้เองเมื่อรับสินค้า ไม่ใช่ค่าที่คุณกำหนด
แก้ไขและลบ
PATCH /products/{id} รับฟิลด์เดียวกันทั้งหมดแบบไม่บังคับ และเพิ่ม isActive ส่วน altUnits มีพฤติกรรมพิเศษ คือถ้าไม่ส่งจะคงหน่วยเดิมไว้ ถ้าส่งอาร์เรย์เต็มจะแทนที่ทั้งชุด และการส่ง [] จะลบหน่วยทางเลือกทั้งหมด
DELETE /products/{id} ลบแบบ soft delete เอกสารที่อ้างอิงสินค้านั้นไม่ได้รับผลกระทบ และการดึงรายการด้วย isDeleted=true ยังเห็นสินค้านั้นอยู่
การกู้คืน (
PATCH /products/{id}/restore) ไม่เปิดให้ใช้ผ่าน Open API (ใช้ได้ในเว็บแอปเท่านั้น) หากต้องการสิ่งที่ย้อนกลับได้จากระบบที่เชื่อมต่อ ให้ใช้isActive: falseผ่านPATCH /products/{id}แทน
รูปภาพ
การอ่านจะเห็นรูปที่สินค้ามีอยู่ในคีย์ images เป็น { id, url, sortOrder }
การอัปโหลดและลบรูป (
POST /products/{id}/images,DELETE /products/{id}/images/{imageId}) ไม่เปิดให้ใช้ผ่าน Open API (ใช้ได้ในเว็บแอปเท่านั้น) เพราะเป็นการรับส่งไฟล์แบบ multipart ไม่ใช่ส่วนหนึ่งของสัญญาการเชื่อมต่อข้อมูล
คลังสินค้า
GET /warehouses → อาร์เรย์ธรรมดา ใน data ไม่แบ่งหน้า ไม่มีตัวกรอง
curl -X GET "$ABSERP_API_URL/openapi/v1/warehouses" \
-H "Authorization: Bearer $ABSERP_API_KEY"{
"success": true,
"data": [
{
"id": 1,
"name": "คลังหลัก",
"code": "WH-01",
"address": null,
"contactName": null,
"contactPhone": null,
"isDefault": true,
"isSystem": true,
"createdAt": 1735689600
}
]
}หากระบบของคุณไม่มีเงื่อนไขอื่น ให้ใช้แถวที่ isDefault: true เป็น warehouseId ของเอกสารขาย ทุกองค์กรมีคลังเริ่มต้นหนึ่งคลังเสมอ
รหัสข้อผิดพลาด
| สถานะ | รหัส | สาเหตุ |
|---|---|---|
| 400 | BadRequest | ขาด code/name/productType/vatType/purchaseVatType หรือค่า enum ไม่ถูกต้อง หรือ initialStock.date ผิดรูปแบบ |
| 400 | NoDefaultWarehouse | ส่ง initialStock สำหรับสินค้าชนิด inventory โดยไม่มี warehouseId และองค์กรยังไม่ได้ตั้งค่าคลังเริ่มต้น |
| 404 | NotFound | ไม่พบ :id ในองค์กรนี้ |
| 409 | BadRequest | code ซ้ำในองค์กรนี้ (ตอนสร้าง) หรือการแก้ไขลบ/เปลี่ยนอัตราส่วนของ altUnits ที่มีเอกสารที่ยืนยันแล้วอ้างอิงอยู่ |
ทั้งสองกรณี 409 ยังคงมี errorCode: "BadRequest" เหมือนเดิม ให้แยกด้วย statusCode ไม่ใช่ errorCode เพื่อแยกจากความผิดพลาดในการตรวจสอบข้อมูลทั่วไป
ขั้นตอนถัดไป
อ่านต่อที่ รายการบัญชี