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

เอกสารขาย

สร้าง ค้นหา อ่าน แก้ไข แปลงเอกสาร และเปลี่ยนสถานะใบเสนอราคา ใบกำกับภาษี ใบเสร็จรับเงิน และเอกสารขายอื่น ๆ

เอกสารขายเป็นส่วนที่ใช้งานหนักที่สุดของ API นี้ เป็นจุดที่ระบบ POS แปลงยอดขายของวันเป็นใบกำกับภาษี หรือร้านค้าออนไลน์ออกใบเสร็จรับเงินเมื่อลูกค้าชำระเงิน

เอกสารขายทั้งเจ็ดชนิดใช้ endpoint ชุดเดียวกันและ payload รูปแบบเดียวกัน โดยใช้ documentType เป็นตัวระบุว่ากำลังสร้างเอกสารชนิดใด

รหัสเอกสาร
QTใบเสนอราคา
SOใบสั่งขาย
IVใบแจ้งหนี้ / ใบกำกับภาษี
REใบเสร็จรับเงิน
CSขายสด
CNใบลดหนี้
DNใบเพิ่มหนี้

เอกสารมัดจำ (DP) มีอยู่ใน ABSERP แต่สร้างผ่านขั้นตอนเงินมัดจำในเว็บแอปเท่านั้น ไม่ได้สร้างผ่าน endpoint นี้

สร้างเอกสาร

POST /sales/documents → 201 คืนเอกสาร ตรง ๆ (ไม่ห่อด้วย data)

ต้องระบุ documentType, contactId, issueDate และรายการสินค้าอย่างน้อยหนึ่งรายการ แต่ละรายการต้องมี name และ quantity มากกว่าศูนย์

bash
curl -X POST "$ABSERP_API_URL/openapi/v1/sales/documents" \
  -H "Authorization: Bearer $ABSERP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "documentType": "IV",
    "contactId": 12,
    "issueDate": "2026-09-08",
    "dueDate": "2026-10-08",
    "vatRate": 7,
    "currency": "THB",
    "warehouseId": 1,
    "note": "คำสั่งซื้อ #A-1099 จากร้านค้าออนไลน์",
    "items": [
      {
        "productId": 5,
        "name": "Widget",
        "unit": "ชิ้น",
        "quantity": 10,
        "unitPrice": 300,
        "discountPercent": 0,
        "sortOrder": 0
      }
    ]
  }'

ฟิลด์ระดับเอกสาร

ฟิลด์ชนิดจำเป็นหมายเหตุ
documentTypestringใช่หนึ่งในเจ็ดรหัสด้านบน
contactIdintegerใช่ลูกค้า ดูผู้ติดต่อ
issueDatestringใช่YYYY-MM-DD
dueDatestringไม่YYYY-MM-DD
vatRatenumberไม่เปอร์เซ็นต์ เช่น 7 เว้นไว้หรือใส่ 0 สำหรับเอกสารที่ไม่มี VAT
discountAmountnumberไม่ส่วนลดระดับเอกสาร เพิ่มจากส่วนลดรายบรรทัด
currencystringไม่รหัส 3 ตัวอักษร ถ้าไม่ส่งจะใช้สกุลเงินขององค์กร (THB)
notestringไม่แสดงบนเอกสาร
warehouseIdintegerไม่คลังที่ตัดสต็อก
project / projectIdstring / integerไม่ระบุโครงการ (ไม่บังคับ)
whtRatenumberไม่เปอร์เซ็นต์ภาษีหัก ณ ที่จ่าย 0–100
whtAmountnumberไม่จำนวนเงินภาษีหัก ณ ที่จ่าย
bankIdintegerไม่ใช้กับ RE และ CS เท่านั้น — บัญชีที่เงินเข้า ส่งกับชนิดอื่นจะได้ 400
itemsarrayใช่อย่างน้อยหนึ่งรายการ

ฟิลด์รายบรรทัด

