Skip to main content

Borrow Fish

Borrow Fish manages fish/product borrowing before the normal sales-order flow. It is close to Sales Orders because it starts with a customer, branch/warehouse context, product lines, quantities, and later can become a sales order. The important differences are borrow type, pickup/license-plate rules for non-main SPEC borrows, expected purchase quantity, acceptance/stock-cut workflow, retry handling, and conversion to sales order.

App Entry Points​

AppTarget
MobileBorrowList -> BorrowDetail
POS TerminalborrowFish -> BorrowDetailForm in HomeScreen; StorefrontPosStack borrow list/workspace

Borrow Types​

TypeLabel
SPECCustomer sample/inspection
MARKETMarket visit
VIDEOVideo shooting
SEMINARSeminar
DESTROYDestroy
LABResearch/lab

The type must be selected before products can be added. For a non-main branch, changing a borrow to SPEC is blocked when the document already has product items; users must remove items first because SPEC adds required pickup and customer-license-plate rules.

Lifecycle​

API statusUI labelMeaning
NEWสร้างใหม่Draft; editing follows the owner guard.
WAITINGรอดำเนินการWaiting for processing.
ACCEPTEDรับงานแล้วWork accepted; action guards also check acceptedAt.
DONEเสร็จสิ้นCompleted; a failed sub-borrow may still expose retry.
PARTIAL_CONVERTEDแปลงและรอคืนSome items became a sales order; items with remaining lots can be returned.
CONVERTEDแปลงเป็นใบสั่งขายแล้วConverted to a sales order.
CANCELEDยกเลิกCanceled.

The app refreshes status from the backend after each successful action. Conversion requires acceptance and the permission rules below; it does not require DONE. After /return-items, the app displays the refreshed backend status rather than assigning a final status locally.

End-To-End Workflow​

Header Rules​

TopicBehavior
CustomerBorrow starts from a selected customer and stores customer code/name context.
Branch and warehouse groupPayload carries branchId, whGrpCode, and whGrpName from selected branch/document context.
Borrow typeRequired before adding products or confirming.
NotesStored in notes and can be updated while the draft is editable.
dateOfPickupSent only for SPEC; non-SPEC payload clears it to null.
Customer license plateRequired before adding items for non-main SPEC; kept in customerLicensePlate.
Pickup date edit lockFor non-main SPEC, pickup date cannot be changed after items exist.
Past pickup dateMobile blocks pickup dates before the current day.

Product And Quantity Rules​

Important item rules:

RuleMain BranchNon-main Branch
Expected purchase quantityNot required; payload sends 0/undefined.Required and must be a number greater than 0.
Quantity per lineSPEC (customer sample/inspection) only: maximum 3. Other borrow types have no per-line quantity cap.Maximum 5 for every borrow type.
Number of product rowsSPEC only: maximum 3 rows. Other borrow types have no row-count cap.No 3-row cap in the UI guard.
SPEC pickup/license plateNot required by the branch-specific guard.Required before adding product.
Quantity formatPositive integer only.Positive integer only.
Stock guardAdd checks onHand - booking; edit allows available stock plus the original item quantity.Same.

The item payload is built from product inventory data, including product code/name, quantity, expected quantity, price, amount, weight, warehouse, company, tax/VAT, UOM, weight-base flag, step-price flag, and editPriceBy = null.

Editing Existing Items​

Existing item edits are allowed only while the document is an editable draft. On edit, the app reloads the current product inventory by product code and calculates maxEditableStock = availableStock + originalQty for the same product. This lets the user keep or reduce an existing reservation while still preventing the final quantity from exceeding stock.

For non-main branch edits, expectedQty remains required and must be greater than 0. The per-line quantity cap remains 5 for every non-main borrow type. On the main branch, the cap is 3 only for SPEC; other borrow types have no per-line cap. All types still require a positive integer quantity and available stock.

Product Picker And First-Item Persistence​

  • When an existing borrow has a borrowId, adding products does not show a blocking full-screen loading state. The product picker remains open after a successful add so the operator can continue adding products.
  • A new draft follows a two-stage persistence flow: create the borrow header first, use the returned borrowId as the item-create identity, then create the first item.
  • borrowNo is a display/document number and is not used as a substitute for borrowId when creating the item.
  • After the first item is created, the app refreshes the borrow detail/list and synchronizes the route with the returned borrow identity. The header and first item therefore appear immediately instead of only after a second item is added.
  • Mobile and POS Terminal follow the same persistence behavior.

