ข้ามไปเนื้อหาหลัก

สินค้าและคลังสินค้า

ฐานข้อมูลสินค้าที่อยู่เบื้องหลังรายการในเอกสาร พร้อมรายการคลังสินค้าที่สต็อกเคลื่อนไหว

รายการในเอกสารจะเป็นข้อความอิสระก็ได้ แต่การผูกกับสินค้าเป็นสิ่งที่ทำให้สต็อกเคลื่อนไหว ต้นทุนไหลเข้าบัญชี และบัญชีรายได้ถูกต้อง response ของสินค้าห่อด้วย data

ค้นหาสินค้า

GET /products → แบ่งหน้า อยู่ใน data

พารามิเตอร์หมายเหตุ
searchค้นจากรหัส ชื่อ และบาร์โค้ด
categoryใช้ ชื่อ หมวด ไม่ใช่ id
productTypeinventory, nonInventory หรือ service
warehouseIdจำกัดตัวเลขสต็อกเฉพาะคลังเดียว
isActivetrue / false
isDeletedtrue เพื่อรวมแถวที่ลบแบบ soft delete
page / limitดูการแบ่งหน้า
bash
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

bash
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 }
    ]
  }'
ฟิลด์ชนิดจำเป็นหมายเหตุ
codestringใช่ไม่เกิน 50 ตัวอักษร ไม่ซ้ำในองค์กร
namestringใช่ไม่เกิน 255 ตัวอักษร
productTypestringใช่ดูด้านบน
vatType / purchaseVatTypestringใช่ดูด้านบน
descriptionstringไม่แสดงบนเอกสารขาย
purchaseDescriptionstringไม่แสดงบนเอกสารซื้อ
categorystringไม่ไม่เกิน 100 ตัวอักษร
unitstringไม่หน่วยหลัก เช่น ชิ้น
barcodestringไม่ไม่เกิน 100 ตัวอักษร
costPrice / salePricenumberไม่0 หรือมากกว่า
taxRatenumberไม่0–100
incomeAccount / expenseAccountstringไม่รหัสบัญชีจากผังบัญชี
reorderPointnumberไม่ใช้กับรายการสินค้าใกล้หมด
initialStockobjectไม่date (YYYY-MM-DD), quantity, unitCost — ยอดยกมา
altUnitsarrayไม่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 พร้อมภาพรวมสต็อก

json
{
  "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 ไม่แบ่งหน้า ไม่มีตัวกรอง

bash
curl -X GET "$ABSERP_API_URL/openapi/v1/warehouses" \
  -H "Authorization: Bearer $ABSERP_API_KEY"
json
{
  "success": true,
  "data": [
    {
      "id": 1,
      "name": "คลังหลัก",
      "code": "WH-01",
      "address": null,
      "contactName": null,
      "contactPhone": null,
      "isDefault": true,
      "isSystem": true,
      "createdAt": 1735689600
    }
  ]
}

หากระบบของคุณไม่มีเงื่อนไขอื่น ให้ใช้แถวที่ isDefault: true เป็น warehouseId ของเอกสารขาย ทุกองค์กรมีคลังเริ่มต้นหนึ่งคลังเสมอ

รหัสข้อผิดพลาด

สถานะรหัสสาเหตุ
400BadRequestขาด code/name/productType/vatType/purchaseVatType หรือค่า enum ไม่ถูกต้อง หรือ initialStock.date ผิดรูปแบบ
400NoDefaultWarehouseส่ง initialStock สำหรับสินค้าชนิด inventory โดยไม่มี warehouseId และองค์กรยังไม่ได้ตั้งค่าคลังเริ่มต้น
404NotFoundไม่พบ :id ในองค์กรนี้
409BadRequestcode ซ้ำในองค์กรนี้ (ตอนสร้าง) หรือการแก้ไขลบ/เปลี่ยนอัตราส่วนของ altUnits ที่มีเอกสารที่ยืนยันแล้วอ้างอิงอยู่

ทั้งสองกรณี 409 ยังคงมี errorCode: "BadRequest" เหมือนเดิม ให้แยกด้วย statusCode ไม่ใช่ errorCode เพื่อแยกจากความผิดพลาดในการตรวจสอบข้อมูลทั่วไป

ขั้นตอนถัดไป

อ่านต่อที่ รายการบัญชี