feat(gtin): add isValidGtin and getGtinInfo - #563
hyanmandian wants to merge 3 commits into
Conversation
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Advanced Run ID: 📒 Files selected for processing (6)
Included review availability: Your plan provides up to 10 included reviews per hour; 0 remain after this review. 📝 WalkthroughWalkthroughThis change adds GS1 modulo-10 support, GTIN parsing and classification, length-filtered validation, public exports, package entries, API reports, tests, and English and Portuguese documentation. ChangesGTIN utilities
Priority: ➖ Normal Estimated code review effort: 3 (Moderate) | ~25 minutes Change: Feature Sequence Diagram(s)sequenceDiagram
participant Caller
participant isValidGtin
participant getGtinInfo
participant mod10
Caller->>isValidGtin: submit value and optional lengths
isValidGtin->>getGtinInfo: parse and classify value
getGtinInfo->>mod10: validate GS1 check digit
mod10-->>getGtinInfo: return check result
getGtinInfo-->>isValidGtin: return metadata or null
isValidGtin-->>Caller: return boolean
Merge Risk: ⚪ Minimal · up to The PR adds GTIN validation and metadata APIs with documented public exports and coverage for supported formats. No merge-blocking production risk is identified. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Tree-shaking report✅ No size regression. 9 grew, 9 new out of 165 exports.
What changed (18)
All exports (165)
How this is measuredEvery export is imported alone into an esbuild consumer bundle (minified, tree-shaken) built from the head and from the base of this pull request; the sizes are the resulting bundles, gzip is their gzipped size. 🔴 marks a regression: a pre-existing export that grew more than 20% and more than 256 B, or the bundle importing every pre-existing export growing more than 5%. 🟡 is growth under the threshold, 🟢 a decrease, ⚪ no change, 🆕 an export that does not exist on the base (never a regression), 🗑️ an export that was removed. An intentional increase is accepted with the |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## claude/get-state-by-cep #563 +/- ##
=========================================================
Coverage 100.00% 100.00%
=========================================================
Files 193 195 +2
Lines 2107 2131 +24
Branches 621 630 +9
=========================================================
+ Hits 2107 2131 +24
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
|
@coderabbitai review |
|
|
@coderabbitai review |
✅ Action performedReview finished.
|
GS1 keys use a modulo 10 that differs from the Luhn one only in the weight (3 instead of 2) and in adding the products as they are instead of their digits (GS1 General Specifications, section 7.9.1). It comes as `options.variant`, the way `mod11` takes its variants, with `"luhn"` as the default so the boleto, credit card, bank account and IE callers are untouched.
The NF-e requires a valid GTIN in `cEAN` and `cEANTrib` (rules I03-10 and I12-10 of SEFAZ NT 2021.003, rejections 611 and 612), so the product barcode sits next to the NCM, CFOP and CST utilities. `isValidGtin(value, options?)` covers GTIN-8, GTIN-12, GTIN-13 and GTIN-14 with the GS1 modulo 10 check digit and takes `lengths` to accept only some of them. `getGtinInfo(value)` returns the type, the length, the three digit GS1 Prefix read from the 14 digit form the way the "Tabela Prefixo GS1" of the Portal da NF-e tells, whether it is one of GS1 Brasil (789, 790), whether it falls in a Restricted Circulation Number range of the General Specifications (tables 1-4 and 1-5) and the check digit. The prefix never changes the verdict: the SEFAZ table lists the restricted, ISSN, ISBN and coupon ranges as valid, and a copy of the Member Organisation list would turn down valid numbers as GS1 assigns new ranges. The vectors are the examples GS1 publishes (6291041500213, 9521234500018, 09524141234564, 061414112345) and synthetic values worked out by hand.
The check digit paragraphs said the sum is subtracted from "the next multiple of ten". Read literally that gives 10 for a sum that already is a multiple of ten. Table 7-8 of the General Specifications says "nearest equal or higher multiple of ten", which is what the code does, so use that phrasing in both JSDoc blocks and in both docs pages. Three test titles credited GS1 numbers to the wrong key: 9521234500018 appears in the General Specifications as a GLN, 09524141234564 as a GRAI, and 061414112345 is the 12 digit body of a GTIN-13 built on the U.P.C. Company Prefix 614141, not a published GTIN-12. All three are valid vectors, since section 7.9.1 is the same rule for every fixed length GS1 key, so only the titles change. The isRestrictedCirculation row of both docs listed three restricted ranges of table 1-4 and left out the GS1 Prefix 0000000, which the code already flags through the GS1-8 reading and which the tests already cover. Say so in the row, and widen the prefix description from "GS1-8 Prefix for a GTIN-8" to the condition the code tests: the first six digits of the 14 digit form are zeros.
86268ba to
3b5340a
Compare
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
commit: |
|
@coderabbitai review |
✅ Action performedReview finished.
|
What
Product barcode utilities, next to the NCM, CFOP and CST ones used for NF-e items: the NF-e requires a valid GTIN in
cEANandcEANTrib.isValidGtin(value, options?): GTIN-8, GTIN-12, GTIN-13 and GTIN-14 with the GS1 modulo 10 check digit, and alengthsoption to accept only some of them.getGtinInfo(value):null, or the type, length, GS1 Prefix, whether the prefix is one of GS1 Brasil, whether it is in a Restricted Circulation Number range, and the check digit.src/_internals/mod10gainsoptions.variant("luhn"default,"gs1"), the waymod11takes its variants. The GS1 rule differs from Luhn in the weight (3 instead of 2) and in adding the products as they are, so it is one function with an option, not a sibling. Existing callers are untouched.API
Decisions:
typeandlengthdescribe the value as written. The prefix is read from the 14 digit form the way SEFAZ tells: positions 7 to 9 when the first six are zeros (a GTIN-8), positions 2 to 4 otherwise. GS1 leaves the prefixes 0000001 to 0000099 unused for exactly that reason, so the two readings never collide.isRestrictedCirculationis derived from the ranges of the General Specifications, which are fixed by the standard.isValidGtinis built ongetGtinInforather than the other way round, which is the opposite of theget-boleto-info/get-iban-info/get-certidao-infopairs. Those validators need nothing the parser computes; this one does, sinceoptions.lengthsfilters on exactly theGtinLengththe parse produces. Flipping the direction would leavegetGtinInfohaving to turndigits.lengthback into aGtinLengthafterisValidGtinalready guaranteed it, which needs either a type assertion (nosrcfile outside the tests has one) or a secondGTIN_LENGTHS.findwith anundefinedbranch that nothing can reach, and so nothing can cover or kill a mutant in. The cost of keeping the sound direction is 112 B and one object allocation perisValidGtincall. The constants stay insrc/get-gtin-info/constants.ts, the folder that owns them, asget-certidao-infodoes foris-valid-certidao.Sources
mod10test); table 1-4 (GS1 Prefix ranges: 02, 04, 20 to 29 and 0000000 for Restricted Circulation Numbers, 0000001 to 0000099 unused to avoid collision with GTIN-8, 952 for demonstrations); table 1-5 (GS1-8 Prefixes: 000 to 099 and 200 to 299 restricted); section 1.2.2.2.1 (RCNs are not globally unique). The 952 numbers used as vectors are published there as a GLN (9521234500018) and a GRAI (09524141234564); section 7.9.1 is "identical for all fixed length numeric GS1 data structures", so they carry the same check digit and are valid GTIN vectors.cEANand I12cEANTribtake GTIN-8, 12, 13 or 14 or the literal "SEM GTIN"; rules I03-10 and I12-10 (check digit, rejections 611 and 612); rules I03-20 and I12-20 (prefix against the table below); rules 9I03-10 and 9I12-10 ("prefixo do Brasil (iniciado em 789 ou 790)", looked up in the Cadastro Centralizado de GTIN).No third party implementation was used. gs1.org and the Portal da NF-e answer 403 or a redirect loop to a plain fetch; both open in a browser (curl with a browser user agent and a cookie jar also works).
Verification
npm run check: passnpm run test -- --run: 186 files, 6192 passednpm run test:coverage: 100% statements, branches, functions and linesnpm run build: pass (attw and publint clean)npm run check:api:update: report updated and committed (additions only)npm run check:unused: passnpm run check:duplication: 0 clonesnpm run check:tree-shaking: pass;isValidGtin1711 B (1046 B min),getGtinInfo1599 B (1003 B min)npm run check:commits: passnpm run test:mutationonget-gtin-info.ts,is-valid-gtin.tsandmod10.ts: 100% (onemod10mutant killed by timeout)npm run test:bunandnpm run test:deno: 6192 passed, 0 failednpm run build:docsandnpm run build:jsr: run, output committed (jsr.jsongains./is-valid-gtinand./get-gtin-info)Review follow-ups
9521234500018is published as a GLN,09524141234564as a GRAI, and061414112345is the 12 digit body of a GTIN-13 built on the U.P.C. Company Prefix 614141, not a published GTIN-12. The vectors themselves are correct and unchanged.isRestrictedCirculationrow of both docs pages now also names the GS1 Prefix0000000of table 1-4, which the code already flags through the GS1-8 reading and whichget-gtin-info.test.tsalready covers, and theprefixdescription states the condition the code tests (the first six digits of the 14 digit form are zeros) instead of "GS1-8 Prefix for a GTIN-8".Rebase onto #562
Rebased from
mainontoclaude/get-state-by-cep, so this branch now carries #588, #558, #559, #560 and #562 underneath it. Conflicts resolved:docs/llms.txtanddocs/llms-full.txtare no longer tracked (they are generated now), so both weregit rm-ed.docs/utilities.mdanddocs/pt-br/utilities.mdwas ported into the new per-utility format of feat: Standard Schema wrapper, JSR, pkg.pr.new, docs previews and a playground #556: a short paragraph per function, a bullet list for the options, the structures and the edge cases, theGtinInfofield table kept as a table, thejavascriptblocks, and one sharedSource:line at the end of the family carrying the GS1 and SEFAZ links that used to be inline.src/index.tsandsrc/index.test.tskept strictly alphabetical.jsr.json(new onmain) regenerated withnpm run build:jsr, andreports/api/brazilian-utils.api.mdwithnpm run check:api:update. Both are folded into the commits that own them, with no separate "regenerate" commit.Re-verified on the rebased branch:
npm run check,npm run test:coverage(100% statements, branches, functions and lines),npm run build,npm run check:unused,npm run check:duplicationandnpm run check:commitsall pass.Open points
"00000000","0000000000000") has a correct check digit and sits in a restricted range, so it is accepted and flaggedisRestrictedCirculation. Neither GS1 nor the NT states a rule against it, so none was added.0is, for GS1, a shorter GTIN in the 14 digit form (NT rule 9I03-40 also reads GTIN-14 ascEAN > 09999999999999). It is reported with the length it was written with ("GTIN-14",14); only the prefix follows the normalized reading.isBrazilian, for the snapshot reason above.isBrazilianon a GTIN-8 rests on SEFAZ, not on GS1. Table 1-5 of the General Specifications only says the GS1-8 Prefixes 300 to 951 are "used to issue GTIN-8s"; it does not publish the per Member Organisation split of that space. The "Tabela Prefixo GS1" has a single prefix sheet whose instructions cover both readings, and its 789 to 790 row is what this follows, soisBrazilianis what SEFAZ would compute, which is what the utility claims.Signed-off-byline, matching the history ofmain; add it on merge if the DCO check asks for it.Summary by CodeRabbit