diff --git a/src/cz/dic.spec.ts b/src/cz/dic.spec.ts index 7ac31f21..b40f3e51 100644 --- a/src/cz/dic.spec.ts +++ b/src/cz/dic.spec.ts @@ -25,4 +25,46 @@ describe('cz/dic', () => { expect(result.error).toBeInstanceOf(InvalidChecksum); }); + + // Special 9-digit case (DIČ starting with 6), see issue #164. + // These are valid per the official EU VIES service but were falsely + // rejected because checkSpecial diverged from python-stdnum. + it('validate:CZ687836437', () => { + const result = validate('CZ687836437'); + + expect(result.isValid).toBe(true); + }); + + it('validate:640903926', () => { + const result = validate('640903926'); + + expect(result.isValid).toBe(true); + }); + + it('validate:681208919', () => { + const result = validate('681208919'); + + expect(result.isValid).toBe(true); + }); + + // VAT-group ID (format 699nnnnnk). + it('validate:699005400', () => { + const result = validate('699005400'); + + expect(result.isValid).toBe(true); + }); + + // Wrong check digit on the special case must still be rejected. + it('validate:687836438', () => { + const result = validate('687836438'); + + expect(result.error).toBeInstanceOf(InvalidChecksum); + }); + + // Normal (non-special) 9-digit DIČ still delegates to rc and validates. + it('validate:CZ991231123', () => { + const result = validate('CZ991231123'); + + expect(result.isValid && result.compact).toEqual('991231123'); + }); }); diff --git a/src/cz/dic.ts b/src/cz/dic.ts index 7a7f6a4a..740aa33d 100644 --- a/src/cz/dic.ts +++ b/src/cz/dic.ts @@ -12,6 +12,7 @@ import * as exceptions from '../exceptions'; import { strings, weightedSum } from '../util'; +import { pymod } from '../util/pymod'; import { Validator, ValidateReturn } from '../types'; import { validate as rcValidate } from './rc'; @@ -41,17 +42,21 @@ function checkLegal(value: string): boolean { } function checkSpecial(value: string): boolean { - // check = sum((8 - i) * int(n) for i, n in enumerate(number)) % 11 - - // return str((8 - (10 - check) % 11) % 10) - - const [front, check] = strings.splitAt(value, -1); + // Reference: python-stdnum cz/dic.py::calc_check_digit_special + // check = sum((8 - i) * int(n) for i, n in enumerate(number[1:8])) % 11 + // return str((8 - (10 - check) % 11) % 10) + // + // The weighted sum runs over digits 2..8 only: the leading 6 and the check + // digit are excluded (weights [8, 7, 6, 5, 4, 3, 2]). The final reduction + // relies on Python's always-non-negative modulo, so pymod is used to match + // it -- JS '%' keeps the dividend's sign and would yield a negative digit. + const [, front, check] = strings.splitAt(value, 1, -1); const sum = weightedSum(front, { modulus: 11, - weights: [8, 7, 6, 5, 4, 3, 2, 1], + weights: [8, 7, 6, 5, 4, 3, 2], }); - const digit = String((8 - ((10 - sum) % 11)) % 10); + const digit = String(pymod(8 - ((10 - sum) % 11), 10)); return digit === check; }