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

ขั้นตอนฝากเงิน

หน้านี้สรุป flow เมนู ฝากเงิน ตั้งแต่ list, detail/create form, แหล่งข้อมูลของแต่ละ input, query API, payload submit, และ action ที่เกี่ยวข้อง

ขอบเขต

เอกสารอ้างอิงจาก implementation หลัก:

  • apps/mobile/src/screens/depositStack/depositListScreen/index.tsx
  • apps/mobile/src/screens/depositStack/depositDetailScreen/index.tsx
  • apps/mobile/src/services/deposit.ts
  • apps/mobile/src/services/common.ts
  • apps/mobile/src/services/invoices.ts
  • apps/pos-terminal/src/screens/HomeScreen/DepositDetailForm.tsx
  • apps/pos-terminal/src/services/deposit.ts
  • apps/pos-terminal/src/services/splitMenuData.ts

Mobile และ POS Terminal ใช้ contract ของ deposit เหมือนกันเกือบทั้งหมด ต่างกันหลัก ๆ คือ POS Terminal ใช้ split-pane form และ invoice picker ผ่าน getInvoiceSplitList() ก่อน normalize เป็น SplitListItem

สรุปเมนู

เมนู ฝากเงิน ใช้สำหรับสร้างรายการนำเงินสดไปฝากเข้าบัญชีธนาคาร โดยข้อมูลหลักประกอบด้วย:

  • สาขา/คลังที่รับผิดชอบ
  • พนักงานผู้รับผิดชอบ
  • บริษัท
  • วันที่ส่ง
  • บัญชีเงินสดต้นทาง
  • บัญชีธนาคารปลายทาง
  • จำนวนเงิน
  • หมายเหตุ หรือรายการใบแจ้งหนี้ที่เลือก

รายการฝากเงินมี state ที่แสดงใน list:

เงื่อนไขข้อมูลความหมายใน UI
status === "DELETED"รายการถูกลบ
RefKey == nullส่งเข้า B1 ไม่สำเร็จ / รอส่งใหม่
RefKey != nullส่งสำเร็จ

ทางเข้า

จาก list ปกติ:

navigation.navigate('DepositDetail', {
depositId: null,
})

จากการกดรายการเดิม:

navigation.navigate('DepositDetail', {
depositId: item.depositId,
})

จาก transport flow จะเปิด create form พร้อม context เพิ่ม เช่น transportId, transportPayload, amount, driverName, depositDate, companyCode และข้อมูลสาขา/คลัง เพื่อให้สร้างฝากเงินแล้วกลับไป update transport ต่อ

API รายการฝากเงิน

List โหลดผ่าน getDeposits()

GET {POS_SERVER}{PATH}/deposits

Query:

Queryค่า
pagepage ปัจจุบัน เริ่มที่ 1
pageLimit200
sortDESC
sortByCreatedAt
searchkeyword จากช่องค้นหา
whsselectedBranch.whsGrpCode จาก getBranch()
hasRetry"true" เมื่อเลือก filter รอส่งใหม่, ไม่เช่นนั้นส่ง empty string

ใช้ API นี้ในกรณี:

  • เข้า list ครั้งแรก
  • refresh
  • search
  • load more
  • toggle filter ทั้งหมด / รอส่งใหม่
  • reload หลัง delete หรือ retry

การเริ่มต้นหน้าสร้างและรายละเอียด

เมื่อเข้า DepositDetail จะโหลดข้อมูลตั้งต้นพร้อมกัน:

getBranch()
getUser()
getCompanies()

getCompanies() ยิง:

GET {MST_API}{PATH}/companies

โหมดรายละเอียด

ถ้า depositId != null จะโหลด detail:

GET {POS_SERVER}{PATH}/deposits/{depositId}

แล้ว fill form จาก response:

FieldSource
branchIddetail.branchId
branchName`detail.branchName
whGrpCodedetail.whGrpCode
whGrpNamedetail.whGrpName
userCodedetail.userCode
userResponsible`detail.userResponsible
companyCodedetail.companyCode
amountdetail.amount
depositDatedetail.depositDate
cashAccountdetail.cashAccount
cashNamedetail.cashName
bankNamedetail.bankName
bankAccountdetail.bankAccount
notesdetail.notes
depositNodetail.depositNo
createdAtdetail.createdAt

หลัง fill detail จะโหลดบัญชีเงินสด/บัญชีธนาคารด้วย companyCode และ detail.whGrpCode

โหมดสร้างใหม่

ถ้า depositId == null จะสร้าง default form จาก route params ก่อน ถ้าไม่มีจึง fallback ไป branch/user:

FieldDefault
companyCode`route.params.companyCode
companyName`route.params.companyName
whGrpCode`route.params.whGrpCode
whGrpName`route.params.whGrpName
branchId`route.params.branchId
branchNamewhGrpName
userCode`route.params.userCode
userResponsible`route.params.driverName
amount`route.params.amount
depositDate`route.params.depositDate
notes`route.params.notes

บัญชีถูกโหลดด้วย accountWhGrpCode:

accountWhGrpCode = branch.isMain === false
? branch.BranchCode
: whGrpCode

แหล่งข้อมูลสำหรับกรอกฟอร์ม

บริษัท

แหล่งข้อมูล:

GET {MST_API}{PATH}/companies

Search ใน modal เป็น client-side filter จาก companyCode และ companyName

เมื่อเลือกบริษัท:

companyCode = item.companyCode
companyName = item.companyName
cashAccount = ''
cashName = ''
bankName = ''
bankAccount = ''

จากนั้น reload บัญชีด้วย loadBankAndCashOptions(companyCode, form.whGrpCode)

บัญชีเงินสด

แหล่งข้อมูลผ่าน getCashAccounts(whGrpCode):

GET {MST_API}{PATH}/banks
?whGrpCode={whGrpCode}
&acctType=CASH

หลังได้ response จะ dedupe ด้วย acctCode

เมื่อเลือก:

cashAccount = item.acctCode || item.AcctCode
cashName = item.acctName || item.AcctName

Search ใน modal เป็น client-side filter จาก account code และ account name

บัญชีธนาคาร

แหล่งข้อมูลผ่าน getBankAccounts({ companyCode, whGrpCode }):

GET {MST_API}{PATH}/banks
?whGrpCode={whGrpCode}
&acctType=BANK
&companyCode={companyCode}
&ignoreBank={branch.isMain ? 1 : 0}

เมื่อเลือก:

bankAccount = item.acctCode || item.AcctCode
bankName = item.acctName || item.AcctName

Search ใน modal เป็น client-side filter จาก account code และ account name

วันที่ส่ง

Default:

depositDate = dayjs().format('YYYY-MM-DD')

ถ้าเปิด ส่งสลิปย้อนหลัง:

depositDate = dayjs().subtract(1, 'day').format('YYYY-MM-DD')

ถ้าปิดกลับ:

depositDate = dayjs().format('YYYY-MM-DD')

Toggle นี้แสดงเฉพาะ create mode และไม่ใช่ transport flow

จำนวนเงิน

มี 2 mode:

Modeที่มาของจำนวนเงินแก้ไขได้หรือไม่
ไม่เลือกใบแจ้งหนี้ผู้ใช้กรอก form.amount หรือ route param จาก transportแก้ได้เฉพาะ create mode
เลือกใบแจ้งหนี้sum ยอดสุทธิของ invoice ที่เลือกแก้ไม่ได้

สูตรยอดสุทธิ invoice:

Math.floor(Number(invoice.amount))
- Number(invoice.discount)
- Number(invoice.useCreditNote)

ใบแจ้งหนี้

ปุ่มเลือกใบแจ้งหนี้แสดงเมื่อ:

isCreateMode && !isTransportFlow && deptCode !== 14 && deptCode !== 30

Mobile โหลดผ่าน getInvoices():

GET {POS_SERVER}{PATH}/invoices

Query:

Queryค่า
pagepage ปัจจุบัน
pageLimitPAGESIZE
branchIdselectedBranch.branchId
sortDESC
sortByinvoiceId
searchkeyword ใน invoice modal
shippingTypeempty string
invoiceStatusempty string
toB1empty string
inStockempty string
withOutCNempty string
dateFromวันนี้ย้อนหลัง 300 วัน เวลา 00:00:00
dateToวันนี้ เวลา 23:59:59
whGrpCodeselectedBranch.whsGrpCode

POS Terminal ใช้ getInvoiceSplitList({ tab: "all", dateFrom, dateTo }) ซึ่งสุดท้ายเรียก getInvoices() ด้วย filter ชุดเดียวกัน แล้ว normalize เป็น split-list item

เมื่อเลือก invoice:

  • toggle เลือก/ยกเลิกด้วย invoiceId || invoiceStorageId
  • amount ถูกคำนวณจาก invoice ที่เลือกทั้งหมด
  • notes ถูกสร้างจาก invoiceNo คั่นด้วย ,
  • ช่องจำนวนเงินและหมายเหตุจะเป็น read-only

หมายเหตุ

ถ้าไม่เลือก invoice:

notes = form.notes.trim()

ถ้าเลือก invoice:

notes = selectedInvoices
.map(invoice => invoice.invoiceNo)
.filter(Boolean)
.join(', ')

การตรวจสอบก่อนบันทึก

ก่อน submit ต้องผ่านเงื่อนไข:

companyCode ต้องมี
bankAccount ต้องมี
cashAccount ต้องมี

ถ้าไม่ได้เลือก invoice ต้องเช็คเพิ่ม:

amount ต้องเป็นตัวเลขและมากกว่า 0
notes ต้องไม่ว่าง

เมื่อผ่าน validation จะขึ้น confirm dialog:

  • ถ้าเลือก invoice: ฝากเงินจำนวนหลายรายการ ไปที่บัญชีเงินสด... บัญชีธนาคาร...
  • ถ้าไม่เลือก invoice: ฝากเงินจำนวน {amount} ไปที่บัญชีเงินสด... บัญชีธนาคาร...

กดยืนยันแล้วจึง submit จริง

Payload ที่ส่งบันทึก

Payload ถูกสร้างด้วย buildDepositPayload(form, selectedInvoices)

{
"branchId": 1,
"branchName": "คลังหลัก",
"whGrpCode": "01",
"whGrpName": "คลังหลัก",
"userCode": "012345",
"userResponsible": "ชื่อ พนักงาน",
"companyCode": "BFP",
"amount": 12000,
"depositDate": "2026-07-04",
"cashAccount": "111100",
"cashName": "เงินสดหน้าร้าน",
"bankName": "KBank",
"bankAccount": "111200",
"notes": "INV-001, INV-002"
}

Important normalization:

branchId = Number(form.branchId)
branchName = form.whGrpName || form.branchName
companyCode = form.companyCode.replace('DB', '')
amount = selectedInvoices.length > 0
? sum(invoice net amount)
: Math.floor(Number(form.amount))
notes = selectedInvoices.length > 0
? invoiceNo list
: form.notes.trim()

Submit API:

POST {POS_SERVER}{PATH}/deposits
Content-Type: application/json; charset=utf-8

Body คือ DepositPayload

หลังบันทึก

ขั้นตอนฝากเงินปกติ

เมื่อ create สำเร็จ:

navigation.replace('DepositList')

แล้ว list จะ reload เมื่อ focus

ขั้นตอนฝากเงินจากขนส่ง

เมื่อ create deposit สำเร็จและมี transportId + transportPayload:

updateTransport({
transportId,
payload: transportPayload,
})

Mobile จะ navigation.goBack() กลับไปหน้า transport detail
POS Terminal จะเรียก onTransportSaved, onCreated, และ onRefreshList

Action อื่น

ลบรายการฝากเงิน

แสดง action ลบเมื่อ user อยู่ใน department ที่อนุญาต:

-2, 1, 2, 3, 4, 9

API:

DELETE {POS_SERVER}{PATH}/deposits/{depositId}

หลังลบ reload list หน้าแรก

ส่งรายการฝากเงินอีกครั้ง

แสดง action retry เมื่อ item.RefKey == null

กด retry ได้เฉพาะ:

  • เจ้าของรายการ: item.userCode === currentUser.userCode
  • หรือ user override: 012265

API:

POST {POS_SERVER}{PATH}/deposits/{depositId}/retry

Body เป็น {}
หลัง retry reload list หน้าแรก

สิทธิ์

ปุ่มสร้าง

Mobile list แสดงปุ่มสร้างเมื่อ:

  • user มี full access ของ non-main warehouse
  • หรือ department อยู่ในชุด:
-2, 1, 2, 3, 4, 7, 29, 8, 9, 13, 14, 30, 16

ตัวเลือกใบแจ้งหนี้

ถึงแม้สร้าง deposit ได้ แต่ invoice picker จะไม่แสดงสำหรับ department:

14, 30

และไม่แสดงใน transport flow

สรุป API

PurposeMethodEndpoint
Deposit listGET{POS_SERVER}{PATH}/deposits?page=...&pageLimit=200&sort=DESC&sortBy=CreatedAt&search=...&whs=...&hasRetry=...
Deposit detailGET{POS_SERVER}{PATH}/deposits/{depositId}
Create depositPOST{POS_SERVER}{PATH}/deposits
Update deposit service existsPATCH{POS_SERVER}{PATH}/deposits/{depositId}
Delete depositDELETE{POS_SERVER}{PATH}/deposits/{depositId}
Retry depositPOST{POS_SERVER}{PATH}/deposits/{depositId}/retry
CompaniesGET{MST_API}{PATH}/companies
Cash accountsGET{MST_API}{PATH}/banks?whGrpCode=...&acctType=CASH
Bank accountsGET{MST_API}{PATH}/banks?whGrpCode=...&acctType=BANK&companyCode=...&ignoreBank=...
Invoice pickerGET{POS_SERVER}{PATH}/invoices?...dateFrom=...&dateTo=...&whGrpCode=...
Transport callbacktransport serviceupdateTransport({ transportId, payload })