ฟิลด์ชนิดจำเป็นหมายเหตุ
namestringใช่ชื่อรายการที่พิมพ์บนเอกสาร
quantitynumberใช่ต้องมากกว่าศูนย์
productIdintegerไม่เชื่อมบรรทัดกับสินค้า เพื่อให้สต็อกและต้นทุนเคลื่อนไหว
productAltUnitIdintegerไม่ขายด้วยหน่วยทางเลือก เช่น กล่องละ 12 ชิ้น
descriptionstringไม่คำอธิบายบรรทัดที่สอง
unitstringไม่ข้อความอิสระ เช่น ชิ้น
unitPricenumberไม่ค่าเริ่มต้น 0
discountPercentnumberไม่0–100
discountAmountnumberไม่ส่วนลดเป็นจำนวนเงินของบรรทัดนี้
remarkstringไม่หมายเหตุภายใน
sortOrderintegerไม่ลำดับการพิมพ์

บรรทัดที่ไม่ระบุ productId คือบรรทัดข้อความอิสระ จะแสดงบนเอกสารและรวมในยอด แต่ไม่ตัดสต็อก เหมาะกับค่าบริการหรือค่าขนส่ง

ข้อมูลที่คืนกลับ

json
{
  "id": 41,
  "documentNumber": "IV-2026-0001",
  "documentType": "IV",
  "contactId": 12,
  "contactName": "บริษัท เอซีเอ็มอี จำกัด",
  "contactTaxId": "0105551234567",
  "status": "draft",
  "canConvert": true,
  "issueDate": "2026-09-08",
  "dueDate": "2026-10-08",
  "subtotalAmount": 3000,
  "discountAmount": 0,
  "totalAmount": 3000,
  "vatRate": 7,
  "vatAmount": 210,
  "grandTotal": 3210,
  "paidAmount": 0,
  "outstandingAmount": 3210,
  "currency": "THB",
  "warehouseId": 1,
  "whtRate": 0,
  "whtAmount": 0,
  "items": [
    {
      "id": 88,
      "productId": 5,
      "name": "Widget",
      "unit": "ชิ้น",
      "quantity": 10,
      "unitPrice": 300,
      "discountPercent": 0,
      "discountAmount": 0,
      "totalPrice": 3000,
      "sortOrder": 0
    }
  ],
  "createdAt": 1757289600
}

สังเกตว่าอะไรที่เซิร์ฟเวอร์เป็นเจ้าของและคุณไม่ต้องส่ง

  • documentNumber มาจากกฎการออกเลขที่ขององค์กรสำหรับเอกสารชนิดนั้น (คำนำหน้า จำนวนหลัก การรีเซ็ตรายปี/รายเดือน) คุณกำหนดเองไม่ได้
  • ยอดทั้งหมด — subtotalAmount, vatAmount, grandTotal, outstandingAmount — คำนวณจากรายการ ให้ส่งราคาและจำนวน แล้วอ่านยอดกลับมา ไม่ต้องคำนวณเอง
  • status เริ่มที่ draft เสมอ

ค้นหาเอกสาร

GET /sales/documents → แบ่งหน้า คืน ตรง ๆ

พารามิเตอร์หมายเหตุ
typeรหัสชนิดเอกสารหนึ่งค่า
statusกรองตามสถานะ
contactIdรหัสลูกค้า
fromDate / toDateYYYY-MM-DD รวมวันปลายทั้งสองข้าง อ้างอิงวันที่เอกสาร
searchค้นจากเลขที่เอกสารและชื่อผู้ติดต่อ
refDocumentIdเอกสารที่อ้างอิงเอกสารนี้
sourceDocumentIdเอกสารที่แปลงมาจากเอกสารนี้
consolidatedDocumentIdเอกสารที่ถูกรวมเข้าเอกสารนี้
page / limitดูการแบ่งหน้า
bash
curl -X GET "$ABSERP_API_URL/openapi/v1/sales/documents?type=IV&status=sent&fromDate=2026-09-01&toDate=2026-09-30&limit=100" \
  -H "Authorization: Bearer $ABSERP_API_KEY"
