From 96eb8c0360afca631aceca83587ab47cd5ee256e Mon Sep 17 00:00:00 2001 From: DMZ22 Date: Fri, 24 Jul 2026 10:02:15 +0530 Subject: [PATCH] Add Oman VAT number (VATIN) validation The Oman VAT identification number (VATIN), issued by the Oman Tax Authority, consists of the letters OM followed by 10 digits (12 characters in total). It has no check digit and is verified online through the tax authority portal, so this validates length and formatting. Partially addresses #408 (the VAT/VATIN form; the 7-digit entity "tax card number" is an unstructured all-digit sequence for which format validation adds little value). --- stdnum/om/__init__.py | 19 +++++++++ stdnum/om/vat.py | 84 +++++++++++++++++++++++++++++++++++++++ tests/test_om_vat.doctest | 65 ++++++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 stdnum/om/__init__.py create mode 100644 stdnum/om/vat.py create mode 100644 tests/test_om_vat.doctest diff --git a/stdnum/om/__init__.py b/stdnum/om/__init__.py new file mode 100644 index 00000000..5b3f3956 --- /dev/null +++ b/stdnum/om/__init__.py @@ -0,0 +1,19 @@ +# __init__.py - collection of Omani numbers +# coding: utf-8 +# +# Copyright (C) 2026 Devashish Moghe +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . + +"""Collection of Omani numbers.""" diff --git a/stdnum/om/vat.py b/stdnum/om/vat.py new file mode 100644 index 00000000..3400feda --- /dev/null +++ b/stdnum/om/vat.py @@ -0,0 +1,84 @@ +# vat.py - functions for handling Oman VAT numbers +# coding: utf-8 +# +# Copyright (C) 2026 Devashish Moghe +# +# This library is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License as published by the Free Software Foundation; either +# version 2.1 of the License, or (at your option) any later version. +# +# This library is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public +# License along with this library; if not, see . + +"""VAT (Oman value added tax number). + +The Oman VAT identification number (VATIN) is issued by the Oman Tax +Authority to businesses registered for value added tax. It consists of the +letters ``OM`` followed by 10 digits (12 characters in total). + +There is no check digit; a number can be verified online through the Oman +Tax Authority portal. + +More information: + +* https://tms.taxoman.gov.om/portal/web/taxportal/vatin-validation + +>>> validate('OM1100006083') +'OM1100006083' +>>> validate('OM 1100 0060 83') +'OM1100006083' +>>> validate('110000608312') # missing prefix +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> validate('OM110000608') # too short +Traceback (most recent call last): + ... +InvalidLength: ... +>>> validate('OM11000060AB') +Traceback (most recent call last): + ... +InvalidFormat: ... +""" + +from __future__ import annotations + +import re + +from stdnum.exceptions import * +from stdnum.util import clean + + +# the VATIN consists of the OM prefix followed by ten digits +_vatin_re = re.compile(r'^OM[0-9]{10}$') + + +def compact(number: str) -> str: + """Convert the number to the minimal representation. This strips the + number of any valid separators and removes surrounding whitespace.""" + return clean(number, ' -').upper().strip() + + +def validate(number: str) -> str: + """Check if the number is a valid Oman VAT number. This checks the + length and formatting.""" + number = compact(number) + if len(number) != 12: + raise InvalidLength() + if not _vatin_re.match(number): + raise InvalidFormat() + return number + + +def is_valid(number: str) -> bool: + """Check if the number is a valid Oman VAT number.""" + try: + return bool(validate(number)) + except ValidationError: + return False diff --git a/tests/test_om_vat.doctest b/tests/test_om_vat.doctest new file mode 100644 index 00000000..512a4526 --- /dev/null +++ b/tests/test_om_vat.doctest @@ -0,0 +1,65 @@ +test_om_vat.doctest - more detailed doctests for the stdnum.om.vat module + +Copyright (C) 2026 Devashish Moghe + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, see . + + +This file contains more detailed doctests for the stdnum.om.vat module. It +tries to validate a number of numbers that have been found online. + +>>> from stdnum.om import vat +>>> from stdnum.exceptions import * + + +Tests for some corner cases. + +>>> vat.validate('OM1100006083') +'OM1100006083' +>>> vat.validate('om1100006083') +'OM1100006083' +>>> vat.compact('OM 1100 0060 83') +'OM1100006083' +>>> vat.is_valid('OM1100006083') +True +>>> vat.is_valid('OM110000608') +False +>>> vat.validate('OM110000608') +Traceback (most recent call last): + ... +InvalidLength: ... +>>> vat.validate('110000608312') +Traceback (most recent call last): + ... +InvalidFormat: ... +>>> vat.validate('OM11000060AB') +Traceback (most recent call last): + ... +InvalidFormat: ... + + +These have been found online and should all be valid numbers. + +>>> numbers = ''' +... +... OM1100002920 +... OM1100005523 +... OM1100006083 +... OM1100006999 +... OM1100011333 +... OM1100018164 +... +... ''' +>>> [x for x in numbers.splitlines() if x and not vat.is_valid(x)] +[]