Confirm, Accept, Complete, Retry​

Confirm saves the latest header first, then calls confirmBorrow; draft editing is locked after confirmation. Acceptance records acceptedAt. The return/stock-cut action still requires processing permission, an accepted document, and eligible items.

completeBorrowStock(borrowId, detail) dispatches requests in this order on Mobile, POS HomeScreen, and Storefront POS:

PriorityCurrent detailPOST endpointBody
1borrowStatus === "PARTIAL_CONVERTED"/api/borrow/{id}/return-itemsIDs of all items with nonempty lots
2Other eligible status and hasFailedSubBorrow/api/borrow/{id}/retryNo body
3Other eligible status without failed sub-borrow/api/borrow/{id}/approveNo body

PARTIAL_CONVERTED takes priority even when hasFailedSubBorrow is true. It can expose return despite convertedOrderId/convertedAt, provided at least one item still has lots. For other statuses, the existing guard permits accepted WAITING/ACCEPTED documents and DONE with a failed sub-borrow, and blocks already-converted documents.

Example: items 11 and 12 have empty lots after conversion, while item 13 still has lots. Confirming return sends:

POST /api/borrow/{id}/return-items
{
"borrowItemIds": [13]
}

Lots already belong to each item; there is no lot-selection or return-item-selection step. The payload includes all items whose lots is a nonempty array, excludes converted rows, and removes duplicate IDs. No request is sent if there are no remaining items or any remaining item has an invalid ID. API errors are shown without falling back to /approve or /retry; successful actions refresh the document and related list/workspace data.

Convert To Sales Order​

The convert button requires all of these conditions:

ConditionRequired value
BranchMain branch (isMainBranch = true)
Borrow typeSPEC
Saved and acceptedNonempty borrowId and acceptedAt; DONE is not required
Not already convertedNo convertedOrderId or convertedAt, and neither CONVERTED nor PARTIAL_CONVERTED
UserDocument owner, or department -2, 1, 2, 4, 7, 8, 9, 13, 16, 20, 21, 23, 29

Pressing convert opens the shared BorrowConversionModal on Mobile, POS HomeScreen, and Storefront POS:

  1. Each opening starts with no items selected. Select individual rows or เลือกทั้งหมด (Select all); selecting all again clears the selection.
  2. Each row shows only product name, product code, and quantity. A visible checkbox/checkmark and selected border indicate selection; the modal also shows the selected count.
  3. Select at least one item and press ยืนยัน. The app validates the selection against the current items and submits only those borrow item IDs:
POST /api/borrow/{id}/convert
{
"borrowItemIds": [11, 12]
}

The compact layout uses a product-name font of 14.4 (10% smaller), card padding 7 (50% smaller), list/card gap 6, and line gap 2. While submitting, the label becomes กำลังแปลง… and selection, dismissal, and duplicate submission are blocked. If the API fails, the modal preserves the selection and shows the error inline.

A successful conversion refreshes the borrow detail, including backend status and convertedOrderId. PARTIAL_CONVERTED displays แปลงและรอคืน in the lists. In the detail, a row with empty, missing, or null lots under this status is treated as converted and receives a light red background (rgba(239,68,68,0.16)). Rows with lots retain their normal background and are included in the remaining-return flow. Empty lots under other statuses do not trigger the converted-row styling. A partially converted document cannot be converted again.

In StorefrontPosStack, a converted borrow exposes Go to sales order. The handoff loads getOrderDetail(convertedOrderId) before opening the product workspace in order mode. Persisted order fields are the source of truth for items, pickup/shipping, addresses, route, coordinates, and document flags. The terminal additionally loads the customer profile by customerCode for tier, points, benefits, and address fallback; failure to load that profile does not prevent the order from opening.

Borrow item identity​

Both payload builders use the first non-null ID field in itemId → borrowItemId → borrowDetailId → id. Numeric strings are normalized to numbers; IDs must be positive safe integers. Product codes and the borrow document ID are never substituted for item IDs. Conversion also rejects an empty or stale selection and removes duplicate IDs.