json
{
  "items": [ { "id": 41, "documentNumber": "IV-2026-0001", "grandTotal": 3210 } ],
  "pagination": {
    "previousPage": null,
    "currentPage": 1,
    "nextPage": null,
    "totalPages": 1,
    "totalRows": 1
  }
}

แถวในรายการมีฟิลด์เหมือนกับการอ่านรายละเอียด รวมถึง items ควรใช้ตัวกรองขอเฉพาะข้อมูลที่ต้องการ ดีกว่าการดึงมาทั้งหมดแล้วกรองฝั่งไคลเอนต์ เพราะประหยัดโควตากว่ามาก

อ่านเอกสารรายฉบับ

GET /sales/documents/{id} → คืนเอกสาร ตรง ๆ

404 หมายถึงไม่พบเอกสารนั้น ในองค์กรนี้ ซึ่งเป็นคำตอบเดียวกับที่คุณจะได้เมื่อถาม id ของคนอื่น โดยเจตนา

แก้ไขเอกสาร

PATCH /sales/documents/{id} → 200 { "success": true, "message": "..." }

แก้ได้เฉพาะเอกสารสถานะ draft เมื่อเอกสารเดินหน้าไปแล้ว — ส่งแล้ว ชำระแล้ว หรือแปลงแล้ว — API จะตอบ 400 พร้อม errorCode: "InvalidStatusTransition" การแก้เอกสารที่ออกไปแล้วให้ทำตามหลักบัญชี คือออกใบลดหนี้ ไม่ใช่แก้ประวัติ

เปลี่ยนสถานะ

PATCH /sales/documents/{id}/status → 200 { "success": true, "message": "Status updated" }

bash
curl -X PATCH "$ABSERP_API_URL/openapi/v1/sales/documents/41/status" \
  -H "Authorization: Bearer $ABSERP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "status": "sent", "remark": "ส่งอีเมลให้ลูกค้าแล้ว" }'
ฟิลด์ชนิดหมายเหตุ
statusstringสถานะเป้าหมาย ต้องเป็นการเปลี่ยนที่อนุญาตจากสถานะปัจจุบัน
remarkstringไม่บังคับ เก็บไว้ในประวัติสถานะ
confirmNegativeStockbooleanไม่บังคับ ใส่ true เพื่อยอมรับการตัดสต็อกที่ทำให้ยอดติดลบ

เอกสารแต่ละชนิดมีชุดการเปลี่ยนสถานะที่อนุญาตต่างกัน ดูข้อมูลอ้างอิงชนิดเอกสาร การเปลี่ยนที่ไม่อนุญาตจะได้ 400 พร้อม errorCode: "InvalidStatusTransition" และไม่มีอะไรเปลี่ยน

GET /sales/documents/{id}/timeline ไม่เปิดให้ใช้ผ่าน Open API (ใช้ได้ในเว็บแอปเท่านั้น) สถานะปัจจุบันของเอกสารมีอยู่ในทุกการอ่าน และประวัติสถานะทั้งหมดดูได้ใน ABSERP หากต้องการร่องรอยการตรวจสอบฝั่งคุณ ให้บันทึกการเปลี่ยนสถานะที่คุณสั่งผ่าน PATCH /sales/documents/{id}/status ไว้เอง

แปลงเอกสาร

POST /sales/convert → 201 คืนเอกสาร ฉบับใหม่ ตรง ๆ

bash
curl -X POST "$ABSERP_API_URL/openapi/v1/sales/convert" \
  -H "Authorization: Bearer $ABSERP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "fromId": 41, "toType": "RE" }'

toType รับค่า SO, IV, CS, RE, CN หรือ DN ระบบจะคัดลอกรายการ ลูกค้า และยอดเงินไปให้ เอกสารใหม่ได้เลขที่ของตัวเอง และเอกสารทั้งสองยังเชื่อมโยงกัน คุณค้นเอกสารลูกได้ภายหลังด้วย sourceDocumentId

