Skip to content

Dingoo Barcode Format

Barcodes (order IDs) generated by the invoice flow follow an 18-character format. They serve both as a unique shipment identifier and as a scannable barcode on the physical label.

Structure

[country(2)] [zone(4)] [size(1)] [serial(8)] [volume(2)] [checkdigit(1)]
Field Length Description
country 2 chars ISO country code (e.g. PT)
zone 4 digits Distribution zone code (zero-padded)
size 1 digit Package size code (1–5, or 0 if unknown)
serial 8 digits Sequential number fetched from MongoDB (zero-padded)
volume 2 digits Parcel index within the invoice (1 to 99, zero-padded)
checkdigit 1 digit Integrity check digit

Example: PT9876100000123018

PT   9876   1   00000123   01   8
↑     ↑     ↑      ↑        ↑   ↑
country zone size serial  vol  cd
  • country = PT
  • zone = 9876
  • size = 1
  • serial = 00000123
  • volume = 01 (first parcel of the invoice)
  • check digit = 8

Barcode Generation per Invoice

When an invoice with N parcels is imported:

  1. A unique serial is fetched from MongoDB for each parcel (via an atomic counter).
  2. The volume number is assigned sequentially: parcel 1 gets 01, parcel 2 gets 02, etc.
  3. The size code comes from the size field of each InvoiceOrderItem (1–5).
  4. The check digit is computed from the 15 numeric characters that make up the barcode body.

Check Digit Algorithm

The check digit protects against transcription errors. It is computed from the 15 numeric characters that sit between the 2-char country code and the check digit itself.

P  T  9  8  7  6  1  0  0  0  0  0  1  2  3  0  1  ?
└──┘  └─────────────────────────────────────────┘  └┘
 country       numeric part (15 digits)             cd

The numeric part is the direct concatenation of the four inner fields:

numeric_part = zone(4) + size(1) + serial(8) + volume(2)
             = "9876"  +  "1"  + "00000123" +  "01"
             = "987610000012301"    (15 digits)

Steps

  1. Reverse the 15-digit numeric part.
  2. For each digit at position i (starting at 0), assign weight 3 if i is even, 1 if i is odd.
  3. Sum all digit × weight products.
  4. Check digit = (10 − (sum mod 10)) mod 10.

Worked Example — PT9876100000123018

Numeric part: 987610000012301

Reversed: 103210000016789

i Digit Weight Product
0 1 3 3
1 0 1 0
2 3 3 9
3 2 1 2
4 1 3 3
5 0 1 0
6 0 3 0
7 0 1 0
8 0 3 0
9 0 1 0
10 1 3 3
11 6 1 6
12 7 3 21
13 8 1 8
14 9 3 27

Sum = 3 + 9 + 2 + 3 + 3 + 6 + 21 + 8 + 27 = 82

Check digit = (10 − (82 mod 10)) mod 10 = (10 − 2) mod 10 = 8

The last character of PT9876100000123018 is indeed 8.

Python Reference

def _check_digit(numeric_part: str) -> int:
    digits = list(map(int, numeric_part[::-1]))
    total = sum(d * (3 if i % 2 == 0 else 1) for i, d in enumerate(digits))
    return (10 - (total % 10)) % 10

Size Codes

Code Description
1 Extra Small
2 Small
3 Medium
4 Large
5 Extra Large

Relabeling: orderId vs confirmedBarcode

orderId is generated once, when the parcel is first created, using the invoice's parcel count and position at that moment (e.g. the 4th parcel of a 4-parcel invoice gets volume 04). It never changes afterwards — it's the stable internal key used for every API call, DB lookup and status history (GET /orders/{order_id}, PUT /cancel_order, orderFlows, etc.).

That volume number embedded in orderId can become stale: if parcels of the same invoice are later cancelled or added (e.g. via PUT /cancel_order or POST /update_invoice_orders), the parcel's position among the currently active parcels of the invoice changes, but orderId doesn't reflect that anymore.

To keep printed labels correct without renaming orderId, the orders collection has a separate confirmedBarcode field:

  • Whenever the active-parcel numbering of an invoice is recalculated (cancel, uncancel, or add), each active parcel gets a new barcode value written to confirmedBarcode — same country/zone/size/serial as the original, only volume (renumbered 1..N among the active parcels) and the check digit change. See refresh_invoice_label_numbering in external-api/src/utils/databases_util.py.
  • orderId itself is never modified by this process.
  • The label generator (generateOrderLabel.js in the BO) prints and encodes confirmedBarcode when present, falling back to orderId otherwise — so the printed number and the scanned barcode always match each other, even though they may no longer match the volume digits inside orderId.
  • Scanning already resolves by priority confirmedBarcodepossibleBarcodesorderId (see findOrderByBarcode in dingoo-api/app/bo/routes/v1/orders.js), so a relabeled parcel still scans correctly.

In short: orderId is "when/how this parcel was first created." confirmedBarcode (when set) is "what's actually printed and scanned right now." They can legitimately differ after cancellations.