From 6e0dbe8a31b9451b75c7324cec8305094dbbaef3 Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Mon, 8 Jul 2024 22:16:19 +0200 Subject: [PATCH 1/3] #4 Create a simple CLI to run within other applications --- src/batch.py | 18 ++++++++++--- src/single.py | 3 ++- src/validate_bzst.py | 4 +-- src/validate_hmrc.py | 15 ++++++----- src/validate_vies.py | 4 +-- src/validate_workflow.py | 8 +++--- src/vatvalidation.py | 14 ++++++---- src/vatvalidation_cli.py | 55 ++++++++++++++++++++++++++++++++++++++++ 8 files changed, 96 insertions(+), 25 deletions(-) create mode 100644 src/vatvalidation_cli.py diff --git a/src/batch.py b/src/batch.py index 8727a69..4699a05 100644 --- a/src/batch.py +++ b/src/batch.py @@ -8,7 +8,7 @@ columns = ["key1", "key2", "ownvat", "foreignvat", "company", "street", "zip", "town"] -def validatebatch(inputfile, outputfile="", type="vies", lang="en"): +def validatebatch(inputfile, outputfile="", type="vies", lang="en", iscli=False): """ Validate the batch file and write the results to the output file. """ @@ -21,13 +21,20 @@ def validatebatch(inputfile, outputfile="", type="vies", lang="en"): match ext: case "csv": - processcsv(inputfile, outputfile, type, lang) + resultcode = processcsv(inputfile, outputfile, type, lang) + if iscli: + return resultcode case "xlsx": - processxlsx(inputfile, outputfile, type, lang) + resultcode = processxlsx(inputfile, outputfile, type, lang) + if iscli: + return resultcode case "json": - processjson(inputfile, outputfile, type, lang) + resultcode = processjson(inputfile, outputfile, type, lang) + if iscli: + return resultcode case _: print("Unsupported file format") + return 127 def processcsv(inputfile, outputfile, type, lang): @@ -56,6 +63,7 @@ def processcsv(inputfile, outputfile, type, lang): town=row["town"], type=type, lang=lang, + iscli=True ) # append the result to the results list results.append(message) @@ -88,6 +96,7 @@ def processxlsx(inputfile, outputfile, type, lang): town=row["town"], type=type, lang=lang, + iscli=True, ) # append the result to the results list results.append(message) @@ -117,6 +126,7 @@ def processjson(inputfile, outputfile, type, lang): town=row["town"], type=type, lang=lang, + iscli=True, ) # append the result to the results list results.append(message) diff --git a/src/single.py b/src/single.py index 9a549b2..1c4a059 100644 --- a/src/single.py +++ b/src/single.py @@ -12,6 +12,7 @@ def validatesingle( town="", type="vies", lang="en", + iscli=True ): data = { "key1": key1, @@ -25,6 +26,6 @@ def validatesingle( "type": type, "lang": lang, } - r = validate_workflow.start_validation(data) + r = validate_workflow.start_validation(data, iscli) return r diff --git a/src/validate_bzst.py b/src/validate_bzst.py index ddb9bdc..0226031 100644 --- a/src/validate_bzst.py +++ b/src/validate_bzst.py @@ -51,7 +51,7 @@ def gettext(nodelist): def load_codes(lang, errorcode): if errorcode is None: - return None + return "" for code in codes_bzst.returncodes: if code["status"] == errorcode: @@ -59,7 +59,7 @@ def load_codes(lang, errorcode): return None -def start_validation(payload): +def start_validation(payload, iscli=True): logger.debug(payload) # map requested fields to bzst request diff --git a/src/validate_hmrc.py b/src/validate_hmrc.py index 28a547b..fc2dbab 100644 --- a/src/validate_hmrc.py +++ b/src/validate_hmrc.py @@ -62,15 +62,14 @@ def defaultencode(o): def load_codes(lang, message): if message is None: - return message - + return "" for code in codes_hmrc.returncodes: if message.startswith(code["status"]): return code[lang] return message -def start_validation(payload): +def start_validation(payload, iscli=True): try: resp = http.request("GET", URL + payload["foreignvat"][2:]) logger.debug(resp.status, resp.data) @@ -84,7 +83,9 @@ def start_validation(payload): "type": "HMRC", "valid": resp.status == 200, "errorcode": result.get("errorcode", ""), - "errorcode_description": load_codes(payload["lang"], result["message"]), + "errorcode_description": load_codes( + payload["lang"], result.get("message", None) + ), "valid_from": "", "valid_to": "", "timestamp": datetime.datetime.now(datetime.timezone.utc).strftime( @@ -94,9 +95,9 @@ def start_validation(payload): if "target" in result: validationresult["company"] = result["target"]["name"] validationresult["address"] = ( - result["target"]["address"]["line1"] - + chr(13) - + result["target"]["address"]["line2"] + result["target"]["address"]["line1"].strip() + "" + if iscli + else chr(13) + result["target"]["address"]["line2"].strip() ) validationresult["town"] = "" validationresult["zip"] = result["target"]["address"]["postcode"] diff --git a/src/validate_vies.py b/src/validate_vies.py index 690e103..b722822 100644 --- a/src/validate_vies.py +++ b/src/validate_vies.py @@ -64,7 +64,7 @@ def defaultencode(o): def load_codes(lang, errorcode): if errorcode is None: - return None + return "" for code in codes_vies.returncodes: if code["status"] == errorcode: @@ -72,7 +72,7 @@ def load_codes(lang, errorcode): return None -def start_validation(payload): +def start_validation(payload, iscli=True): logger.debug(payload) foreign_vat = payload["foreignvat"] diff --git a/src/validate_workflow.py b/src/validate_workflow.py index d66e780..846b11b 100644 --- a/src/validate_workflow.py +++ b/src/validate_workflow.py @@ -51,7 +51,7 @@ def return_fielderror(fieldname): } -def start_validation(payload): +def start_validation(payload, iscli=True): logger.debug(payload) if "key1" not in payload: @@ -86,10 +86,10 @@ def start_validation(payload): if payload["ownvat"].upper().startswith("DE") and not payload[ "foreignvat" ].upper().startswith("GB"): - response = validate_bzst.start_validation(payload) + response = validate_bzst.start_validation(payload, iscli) elif payload["foreignvat"].upper().startswith("GB"): - response = validate_hmrc.start_validation(payload) + response = validate_hmrc.start_validation(payload, iscli) else: - response = validate_vies.start_validation(payload) + response = validate_vies.start_validation(payload, iscli) return response diff --git a/src/vatvalidation.py b/src/vatvalidation.py index 0dc2058..8f8ea45 100644 --- a/src/vatvalidation.py +++ b/src/vatvalidation.py @@ -118,9 +118,7 @@ def vatvalidationClose(self, event): self.Close() def vatvalidationGitHub(self, event): - webbrowser.open_new_tab( - "https://github.com/dseichter/VATValidation" - ) + webbrowser.open_new_tab("https://github.com/dseichter/VATValidation") def validateSingle(self, event): wx.MessageBox( @@ -137,11 +135,17 @@ def validateSingle(self, event): town=self.textTown.GetValue(), type=settings.load_value_from_json_file("interface"), lang=settings.load_value_from_json_file("language"), + iscli=False, ) - self.textResultIsValid.SetValue("Yes" if message["valid"] else "No") self.textResultCode.SetValue(message["errorcode"]) - self.textResultDetails.SetValue(message["errorcode_description"]) + self.textResultDetails.SetValue(message.get("errorcode_description", "")) + + # In case of empty errorcode_description, load the company, address twon, zip and street into textResultDetails + if message.get("errorcode_description", "") == "": + self.textResultDetails.SetValue( + f"Company: {message['company']}\nAddress: {message['address']}\nTown: {message['town']}\nZip: {message['zip']}\nStreet: {message['street']}" + ) def validateBatch(self, event): if ( diff --git a/src/vatvalidation_cli.py b/src/vatvalidation_cli.py new file mode 100644 index 0000000..6c5d5ba --- /dev/null +++ b/src/vatvalidation_cli.py @@ -0,0 +1,55 @@ +import argparse +import sys +import helper +import batch + +# Create the parser with an extended description or epilog +parser = argparse.ArgumentParser( + description=helper.NAME + " CLI" + " - " + helper.VERSION, + epilog="For more information, visit our GitHub repository: https://github.com/dseichter/VATValidation\r\n\n" + "License: MIT License - see LICENSE file at the root of the repository for details.", +) + +# Add the version argument +parser.add_argument("--version", action="version", version=helper.VERSION) + +# Add the input file argument +parser.add_argument( + "--input", type=str, help="Input file path for VAT numbers.", required=True +) + +# Add the output file argument +parser.add_argument( + "--output", type=str, help="Output file path for validation results.", required=True +) + +# Check if no arguments were passed +if len(sys.argv) == 1: + parser.print_help(sys.stderr) + sys.exit(1) + +# Parse the arguments +args = parser.parse_args() + +# You can now use args.input and args.output for further processing +print( + "Start batch validation with input file: " + + args.input + + " and output file: " + + args.output +) +response = batch.validatebatch(inputfile=args.input, outputfile=args.output, iscli=True) + +match response: + case 0: + print("Validation successful") + exit(0) + case 127: + print("Unsupported file format") + exit(127) + case True: + print("Validation successful") + exit(0) + case _: + print("Validation failed: " + str(response)) + exit(1) From 4fa669950dbe919fb643fe99cdca9bbe5b089755 Mon Sep 17 00:00:00 2001 From: Daniel Seichter Date: Mon, 8 Jul 2024 22:22:17 +0200 Subject: [PATCH 2/3] #4 Build cli during release --- .github/workflows/release.yml | 79 ++++++++++++++++++++++++++++++++-- icons/Tick_Box.ico | Bin 0 -> 33588 bytes 2 files changed, 75 insertions(+), 4 deletions(-) create mode 100644 icons/Tick_Box.ico diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c7772c8..cfd7843 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -28,13 +28,42 @@ jobs: run: pip install -r src/requirements.txt - name: Build Windows binary - run: pyinstaller --onefile -w src/vatvalidation.py -n VATValidation-windows-${{ github.ref_name }}.exe + run: pyinstaller --onefile -w src/vatvalidation.py -n VATValidation-windows-${{ github.ref_name }}.exe --icon=icons/Tick_Box.ico - name: Upload artifact uses: actions/upload-artifact@v4 with: name: VATValidation-windows-${{ github.ref_name }}.exe - path: dist/VATValidation-windows-${{ github.ref_name }}.exe + path: dist/VATValidation-windows-${{ github.ref_name }}.exe + + build-windows-binary-cli: + runs-on: windows-latest + steps: + + - name: 'Checkout' + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Create and start virtual environment + run: | + python3 -m venv venv + venv\Scripts\activate.bat + + - name: Install dependencies + run: pip install -r src/requirements.txt + + - name: Build Windows binary + run: pyinstaller --onefile -w src/vatvalidation_cli.py -n VATValidation-cli-windows-${{ github.ref_name }}.exe + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: VATValidation-cli-windows-${{ github.ref_name }}.exe + path: dist/VATValidation-cli-windows-${{ github.ref_name }}.exe build-linux-binary: runs-on: ubuntu-latest @@ -60,7 +89,7 @@ jobs: run: pip install -r src/requirements.txt - name: Build Linux binary - run: pyinstaller --onefile src/vatvalidation.py -n VATValidation-linux-${{ github.ref_name }} + run: pyinstaller --onefile src/vatvalidation.py -n VATValidation-linux-${{ github.ref_name }} --icon=icons/Tick_Box.ico - name: Upload artifact uses: actions/upload-artifact@v4 @@ -68,6 +97,38 @@ jobs: name: VATValidation-linux-${{ github.ref_name }} path: dist/VATValidation-linux-${{ github.ref_name }} + build-linux-binary-cli: + runs-on: ubuntu-latest + steps: + + - name: 'Checkout' + uses: actions/checkout@v4 + + - name: Install dependencies + run: sudo apt-get install build-essential libgtk-3-dev + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: 3.12 + + - name: Create and start virtual environment + run: | + python3 -m venv venv + source venv/bin/activate + + - name: Install dependencies + run: pip install -r src/requirements.txt + + - name: Build Linux binary + run: pyinstaller --onefile src/vatvalidation.py -n VATValidation-cli-linux-${{ github.ref_name }} + + - name: Upload artifact + uses: actions/upload-artifact@v4 + with: + name: VATValidation-cli-linux-${{ github.ref_name }} + path: dist/VATValidation-cli-linux-${{ github.ref_name }} + deploy: runs-on: ubuntu-latest needs: [build-windows-binary, build-linux-binary] @@ -76,10 +137,18 @@ jobs: with: name: VATValidation-linux-${{ github.ref_name }} + - uses: actions/download-artifact@v4 + with: + name: VATValidation-cli-linux-${{ github.ref_name }} + - uses: actions/download-artifact@v4 with: name: VATValidation-windows-${{ github.ref_name }}.exe + - uses: actions/download-artifact@v4 + with: + name: VATValidation-cli-windows-${{ github.ref_name }}.exe + - name: Create Release id: create_release uses: softprops/action-gh-release@v2 @@ -91,4 +160,6 @@ jobs: generate_release_notes: true files: | VATValidation-linux-${{ github.ref_name }} - VATValidation-windows-${{ github.ref_name }}.exe \ No newline at end of file + VATValidation-windows-${{ github.ref_name }}.exe + VATValidation-cli-linux-${{ github.ref_name }} + VATValidation-cli-windows-${{ github.ref_name }}.exe \ No newline at end of file diff --git a/icons/Tick_Box.ico b/icons/Tick_Box.ico new file mode 100644 index 0000000000000000000000000000000000000000..c23c1cad3dcde2f87b3bdf7457de09b37aaa106c GIT binary patch literal 33588 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 Date: Mon, 8 Jul 2024 22:26:26 +0200 Subject: [PATCH 3/3] #4 Adjust readme --- README.md | 28 ++++++++++++++++++++++++++++ src/helper.py | 2 +- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a3b2104..174068b 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,34 @@ If you like this software, please don't hesitate to give it a :star: or send me VAT Validation supports you in checking and validating your master data. Whether you want to check a VAT number directly at your workplace or integrate it into your ERP/CRM applications. +## Command Line tool + +If you want to check batch files (e.g. json, csv or XLSX) you can download the releases like VATValidation-cli-windows-v2024-07-08.exe and rename it to vatvalidation_cli.exe. + +Just run the tool and by providing an input and an output file. For further information, please run + +``vatvalidation_cli.exe`` + +```shell +usage: vatvalidation_cli.py [-h] [--version] --input INPUT --output OUTPUT + +VAT-Validation CLI - v2024-07-06 + +options: + -h, --help show this help message and exit + --version show program's version number and exit + --input INPUT Input file path for VAT numbers. + --output OUTPUT Output file path for validation results. + +For more information, visit our GitHub repository: https://github.com/dseichter/VATValidation License: MIT License - see LICENSE file at the root of the repository for details. +``` + +Only batch processing is provided. + +## Graphical User Interface + +I provide a graphical user interface for single and batch validation. + ### Single Validation You can use the software at your workplace and check directly against the official supported interfaces of BZSt, VIES and/or HMRC. diff --git a/src/helper.py b/src/helper.py index d00b020..c920378 100644 --- a/src/helper.py +++ b/src/helper.py @@ -3,7 +3,7 @@ import logging -VERSION = "v2024-07-06" +VERSION = "v2024-07-08" UPDATEURL = 'https://api.github.com/repos/dseichter/VATValidation/releases/latest' RELEASES = 'https://github.com/dseichter/VATValidation/releases' NAME = 'VAT-Validation'