﻿=== SERIAL NUMBER FEATURE END-TO-END TRACE (POS Codebase) ===
Generated: Wed Oct 14 2026
Working dir: C:\xampp\htdocs\pos

Note: All file references use full relative paths from workspace root C:\xampp\htdocs\pos.
=== 1. PRODUCT DETAILS - How 'serializable' is saved and loaded ===

a) View (form field)
- views/productdetails.php:301-307
  <label for="serializable">Serializable</label>
  <select name="serializable" id="serializable" class="form-control form-control-sm">
      <option value="0">No</option>
      <option value="1">Yes</option>
  </select>

b) JS (population on edit/save payload)
- js/productdetails.js:23: serializablefield=#serializable
- js/productdetails.js:101: convertSelectToToggle("serializable")  (UI toggle wraps select)
- js/productdetails.js:271-272, 360, 486, 606: save payload includes serializable (form value sent to controller)
- js/productdetails.js:486: serializablefield.val(data[0].serializable).trigger('change')  (on edit load)
- js/productdetails.js:606: serializablefield.val("0").trigger('change')  (clear form)

c) Controller
- controllers/productoperations.php:117: =['serializable'];
- controllers/productoperations.php:152-154: passed to model saveProduct
- controllers/productoperations.php:159-161: getproductdetails calls ->getProductDetails(,,)

d) Model (save/update + get)
- models/product.php:26:  parameter in saveProduct signature
- models/product.php:52:  = (!isset() ||  === '') ? 0 : ;  (normalization)
- models/product.php:76-82: SQL call to spsaveproduct(...) with {} (7th positional boolean/param for serializable)
- models/product.php:97: getProductDetails calls spgetproductdetails({->branchid},{->clientid},'{}',{},) and returns JSON

e) Stored Procedure (save product)
- migrations/db_sps_and_functions.sql / migrations/database_sps_*.sql reference spsaveproduct (created in DB)
- Procedure: spsaveproduct (see full DDL above): takes $canserialize BOOL param and inserts/updates products.serializable = . Also on insert sets fields including serializable.

f) Products table column
- DB: products.serializable (tinyint(1), nullable, default varies; see schema) - confirmed via information_schema scan earlier.

=== 2. RECEIVING ITEMS (GRN) - How serial numbers are captured ===

a) View
- views/receiveitems.php:253-312: Serial numbers modal #serialsmodal
  - #serialitemid (hidden), #serialitemname, #serialquantity
  - #serialnumbers input (accepts multiple serial numbers separated by comma)
  - #saveserialnumbers button (adds to list)
  - #serialstable (list with edit/delete)
  - #updateserials button (saves serials back to selected row)
- Table column "Serial #" in received items: views/receiveitems.php:236, and JS adds button per serializable row.

b) JS
- js/receiveitems.js:378-406: Opens modal when clicking .addserials; stores serializable flag and row id in data attributes
- js/receiveitems.js:390-403: Populates existing serials from row attr data-serial-nos (comma separated)
- js/receiveitems.js:407-586: addserialnumbers() logic - splits by comma, dedupes (uniqueArrayValues), prevents duplicates, allows edit/delete, updates summary
- js/receiveitems.js:560-587: #updateserials click - validates count equals quantity; sets data-serial-nos on parent row; updates button to "Edit serials"
- js/receiveitems.js:134-263: Save - checks missingserials if row has data-serializable==1 but data-serial-nos==""; builds TableData - if serializable, expands each serial number as separate line with quantity 1 per serial (lines 169-179)
- js/receiveitems.js:104-113: When adding PO items, row has data-serializable='' and data-serial-nos=''

c) Controller
- controllers/goodsreceivedoperations.php:5-34
  - Receives TableData (JSON) with each entry possibly having serialno (line 27)
  - Calls ->saveTempGoodsReceived(,...,) (line 28)
  - Calls ->saveGoodsReceived(...) to commit

d) Models + SPs
- models/goodsreceived.php:17-22: saveTempGoodsReceived calls spsavetempgoodsreceived({->branchid},'{}','{}','{}',{},'{}')
- models/goodsreceived.php:24-42: saveGoodsReceived validates delivery note, gets active session, calls spsavegoodsreceived(...) with sessionid
- SPs: spsavetempgoodsreceived, spsavegoodsreceived

e) DB tables/SPs for receiving serials
- temp table: 	empgoodsreceived (refno, purchaseorderno, itemcode, quantity, serialno, branchid) - schema shown
- final: goodsreceiveddetails (id, grnno, itemcode, purchaseorderno, quantity, serialno, branchid) - schema shown; serialno varchar(100)
- SP spsavegoodsreceived (partial shown): inserts from tempgoodsreceived into goodsreceiveddetails including serialno