นี่คือเส้นทางปกติของงานขาย QT → SO → IV → RE ควรใช้การแปลงเอกสารแทนการสร้างใหม่ เพื่อให้สายเอกสารตรวจสอบย้อนหลังได้และไม่นับยอดซ้ำ

ข้อมูลสำหรับพิมพ์เอกสาร

GET /sales/documents/{id}/print-data คืนข้อมูลเป็น JSON ไม่ใช่ไฟล์ PDF ประกอบด้วยตัวเอกสารเอง (โครงสร้างเดียวกับการอ่าน GET /sales/documents/{id}) รวมกับ organizationName และ organizationLogo สำหรับใช้จัดหน้าเอกสาร

bash
curl -X GET "$ABSERP_API_URL/openapi/v1/sales/documents/41/print-data" \
  -H "Authorization: Bearer $ABSERP_API_KEY"

การจัดหน้าเป็นไฟล์ PDF จริงเป็นหน้าที่ของฝั่งไคลเอนต์ — เรียก endpoint นี้เพื่อเอาข้อมูลไปเรนเดอร์เป็น PDF เอง (เช่นด้วย jspdf, @react-pdf/renderer หรือไลบรารีที่เทียบเท่าฝั่งของคุณ) ไม่มี endpoint ใดใน Open API ที่คืนไฟล์ PDF โดยตรง

ลบเอกสาร

DELETE /sales/documents/{id} → 204 No Content ไม่มี body

การลบมีไว้สำหรับความผิดพลาดที่พบทันที เช่น เอกสารร่างที่ยังไม่มีใครเห็น เอกสารที่ออกไปแล้วควรยกเลิก (void) หรือออกใบลดหนี้แทน เพื่อให้เลขที่เอกสารและบัญชียังสมบูรณ์

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

นอกจากรหัสทั่วไปในข้อผิดพลาดและการแบ่งหน้า รหัสต่อไปนี้เฉพาะกับเอกสารขาย

สถานะรหัสสาเหตุ
404SalesDocumentNotFoundไม่พบ :id ในองค์กรนี้
404CustomerNotFoundไม่พบ contactId ในองค์กรนี้
400BankNotFoundไม่พบ bankId ในองค์กรนี้
400BankNotAllowedForDocumentTypeส่ง bankId กับชนิดเอกสารที่ไม่ใช่ RE/CS
400InvalidStatusTransitionการเปลี่ยนสถานะใน PATCH .../status ไม่ใช่การเปลี่ยนที่อนุญาตจากสถานะปัจจุบัน ดูข้อมูลอ้างอิงชนิดเอกสาร
400SalesDocumentCannotConvertชนิดหรือสถานะของเอกสารต้นทางไม่อนุญาตให้ POST /sales/convert
400SalesDocumentInvalidConversionTargettoType ไม่ใช่ปลายทางที่อนุญาตสำหรับชนิดของเอกสารต้นทาง
400SalesDocumentHasOutstandingDepositsการเปลี่ยนสถานะจะทำให้มีเงินมัดจำค้างอยู่โดยไม่มีที่ให้นำไปใช้
400SalesDocumentHasReservedDepositsแก้ไขเอกสารร่างที่มีการแปลงเอกสารจองเงินมัดจำไว้แล้ว ให้ลบเอกสารนี้แล้วแปลงเอกสารต้นทางใหม่แทน
400NoDefaultWarehouseเอกสารที่ตัดสต็อกไม่มี warehouseId และองค์กรยังไม่ได้ตั้งค่าคลังเริ่มต้น
409SalesDocumentAlreadyConsolidatedเอกสารนี้ถูกรวมเข้ากับเอกสารอื่นไปแล้ว
422NegativeStockBlockedการยืนยันจะทำให้สต็อกติดลบ และนโยบายองค์กรบล็อกกรณีนี้
422NegativeStockWarnConfirmRequiredเหมือนกัน แต่องค์กรตั้งไว้แค่เตือน ให้ส่งซ้ำพร้อม confirmNegativeStock: true

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

อ่านต่อที่ เอกสารซื้อ