Two print paths are exposed:

PrintBehavior
Borrow packPrints customer/borrow/product packing document.
Borrow storagePrints warehouse/storage-facing borrow document.

Both print actions require at least one product item. PARTIAL_CONVERTED remains printable. Printing does not change business status by itself.

Permissions And Guards​

PermissionRule
Terminal menu keyborrowFish
Menu deptCode set-2, 1, 2, 3, 4, 7, 29, 21, 8, 9, 12, 13, 14, 30, 16, 20, 22, 23, 27, 28
Create borrowAdmin, or dept -2, 1, 2, 4, 7, 8, 9, 13, 16, 21, 29
Cancel/delete borrowAdmin; owner when status NEW; or allowed cancel depts/users while status is NEW or WAITING
Cancel departments-2, 1, 2, 4, 9; dept 8 and 22 also override for NEW/WAITING
Cancel override user codes008045, 012521, 012508, 008051, 012388, 012428, 008060
B1 processingAdmin, or dept 2, 8, 12, 22, 28
Owner processingThe document owner can process if borrow type is not DESTROY
Processing guardMain branch with B1 permission, or owner processing as above
Draft editingStatus must be NEW and current user must own the draft, or the draft is not persisted yet
Borrow type editingAllowed before persistence or by the document owner

Mobile vs POS Terminal​

TopicMobilePOS Terminal
NavigationFull borrow stack list/detailHomeScreen split-pane menu borrowFish
Product flowBorrow detail screen and product-flow hookBorrow detail form, panels, and product picker
Header saveSaves through borrow service when existing detail changesSaves raw detail through HomeScreen handlers
ValidationSame business rules in mobile componentsSame business rules in HomeScreen handlers
PrintingprintBorrowPack, printBorrowStorageSame print utilities
ConversionShared item-selection modal and selected borrowItemIdsSame modal and contract in HomeScreen and Storefront POS; Storefront can open convertedOrderId in order mode
Partial returncompleteBorrowStock sends remaining item IDs to /return-itemsSame routing and lots filter in both Terminal flows

API And Data Map​

OperationService
ListgetBorrows
DetailgetBorrowDetail(borrowId)
Create/update/delete headercreateBorrow, updateBorrow, deleteBorrow
Item CRUDcreateBorrowItem, updateBorrowItem, deleteBorrowItem
ConfirmconfirmBorrow
AcceptacceptBorrow
Complete/cut stock dispatchercompleteBorrowStock(borrowId, detail)
Normal completionapproveBorrow
Return remaining itemsreturnBorrowItems(borrowId, payload)
Retry failed sub-borrowretryBorrow
Convert to sales orderconvertBorrow(borrowId, payload)

Developer Handoff Map​

AreaCode
Mobile permissions/helpersapps/mobile/src/utils/borrow.ts
Mobile list/detailapps/mobile/src/screens/borrowStack/borrowListScreen, borrowDetailScreen
Mobile product flowapps/mobile/src/screens/borrowStack/borrowDetailScreen/useBorrowProductFlow.ts
Terminal permissions/panelsapps/pos-terminal/src/screens/HomeScreen/BorrowPanels.tsx
Terminal detailapps/pos-terminal/src/screens/HomeScreen/BorrowDetailForm.tsx
Terminal handlersapps/pos-terminal/src/screens/HomeScreen/index.tsx
Shared conversion UIpackages/rn-ui/src/BorrowConversionModal.tsx
Shared guards, status, item IDs and payloadspackages/utils/src/borrow.ts
Payload typespackages/types/src/borrow.ts
Storefront flowapps/pos-terminal/src/screens/StorefrontPosStack/borrowFlow.ts
Mobile/shared regression testsapps/mobile/__tests__/borrowConversionModal.test.tsx, borrowConversionService.test.ts, borrowPartialConversion.test.ts, borrowReturnItemsService.test.ts
Storefront regression testsapps/pos-terminal/src/screens/StorefrontPosStack/__tests__/borrowFlow.test.ts
Servicesapps/*/src/services/borrow.ts
Converted targetSales Order docs and order services