=== 3. SELLING (POS/touchscreen) - Serial numbers during sale ===

a) Touchscreen Sale v2 (UI)
- views/touchscreensale_v2.php contains cart table (line ~190-204 shows structure). Serial handling appears in cart rendering.
- js/touchscreensale_v2.js: when loading held sale/jobcard, renders rows; for serializable items includes input for serialno (seen in grep context). Example pattern from v2: adds data-serializable, data-serial-nos, and a serial input/button when serializable. (Detailed snippets captured in search).

b) Makesales (legacy/alternative UI)
- js/makesales.js shows serial numbers modal elements and logic:
  - serialnumbersmodal, serialnumbersdropdownlist, addsserialnumberstolistbutton, serialnumberslist (vars ~35-41)
  - Fetch existing available serial numbers for product: lines ~979-995 call controller with getexistingproductserialnumbers:true, populate dropdown, show modal
  - Save selected serials back to row (data-serial-nos) - line ~1000+ logic

c) How existing available serial numbers are fetched
- Controller: controllers/productoperations.php:162-165
  - if(isset(['getexistingproductserialnumbers'])){ =['productid']; echo ->getavailableserialnumbers(); }
- Model: models/product.php:213-218
  - public function getavailableserialnumbers(){ return ->getJSON("CALL spgetavailableproductserialnumbers({->clientid},{})"); }
- SP: spgetavailableproductserialnumbers (clientid, itemid)
  - SELECT serialno FROM goodsreceiveddetails g WHERE serialno NOT IN(SELECT serialno FROM possalesdetails WHERE itemcode=) AND itemcode= ORDER BY serialno;

Comparison notes: Both makesales.js and touchscreen variants support serial capture; touchscreen v2 renders inline serial input/button per serializable item in cart. Makesales uses modal with dropdown of available serials. Both attach serials to cart row (data-serial-nos / actual serialno field) and include in save payload.

=== 4. POSSALE SAVE - How serial numbers are persisted ===

a) Controllers (save actions)
- controllers/possalesoperations.php captures serialno from sale items in multiple save paths:
  - Line ~74,77:  = ['serialno']; ->saveTemporarySale(..., , ...)
  - Line ~337,340: same for other flow
  - Line ~880,883: isset(['serialno']) ? ['serialno'] : ''

b) Model (tempsale + save)
- models/possale.php (referenced): calls saveTemporarySale with serialno parameter and ultimately calls spsavepossale (via sale model in some code paths; also direct in models/sale.php).
- models/sale.php:22: $sql = "CALL spsavepossale({->branchid},'{}',{},{},'{}','{}',{},{},{},{})";

c) Temp tables
- 	empsale: columns include serialno (varchar(50) in tempsale schema). Described: refno, itemcode, unitprice, discount, quantity, serialno, taxtypeid, taxrate, description, branchid, uom.
- 	emppossalespayment: used in SP.

d) Final tables
- possalesdetails: columns include serialno (varchar(100)). Described: id, possaleid, itemcode, quantity, unitprice, discount, serialno, taxtypeid, taxrate, description, branchid, uom.

e) Stored procedure that reads tempsale.serialno and writes to possalesdetails.serialno
- SP name: spsavepossale (defined in migrations: add_sessionid_to_transactions.sql, db_sps_and_functions.sql, etc.)
- Evidence from migrations:
  - add_sessionid_to_transactions.sql:79: INSERT INTO possalesdetails (branchid, possaleid, itemcode, quantity, unitprice, discount, serialno, taxtypeid, taxrate, description) SELECT ... serialno ... FROM tempsale WHERE ...
  - add_sessionid_to_transactions.sql:431: INSERT INTO 	empsale includes serialno
  - Also declares cursor from tempsale selecting serialno
- Key line (from migration context): possalesdetails insert takes serialno from tempsale. The actual SP body in full DB migrates; the migration file add_sessionid_to_transactions.sql contains the definition text (CREATE PROCEDURE spsavepossale ...) showing it reads serialno from tempsale and inserts into possalesdetails.serialno.

f) Migration files defining spsavepossale
- migrations/add_sessionid_to_transactions.sql (defines/updates spsavepossale, includes sessionid param and tempsale/possalesdetails with serialno)
- migrations/add_wallet_integration_to_possales.sql (updates spsavepossale)
- migrations/db_sps_and_functions.sql, migrations/database_sps_and_functions.sql, migrations/database_sps_functions.sql also contain spsavepossale definitions/inserts involving serialno

=== 5. DATABASE - Tables storing physical serial numbers (not numbering sequences) ===

Numbering sequences (not the individual tracked serials tied to stock history in the same way): serials, masterserials hold document prefixes/currentno.
- Tables_in_pos (%serial%): masterserials, serials (confirmed)

Individual serial numbers tied to products/stock (where physical serials are recorded):
From information_schema scan:
- goodsreceiveddetails.serialno (varchar, nullable) - stores serials received per GRN line
- possalesdetails.serialno (varchar, nullable) - stores serial sold per sale line
- tempsale.serialno, tempgoodsreceived.serialno (temp tables)
- heldsalesdetails.serialno
- jobcarddetails.serialno (text)
- materialissuedetails.serialno, materialreceiptdetails.serialno
- returninwards.serialno, returnoutwards.serialno
- stocktransferdetails.serialno, tempstocktransfer.serialno, tempreturns.serialno
- machines.serial_no (separate entity)
- vwwarehouseitembalances.serializable (view)

Physical tracking table used by availability checks: goodsreceiveddetails is the source of available serials (see SP). Also possalesdetails records consumed serials.

Exact schema (key tables):
- goodsreceiveddetails (C:\xampp\htdocs\pos\migrations\database.sql context): stores individual serials received with grnno/itemcode/purchaseorderno/quantity/serialno/branchid
- possalesdetails: stores serial sold against possaleid/itemcode with serialno
- SP that reads/writes: spsavegoodsreceived writes to goodsreceiveddetails (including serialno from tempgoodsreceived); spsavepossale writes to possalesdetails (including serialno from tempsale). spgetavailableproductserialnumbers reads goodsreceiveddetails to find serials NOT in possalesdetails for given item.

Conclusion: The "physical" individual serial numbers are stored in goodsreceiveddetails.serialno (received inventory of serials) and consumed/recorded in possalesdetails.serialno (sold). Temp tables hold transient serials during operations.

=== 6. ALL FILES REFERENCING serializable/serialno/serialnumber (grouped) ===

a) References to serializable (from grep results):
- controllers/productoperations.php:117, 152, 430
- models/product.php:26, 52, 76
- scratch.php:53, 73
- migrations/create_jobcard_sps.php:230
- views/productdetails.php:301, 302
- Also products table/view references (DB schema)

b) References to serialno (from grep/search):
Controllers:
- controllers/goodsreceivedoperations.php:27,28
- controllers/possalesoperations.php:74,77,337,340,880,883
- controllers/productoperations.php:35,36,169,170 (note: also serial number validation via checkSerialNumberAvailability)
- controllers/rawmaterialsoperations.php:238,241,274,275,290,292
- controllers/returnoperations.php:15,16,35,36
- controllers/printcustomerreceipt.php:164,165 (display on receipt)
- controllers/receiptapi.php:77
- controllers/productoperations.php also has getexisting/check serialnumber endpoints

Models:
- models/product.php:213-250 (getavailableserialnumbers, checkSerialNumberAvailability)
- models/goodsreceived.php:17,19,28 (passes serialno)
- models/possale/sale usage via tempsale

JS:
- js/receiveitems.js: numerous (serials modal, add serials, store data-serial-nos)
- js/touchscreensale_v2.js: serializable/serialno in cart rendering/handling
- js/makesales.js: serial numbers modal, fetch existing, store per row
- js/jobcards.js:540,541 (display sold items with S/N)
- Other JS files reference serialno in cart/held sale contexts

Views:
- views/receiveitems.php: modal for serial numbers (input #serialnumbers, table, buttons)
- views/touchscreensale_v2.php: cart includes serial handling (as rendered by JS)
- views/productdetails.php: serializable select

c) References to serialnumber/serialnumbers (keywords):
- controllers/productoperations.php:162-172 (endpoints getexistingproductserialnumbers, checkproductserialnumber)
- js/makesales.js:35-41, 957-1001 (modal elements, logic)
- views/receiveitems.php:284-312 (UI text/fields)
- js/receiveitems.js: modal and functions addserialnumbers, saveserials

d) DB objects
- Tables: tempsale, tempgoodsreceived, possalesdetails, goodsreceiveddetails, heldsalesdetails, jobcarddetails, materialissuedetails, materialreceiptdetails, returninwards, returnoutwards, stocktransferdetails, tempstocktransfer, tempreturns, machines(serial_no), products(serializable), vwwarehouseitembalances(serializable), serials, masterserials
- SPs: spsavetempgoodsreceived, spsavegoodsreceived, spsavepossale, spgetavailableproductserialnumbers, spsaveproduct (takes canserialize), check/related SPs
- Migrations: add_sessionid_to_transactions.sql, add_wallet_integration_to_possales.sql, db_sps_and_functions.sql, database_sps_and_functions.sql, database_sps_functions.sql, database.sql, create_jobcard_sps.php (references serializable)

All paths above are relative to C:\xampp\htdocs\pos.

