├── .coveragerc ├── .github ├── pr_checks_config.yml └── workflows │ ├── pr_checks.yml │ ├── pytest_action.yml │ ├── pythonpublish.yml │ └── security-scan.yml ├── .gitignore ├── LICENSE ├── README.md ├── docs ├── Makefile ├── _api │ ├── api.rst │ ├── netsuitesdk.client.rst │ ├── netsuitesdk.constants.rst │ ├── netsuitesdk.exceptions.rst │ ├── netsuitesdk.netsuite_types.rst │ ├── netsuitesdk.rst │ └── netsuitesdk.utils.rst ├── _examples │ ├── search_all.rst │ ├── search_customer_by_name.rst │ ├── search_transactions_by_accounts.rst │ └── upsert_vendor_bill.rst ├── conf.py ├── examples.rst ├── for_contributors.rst ├── index.rst ├── installation.rst ├── make.bat └── quickstart.rst ├── netsuitesdk ├── __init__.py ├── api │ ├── __init__.py │ ├── accounts.py │ ├── adv_inter_company_journal_entries.py │ ├── base.py │ ├── billing_account.py │ ├── classifications.py │ ├── credit_memos.py │ ├── currencies.py │ ├── custom_lists.py │ ├── custom_record_types.py │ ├── custom_records.py │ ├── custom_segments.py │ ├── customers.py │ ├── departments.py │ ├── employees.py │ ├── expense_categories.py │ ├── expense_reports.py │ ├── files.py │ ├── folders.py │ ├── invoices.py │ ├── items.py │ ├── journal_entries.py │ ├── locations.py │ ├── price_level.py │ ├── projects.py │ ├── subsidiaries.py │ ├── tax_groups.py │ ├── tax_items.py │ ├── terms.py │ ├── usage.py │ ├── vendor_bills.py │ ├── vendor_credits.py │ ├── vendor_payments.py │ └── vendors.py ├── connection.py ├── errors │ ├── __init__.py │ ├── errors.py │ ├── helpers.py │ └── parser.py └── internal │ ├── __init__.py │ ├── client.py │ ├── constants.py │ ├── exceptions.py │ ├── netsuite_types.py │ └── utils.py ├── pytest.ini ├── requirements.txt ├── setup.py └── test ├── integration ├── __init__.py ├── conftest.py ├── data │ ├── adv_inter_company_journal_entries │ │ └── data.json │ ├── billing_account │ │ └── data.json │ ├── credit_memo │ │ └── data.json │ ├── custom_record │ │ └── data.json │ ├── customers │ │ └── data.json │ ├── employee │ │ └── data.json │ ├── expense_reports │ │ └── data.json │ ├── file │ │ └── data.json │ ├── folder │ │ └── data.json │ ├── invoices │ │ └── data.json │ ├── journal_entries │ │ └── data.json │ ├── usage │ │ └── data.json │ ├── vendor │ │ └── data.json │ ├── vendor_bills │ │ ├── 860860_sb1.json │ │ ├── data_expense_and_items.json │ │ ├── data_expenses_only.json │ │ └── data_items_only.json │ ├── vendor_credit │ │ └── data.json │ └── vendor_payment │ │ └── data.json ├── test_accounts.py ├── test_adv_inter_company_journal_entries.py ├── test_billing_account.py ├── test_classifications.py ├── test_credit_memos.py ├── test_currencies.py ├── test_custom_list.py ├── test_custom_record_types.py ├── test_custom_records.py ├── test_custom_segments.py ├── test_customers.py ├── test_departments.py ├── test_employees.py ├── test_expense_reports.py ├── test_files.py ├── test_folder.py ├── test_invoices.py ├── test_items.py ├── test_journal_entries.py ├── test_locations.py ├── test_subsidiaries.py ├── test_terms.py ├── test_usage.py ├── test_vendor_bills.py ├── test_vendor_credit.py ├── test_vendor_payments.py └── test_vendors.py └── internal ├── __init__.py ├── conftest.py ├── test_base.py ├── test_count.py ├── test_get.py ├── test_get_all.py ├── test_get_complex_type_element.py ├── test_helper.py ├── test_logout.py ├── test_parser.py ├── test_search.py └── test_upsert.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | source = netsuitesdk/ 3 | omit = ./venv/*, tests/*, netsuitesdk/api/billing_account.py, netsuitesdk/api/credit_memos.py, netsuitesdk/api/custom_lists.py, netsuitesdk/api/vendor_credits.py, netsuitesdk/api/usage.py, netsuitesdk/api/invoices.py, netsuitesdk/api/terms.py, netsuitesdk/api/custom_segments.py -------------------------------------------------------------------------------- /.github/pr_checks_config.yml: -------------------------------------------------------------------------------- 1 | pr_checks: 2 | title: 3 | - name: 'prefix_check' 4 | regex: '^(?i)(fix|feat|test|chore|refactor|build):' 5 | message_if_not_matching: 'PR title must start with "fix:", "feat:", "chore:", "refactor", or "test:" (case-insensitive)' 6 | 7 | description: 8 | - name: 'clickup_check' 9 | regex: '(?i)app.clickup.com' 10 | message_if_not_matching: 'PR description must contain a link to a ClickUp (case-insensitive)' -------------------------------------------------------------------------------- /.github/workflows/pr_checks.yml: -------------------------------------------------------------------------------- 1 | name: Strong PR Checks 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, edited] 6 | 7 | permissions: 8 | pull-requests: write 9 | contents: read 10 | 11 | jobs: 12 | pr_checks: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - name: Run strong checks 16 | uses: fylein/fyle-pr-action@v1 17 | with: 18 | github-token: ${{ secrets.GITHUB_TOKEN }} 19 | config-file: .github/pr_checks_config.yml -------------------------------------------------------------------------------- /.github/workflows/pytest_action.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: 4 | pull_request: 5 | types: [assigned, opened, synchronize, reopened] 6 | 7 | jobs: 8 | pytest: 9 | runs-on: ubuntu-latest 10 | environment: CI Environment 11 | steps: 12 | - uses: actions/checkout@v2 13 | continue-on-error: true 14 | - name: Run Tests 15 | env: 16 | NS_ACCOUNT: ${{ secrets.NS_ACCOUNT }} 17 | NS_CONSUMER_KEY: ${{ secrets.NS_CONSUMER_KEY }} 18 | NS_CONSUMER_SECRET: ${{ secrets.NS_CONSUMER_SECRET }} 19 | NS_TOKEN_KEY: ${{ secrets.NS_TOKEN_KEY }} 20 | NS_TOKEN_SECRET: ${{ secrets.NS_TOKEN_SECRET }} 21 | NS_APPID: ${{ secrets.NS_APPID }} 22 | NS_EMAIL: ${{ secrets.NS_EMAIL }} 23 | NS_PASSWORD: ${{ secrets.NS_PASSWORD }} 24 | run: | 25 | pip install -r requirements.txt && pip install pytest pytest-coverage 26 | python -m pytest test/ --cov --junit-xml=test-reports/report.xml --cov-report=term-missing --cov-fail-under=85 | tee pytest-coverage.txt 27 | echo "STATUS=$(cat pytest-coverage.txt | grep 'Required test' | awk '{ print $1 }')" >> $GITHUB_ENV 28 | echo "${{ env.STATUS }}" 29 | echo "FAILED=$(cat test-reports/report.xml | awk -F'=' '{print $5}' | awk -F' ' '{gsub(/"/, "", $1); print $1}')" >> $GITHUB_ENV 30 | - name: Pytest coverage comment 31 | uses: MishaKav/pytest-coverage-comment@main 32 | if: ${{ always() && github.ref != 'refs/heads/master' }} 33 | with: 34 | create-new-comment: true 35 | pytest-coverage-path: ./pytest-coverage.txt 36 | junitxml-path: ./test-reports/report.xml 37 | - name: Evaluate Coverage 38 | if: ${{ (env.STATUS == 'FAIL') || (env.FAILED > 0) }} 39 | run: exit 1 -------------------------------------------------------------------------------- /.github/workflows/pythonpublish.yml: -------------------------------------------------------------------------------- 1 | name: Upload Python Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Set up Python 13 | uses: actions/setup-python@v1 14 | with: 15 | python-version: '3.x' 16 | - name: Install dependencies 17 | run: | 18 | python -m pip install --upgrade pip 19 | pip install setuptools wheel twine 20 | - name: Build and publish 21 | env: 22 | TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} 23 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} 24 | run: | 25 | python setup.py sdist bdist_wheel 26 | twine upload dist/* 27 | -------------------------------------------------------------------------------- /.github/workflows/security-scan.yml: -------------------------------------------------------------------------------- 1 | name: Security Scan 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, reopened] 6 | branches: 7 | - master 8 | 9 | jobs: 10 | security-scan: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - name: Get changed files 18 | id: changed-files 19 | uses: tj-actions/changed-files@v46 20 | with: 21 | files: | 22 | **/requirements.txt 23 | 24 | - name: Run Vulnerability Scanner 25 | if: steps.changed-files.outputs.any_changed == 'true' 26 | uses: fylein/vulnerability-scan-action@master 27 | with: 28 | github_token: ${{ secrets.GITHUB_TOKEN }} 29 | is_submodule: true 30 | 31 | - name: Skip Vulnerability Scanner 32 | if: steps.changed-files.outputs.any_changed != 'true' 33 | run: echo "No changes to dependency files, skipping vulnerability scan." 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Dockerfile 10 | Dockerfile 11 | 12 | # test_files 13 | dry_run.py 14 | test_* 15 | !test/*/* 16 | !test/* 17 | 18 | # IDEs 19 | .DS_Store 20 | 21 | # Folders 22 | test_scripts/ 23 | 24 | # cache file 25 | cache.db 26 | 27 | # Distribution / packaging 28 | .Python 29 | build/ 30 | develop-eggs/ 31 | dist/ 32 | downloads/ 33 | eggs/ 34 | .eggs/ 35 | lib/ 36 | lib64/ 37 | parts/ 38 | sdist/ 39 | var/ 40 | wheels/ 41 | *.egg-info/ 42 | .installed.cfg 43 | *.egg 44 | .idea/ 45 | MANIFEST 46 | 47 | # PyInstaller 48 | # Usually these files are written by a python script from a template 49 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 50 | *.manifest 51 | *.spec 52 | 53 | # Installer logs 54 | pip-log.txt 55 | pip-delete-this-directory.txt 56 | 57 | # Unit test / coverage reports 58 | htmlcov/ 59 | .tox/ 60 | .coverage 61 | .coverage.* 62 | .cache 63 | nosetests.xml 64 | coverage.xml 65 | *.cover 66 | .hypothesis/ 67 | .pytest_cache/ 68 | 69 | # Translations 70 | *.mo 71 | *.pot 72 | 73 | # Django stuff: 74 | *.log 75 | local_settings.py 76 | db.sqlite3 77 | 78 | # Flask stuff: 79 | instance/ 80 | .webassets-cache 81 | 82 | # Scrapy stuff: 83 | .scrapy 84 | 85 | # Sphinx documentation 86 | docs/_build/ 87 | 88 | # PyBuilder 89 | target/ 90 | 91 | # Jupyter Notebook 92 | .ipynb_checkpoints 93 | 94 | # pyenv 95 | .python-version 96 | 97 | # celery beat schedule file 98 | celerybeat-schedule 99 | 100 | # SageMath parsed files 101 | *.sage.py 102 | 103 | # Environments 104 | .env 105 | .venv 106 | env/ 107 | venv/ 108 | ENV/ 109 | env.bak/ 110 | venv.bak/ 111 | 112 | # Spyder project settings 113 | .spyderproject 114 | .spyproject 115 | 116 | # Rope project settings 117 | .ropeproject 118 | 119 | # mkdocs documentation 120 | /site 121 | 122 | # mypy 123 | .mypy_cache/ 124 | 125 | test.py 126 | secrets.sh 127 | 128 | make_mock.py 129 | private/ 130 | 131 | cov_html/ 132 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 fylein 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line, and also 5 | # from the environment for the first two. 6 | SPHINXOPTS ?= 7 | SPHINXBUILD ?= sphinx-build 8 | SOURCEDIR = . 9 | BUILDDIR = _build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | buildapi: 18 | sphinx-apidoc -fMeT ../netsuitesdk -o _api 19 | @echo "Auto-generation of API documentation finished. " \ 20 | "The generated files are in '_api/'" 21 | 22 | # Catch-all target: route all unknown targets to Sphinx using the new 23 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 24 | %: Makefile 25 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) -------------------------------------------------------------------------------- /docs/_api/api.rst: -------------------------------------------------------------------------------- 1 | The netsuite-sdk-py API Reference 2 | =================================== 3 | 4 | Modules 5 | ---------- 6 | 7 | .. toctree:: 8 | 9 | netsuitesdk.client 10 | netsuitesdk.constants 11 | netsuitesdk.exceptions 12 | netsuitesdk.netsuite_types 13 | netsuitesdk.utils 14 | 15 | Classes 16 | --------- 17 | 18 | • :class:`~netsuitesdk.client.NetSuiteClient` 19 | • :class:`~netsuitesdk.utils.PaginatedSearch` 20 | • :class:`~netsuitesdk.utils.User` 21 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.client.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk.client module 2 | ========================= 3 | 4 | .. automodule:: netsuitesdk.client 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.constants.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk.constants module 2 | ============================ 3 | 4 | .. automodule:: netsuitesdk.constants 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.exceptions.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk.exceptions module 2 | ============================= 3 | 4 | .. automodule:: netsuitesdk.exceptions 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.netsuite_types.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk.netsuite\_types module 2 | ================================== 3 | 4 | .. automodule:: netsuitesdk.netsuite_types 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk package 2 | =================== 3 | 4 | .. automodule:: netsuitesdk 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | 9 | Submodules 10 | ---------- 11 | 12 | .. toctree:: 13 | 14 | netsuitesdk.client 15 | netsuitesdk.constants 16 | netsuitesdk.exceptions 17 | netsuitesdk.netsuite_types 18 | netsuitesdk.utils 19 | -------------------------------------------------------------------------------- /docs/_api/netsuitesdk.utils.rst: -------------------------------------------------------------------------------- 1 | netsuitesdk.utils module 2 | ======================== 3 | 4 | .. automodule:: netsuitesdk.utils 5 | :members: 6 | :undoc-members: 7 | :show-inheritance: 8 | -------------------------------------------------------------------------------- /docs/_examples/search_all.rst: -------------------------------------------------------------------------------- 1 | Retrieve all objects of given NetSuite type 2 | ============================================= 3 | 4 | .. literalinclude:: ../../examples/search_all.py -------------------------------------------------------------------------------- /docs/_examples/search_customer_by_name.rst: -------------------------------------------------------------------------------- 1 | Search Customer by name 2 | ============================================= 3 | 4 | .. literalinclude:: ../../examples/search_customer_by_name.py -------------------------------------------------------------------------------- /docs/_examples/search_transactions_by_accounts.rst: -------------------------------------------------------------------------------- 1 | Search for all transactions who belong to a list of accounts 2 | ============================================================== 3 | 4 | .. literalinclude:: ../../examples/search_transactions_by_accounts.py -------------------------------------------------------------------------------- /docs/_examples/upsert_vendor_bill.rst: -------------------------------------------------------------------------------- 1 | Upsert a vendor bill with expenses 2 | ============================================= 3 | 4 | .. literalinclude:: ../../examples/upsert_vendor_bill.py -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Configuration file for the Sphinx documentation builder. 2 | # 3 | # This file only contains a selection of the most common options. For a full 4 | # list see the documentation: 5 | # http://www.sphinx-doc.org/en/master/config 6 | 7 | # -- Path setup -------------------------------------------------------------- 8 | 9 | # If extensions (or modules to document with autodoc) are in another directory, 10 | # add these directories to sys.path here. If the directory is relative to the 11 | # documentation root, use os.path.abspath to make it absolute, like shown here. 12 | # 13 | import os 14 | import sys 15 | # sys.path.insert(0, os.path.abspath('.')) 16 | 17 | sys.path.insert(0, os.path.abspath('..')) 18 | 19 | 20 | # -- Project information ----------------------------------------------------- 21 | 22 | project = 'netsuite-sdk-py' 23 | copyright = '2019, Sivaramakrishnan Narayanan, Lothar Spiegel' 24 | author = 'Sivaramakrishnan Narayanan, Lothar Spiegel' 25 | 26 | 27 | # -- General configuration --------------------------------------------------- 28 | 29 | # Add any Sphinx extension module names here, as strings. They can be 30 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 31 | # ones. 32 | extensions = [ 33 | 'sphinx.ext.autodoc', 34 | 'sphinx.ext.viewcode', 35 | ] 36 | 37 | # Add any paths that contain templates here, relative to this directory. 38 | templates_path = ['_templates'] 39 | 40 | # List of patterns, relative to source directory, that match files and 41 | # directories to ignore when looking for source files. 42 | # This pattern also affects html_static_path and html_extra_path. 43 | exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] 44 | 45 | 46 | # -- Options for HTML output ------------------------------------------------- 47 | 48 | # The theme to use for HTML and HTML Help pages. See the documentation for 49 | # a list of builtin themes. 50 | # 51 | html_theme = 'classic' 52 | 53 | # Add any paths that contain custom static files (such as style sheets) here, 54 | # relative to this directory. They are copied after the builtin static files, 55 | # so a file named "default.css" will overwrite the builtin "default.css". 56 | html_static_path = ['_static'] 57 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | =========================================== 3 | 4 | More elaborate examples to show basic usage of this netsuite sdk: 5 | 6 | .. toctree:: 7 | 8 | _examples/search_all 9 | _examples/search_customer_by_name 10 | _examples/search_transactions_by_accounts 11 | 12 | _examples/upsert_vendor_bill 13 | -------------------------------------------------------------------------------- /docs/for_contributors.rst: -------------------------------------------------------------------------------- 1 | For Contributors 2 | ==================== 3 | 4 | Todo 5 | ----- 6 | 7 | - In :mod:`netsuitesdk.netsuite_types` add missing types. Actually, at the moment only a fraction of all NetSuite types are listed. Even better: add utility functions to lookup the namespace which contains a NetSuite type. Maybe overwrite `__getattribute__` of class :class:`~netsuitesdk.client.NetSuiteClient` 8 | 9 | - Test and document token based authentication (in principle already implemented with :func:`~netsuitesdk.client.NetSuiteClient.create_token_passport()` and by passing created passport as `tokenPassport` in headers of requests like get, etc.) 10 | 11 | - Test and document more advanced search functionality (like join searches, ..). 12 | 13 | - Add & test netsuite operations: add, update, delete 14 | 15 | - Refactor all request functions like get, search, upsert, .. in :mod:`netsuitesdk.client` using decorator `request_service` -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. netsuite-sdk-py documentation master file, created by 2 | sphinx-quickstart on Tue Jun 25 16:26:07 2019. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to netsuite-sdk-py's documentation! 7 | =========================================== 8 | 9 | Netsuite-sdk-py is a Python SDK using the SOAP client library zeep(https://python-zeep.readthedocs.io/en/master/) for accessing NetSuite resources via the NetSuite SOAP web service SuiteTalk(http://www.netsuite.com/portal/platform/developer/suitetalk.shtml). 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | :caption: Contents: 14 | 15 | installation.rst 16 | quickstart.rst 17 | examples.rst 18 | _api/api.rst 19 | for_contributors.rst 20 | 21 | 22 | Indices and tables 23 | ================== 24 | 25 | * :ref:`genindex` 26 | * :ref:`modindex` 27 | * :ref:`search` 28 | -------------------------------------------------------------------------------- /docs/installation.rst: -------------------------------------------------------------------------------- 1 | Installation 2 | =========================================== 3 | 4 | Installation via Pip: :: 5 | 6 | $ pip install netsuitesdk 7 | 8 | .. note:: This python NetSuite SDK uses the SOAP/WSDL library `Zeep `_ which should automatically be installed when running above pip-command. Otherwise you can run :code:`$ pip install zeep` first. -------------------------------------------------------------------------------- /docs/make.bat: -------------------------------------------------------------------------------- 1 | @ECHO OFF 2 | 3 | pushd %~dp0 4 | 5 | REM Command file for Sphinx documentation 6 | 7 | if "%SPHINXBUILD%" == "" ( 8 | set SPHINXBUILD=sphinx-build 9 | ) 10 | set SOURCEDIR=. 11 | set BUILDDIR=_build 12 | 13 | if "%1" == "" goto help 14 | if "%1" == "buildapi" goto buildapi 15 | 16 | %SPHINXBUILD% >NUL 2>NUL 17 | if errorlevel 9009 ( 18 | echo. 19 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx 20 | echo.installed, then set the SPHINXBUILD environment variable to point 21 | echo.to the full path of the 'sphinx-build' executable. Alternatively you 22 | echo.may add the Sphinx directory to PATH. 23 | echo. 24 | echo.If you don't have Sphinx installed, grab it from 25 | echo.http://sphinx-doc.org/ 26 | exit /b 1 27 | ) 28 | 29 | %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 30 | goto end 31 | 32 | :help 33 | %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% 34 | 35 | :buildapi 36 | sphinx-apidoc -fMeT ../netsuitesdk -o _api 37 | echo.Auto-generation of API documentation finished. 38 | echo.The generated files are in '_api/' 39 | 40 | :end 41 | popd 42 | 43 | -------------------------------------------------------------------------------- /docs/quickstart.rst: -------------------------------------------------------------------------------- 1 | Quickstart 2 | =========================================== 3 | 4 | There are the following options to access a NetSuite account via web services: 5 | 6 | - Either pass credentials (email, password, role and account Id) via login and start a request session 7 | 8 | - Pass credentials in the header of each request 9 | 10 | - Use token based authentication (within each request) 11 | 12 | Login with credentials 13 | ------------------------ 14 | 15 | The following code performs a login to a NetSuite account and starts a web service session: :: 16 | 17 | from netsuitesdk import NetSuiteClient 18 | 19 | # Initialize the NetSuite client instance by passing the application Id 20 | # which will be passed to the request header in the login operation. 21 | ns = NetSuiteClient(caching='sqlite', debug=True) 22 | 23 | passport = ns.create_passport(email=NS_EMAIL, 24 | password=NS_PASSWORD, 25 | role=NS_ROLE, 26 | account=NS_ACCOUNT) 27 | 28 | # Authenticate the user and start a new webservice session in NetSuite 29 | ns.login(app_id=NS_APPID, passport=passport) 30 | 31 | # Make requests. All requests done in this session will be identified 32 | # with the application Id passed to the login operation 33 | 34 | ns.logout() 35 | 36 | To avoid storing the credentials in the python source code file, one can use 37 | python-dotenv (:code:`$ pip install python-dotenv`) to load authentication 38 | credentials from a .env-file as environment variables. The .env-file would look something like this:: 39 | 40 | NS_EMAIL = '*****@example.com' 41 | NS_PASSWORD = '*******' 42 | NS_ROLE = '1047' 43 | NS_ACCOUNT = '*********' 44 | NS_APPID = '********-****-****-****-************' 45 | 46 | and the variables could be loaded as follows: :: 47 | 48 | import os 49 | from dotenv import load_dotenv 50 | 51 | load_dotenv() 52 | NS_EMAIL = os.getenv("NS_EMAIL") 53 | NS_PASSWORD = os.getenv("NS_PASSWORD") 54 | NS_ROLE = os.getenv("NS_ROLE") 55 | NS_ACCOUNT = os.getenv("NS_ACCOUNT") 56 | NS_APPID = os.getenv("NS_APPID") 57 | 58 | Remarks and possible errors regarding authentication 59 | ------------------------------------------------------ 60 | 61 | .. note:: NetSuite requires two-factor authentication (2FA) for all Administrator and other highly privileged roles in all NetSuite accounts. Instead, you can login with a non-highly privileged role or use token based authentication (TBA) with your requests. For TBA, see below. 62 | 63 | If login fails, a :class:`~netsuitesdk.exceptions.NetSuiteLoginError` is thrown. 64 | 65 | For more information about NetSuite authentication, see: (``_) 66 | 67 | Passing credentials with requests 68 | ----------------------------------- 69 | Every request can either be done inside a session as outlined above or one can 70 | additionally pass a passport in the headers of the request. 71 | This is done as follows: :: 72 | 73 | # First create a passport: 74 | passport = ns.create_passport(email=NS_EMAIL, 75 | password=NS_PASSWORD, 76 | role=NS_ROLE, 77 | account=NS_ACCOUNT) 78 | # In the request, pass a `headers` dictionary with passport and applicationInfo: 79 | headers = { 80 | 'applicationInfo': ns.ApplicationInfo(applicationId=NS_APPID), 81 | 'passport': passport, 82 | } 83 | vendor = ns.get('vendor', internalId=1, headers=headers) 84 | 85 | .. note:: In the basic examples below for the requests, the headers has to be added if they are used without login 86 | 87 | Token based authentication 88 | ----------------------------- 89 | tba 90 | 91 | Get Request 92 | ------------- 93 | A basic example: :: 94 | 95 | # `ns` is a reference to a :class:`~netsuitesdk.client.NetSuiteClient` instance 96 | vendor = ns.get('vendor', internalId=12) 97 | ns.print_values(vendor) 98 | 99 | Search 100 | ------- 101 | To perform a search request, :func:`~netsuitesdk.client.NetSuiteClient.search` can be used. 102 | Further, the SDK provides some utility functions/classes: 103 | 104 | - :func:`~netsuitesdk.client.NetSuiteClient.basic_stringfield_search`: A basic example: :: 105 | 106 | # `ns` is a reference to a :class:`~netsuitesdk.client.NetSuiteClient` instance 107 | records = ns.basic_stringfield_search(type_name='Vendor', 108 | attribute='entityId', 109 | value='Alexander Valley Vineyards', 110 | operator='is') 111 | print(records[0].internalId) 112 | 113 | - :class:`~netsuitesdk.utils.PaginatedSearch`: An utility class that uses the NetSuite requests `search` and `searchMoreWithId` to perform a search and paginate the results. It is used in the following examples: 114 | - :doc:`_examples/search_all` 115 | 116 | - :doc:`_examples/upsert_vendor_bill` 117 | 118 | Upsert 119 | -------- 120 | Basic example: :: 121 | 122 | # `ns` is a reference to a :class:`~netsuitesdk.client.NetSuiteClient` instance 123 | vendor = ns.Vendor() 124 | vendor.externalId = 'test_vendor' 125 | vendor.companyName = 'Another Test Inc.' 126 | ref = ns.upsert(record=vendor) 127 | 128 | See also 129 | - :doc:`_examples/upsert_vendor_bill` 130 | 131 | UpsertList 132 | ------------- 133 | Basic example: :: 134 | 135 | # `ns` is a reference to a :class:`~netsuitesdk.client.NetSuiteClient` instance 136 | customer1 = ns.Customer(externalId='customer', email='test1@example.com') 137 | customer2 = ns.Customer(externalId='another_customer', email='test2@example.com') 138 | ns.upsertList(records=[customer1, customer2]) 139 | -------------------------------------------------------------------------------- /netsuitesdk/__init__.py: -------------------------------------------------------------------------------- 1 | from .connection import NetSuiteConnection 2 | from .internal.exceptions import * 3 | 4 | 5 | __all__ = [ 6 | 'NetSuiteConnection' 7 | 'NetSuiteError', 8 | 'NetSuiteLoginError', 9 | 'NetSuiteRequestError', 10 | 'NetSuiteTypeError', 11 | 'NetSuiteRateLimitError' 12 | ] 13 | 14 | name = "netsuitesdk" 15 | -------------------------------------------------------------------------------- /netsuitesdk/api/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fylein/netsuite-sdk-py/c66c398997d0e624101a26b036958d88a55a85d2/netsuitesdk/api/__init__.py -------------------------------------------------------------------------------- /netsuitesdk/api/accounts.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Accounts(ApiBase): 7 | 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='Account') 10 | 11 | -------------------------------------------------------------------------------- /netsuitesdk/api/adv_inter_company_journal_entries.py: -------------------------------------------------------------------------------- 1 | from .journal_entries import JournalEntries 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class AdvInterCompanyJournalEntries(JournalEntries): 8 | SIMPLE_FIELDS = [ 9 | 'memo', 10 | 'tranDate', 11 | 'tranId' 12 | ] 13 | 14 | RECORD_REF_FIELDS = [ 15 | 'class', 16 | 'currency', 17 | 'department', 18 | 'location', 19 | 'subsidiary', 20 | 'toSubsidiary' 21 | ] 22 | 23 | TYPE_NAME = 'advInterCompanyJournalEntry' 24 | -------------------------------------------------------------------------------- /netsuitesdk/api/base.py: -------------------------------------------------------------------------------- 1 | import zeep 2 | import logging 3 | from collections import OrderedDict 4 | 5 | from netsuitesdk.internal.client import NetSuiteClient 6 | from netsuitesdk.internal.utils import PaginatedSearch 7 | from typing import List, Generator 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | # TODO: introduce arg and return types 13 | class ApiBase: 14 | def __init__(self, ns_client: NetSuiteClient, type_name): 15 | self.ns_client = ns_client 16 | self.type_name = type_name 17 | 18 | def search(self, attribute, value, operator): 19 | """ 20 | Search Record 21 | :param attribute: name of the field, eg. entityId 22 | :param value: value of the field, eg. Amazon 23 | :param operator: search matching operator, eg., 'contains', 'is', 'anyOf' 24 | :return: 25 | """ 26 | records = self.ns_client.basic_stringfield_search( 27 | type_name=self.type_name, 28 | attribute=attribute, 29 | value=value, 30 | operator=operator 31 | ) 32 | 33 | return records 34 | 35 | def get_all(self): 36 | generated_records = self.get_all_generator() 37 | all_records = [] 38 | for records in generated_records: 39 | all_records.extend(records) 40 | return all_records 41 | 42 | def count(self): 43 | record_type_search_field = self.ns_client.SearchBooleanField(searchValue=False) 44 | basic_search = self.ns_client.basic_search_factory( 45 | type_name=self.type_name, 46 | isInactive=record_type_search_field, 47 | ) 48 | 49 | ps = PaginatedSearch(client=self.ns_client, type_name=self.type_name, pageSize=10, perform_search=True,basic_search=basic_search) 50 | return ps.total_records 51 | 52 | def get_all_generator(self, page_size=20): 53 | """ 54 | Returns a generator which is more efficient memory-wise 55 | """ 56 | return self.create_paginated_search(page_size=page_size) 57 | 58 | def get(self, internalId=None, externalId=None) -> OrderedDict: 59 | return self._get(internalId=internalId, externalId=externalId) 60 | 61 | def get_ref(self, internalId=None, externalId=None) -> OrderedDict: 62 | return self._serialize(self.ns_client.RecordRef(type=self.type_name.lower(), 63 | internalId=internalId, externalId=externalId)) 64 | 65 | def post(self, data) -> OrderedDict: 66 | raise NotImplementedError('post method not implemented') 67 | 68 | def delete(self, internalId=None, externalId=None) -> OrderedDict: 69 | record = self.ns_client.delete(recordType=self.type_name, internalId=internalId, externalId=externalId) 70 | return record 71 | 72 | def _serialize(self, record) -> OrderedDict: 73 | """ 74 | record: single record 75 | Returns a dict 76 | """ 77 | return zeep.helpers.serialize_object(record) 78 | 79 | def _serialize_array(self, records) -> List[OrderedDict]: 80 | """ 81 | records: a list of records 82 | Returns an array of dicts 83 | """ 84 | return zeep.helpers.serialize_object(records) 85 | 86 | @staticmethod 87 | def _paginated_search_to_generator(paginated_search) -> List: 88 | records = [] 89 | 90 | if paginated_search.num_records == 0: 91 | return records 92 | 93 | num_pages = paginated_search.total_pages 94 | logger.debug('total pages = %d, records in page = %d', paginated_search.total_pages, paginated_search.num_records) 95 | logger.debug(f'current page index {paginated_search.page_index}') 96 | logger.debug('going to page %d', 0) 97 | 98 | for p in range(1, num_pages + 1): 99 | logger.debug('going to page %d', p) 100 | paginated_search.goto_page(p) 101 | logger.debug(f'current page index {paginated_search.page_index}') 102 | records.extend(paginated_search.records) 103 | 104 | return records 105 | 106 | @staticmethod 107 | def _paginated_search_generator(paginated_search: PaginatedSearch): 108 | if paginated_search.num_records == 0: 109 | return 110 | 111 | num_pages = paginated_search.total_pages 112 | logger.debug('total pages = %d, records in page = %d', paginated_search.total_pages, paginated_search.num_records) 113 | logger.debug(f'current page index {paginated_search.page_index}') 114 | logger.debug('going to page %d', 0) 115 | 116 | for p in range(1, num_pages + 1): 117 | logger.debug('going to page %d', p) 118 | paginated_search.goto_page(p) 119 | logger.debug(f'current page index {paginated_search.page_index}') 120 | yield paginated_search.records 121 | 122 | def create_paginated_search(self, page_size): 123 | ps = PaginatedSearch(client=self.ns_client, type_name=self.type_name, pageSize=page_size) 124 | return self._paginated_search_generator(paginated_search=ps) 125 | 126 | def _search_all_generator(self, page_size): 127 | ps = PaginatedSearch(client=self.ns_client, type_name=self.type_name, pageSize=page_size) 128 | return self._paginated_search_to_generator(paginated_search=ps) 129 | 130 | def _get_all(self) -> List[OrderedDict]: 131 | records = self.ns_client.getAll(recordType=self.type_name) 132 | return self._serialize_array(records) 133 | 134 | def _get_all_generator(self): 135 | res = self._get_all() 136 | for r in res: 137 | yield r 138 | 139 | def _get(self, internalId=None, externalId=None) -> OrderedDict: 140 | record = self.ns_client.get(recordType=self.type_name, internalId=internalId, externalId=externalId) 141 | return record 142 | 143 | def build_simple_fields(self, fields, source, target): 144 | for field in fields: 145 | if field in source: 146 | target[field] = source[field] 147 | 148 | def build_record_ref_fields(self, fields, source, target): 149 | for field in fields: 150 | if field in source and source[field]: 151 | target[field] = self.ns_client.RecordRef(**(source[field])) 152 | -------------------------------------------------------------------------------- /netsuitesdk/api/billing_account.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class BillingAccount(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'createdBy', 12 | 'createdDate', 13 | 'customFieldList', 14 | 'customerDefault', 15 | 'frequency', 16 | 'idNumber', 17 | 'inactive', 18 | 'lastBillCycleDate', 19 | 'lastBillDate', 20 | 'memo', 21 | 'name', 22 | 'nextBillCycleDate', 23 | 'startDate', 24 | 'nullFieldList', 25 | ] 26 | 27 | RECORD_REF_FIELDS = [ 28 | 'billingSchedule', 29 | 'cashSaleForm', 30 | 'class', 31 | 'currency', 32 | 'customForm', 33 | 'customer', 34 | 'department', 35 | 'invoiceForm', 36 | 'location', 37 | 'subsidiary', 38 | ] 39 | 40 | def __init__(self, ns_client): 41 | ApiBase.__init__(self, ns_client=ns_client, type_name='BillingAccount') 42 | 43 | def post(self, data) -> OrderedDict: 44 | assert data['externalId'], 'missing external id' 45 | billing_account = self.ns_client.BillingAccount(**data) 46 | 47 | self.build_simple_fields(self.SIMPLE_FIELDS, data, billing_account) 48 | 49 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, billing_account) 50 | 51 | logger.debug('able to create billing account = %s', billing_account) 52 | 53 | res = self.ns_client.upsert(billing_account) 54 | return self._serialize(res) 55 | -------------------------------------------------------------------------------- /netsuitesdk/api/classifications.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Classifications(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='Classification') 9 | -------------------------------------------------------------------------------- /netsuitesdk/api/credit_memos.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class CreditMemos(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'accountingBookDetailList', 12 | 'altHandlingCost', 13 | 'altShippingCost', 14 | 'amountPaid', 15 | 'amountRemaining', 16 | 'applied', 17 | 'applyList', 18 | 'autoApply', 19 | 'balance', 20 | 'billingAddress', 21 | 'contribPct', 22 | 'createdDate', 23 | 'currencyName', 24 | 'customFieldList', 25 | 'deferredRevenue', 26 | 'discountRate', 27 | 'discountTotal', 28 | 'email', 29 | 'estGrossProfit', 30 | 'estGrossProfitPercent', 31 | 'exchangeRate', 32 | 'excludeCommission', 33 | 'fax', 34 | 'giftCertApplied', 35 | 'giftCertAvailable', 36 | 'giftCertTotal', 37 | 'handlingCost', 38 | 'handlingTax1Rate', 39 | 'handlingTax2Rate', 40 | 'internalId', 41 | 'isMultiShipTo', 42 | 'isTaxable', 43 | 'itemList', 44 | 'lastModifiedDate', 45 | 'memo', 46 | 'message', 47 | 'onCreditHold', 48 | 'otherRefNum', 49 | 'partnersList', 50 | 'recognizedRevenue', 51 | 'revRecOnRevCommitment', 52 | 'revenueStatus', 53 | 'salesEffectiveDate', 54 | 'salesTeamList', 55 | 'shippingCost', 56 | 'shippingTax1Rate', 57 | 'shippingTax2Rate', 58 | 'source', 59 | 'status', 60 | 'subTotal', 61 | 'syncPartnerTeams', 62 | 'syncSalesTeams', 63 | 'tax2Total', 64 | 'taxDetailsList', 65 | 'taxDetailsOverride', 66 | 'taxPointDate', 67 | 'taxRate', 68 | 'taxRegOverride', 69 | 'taxTotal', 70 | 'toBeEmailed', 71 | 'toBeFaxed', 72 | 'toBePrinted', 73 | 'total', 74 | 'totalCostEstimate', 75 | 'tranDate', 76 | 'tranId', 77 | 'tranIsVsoeBundle', 78 | 'unapplied', 79 | 'vatRegNum', 80 | 'vsoeAutoCalc', 81 | 'nullFieldList', 82 | ] 83 | 84 | RECORD_REF_FIELDS = [ 85 | 'account', 86 | 'billAddressList', 87 | 'class', 88 | 'createdFrom', 89 | 'currency', 90 | 'customForm', 91 | 'department', 92 | 'discountItem', 93 | 'entityTaxRegNum', 94 | 'giftCert', 95 | 'handlingTaxCode', 96 | 'job', 97 | 'leadSource', 98 | 'location', 99 | 'messageSel', 100 | 'nexus', 101 | 'partner', 102 | 'postingPeriod', 103 | 'promoCode', 104 | 'salesGroup', 105 | 'salesRep', 106 | 'shipMethod', 107 | 'shippingTaxCode', 108 | 'subsidiary', 109 | 'subsidiaryTaxRegNum', 110 | 'taxItem', 111 | ] 112 | 113 | def __init__(self, ns_client): 114 | ApiBase.__init__(self, ns_client=ns_client, type_name='CreditMemo') 115 | 116 | def post(self, data) -> OrderedDict: 117 | assert data['externalId'], 'missing external id' 118 | credit_memo = self.ns_client.CreditMemo(**data) 119 | 120 | credit_memo['entity'] = self.ns_client.RecordRef(**(data['entity'])) 121 | 122 | self.build_simple_fields(self.SIMPLE_FIELDS, data, credit_memo) 123 | 124 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, credit_memo) 125 | 126 | logger.debug('able to create credit memo = %s', credit_memo) 127 | 128 | res = self.ns_client.upsert(credit_memo) 129 | return self._serialize(res) 130 | -------------------------------------------------------------------------------- /netsuitesdk/api/currencies.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Currencies(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='Currency') 9 | 10 | def get_all(self): 11 | return self._get_all() 12 | 13 | def get_all_generator(self, page_size=20): 14 | """ 15 | Returns a generator which is more efficient memory-wise 16 | """ 17 | return self._get_all_generator() 18 | -------------------------------------------------------------------------------- /netsuitesdk/api/custom_lists.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class CustomLists(ApiBase): 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='CustomList') 10 | 11 | def get_all(self): 12 | return self._get_all() 13 | 14 | def get_all_generator(self, page_size=20): 15 | """ 16 | Returns a generator which is more efficient memory-wise 17 | """ 18 | return self._get_all_generator() 19 | -------------------------------------------------------------------------------- /netsuitesdk/api/custom_record_types.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.internal.utils import PaginatedSearch 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class CustomRecordTypes(ApiBase): 10 | 11 | def __init__(self, ns_client): 12 | ApiBase.__init__(self, ns_client=ns_client, type_name='CustomRecordType') 13 | 14 | def get_all_by_id(self, internalId): 15 | cr_type = self.ns_client.CustomRecordSearchBasic( 16 | recType=self.ns_client.CustomRecordType( 17 | internalId=internalId 18 | ) 19 | ) 20 | ps = PaginatedSearch(client=self.ns_client, type_name='CustomRecordType', search_record=cr_type, pageSize=20) 21 | return list(self._paginated_search_to_generator(paginated_search=ps)) 22 | -------------------------------------------------------------------------------- /netsuitesdk/api/custom_records.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class CustomRecords(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'allowAttachments', 12 | 'allowInlineEditing', 13 | 'allowNumberingOverride', 14 | 'allowQuickSearch', 15 | 'altName', 16 | 'autoName', 17 | 'created', 18 | 'customFieldList', 19 | 'customRecordId', 20 | 'description', 21 | 'disclaimer', 22 | 'enablEmailMerge', 23 | 'enableNumbering', 24 | 'includeName', 25 | 'internalId', 26 | 'isAvailableOffline', 27 | 'isInactive', 28 | 'isNumberingUpdateable', 29 | 'isOrdered', 30 | 'lastModified', 31 | 'name', 32 | 'numberingCurrentNumber', 33 | 'numberingInit', 34 | 'numberingMinDigits', 35 | 'numberingPrefix', 36 | 'numberingSuffix', 37 | 'recordName', 38 | 'scriptId', 39 | 'showCreationDate', 40 | 'showCreationDateOnList', 41 | 'showId', 42 | 'showLastModified', 43 | 'showLastModifiedOnList', 44 | 'showNotes', 45 | 'showOwner', 46 | 'showOwnerAllowChange', 47 | 'showOwnerOnList', 48 | 'translationsList', 49 | 'usePermissions', 50 | 'nullFieldList', 51 | ] 52 | 53 | RECORD_REF_FIELDS = [ 54 | 'customForm', 55 | 'owner', 56 | 'parent', 57 | 'recType', 58 | ] 59 | 60 | def __init__(self, ns_client): 61 | ApiBase.__init__(self, ns_client=ns_client, type_name='CustomRecord') 62 | 63 | def post(self, data) -> OrderedDict: 64 | assert data['externalId'], 'missing external id' 65 | record = self.ns_client.CustomRecord(externalId=data['externalId']) 66 | 67 | self.build_simple_fields(self.SIMPLE_FIELDS, data, record) 68 | 69 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, record) 70 | 71 | logger.debug('able to create custom record = %s', record) 72 | res = self.ns_client.upsert(record) 73 | return self._serialize(res) 74 | -------------------------------------------------------------------------------- /netsuitesdk/api/custom_segments.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class CustomSegments(ApiBase): 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='CustomSegment') 10 | 11 | def get_all(self): 12 | return self._get_all() 13 | 14 | def get_all_generator(self, page_size=20): 15 | """ 16 | Returns a generator which is more efficient memory-wise 17 | """ 18 | return self._get_all_generator() 19 | -------------------------------------------------------------------------------- /netsuitesdk/api/customers.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | from netsuitesdk.internal.utils import PaginatedSearch 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Customers(ApiBase): 11 | SIMPLE_FIELDS = [ 12 | 'accountNumber', 13 | 'addressbookList', 14 | 'aging', 15 | 'aging1', 16 | 'aging2', 17 | 'aging3', 18 | 'aging4', 19 | 'alcoholRecipientType', 20 | 'altEmail', 21 | 'altName', 22 | 'altPhone', 23 | 'balance', 24 | 'billPay', 25 | 'clickStream', 26 | 'comments', 27 | 'companyName', 28 | 'consolAging', 29 | 'consolAging1', 30 | 'consolAging2', 31 | 'consolAging3', 32 | 'consolAging4', 33 | 'consolBalance', 34 | 'consolDaysOverdue', 35 | 'consolDepositBalance', 36 | 'consolOverdueBalance', 37 | 'consolUnbilledOrders', 38 | 'contactRolesList', 39 | 'contribPct', 40 | 'creditCardsList', 41 | 'creditHoldOverride', 42 | 'creditLimit', 43 | 'currencyList', 44 | 'customFieldList', 45 | 'dateCreated', 46 | 'daysOverdue', 47 | 'defaultAddress', 48 | 'defaultOrderPriority', 49 | 'depositBalance', 50 | 'displaySymbol', 51 | 'downloadList', 52 | 'email', 53 | 'emailPreference', 54 | 'emailTransactions', 55 | 'endDate', 56 | 'entityId', 57 | 'estimatedBudget', 58 | 'externalId', 59 | 'fax', 60 | 'faxTransactions', 61 | 'firstName', 62 | 'firstVisit', 63 | 'giveAccess', 64 | 'globalSubscriptionStatus', 65 | 'groupPricingList', 66 | 'homePhone', 67 | 'internalId', 68 | 'isBudgetApproved', 69 | 'isInactive', 70 | 'isPerson', 71 | 'itemPricingList', 72 | 'keywords', 73 | 'language', 74 | 'lastModifiedDate', 75 | 'lastName', 76 | 'lastPageVisited', 77 | 'lastVisit', 78 | 'middleName', 79 | 'mobilePhone', 80 | 'negativeNumberFormat', 81 | 'numberFormat', 82 | 'openingBalance', 83 | 'openingBalanceDate', 84 | 'overdueBalance', 85 | 'overrideCurrencyFormat', 86 | 'partnersList', 87 | 'password', 88 | 'password2', 89 | 'phone', 90 | 'phoneticName', 91 | 'printOnCheckAs', 92 | 'printTransactions', 93 | 'referrer', 94 | 'reminderDays', 95 | 'requirePwdChange', 96 | 'resaleNumber', 97 | 'salesTeamList', 98 | 'salutation', 99 | 'sendEmail', 100 | 'shipComplete', 101 | 'stage', 102 | 'startDate', 103 | 'subscriptionsList', 104 | 'symbolPlacement', 105 | 'syncPartnerTeams', 106 | 'taxExempt', 107 | 'taxRegistrationList', 108 | 'taxable', 109 | 'thirdPartyAcct', 110 | 'thirdPartyCountry', 111 | 'thirdPartyZipcode', 112 | 'title', 113 | 'unbilledOrders', 114 | 'url', 115 | 'vatRegNumber', 116 | 'visits', 117 | 'webLead', 118 | 'nullFieldList', 119 | ] 120 | 121 | RECORD_REF_FIELDS = [ 122 | 'accessRole', 123 | 'assignedWebSite', 124 | 'buyingReason', 125 | 'buyingTimeFrame', 126 | 'campaignCategory', 127 | 'category', 128 | 'customForm', 129 | 'defaultTaxReg', 130 | 'drAccount', 131 | 'entityStatus', 132 | 'fxAccount', 133 | 'image', 134 | 'leadSource', 135 | 'openingBalanceAccount', 136 | 'parent', 137 | 'partner', 138 | 'prefCCProcessor', 139 | 'priceLevel', 140 | 'receivablesAccount', 141 | 'salesGroup', 142 | 'salesReadiness', 143 | 'salesRep', 144 | 'shippingItem', 145 | 'sourceWebSite', 146 | 'taxItem', 147 | 'terms', 148 | 'territory', 149 | ] 150 | 151 | def __init__(self, ns_client): 152 | ApiBase.__init__(self, ns_client=ns_client, type_name='Customer') 153 | 154 | def get_records_generator(self, last_modified_date=None, active=None): 155 | """ 156 | Get customers based on lastModifiedDate and active status 157 | :param last_modified_date: The date after which to search for customers (YYYY-MM-DDT%HH:MM:SS) 158 | :param active: Boolean to filter by active status. None means no filter on active status 159 | :return: Generator of customers matching the criteria 160 | """ 161 | search_fields = {} 162 | 163 | # Add active status filter if specified 164 | if active is not None: 165 | search_fields['isInactive'] = self.ns_client.SearchBooleanField( 166 | searchValue=not active 167 | ) 168 | 169 | # Add last modified date filter if specified 170 | if last_modified_date: 171 | search_fields['lastModifiedDate'] = self.ns_client.SearchDateField( 172 | searchValue=last_modified_date, 173 | operator='after' 174 | ) 175 | 176 | # Create basic search with the specified conditions 177 | basic_search = self.ns_client.basic_search_factory( 178 | type_name=self.type_name, 179 | **search_fields 180 | ) 181 | 182 | # Create paginated search 183 | paginated_search = PaginatedSearch( 184 | client=self.ns_client, 185 | type_name=self.type_name, 186 | basic_search=basic_search, 187 | pageSize=20 188 | ) 189 | 190 | # Return generator of results 191 | return self._paginated_search_generator(paginated_search=paginated_search) 192 | 193 | def post(self, data) -> OrderedDict: 194 | assert data['externalId'], 'missing external id' 195 | customer = self.ns_client.Customer(externalId=data['externalId']) 196 | 197 | customer['currency'] = self.ns_client.RecordRef(**(data['currency'])) 198 | 199 | customer['subsidiary'] = self.ns_client.RecordRef(**(data['subsidiary'])) 200 | 201 | customer['representingSubsidiary'] = self.ns_client.RecordRef(**(data['representingSubsidiary'])) 202 | 203 | customer['monthlyClosing'] = self.ns_client.RecordRef(**(data['monthlyClosing'])) 204 | 205 | self.build_simple_fields(self.SIMPLE_FIELDS, data, customer) 206 | 207 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, customer) 208 | 209 | logger.debug('able to create customer = %s', customer) 210 | res = self.ns_client.upsert(customer) 211 | return self._serialize(res) 212 | -------------------------------------------------------------------------------- /netsuitesdk/api/departments.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Departments(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='Department') 9 | -------------------------------------------------------------------------------- /netsuitesdk/api/employees.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from netsuitesdk.internal.utils import PaginatedSearch 4 | 5 | from .base import ApiBase 6 | import logging 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class Employees(ApiBase): 12 | def __init__(self, ns_client): 13 | ApiBase.__init__(self, ns_client=ns_client, type_name='Employee') 14 | 15 | def get_all_generator(self, is_inactive=False, page_size=50, last_modified_date_query={}): 16 | # Get Only Employee Items using SearchBooleanField 17 | record_type_search_field = self.ns_client.SearchBooleanField(searchValue=is_inactive) 18 | 19 | date_search = None 20 | if 'search_value' in last_modified_date_query and 'operator' in last_modified_date_query: 21 | if last_modified_date_query['search_value'] and last_modified_date_query['operator']: 22 | date_search = self.ns_client.SearchDateField( 23 | searchValue=last_modified_date_query['search_value'], 24 | operator=last_modified_date_query['operator'] 25 | ) 26 | 27 | basic_search = self.ns_client.basic_search_factory( 28 | type_name='Employee', 29 | isInactive=record_type_search_field, 30 | lastModifiedDate=date_search 31 | ) 32 | 33 | paginated_search = PaginatedSearch( 34 | client=self.ns_client, 35 | type_name='Employee', 36 | basic_search=basic_search, 37 | pageSize=page_size 38 | ) 39 | 40 | return self._paginated_search_generator(paginated_search=paginated_search) 41 | 42 | def post(self, data) -> OrderedDict: 43 | assert data['externalId'], 'missing external id' 44 | employee = self.ns_client.Employee(externalId=data['externalId']) 45 | 46 | employee['defaultExpenseReportCurrency'] = self.ns_client.RecordRef(**(data['defaultExpenseReportCurrency'])) 47 | 48 | employee['subsidiary'] = self.ns_client.RecordRef(**(data['subsidiary'])) 49 | 50 | employee['workCalendar'] = self.ns_client.RecordRef(**(data['workCalendar'])) 51 | 52 | if 'defaultAcctCorpCardExp' in data and data['defaultAcctCorpCardExp']: 53 | employee['defaultAcctCorpCardExp'] = self.ns_client.RecordRef(**(data['defaultAcctCorpCardExp'])) 54 | 55 | if 'entityId' in data: 56 | employee['entityId'] = data['entityId'] 57 | 58 | if 'firstName' in data: 59 | employee['firstName'] = data['firstName'] 60 | 61 | if 'lastName' in data: 62 | employee['lastName'] = data['lastName'] 63 | 64 | if 'email' in data: 65 | employee['email'] = data['email'] 66 | 67 | if 'inheritIPRules' in data: 68 | employee['inheritIPRules'] = data['inheritIPRules'] 69 | 70 | if 'payFrequency' in data: 71 | employee['payFrequency'] = data['payFrequency'] 72 | 73 | if 'location' in data: 74 | employee['location'] = data['location'] 75 | 76 | if 'department' in data: 77 | employee['department'] = data['department'] 78 | 79 | logger.debug('able to create employee = %s', employee) 80 | res = self.ns_client.upsert(employee) 81 | return self._serialize(res) 82 | -------------------------------------------------------------------------------- /netsuitesdk/api/expense_categories.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class ExpenseCategory(ApiBase): 7 | 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='ExpenseCategory') 10 | -------------------------------------------------------------------------------- /netsuitesdk/api/expense_reports.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | from netsuitesdk.internal.utils import PaginatedSearch 7 | 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class ExpenseReports(ApiBase): 13 | """ 14 | ExpenseReports are not directly searchable - only via as employees 15 | """ 16 | 17 | def __init__(self, ns_client): 18 | ApiBase.__init__(self, ns_client=ns_client, type_name='ExpenseReport') 19 | 20 | def get_all_generator(self): 21 | record_type_search_field = self.ns_client.SearchStringField(searchValue='ExpenseReport', operator='contains') 22 | basic_search = self.ns_client.basic_search_factory('Employee', recordType=record_type_search_field) 23 | paginated_search = PaginatedSearch(client=self.ns_client, 24 | type_name='Employee', 25 | basic_search=basic_search, 26 | pageSize=20) 27 | return self._paginated_search_to_generator(paginated_search=paginated_search) 28 | 29 | def post(self, data) -> OrderedDict: 30 | assert data['externalId'], 'missing external id' 31 | er = self.ns_client.ExpenseReport() 32 | expense_list = [] 33 | for eod in data['expenseList']: 34 | if 'customFieldList' in eod and eod['customFieldList']: 35 | custom_fields = [] 36 | for field in eod['customFieldList']: 37 | if field['type'] == 'String': 38 | custom_fields.append( 39 | self.ns_client.StringCustomFieldRef( 40 | scriptId=field['scriptId'] if 'scriptId' in field else None, 41 | internalId=field['internalId'] if 'internalId' in field else None, 42 | value=field['value'] 43 | ) 44 | ) 45 | elif field['type'] == 'Select': 46 | custom_fields.append( 47 | self.ns_client.SelectCustomFieldRef( 48 | scriptId=field['scriptId'] if 'scriptId' in field else None, 49 | internalId=field['internalId'] if 'internalId' in field else None, 50 | value=self.ns_client.ListOrRecordRef( 51 | internalId=field['value'] 52 | ) 53 | ) 54 | ) 55 | eod['customFieldList'] = self.ns_client.CustomFieldList(custom_fields) 56 | ere = self.ns_client.ExpenseReportExpense(**eod) 57 | expense_list.append(ere) 58 | 59 | er['expenseList'] = self.ns_client.ExpenseReportExpenseList(expense=expense_list) 60 | 61 | if 'expenseReportCurrency' in data: 62 | er['expenseReportCurrency'] = self.ns_client.RecordRef(**(data['expenseReportCurrency'])) 63 | 64 | if 'memo' in data: 65 | er['memo'] = data['memo'] 66 | 67 | if 'tranDate' in data: 68 | er['tranDate'] = data['tranDate'] 69 | 70 | if 'tranId' in data: 71 | er['tranId'] = data['tranId'] 72 | 73 | if 'class' in data: 74 | er['class'] = data['class'] 75 | 76 | if 'location' in data: 77 | er['location'] = data['location'] 78 | 79 | if 'department' in data: 80 | er['department'] = data['department'] 81 | 82 | if 'account' in data: 83 | er['account'] = self.ns_client.RecordRef(**(data['account'])) 84 | 85 | if 'accountingApproval' in data: 86 | er['accountingApproval'] = data['accountingApproval'] 87 | 88 | if 'supervisorApproval' in data: 89 | er['supervisorApproval'] = data['supervisorApproval'] 90 | 91 | if 'acctCorpCardExp' in data: 92 | er['acctCorpCardExp'] = data['acctCorpCardExp'] 93 | 94 | if 'externalId' in data: 95 | er['externalId'] = data['externalId'] 96 | 97 | if 'entity' in data: 98 | er['entity'] = self.ns_client.RecordRef(**(data['entity'])) 99 | 100 | logger.debug('able to create er = %s', er) 101 | res = self.ns_client.upsert(er, 'expense_report') 102 | 103 | return self._serialize(res) 104 | -------------------------------------------------------------------------------- /netsuitesdk/api/files.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Files(ApiBase): 10 | def __init__(self, ns_client): 11 | ApiBase.__init__(self, ns_client=ns_client, type_name='File') 12 | 13 | def post(self, data) -> OrderedDict: 14 | assert data['externalId'], 'missing external id' 15 | file = self.ns_client.File() 16 | 17 | if 'name' in data: 18 | file['name'] = data['name'] 19 | 20 | if 'folder' in data: 21 | file['folder'] = self.ns_client.RecordRef(**(data['folder'])) 22 | 23 | if 'externalId' in data: 24 | file['externalId'] = data['externalId'] 25 | 26 | if 'content' in data: 27 | file['content'] = data['content'] 28 | 29 | if 'mediaType' in data: 30 | file['mediaType'] = data['mediaType'] 31 | 32 | logger.debug('able to create file = %s', file) 33 | res = self.ns_client.upsert(file) 34 | return self._serialize(res) 35 | -------------------------------------------------------------------------------- /netsuitesdk/api/folders.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Folders(ApiBase): 10 | def __init__(self, ns_client): 11 | ApiBase.__init__(self, ns_client=ns_client, type_name='Folder') 12 | 13 | def post(self, data) -> OrderedDict: 14 | assert data['externalId'], 'missing external id' 15 | folder = self.ns_client.Folder() 16 | 17 | if 'name' in data: 18 | folder['name'] = data['name'] 19 | 20 | if 'externalId' in data: 21 | folder['externalId'] = data['externalId'] 22 | 23 | logger.debug('able to create folder = %s', folder) 24 | res = self.ns_client.upsert(folder) 25 | return self._serialize(res) 26 | -------------------------------------------------------------------------------- /netsuitesdk/api/invoices.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Invoices(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'accountingBookDetailList', 12 | 'altHandlingCost', 13 | 'altShippingCost', 14 | 'amountPaid', 15 | 'amountRemaining', 16 | 'balance', 17 | 'billingAddress', 18 | 'canHaveStackable', 19 | 'contribPct', 20 | 'createdDate', 21 | 'currencyName', 22 | 'customFieldList', 23 | 'deferredRevenue', 24 | 'discountAmount', 25 | 'discountDate', 26 | 'discountRate', 27 | 'discountTotal', 28 | 'dueDate', 29 | 'email', 30 | 'endDate', 31 | 'estGrossProfit', 32 | 'estGrossProfitPercent', 33 | 'exchangeRate', 34 | 'excludeCommission', 35 | 'expCostDiscAmount', 36 | 'expCostDiscPrint', 37 | 'expCostDiscRate', 38 | 'expCostDiscTax1Amt', 39 | 'expCostDiscTaxable', 40 | 'expCostList', 41 | 'expCostTaxRate1', 42 | 'expCostTaxRate2', 43 | 'externalId', 44 | 'fax', 45 | 'fob', 46 | 'giftCertApplied', 47 | 'giftCertRedemptionList', 48 | 'handlingCost', 49 | 'handlingTax1Rate', 50 | 'handlingTax2Rate', 51 | 'internalId', 52 | 'isTaxable', 53 | 'itemCostDiscAmount', 54 | 'itemCostDiscPrint', 55 | 'itemCostDiscRate', 56 | 'itemCostDiscTax1Amt', 57 | 'itemCostDiscTaxable', 58 | 'itemCostList', 59 | 'itemCostTaxRate1', 60 | 'itemCostTaxRate2', 61 | 'itemList', 62 | 'lastModifiedDate', 63 | 'linkedTrackingNumbers', 64 | 'memo', 65 | 'message', 66 | 'onCreditHold', 67 | 'otherRefNum', 68 | 'partnersList', 69 | 'promotionsList', 70 | 'recognizedRevenue', 71 | 'recurringBill', 72 | 'revRecEndDate', 73 | 'revRecOnRevCommitment', 74 | 'revRecStartDate', 75 | 'revenueStatus', 76 | 'salesEffectiveDate', 77 | 'salesTeamList', 78 | 'shipDate', 79 | 'shipGroupList', 80 | 'shipIsResidential', 81 | 'shippingAddress', 82 | 'shippingCost', 83 | 'shippingTax1Rate', 84 | 'shippingTax2Rate', 85 | 'source', 86 | 'startDate', 87 | 'status', 88 | 'subTotal', 89 | 'syncPartnerTeams', 90 | 'syncSalesTeams', 91 | 'tax2Total', 92 | 'taxDetailsList', 93 | 'taxDetailsOverride', 94 | 'taxPointDate', 95 | 'taxRate', 96 | 'taxRegOverride', 97 | 'taxTotal', 98 | 'timeDiscAmount', 99 | 'timeDiscPrint', 100 | 'timeDiscRate', 101 | 'timeDiscTax1Amt', 102 | 'timeDiscTaxable', 103 | 'timeList', 104 | 'timeTaxRate1', 105 | 'timeTaxRate2', 106 | 'toBeEmailed', 107 | 'toBeFaxed', 108 | 'toBePrinted', 109 | 'total', 110 | 'totalCostEstimate', 111 | 'trackingNumbers', 112 | 'tranDate', 113 | 'tranId', 114 | 'tranIsVsoeBundle', 115 | 'vatRegNum', 116 | 'vsoeAutoCalc', 117 | 'nullFieldList', 118 | ] 119 | 120 | RECORD_REF_FIELDS = [ 121 | 'account', 122 | 'approvalStatus', 123 | 'billAddressList', 124 | 'billingAccount', 125 | 'billingSchedule', 126 | 'class', 127 | 'createdFrom', 128 | 'currency', 129 | 'customForm', 130 | 'department', 131 | 'discountItem', 132 | 'entityTaxRegNum', 133 | 'expCostDiscount', 134 | 'expCostTaxCode', 135 | 'handlingTaxCode', 136 | 'itemCostDiscount', 137 | 'itemCostTaxCode', 138 | 'job', 139 | 'leadSource', 140 | 'location', 141 | 'messageSel', 142 | 'nextApprover', 143 | 'nexus', 144 | 'opportunity', 145 | 'partner', 146 | 'postingPeriod', 147 | 'promoCode', 148 | 'revRecSchedule', 149 | 'salesGroup', 150 | 'salesRep', 151 | 'shipAddressList', 152 | 'shipMethod', 153 | 'shippingTaxCode', 154 | 'subsidiary', 155 | 'subsidiaryTaxRegNum', 156 | 'taxItem', 157 | 'terms', 158 | 'timeDiscount', 159 | 'timeTaxCode', 160 | ] 161 | 162 | def __init__(self, ns_client): 163 | ApiBase.__init__(self, ns_client=ns_client, type_name='Invoice') 164 | 165 | def post(self, data) -> OrderedDict: 166 | assert data['externalId'], 'missing external id' 167 | invoice = self.ns_client.Invoice(**data) 168 | 169 | invoice['entity'] = self.ns_client.RecordRef(**(data['entity'])) 170 | 171 | self.build_simple_fields(self.SIMPLE_FIELDS, data, invoice) 172 | 173 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, invoice) 174 | 175 | logger.debug('able to create invoice = %s', invoice) 176 | 177 | res = self.ns_client.upsert(invoice) 178 | return self._serialize(res) 179 | -------------------------------------------------------------------------------- /netsuitesdk/api/items.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | from netsuitesdk.internal.utils import PaginatedSearch 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | class Items(ApiBase): 8 | 9 | def __init__(self, ns_client): 10 | ApiBase.__init__(self, ns_client=ns_client, type_name='Item') 11 | 12 | def get_all_generator(self, is_inactive=False): 13 | # Get Only Active Items using SearchBooleanField 14 | record_type_search_field = self.ns_client.SearchBooleanField(searchValue=is_inactive) 15 | basic_search = self.ns_client.basic_search_factory('Item', isInactive=record_type_search_field) 16 | 17 | paginated_search = PaginatedSearch( 18 | client=self.ns_client, 19 | type_name='Item', 20 | basic_search=basic_search, 21 | pageSize=20 22 | ) 23 | 24 | return self._paginated_search_generator(paginated_search=paginated_search) 25 | -------------------------------------------------------------------------------- /netsuitesdk/api/journal_entries.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | from netsuitesdk.internal.utils import PaginatedSearch 3 | 4 | from .base import ApiBase 5 | import logging 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class JournalEntries(ApiBase): 11 | SIMPLE_FIELDS = [ 12 | 'memo', 13 | 'tranDate', 14 | 'tranId' 15 | ] 16 | 17 | RECORD_REF_FIELDS = [ 18 | 'class', 19 | 'currency', 20 | 'department', 21 | 'location', 22 | 'subsidiary', 23 | 'toSubsidiary' 24 | ] 25 | 26 | TYPE_NAME = 'journalEntry' 27 | 28 | def __init__(self, ns_client): 29 | ApiBase.__init__(self, ns_client=ns_client, type_name=self.TYPE_NAME) 30 | # Uppercase the first letter of the type name to get the class name 31 | self.class_name = self.type_name[:1].upper() + self.type_name[1:] 32 | self.class_ = getattr(ns_client, self.class_name) 33 | self.line_class_ = getattr(ns_client, self.class_name + 'Line') 34 | self.line_list_class_ = getattr(ns_client, self.class_name + 'LineList') 35 | 36 | def get_all_generator(self): 37 | record_type_search_field = self.ns_client.SearchStringField(searchValue=self.class_name, operator='contains') 38 | basic_search = self.ns_client.basic_search_factory('Transaction', recordType=record_type_search_field) 39 | paginated_search = PaginatedSearch(client=self.ns_client, 40 | type_name='Transaction', 41 | basic_search=basic_search, 42 | pageSize=20) 43 | return self._paginated_search_to_generator(paginated_search=paginated_search) 44 | 45 | def post(self, data) -> OrderedDict: 46 | assert data['externalId'], 'missing external id' 47 | je = self.class_(externalId=data['externalId']) 48 | line_list = [] 49 | for eod in data['lineList']: 50 | if 'customFieldList' in eod and eod['customFieldList']: 51 | custom_fields = [] 52 | for field in eod['customFieldList']: 53 | if field['type'] == 'String': 54 | custom_fields.append( 55 | self.ns_client.StringCustomFieldRef( 56 | scriptId=field['scriptId'] if 'scriptId' in field else None, 57 | internalId=field['internalId'] if 'internalId' in field else None, 58 | value=field['value'] 59 | ) 60 | ) 61 | elif field['type'] == 'Select': 62 | custom_fields.append( 63 | self.ns_client.SelectCustomFieldRef( 64 | scriptId=field['scriptId'] if 'scriptId' in field else None, 65 | internalId=field['internalId'] if 'internalId' in field else None, 66 | value=self.ns_client.ListOrRecordRef( 67 | internalId=field['value'] 68 | ) 69 | ) 70 | ) 71 | eod['customFieldList'] = self.ns_client.CustomFieldList(custom_fields) 72 | jee = self.line_class_(**eod) 73 | line_list.append(jee) 74 | 75 | je['lineList'] = self.line_list_class_(line=line_list) 76 | self.build_simple_fields(self.SIMPLE_FIELDS, data, je) 77 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, je) 78 | 79 | logger.debug('able to create je = %s', je) 80 | res = self.ns_client.upsert(je, 'journal_entry') 81 | return self._serialize(res) 82 | -------------------------------------------------------------------------------- /netsuitesdk/api/locations.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Locations(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='Location') 9 | -------------------------------------------------------------------------------- /netsuitesdk/api/price_level.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class PriceLevel(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='PriceLevel') -------------------------------------------------------------------------------- /netsuitesdk/api/projects.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class Projects(ApiBase): 7 | def __init__(self, ns_client): 8 | ApiBase.__init__(self, ns_client=ns_client, type_name='Job') 9 | -------------------------------------------------------------------------------- /netsuitesdk/api/subsidiaries.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | 7 | class Subsidiaries(ApiBase): 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='Subsidiary') 10 | -------------------------------------------------------------------------------- /netsuitesdk/api/tax_groups.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class TaxGroups(ApiBase): 7 | 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='TaxGroup') 10 | -------------------------------------------------------------------------------- /netsuitesdk/api/tax_items.py: -------------------------------------------------------------------------------- 1 | from .base import ApiBase 2 | import logging 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | class TaxItems(ApiBase): 7 | 8 | def __init__(self, ns_client): 9 | ApiBase.__init__(self, ns_client=ns_client, type_name='SalesTaxItem') 10 | -------------------------------------------------------------------------------- /netsuitesdk/api/terms.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | class Terms(ApiBase): 9 | SIMPLE_FIELDS = [ 10 | 'dateDriven', 11 | 'dayDiscountExpires', 12 | 'dayOfMonthNetDue', 13 | 'daysUntilExpiry', 14 | 'daysUntilNetDue', 15 | 'discountPercent', 16 | 'discountPercentDateDriven', 17 | 'dueNextMonthIfWithinDays', 18 | 'externalId', 19 | 'installment', 20 | 'internalId', 21 | 'isInactive', 22 | 'percentagesList', 23 | 'preferred', 24 | 'recurrenceCount', 25 | 'recurrenceFrequency', 26 | 'repeatEvery', 27 | 'splitEvenly', 28 | 'nullFieldList', 29 | ] 30 | 31 | def __init__(self, ns_client): 32 | ApiBase.__init__(self, ns_client=ns_client, type_name='Term') 33 | 34 | def post(self, data) -> OrderedDict: 35 | term = self.ns_client.Term(daysUntilNetDue=data['daysUntilNetDue'], name=data['name']) 36 | 37 | self.build_simple_fields(self.SIMPLE_FIELDS, data, term) 38 | 39 | logger.debug('able to create term = %s', term) 40 | res = self.ns_client.request('add', record=term) 41 | return self._serialize(res) 42 | -------------------------------------------------------------------------------- /netsuitesdk/api/usage.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Usage(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'memo', 12 | 'usageDate', 13 | 'usageQuantity', 14 | 'nullFieldList', 15 | ] 16 | 17 | RECORD_REF_FIELDS = [ 18 | 'customForm', 19 | 'customer', 20 | 'item', 21 | 'subscriptionPlan', 22 | 'usageSubscription', 23 | 'usageSubscriptionLine', 24 | ] 25 | 26 | def __init__(self, ns_client): 27 | ApiBase.__init__(self, ns_client=ns_client, type_name='Usage') 28 | 29 | def post(self, data) -> OrderedDict: 30 | assert data['externalId'], 'missing external id' 31 | usage = self.ns_client.Usage(**data) 32 | 33 | self.build_simple_fields(self.SIMPLE_FIELDS, data, usage) 34 | 35 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, usage) 36 | 37 | logger.debug('able to create usage = %s', usage) 38 | 39 | res = self.ns_client.upsert(usage) 40 | return self._serialize(res) 41 | -------------------------------------------------------------------------------- /netsuitesdk/api/vendor_bills.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from netsuitesdk.internal.utils import PaginatedSearch 4 | 5 | from .base import ApiBase 6 | from typing import List 7 | from collections import OrderedDict 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class VendorBills(ApiBase): 13 | """ 14 | VendorBills are not directly searchable - only via as transactions 15 | """ 16 | 17 | def __init__(self, ns_client): 18 | ApiBase.__init__(self, ns_client=ns_client, type_name='vendorBill') 19 | 20 | def get_all_generator(self): 21 | record_type_search_field = self.ns_client.SearchStringField(searchValue='VendorBill', operator='contains') 22 | basic_search = self.ns_client.basic_search_factory('Transaction', recordType=record_type_search_field) 23 | paginated_search = PaginatedSearch(client=self.ns_client, 24 | type_name='Transaction', 25 | basic_search=basic_search, 26 | pageSize=20) 27 | return self._paginated_search_to_generator(paginated_search=paginated_search) 28 | 29 | def post(self, data) -> OrderedDict: 30 | assert data['externalId'], 'missing external id' 31 | vb = self.ns_client.VendorBill(externalId=data['externalId']) 32 | # If expesess are present, add them 33 | if 'expenseList' in data and data['expenseList']: 34 | expense_list = [] 35 | for eod in data['expenseList']: 36 | if 'customFieldList' in eod and eod['customFieldList']: 37 | custom_fields = [] 38 | for field in eod['customFieldList']: 39 | if field['type'] == 'String': 40 | custom_fields.append( 41 | self.ns_client.StringCustomFieldRef( 42 | scriptId=field['scriptId'] if 'scriptId' in field else None, 43 | internalId=field['internalId'] if 'internalId' in field else None, 44 | value=field['value'] 45 | ) 46 | ) 47 | elif field['type'] == 'Select': 48 | custom_fields.append( 49 | self.ns_client.SelectCustomFieldRef( 50 | scriptId=field['scriptId'] if 'scriptId' in field else None, 51 | internalId=field['internalId'] if 'internalId' in field else None, 52 | value=self.ns_client.ListOrRecordRef( 53 | internalId=field['value'] 54 | ) 55 | ) 56 | ) 57 | eod['customFieldList'] = self.ns_client.CustomFieldList(custom_fields) 58 | vbe = self.ns_client.VendorBillExpense(**eod) 59 | expense_list.append(vbe) 60 | 61 | vb['expenseList'] = self.ns_client.VendorBillExpenseList(expense=expense_list) 62 | 63 | # If items are present, add them 64 | if 'itemList' in data and data['itemList']: 65 | item_list = [] 66 | for eod in data['itemList']: 67 | if 'customFieldList' in eod and eod['customFieldList']: 68 | custom_fields = [] 69 | for field in eod['customFieldList']: 70 | if field['type'] == 'String': 71 | custom_fields.append( 72 | self.ns_client.StringCustomFieldRef( 73 | scriptId=field['scriptId'] if 'scriptId' in field else None, 74 | internalId=field['internalId'] if 'internalId' in field else None, 75 | value=field['value'] 76 | ) 77 | ) 78 | elif field['type'] == 'Select': 79 | custom_fields.append( 80 | self.ns_client.SelectCustomFieldRef( 81 | scriptId=field['scriptId'] if 'scriptId' in field else None, 82 | internalId=field['internalId'] if 'internalId' in field else None, 83 | value=self.ns_client.ListOrRecordRef( 84 | internalId=field['value'] 85 | ) 86 | ) 87 | ) 88 | eod['customFieldList'] = self.ns_client.CustomFieldList(custom_fields) 89 | vbe = self.ns_client.VendorBillItem(**eod) 90 | item_list.append(vbe) 91 | 92 | vb['itemList']= self.ns_client.VendorBillItemList(item=item_list) 93 | 94 | if 'currency' in data: 95 | vb['currency'] = self.ns_client.RecordRef(**(data['currency'])) 96 | 97 | if 'memo' in data: 98 | vb['memo'] = data['memo'] 99 | 100 | if 'tranDate' in data: 101 | vb['tranDate'] = data['tranDate'] 102 | 103 | if 'tranId' in data: 104 | vb['tranId'] = data['tranId'] 105 | 106 | if 'class' in data: 107 | vb['class'] = self.ns_client.RecordRef(**(data['class'])) 108 | 109 | if 'location' in data: 110 | vb['location'] = self.ns_client.RecordRef(**(data['location'])) 111 | 112 | if 'department' in data: 113 | vb['department'] = self.ns_client.RecordRef(**(data['department'])) 114 | 115 | if 'account' in data: 116 | vb['account'] = self.ns_client.RecordRef(**(data['account'])) 117 | 118 | if 'customFieldList' in data: 119 | vb['customFieldList'] = data['customFieldList'] 120 | 121 | if 'entity' in data: 122 | vb['entity'] = self.ns_client.RecordRef(**(data['entity'])) 123 | 124 | if 'taxDetailsOverride' in data: 125 | vb['taxDetailsOverride'] = data['taxDetailsOverride'] 126 | 127 | if 'taxDetailsList' in data and data['taxDetailsList']: 128 | tax_details_list = [] 129 | if 'taxDetails' in data['taxDetailsList']: 130 | for tdl in data['taxDetailsList']['taxDetails']: 131 | tax_details_list.append(self.ns_client.TaxDetails(**tdl)) 132 | vb['taxDetailsList'] = self.ns_client.TaxDetailsList(taxDetails=tax_details_list) 133 | 134 | logger.debug('able to create vb = %s', vb) 135 | res = self.ns_client.upsert(vb, 'bills') 136 | return self._serialize(res) 137 | -------------------------------------------------------------------------------- /netsuitesdk/api/vendor_credits.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class VendorCredits(ApiBase): 10 | SIMPLE_FIELDS = [ 11 | 'accountingBookDetailList', 12 | 'applied', 13 | 'applyList', 14 | 'autoApply', 15 | 'billingAddress', 16 | 'createdDate', 17 | 'currencyName', 18 | 'customFieldList', 19 | 'exchangeRate', 20 | 'expenseList', 21 | 'internalId', 22 | 'itemList', 23 | 'lastModifiedDate', 24 | 'memo', 25 | 'taxDetailsList', 26 | 'taxDetailsOverride', 27 | 'taxPointDate', 28 | 'taxRegOverride', 29 | 'total', 30 | 'tranDate', 31 | 'tranId', 32 | 'transactionNumber', 33 | 'unApplied', 34 | 'userTaxTotal', 35 | 'userTotal', 36 | 'nullFieldList', 37 | ] 38 | 39 | RECORD_REF_FIELDS = [ 40 | 'account', 41 | 'billAddressList', 42 | 'class', 43 | 'createdFrom', 44 | 'currency', 45 | 'customForm', 46 | 'department', 47 | 'entityTaxRegNum', 48 | 'location', 49 | 'nexus', 50 | 'postingPeriod', 51 | 'subsidiary', 52 | 'subsidiaryTaxRegNum', 53 | ] 54 | 55 | def __init__(self, ns_client): 56 | ApiBase.__init__(self, ns_client=ns_client, type_name='VendorCredit') 57 | 58 | def post(self, data) -> OrderedDict: 59 | assert data['externalId'], 'missing external id' 60 | vendor_credit = self.ns_client.VendorCredit(**data) 61 | 62 | vendor_credit['entity'] = self.ns_client.RecordRef(**(data['entity'])) 63 | 64 | self.build_simple_fields(self.SIMPLE_FIELDS, data, vendor_credit) 65 | 66 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, vendor_credit) 67 | 68 | logger.debug('able to create vendor credit = %s', vendor_credit) 69 | 70 | res = self.ns_client.upsert(vendor_credit) 71 | return self._serialize(res) 72 | -------------------------------------------------------------------------------- /netsuitesdk/api/vendor_payments.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from netsuitesdk.internal.utils import PaginatedSearch 4 | 5 | from .base import ApiBase 6 | from collections import OrderedDict 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | class VendorPayments(ApiBase): 12 | """ 13 | VendorPayments are not directly searchable - only via as transactions 14 | """ 15 | 16 | def __init__(self, ns_client): 17 | ApiBase.__init__(self, ns_client=ns_client, type_name='vendorPayment') 18 | 19 | def get_all_generator(self): 20 | record_type_search_field = self.ns_client.SearchStringField(searchValue='VendorPayment', operator='contains') 21 | basic_search = self.ns_client.basic_search_factory('Transaction', recordType=record_type_search_field) 22 | paginated_search = PaginatedSearch(client=self.ns_client, 23 | type_name='Transaction', 24 | basic_search=basic_search, 25 | pageSize=20) 26 | return self._paginated_search_to_generator(paginated_search=paginated_search) 27 | 28 | def post(self, data) -> OrderedDict: 29 | assert data['externalId'], 'missing external id' 30 | vp = self.ns_client.VendorPayment(externalId=data['externalId']) 31 | apply_lists = [] 32 | for eod in data['applyList']['apply']: 33 | vpal = self.ns_client.VendorPaymentApply(**eod) 34 | apply_lists.append(vpal) 35 | 36 | vp['applyList'] = self.ns_client.VendorPaymentApplyList(apply=apply_lists) 37 | 38 | vp['currency'] = self.ns_client.RecordRef(**(data['currency'])) 39 | 40 | if 'amount' in data: 41 | vp['amount'] = data['amount'] 42 | 43 | if 'memo' in data: 44 | vp['memo'] = data['memo'] 45 | 46 | if 'tranDate' in data: 47 | vp['tranDate'] = data['tranDate'] 48 | 49 | if 'tranId' in data: 50 | vp['tranId'] = data['tranId'] 51 | 52 | if 'class' in data: 53 | vp['class'] = self.ns_client.RecordRef(**(data['class'])) 54 | 55 | if 'apAcct' in data: 56 | vp['apAcct'] = self.ns_client.RecordRef(**(data['apAcct'])) 57 | 58 | if 'location' in data: 59 | vp['location'] = self.ns_client.RecordRef(**(data['location'])) 60 | 61 | if 'department' in data: 62 | vp['department'] = self.ns_client.RecordRef(**(data['department'])) 63 | 64 | if 'account' in data: 65 | vp['account'] = self.ns_client.RecordRef(**(data['account'])) 66 | 67 | if 'externalId' in data: 68 | vp['externalId'] = data['externalId'] 69 | 70 | vp['entity'] = self.ns_client.RecordRef(**(data['entity'])) 71 | logger.debug('able to create vp = %s', vp) 72 | res = self.ns_client.upsert(vp) 73 | return self._serialize(res) 74 | -------------------------------------------------------------------------------- /netsuitesdk/api/vendors.py: -------------------------------------------------------------------------------- 1 | from collections import OrderedDict 2 | 3 | from .base import ApiBase 4 | import logging 5 | from netsuitesdk.internal.utils import PaginatedSearch 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Vendors(ApiBase): 11 | SIMPLE_FIELDS = [ 12 | 'accountNumber', 13 | 'addressbookList', 14 | 'altEmail', 15 | 'altName', 16 | 'altPhone', 17 | 'balance', 18 | 'balancePrimary', 19 | 'bcn', 20 | 'billPay', 21 | 'comments', 22 | 'companyName', 23 | 'creditLimit', 24 | 'currencyList', 25 | 'customFieldList', 26 | 'dateCreated', 27 | 'defaultAddress', 28 | 'eligibleForCommission', 29 | 'email', 30 | 'emailPreference', 31 | 'emailTransactions', 32 | 'entityId', 33 | 'fax', 34 | 'faxTransactions', 35 | 'firstName', 36 | 'giveAccess', 37 | 'globalSubscriptionStatus', 38 | 'homePhone', 39 | 'internalId', 40 | 'is1099Eligible', 41 | 'isAccountant', 42 | 'isInactive', 43 | 'isJobResourceVend', 44 | 'isPerson', 45 | 'laborCost', 46 | 'lastModifiedDate', 47 | 'lastName', 48 | 'legalName', 49 | 'middleName', 50 | 'mobilePhone', 51 | 'openingBalance', 52 | 'openingBalanceDate', 53 | 'password', 54 | 'password2', 55 | 'phone', 56 | 'phoneticName', 57 | 'predConfidence', 58 | 'predictedDays', 59 | 'pricingScheduleList', 60 | 'printOnCheckAs', 61 | 'printTransactions', 62 | 'purchaseOrderAmount', 63 | 'purchaseOrderQuantity', 64 | 'purchaseOrderQuantityDiff', 65 | 'receiptAmount', 66 | 'receiptQuantity', 67 | 'receiptQuantityDiff', 68 | 'requirePwdChange', 69 | 'rolesList', 70 | 'salutation', 71 | 'sendEmail', 72 | 'subscriptionsList', 73 | 'taxIdNum', 74 | 'taxRegistrationList', 75 | 'title', 76 | 'unbilledOrders', 77 | 'unbilledOrdersPrimary', 78 | 'url', 79 | 'vatRegNumber', 80 | 'nullFieldList', 81 | ] 82 | 83 | RECORD_REF_FIELDS = [ 84 | 'category', 85 | 'customForm', 86 | 'defaultTaxReg', 87 | 'expenseAccount', 88 | 'image', 89 | 'incoterm', 90 | 'openingBalanceAccount', 91 | 'payablesAccount', 92 | 'taxItem', 93 | 'terms', 94 | ] 95 | 96 | def __init__(self, ns_client): 97 | ApiBase.__init__(self, ns_client=ns_client, type_name='Vendor') 98 | 99 | def get_records_generator(self, last_modified_date=None, active=None): 100 | """ 101 | Get vendors based on lastModifiedDate and active status 102 | :param last_modified_date: The date after which to search for vendors (YYYY-MM-DDT%HH:MM:SS) 103 | :param active: Boolean to filter by active status. None means no filter on active status 104 | :return: Generator of vendors matching the criteria 105 | """ 106 | search_fields = {} 107 | 108 | # Add active status filter if specified 109 | if active is not None: 110 | search_fields['isInactive'] = self.ns_client.SearchBooleanField( 111 | searchValue=not active 112 | ) 113 | 114 | # Add last modified date filter if specified 115 | if last_modified_date: 116 | search_fields['lastModifiedDate'] = self.ns_client.SearchDateField( 117 | searchValue=last_modified_date, 118 | operator='after' 119 | ) 120 | 121 | # Create basic search with the specified conditions 122 | basic_search = self.ns_client.basic_search_factory( 123 | type_name=self.type_name, 124 | **search_fields 125 | ) 126 | 127 | # Create paginated search 128 | paginated_search = PaginatedSearch( 129 | client=self.ns_client, 130 | type_name=self.type_name, 131 | basic_search=basic_search, 132 | pageSize=20 133 | ) 134 | 135 | # Return generator of results 136 | return self._paginated_search_generator(paginated_search=paginated_search) 137 | 138 | def post(self, data) -> OrderedDict: 139 | assert data['externalId'], 'missing external id' 140 | vendor = self.ns_client.Vendor(externalId=data['externalId']) 141 | 142 | vendor['currency'] = self.ns_client.RecordRef(**(data['currency'])) 143 | 144 | vendor['subsidiary'] = self.ns_client.RecordRef(**(data['subsidiary'])) 145 | 146 | vendor['representingSubsidiary'] = self.ns_client.RecordRef(**(data['representingSubsidiary'])) 147 | 148 | vendor['workCalendar'] = self.ns_client.RecordRef(**(data['workCalendar'])) 149 | 150 | self.build_simple_fields(self.SIMPLE_FIELDS, data, vendor) 151 | 152 | self.build_record_ref_fields(self.RECORD_REF_FIELDS, data, vendor) 153 | 154 | logger.debug('able to create vendor = %s', vendor) 155 | res = self.ns_client.upsert(vendor) 156 | return self._serialize(res) 157 | -------------------------------------------------------------------------------- /netsuitesdk/connection.py: -------------------------------------------------------------------------------- 1 | from .api.accounts import Accounts 2 | from .api.billing_account import BillingAccount 3 | from .api.classifications import Classifications 4 | from .api.credit_memos import CreditMemos 5 | from .api.departments import Departments 6 | from .api.currencies import Currencies 7 | from .api.locations import Locations 8 | from .api.vendor_bills import VendorBills 9 | from .api.vendor_credits import VendorCredits 10 | from .api.vendors import Vendors 11 | from .api.subsidiaries import Subsidiaries 12 | from .api.usage import Usage 13 | from .api.journal_entries import JournalEntries 14 | from .api.adv_inter_company_journal_entries import AdvInterCompanyJournalEntries 15 | from .api.employees import Employees 16 | from .api.expense_reports import ExpenseReports 17 | from .api.folders import Folders 18 | from .api.files import Files 19 | from .api.customers import Customers 20 | from .api.projects import Projects 21 | from .api.expense_categories import ExpenseCategory 22 | from .api.custom_lists import CustomLists 23 | from .api.custom_segments import CustomSegments 24 | from .api.custom_record_types import CustomRecordTypes 25 | from .api.custom_records import CustomRecords 26 | from .api.vendor_payments import VendorPayments 27 | from .api.invoices import Invoices 28 | from .api.terms import Terms 29 | from .api.tax_items import TaxItems 30 | from .api.tax_groups import TaxGroups 31 | from .api.price_level import PriceLevel 32 | from .api.items import Items 33 | from .internal.client import NetSuiteClient 34 | 35 | 36 | class NetSuiteConnection: 37 | def __init__(self, account, consumer_key, consumer_secret, token_key, token_secret, 38 | caching=True, caching_timeout=2592000, caching_path=None, 39 | search_body_fields_only=True, page_size: int = 100, wsdl_version: str = '2024_1'): 40 | 41 | ns_client = NetSuiteClient(account=account, caching=caching, caching_timeout=caching_timeout, 42 | caching_path=caching_path, search_body_fields_only=search_body_fields_only, 43 | page_size=page_size, wsdl_version=wsdl_version) 44 | ns_client.connect_tba( 45 | consumer_key=consumer_key, 46 | consumer_secret=consumer_secret, 47 | token_key=token_key, 48 | token_secret=token_secret 49 | ) 50 | self.client = ns_client 51 | self.accounts = Accounts(ns_client) 52 | self.billing_accounts = BillingAccount(ns_client) 53 | self.classifications = Classifications(ns_client) 54 | self.departments = Departments(ns_client) 55 | self.currencies = Currencies(ns_client) 56 | self.locations = Locations(ns_client) 57 | self.vendor_bills = VendorBills(ns_client) 58 | self.vendor_credits = VendorCredits(ns_client) 59 | self.vendors = Vendors(ns_client) 60 | self.subsidiaries = Subsidiaries(ns_client) 61 | self.journal_entries = JournalEntries(ns_client) 62 | self.adv_inter_company_journal_entries = AdvInterCompanyJournalEntries(ns_client) 63 | self.employees = Employees(ns_client) 64 | self.expense_reports = ExpenseReports(ns_client) 65 | self.folders = Folders(ns_client) 66 | self.files = Files(ns_client) 67 | self.expense_categories = ExpenseCategory(ns_client) 68 | self.custom_lists = CustomLists(ns_client) 69 | self.custom_segments = CustomSegments(ns_client) 70 | self.custom_records = CustomRecords(ns_client) 71 | self.custom_record_types = CustomRecordTypes(ns_client) 72 | self.customers = Customers(ns_client) 73 | self.projects = Projects(ns_client) 74 | self.vendor_payments = VendorPayments(ns_client) 75 | self.invoices = Invoices(ns_client) 76 | self.terms = Terms(ns_client) 77 | self.tax_items = TaxItems(ns_client) 78 | self.tax_groups = TaxGroups(ns_client) 79 | self.credit_memos = CreditMemos(ns_client) 80 | self.price_level = PriceLevel(ns_client) 81 | self.usages = Usage(ns_client) 82 | self.items = Items(ns_client) 83 | -------------------------------------------------------------------------------- /netsuitesdk/errors/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fylein/netsuite-sdk-py/c66c398997d0e624101a26b036958d88a55a85d2/netsuitesdk/errors/__init__.py -------------------------------------------------------------------------------- /netsuitesdk/errors/errors.py: -------------------------------------------------------------------------------- 1 | 2 | error_reference = { 3 | "expense_report": { 4 | 'category_reference_error': { 5 | 'regex': r"An error occured in a upsert request: Invalid category reference key \d+ for entity \d+", 6 | 'keys': ['expense_category', 'employee'] 7 | }, 8 | 'account_reference_error': { 9 | 'regex': r"An error occured in a upsert request: Invalid account reference key \d+ for subsidiary \d+", 10 | 'keys': ['account', 'subsidiary'] 11 | }, 12 | 'project_reference_error': { 13 | 'regex': r"An error occured in a upsert request: Invalid customer reference key \d+ for entity \d+", 14 | 'keys': ['customer', 'employee'] 15 | }, 16 | 'location_reference_error': { 17 | 'regex': r"An error occured in a upsert request: Invalid location reference key \d+ for subsidiary \d+", 18 | 'keys': ['location', 'subsidiary'] 19 | }, 20 | 'department_reference_error': { 21 | 'regex':r"An error occured in a upsert request: Invalid department reference key \d+ for subsidiary \d+" , 22 | 'keys': ['department', 'subsidiary'] 23 | }, 24 | 'currency_reference_error': { 25 | 'regex': r"An error occured in a upsert request: Invalid currency reference key \d+ for subsidiary \d+", 26 | 'keys': ['currency', 'subsidiary'] 27 | } 28 | }, 29 | "bills": { 30 | 'bill_account_reference_error': { 31 | 'regex': r"An error occured in a upsert request: Invalid account reference key \d+ for subsidiary \d+", 32 | 'keys': ['account', 'subsidiary'] 33 | }, 34 | 'location_reference_error': { 35 | 'regex': r"An error occured in a upsert request: Invalid location reference key \d+ for subsidiary \d+", 36 | 'keys': ['location', 'subsidiary'] 37 | }, 38 | 'department_reference_error': { 39 | 'regex':r"An error occured in a upsert request: Invalid department reference key \d+ for subsidiary \d+" , 40 | 'keys': ['department', 'subsidiary'] 41 | }, 42 | 'currency_reference_error': { 43 | 'regex': r"An error occured in a upsert request: Invalid currency reference key \d+ for subsidiary \d+", 44 | 'keys': ['currency', 'subsidiary'] 45 | }, 46 | 'vendor_reference_error': { 47 | 'regex': r"An error occured in a upsert request: Invalid entity reference key \d+ for subsidiary \d+", 48 | 'keys': ['vendor', 'subsidiary'] 49 | } 50 | }, 51 | "journal_entry": { 52 | 'location_reference_error': { 53 | 'regex': r"An error occured in a upsert request: Invalid location reference key \d+ for subsidiary \d+", 54 | 'keys': ['location', 'subsidiary'] 55 | }, 56 | 'department_reference_error': { 57 | 'regex':r"An error occured in a upsert request: Invalid department reference key \d+ for subsidiary \d+" , 58 | 'keys': ['department', 'subsidiary'] 59 | }, 60 | 'account_reference_error': { 61 | 'regex': r"An error occured in a upsert request: Invalid account reference key \d+ for subsidiary \d+", 62 | 'keys': ['account', 'subsidiary'] 63 | }, 64 | 'currency_reference_error': { 65 | 'regex': r"An error occured in a upsert request: Invalid currency reference key \d+ for subsidiary \d+", 66 | 'keys': ['currency', 'subsidiary'] 67 | }, 68 | 'project_reference_error': { 69 | 'regex': r"An error occured in a upsert request: Invalid customer reference key \d+ for entity \d+", 70 | 'keys': ['customer', 'employee'] 71 | }, 72 | } 73 | } 74 | 75 | 76 | list_of_dicts = [ 77 | ['expense_category', 'employee'], ['account', 'subsidiary'], 78 | ['customer', 'employee'], ['location', 'subsidiary'], 79 | ['department', 'subsidiary'], ['currency', 'subsidiary'], 80 | ['vendor', 'subsdiary'] 81 | ] 82 | -------------------------------------------------------------------------------- /netsuitesdk/errors/helpers.py: -------------------------------------------------------------------------------- 1 | import re 2 | from .errors import error_reference 3 | 4 | 5 | def replace_numbers(string , replacement1, replacement2, number1, number2): 6 | pattern = r"\b({0}|{1})\b".format(number1, number2) 7 | replaced_string = re.sub(pattern, lambda match: replacement1 if match.group() == str(number1) else replacement2, string) 8 | return replaced_string 9 | 10 | 11 | def convert_to_camelcase(word): 12 | return ''.join(word.title().split('_')) 13 | 14 | 15 | def export_error_matcher(string, export_type): 16 | for _, error_data in error_reference[export_type].items(): 17 | if re.match(error_data['regex'], string): 18 | numbers = re.findall(r'\d+', string) 19 | return {key: int(number) for key, number in zip(error_data['keys'], numbers)} 20 | 21 | return {} 22 | -------------------------------------------------------------------------------- /netsuitesdk/errors/parser.py: -------------------------------------------------------------------------------- 1 | from .helpers import replace_numbers, convert_to_camelcase 2 | from .errors import list_of_dicts 3 | 4 | class ErrorParser(): 5 | 6 | def __init__(self, get_instance): 7 | self.get_instance = get_instance 8 | 9 | def get_entity_values(self, error_dict): 10 | 11 | entity_keys = list(error_dict) 12 | object_1 = self.get_instance(convert_to_camelcase(entity_keys[0]), error_dict[entity_keys[0]]) 13 | object_2 = self.get_instance(convert_to_camelcase(entity_keys[1]), error_dict[entity_keys[1]]) 14 | 15 | if object_1 and object_2: 16 | if entity_keys[0] == 'customer' and entity_keys[1] == 'employee': 17 | return object_1['entityId'], object_2['entityId'] 18 | 19 | if entity_keys[1] == 'employee': 20 | return object_1['name'], object_2['entityId'] 21 | 22 | if entity_keys[0] == 'account': 23 | object_1 = object_1['acctName'] 24 | return object_1, object_2['name'] 25 | 26 | if entity_keys[0] == 'vendor': 27 | return object_1['entityId'], object_2['name'] 28 | 29 | return object_1['name'], object_2['name'] 30 | 31 | 32 | def export_error_parser(self, error_dict, message): 33 | 34 | parsed_message = message 35 | if list(error_dict) in list_of_dicts: 36 | object_1, object_2 = self.get_entity_values(error_dict) 37 | entity_keys = list(error_dict) 38 | parsed_message = replace_numbers(message, object_1, object_2, error_dict[entity_keys[0]], error_dict[entity_keys[1]]) 39 | return parsed_message 40 | -------------------------------------------------------------------------------- /netsuitesdk/internal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fylein/netsuite-sdk-py/c66c398997d0e624101a26b036958d88a55a85d2/netsuitesdk/internal/__init__.py -------------------------------------------------------------------------------- /netsuitesdk/internal/constants.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | SEARCH_RECORD_TYPES = [ 4 | 'account', 5 | 'accountingPeriod', 6 | 'accountingTransaction', 7 | 'billingAccount', 8 | 'billingSchedule', 9 | 'bin', 10 | 'budget', 11 | 'calendarEvent', 12 | 'campaign', 13 | 'charge', 14 | 'classification', 15 | 'contact', 16 | 'contactCategory', 17 | 'costCategory', 18 | 'consolidatedExchangeRate', 19 | 'couponCode', 20 | 'currencyRate', 21 | 'customer', 22 | 'customerCategory', 23 | 'customerMessage', 24 | 'customerStatus', 25 | 'customList', 26 | 'customSegment', 27 | 'customRecord', 28 | 'department', 29 | 'employee', 30 | 'entityGroup', 31 | 'expenseCategory', 32 | 'fairValuePrice', 33 | 'file', 34 | 'folder', 35 | 'giftCertificate', 36 | 'globalAccountMapping', 37 | 'hcmJob', 38 | 'inboundShipment', 39 | 'inventoryNumber', 40 | 'item', 41 | 'itemAccountMapping', 42 | 'itemDemandPlan', 43 | 'itemRevision', 44 | 'itemSupplyPlan', 45 | 'issue', 46 | 'job', 47 | 'jobStatus', 48 | 'jobType', 49 | 'location', 50 | 'manufacturingCostTemplate', 51 | 'manufacturingOperationTask', 52 | 'manufacturingRouting', 53 | 'message', 54 | 'nexus', 55 | 'note', 56 | 'noteType', 57 | 'opportunity', 58 | 'otherNameCategory', 59 | 'partner', 60 | 'partnerCategory', 61 | 'paycheck', 62 | 'paymentMethod', 63 | 'payrollItem', 64 | 'phoneCall', 65 | 'priceLevel', 66 | 'pricingGroup', 67 | 'projectTask', 68 | 'promotionCode', 69 | 'resourceAllocation', 70 | 'revRecSchedule', 71 | 'revRecTemplate', 72 | 'salesRole', 73 | 'salesTaxItem', 74 | 'solution', 75 | 'siteCategory', 76 | 'subsidiary', 77 | 'supportCase', 78 | 'task', 79 | 'taxGroup', 80 | 'taxType', 81 | 'term', 82 | 'timeBill', 83 | 'timeSheet', 84 | 'topic', 85 | 'transaction', 86 | 'unitsType', 87 | 'usage', 88 | 'vendor', 89 | 'vendorCategory', 90 | 'winLossReason', 91 | ] 92 | """ As defined in `SearchRecordType` in https://webservices.netsuite.com/xsd/platform/v2017_2_0/coreTypes.xsd""" 93 | 94 | GET_ALL_RECORD_TYPES = [ 95 | 'budgetCategory', 96 | 'campaignAudience', 97 | 'campaignCategory', 98 | 'campaignChannel', 99 | 'campaignFamily', 100 | 'campaignOffer', 101 | 'campaignSearchEngine', 102 | 'campaignSubscription', 103 | 'campaignVertical', 104 | 'currency', 105 | 'leadSource', 106 | 'state', 107 | 'supportCaseIssue', 108 | 'supportCaseOrigin', 109 | 'supportCasePriority', 110 | 'supportCaseStatus', 111 | 'supportCaseType', 112 | 'taxAcct', 113 | ] 114 | """ As defined in `GetAllRecordType` in https://webservices.netsuite.com/xsd/platform/v2017_2_0/coreTypes.xsd""" -------------------------------------------------------------------------------- /netsuitesdk/internal/exceptions.py: -------------------------------------------------------------------------------- 1 | """ 2 | :class:`NetSuiteError` 3 | :class:`NetSuiteLoginError` 4 | :class:`NetSuiteRequestError` 5 | :class:`NetSuiteTypeError` 6 | :class:`NetSuiteRateLimitError` 7 | """ 8 | 9 | class NetSuiteError(Exception): 10 | """Exception raised for errors during login or other requests (like 11 | get, getAll) to NetSuite.""" 12 | 13 | def __init__(self, message, code=None): 14 | """ 15 | :param str message: Text describing the error 16 | :param str code: Netsuite code specifying the exact error 17 | Possible values are listed in 18 | """ 19 | 20 | self.message = message 21 | self.code = code 22 | 23 | def __str__(self): 24 | if self.code is None: 25 | return self.message 26 | return 'code: {}, message: {}'.format(self.code, self.message) 27 | 28 | 29 | class NetSuiteLoginError(NetSuiteError): 30 | """Exception raised for errors during login to NetSuite""" 31 | 32 | pass 33 | 34 | class NetSuiteRequestError(NetSuiteError): 35 | """Exception raised for errors during requests like get, search, ..""" 36 | 37 | pass 38 | 39 | class NetSuiteTypeError(NetSuiteError): 40 | """Exception raised when requested an invalid netsuite type""" 41 | 42 | pass 43 | 44 | class NetSuiteRateLimitError(NetSuiteError): 45 | """Exception raised for errors during rate limit requests like get, search, ..""" 46 | 47 | pass 48 | -------------------------------------------------------------------------------- /netsuitesdk/internal/netsuite_types.py: -------------------------------------------------------------------------------- 1 | """ 2 | Declares all NetSuite types which are available through attribute lookup `ns.` 3 | of a :class:`~netsuitesdk.client.NetSuiteClient` instance `ns`. 4 | """ 5 | 6 | COMPLEX_TYPES = { 7 | 'ns0': [ 8 | 'BaseRef', 9 | 'GetAllRecord', 10 | 'GetAllResult', 11 | 'RecordList', 12 | 'RecordRef', 13 | 'ListOrRecordRef', 14 | 'SearchColumnStringField', 15 | 'SearchColumnSelectField', 16 | 'SearchResult', 17 | 'SearchEnumMultiSelectField', 18 | 'SearchStringField', 19 | 'SearchMultiSelectField', 20 | 'SearchDateField', 21 | 'SearchLongField', 22 | 'SearchBooleanField', 23 | 'Status', 24 | 'StatusDetail', 25 | 'TokenPassport', 26 | 'TokenPassportSignature', 27 | 'WsRole', 28 | 'DateCustomFieldRef', 29 | 'CustomFieldList', 30 | 'DoubleCustomFieldRef', 31 | 'StringCustomFieldRef', 32 | 'BooleanCustomFieldRef', 33 | 'CustomRecordRef', 34 | 'SelectCustomFieldRef' 35 | ], 36 | 37 | # ns4: https://webservices.netsuite.com/xsd/platform/v2017_2_0/messages.xsd 38 | 'ns4': [ 39 | 'ApplicationInfo', 40 | 'GetAllRequest', 41 | 'GetRequest', 42 | 'GetResponse', 43 | 'GetAllResponse', 44 | 'PartnerInfo', 45 | 'ReadResponse', 46 | 'SearchPreferences', 47 | 'SearchResponse' 48 | ], 49 | 50 | # https://webservices.netsuite.com/xsd/platform/v2017_2_0/common.xsd 51 | 'ns5': [ 52 | 'AccountSearchBasic', 53 | 'Address', 54 | 'CustomerSearchBasic', 55 | 'JobSearchBasic', 56 | 'LocationSearchBasic', 57 | 'TransactionSearchBasic', 58 | 'InboundShipmentSearchBasic', 59 | 'VendorSearchBasic', 60 | 'SubsidiarySearchBasic', 61 | 'EmployeeSearchBasic', 62 | 'FolderSearchBasic', 63 | 'FileSearchBasic', 64 | 'CustomRecordSearchBasic', 65 | 'CustomListSearchBasic', 66 | 'TermSearchBasic', 67 | 'DepartmentSearchBasic', 68 | 'TaxDetails', 69 | 'TaxDetailsList', 70 | 'ItemSearchBasic', 'ItemSearchRowBasic', 71 | 'ClassificationSearchBasic', 72 | 'CurrencyRateSearchBasic', 73 | 'ConsolidatedExchangeRateSearchBasic', 74 | 'BillingAccountSearchBasic' 75 | ], 76 | 77 | 'ns6': [ 78 | 'ItemCostEstimateType', 79 | ], 80 | 81 | # urn:relationships.lists.webservices.netsuite.com 82 | 'ns13': [ 83 | 'BillingAccount', 84 | 'CustomerAddressbook', 'CustomerAddressbookList', 85 | 'Customer', 'CustomerSearch', 'CustomerTaxRegistrationList', 'CustomerTaxRegistration', 86 | 'Vendor', 'VendorSearch', 87 | 'Job', 'JobSearch', 88 | 'VendorAddressbook', 'VendorAddressbookList', 'BillingAccountSearch' 89 | ], 90 | 91 | # urn:accounting_2017_2.lists.webservices.netsuite.com 92 | # https://webservices.netsuite.com/xsd/lists/v2017_2_0/accounting.xsd 93 | 'ns17': [ 94 | 'Account', 'AccountSearch', 95 | 'ExpenseCategory', 'ExpenseCategorySearch', 96 | 'AccountingPeriod', 97 | 'Classification', 'ClassificationSearch', 98 | 'Department', 'DepartmentSearch', 99 | 'Location', 'LocationSearch', 100 | 'Subsidiary', 'SubsidiarySearch', 101 | 'VendorCategory', 'VendorCategorySearch', 102 | 'Term', 'TermSearch', 103 | 'SalesTaxItem', 'SalesTaxItemSearch', 104 | 'TaxGroup', 'TaxGroupSearch', 105 | 'DepartmentSearch', 106 | 'ItemSearch', 'ItemSearchAdvanced', 'ItemSearchRow', 107 | 'ClassificationSearch', 108 | 'PriceLevelSearch' 109 | ], 110 | 111 | 'ns19': [ 112 | 'Invoice', 113 | 'InvoiceItem', 114 | 'InvoiceItemList', 115 | 'TransactionSearch', 116 | 'TransactionSearchAdvanced', 117 | 'Usage', 118 | ], 119 | 120 | # urn:purchases_2017_2.transactions.webservices.netsuite.com 121 | # https://webservices.netsuite.com/xsd/transactions/v2017_2_0/purchases.xsd 122 | 'ns21': [ 123 | 'VendorBill', 124 | 'VendorBillExpense', 125 | 'VendorBillExpenseList', 126 | 'VendorBillItem', 127 | 'VendorBillItemList', 128 | 'VendorCredit', 129 | 'VendorCreditApply', 130 | 'VendorCreditApplyList', 131 | 'VendorCreditExpense', 132 | 'VendorCreditExpenseList', 133 | 'VendorCreditItem', 134 | 'VendorCreditItemList', 135 | 'VendorPayment', 136 | 'VendorPaymentApplyList', 137 | 'VendorPaymentCredit', 138 | 'VendorPaymentCreditList', 139 | 'InboundShipmentSearch', 140 | 'VendorPaymentApply' 141 | ], 142 | 143 | 'ns23': [ 144 | 'CreditMemo', 145 | 'CreditMemoApply', 146 | 'CreditMemoApplyList', 147 | 'CreditMemoItem', 148 | 'CreditMemoItemList', 149 | ], 150 | 151 | # urn:general_2019_2.transactions.webservices.netsuite.com 152 | # https://webservices.netsuite.com/xsd/transactions/v2019_2_0/general.xsd 153 | 'ns31': [ 154 | 'JournalEntry', 155 | 'JournalEntryLine', 156 | 'JournalEntryLineList', 157 | 'AdvInterCompanyJournalEntry', 158 | 'AdvInterCompanyJournalEntryLine', 159 | 'AdvInterCompanyJournalEntryLineList' 160 | ], 161 | 162 | 'ns32': [ 163 | 'CustomRecord', 164 | 'CustomRecordCustomField', 165 | 'CustomRecordSearch', 166 | 'CustomListSearch', 167 | 'CustomRecordType' 168 | ], 169 | 170 | # https://webservices.netsuite.com/xsd/lists/v2019_2_0/employees.xsd 171 | 'ns34': [ 172 | 'EmployeeSearch', 173 | 'Employee' 174 | ], 175 | 176 | # urn:employees_2019_2.transactions.webservices.netsuite.com 177 | # https://webservices.netsuite.com/xsd/transactions/v2019_2_0/employees.xsd 178 | 'ns38': [ 179 | 'ExpenseReport', 180 | 'ExpenseReportExpense', 181 | 'ExpenseReportExpenseList', 182 | ], 183 | 'ns11': [ 184 | 'FolderSearch', 185 | 'Folder', 186 | 'File', 187 | 'FileSearch' 188 | ], 189 | } 190 | 191 | SIMPLE_TYPES = { 192 | # ns1: view-source:https://webservices.netsuite.com/xsd/platform/v2017_2_0/coreTypes.xsd 193 | 'ns1': [ 194 | 'RecordType', 195 | 'GetAllRecordType', 196 | 'SearchRecordType', 197 | 'SearchEnumMultiSelectFieldOperator', 198 | 'SearchStringFieldOperator', 199 | 'SearchDateFieldOperator', 200 | 'SearchLongFieldOperator', 201 | ], 202 | } 203 | -------------------------------------------------------------------------------- /netsuitesdk/internal/utils.py: -------------------------------------------------------------------------------- 1 | class PaginatedSearch: 2 | 3 | default_page_size = 20 4 | 5 | def __init__(self, client, type_name, search_record=None, basic_search=None, pageSize=None, perform_search=True): 6 | """ 7 | PaginatedSearch is a utility class that can be used to perform 8 | a search. 9 | 10 | :param NetSuiteClient client: the NetSuite client proxy object 11 | :param str type_name: the type name to perform the search on 12 | :param Search search_record: object containing the search filters 13 | :param SearchBasic basic_search: object containing basic search filters 14 | :param int pageSize: the number of records per result page 15 | :param bool perform_search: if True, will perform a first search after initializing basic data 16 | """ 17 | 18 | self._ns = client 19 | self._result = None 20 | self._type_name = type_name 21 | self.search_record = search_record or self._ns.search_factory(type_name=self._type_name) 22 | self.basic_search = basic_search 23 | if self.basic_search is not None: 24 | self.search_record.basic = self.basic_search 25 | if perform_search: 26 | self.search() 27 | 28 | @property 29 | def total_records(self): 30 | return self._result.totalRecords 31 | 32 | @property 33 | def page_size(self): 34 | return self._result.pageSize 35 | 36 | @property 37 | def total_pages(self): 38 | return self._result.totalPages 39 | 40 | @property 41 | def page_index(self): 42 | return self._result.pageIndex 43 | 44 | @property 45 | def records(self): 46 | return self._result.records 47 | 48 | @property 49 | def num_records(self): 50 | if self.records: 51 | return len(self.records) 52 | return 0 53 | 54 | def search(self): 55 | """ Call the netsuite operation `search` """ 56 | self._result = self._ns.search(searchRecord=self.search_record) 57 | 58 | def goto_page(self, page_index): 59 | """ After a search was performed, this method utilizes the NetSuite 60 | operation `searchMoreWithId` to retrieve more results """ 61 | 62 | if self._result is None: 63 | return 64 | if page_index > self.total_pages or page_index < 1: 65 | return 66 | self._result = self._ns.searchMoreWithId(searchId=self._result.searchId, 67 | pageIndex=page_index) 68 | -------------------------------------------------------------------------------- /pytest.ini: -------------------------------------------------------------------------------- 1 | [pytest] 2 | log_cli = 1 3 | log_cli_level = INFO 4 | log_cli_format = %(asctime)s [%(levelname)8s] %(name)s: %(message)s (%(filename)s:%(lineno)s) 5 | log_cli_date_format=%Y-%m-%d %H:%M:%S 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zeep==3.4.0 2 | pytest==7.2.1 3 | pytest-cov==2.8.1 4 | pytest-mock==1.11.2 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import setuptools 2 | 3 | with open('README.md', 'r') as f: 4 | long_description = f.read() 5 | 6 | setuptools.setup( 7 | name='netsuitesdk', 8 | version='3.1.0', 9 | author='Siva Narayanan', 10 | author_email='siva@fyle.in', 11 | description='Python SDK for accessing the NetSuite SOAP webservice', 12 | license='MIT', 13 | long_description=long_description, 14 | long_description_content_type='text/markdown', 15 | keywords=['netsuite', 'api', 'python', 'sdk'], 16 | url='https://github.com/fylein/netsuite-sdk-py', 17 | packages=setuptools.find_packages(), 18 | install_requires=['zeep'], 19 | classifiers=[ 20 | 'Topic :: Internet :: WWW/HTTP', 21 | 'Intended Audience :: Developers', 22 | 'Programming Language :: Python :: 3', 23 | 'License :: OSI Approved :: MIT License', 24 | 'Operating System :: OS Independent', 25 | ] 26 | ) 27 | -------------------------------------------------------------------------------- /test/integration/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fylein/netsuite-sdk-py/c66c398997d0e624101a26b036958d88a55a85d2/test/integration/__init__.py -------------------------------------------------------------------------------- /test/integration/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import pytest 5 | from netsuitesdk import NetSuiteConnection 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | @pytest.fixture(scope='module') 10 | def nc(): 11 | NS_ACCOUNT = os.getenv('NS_ACCOUNT') 12 | NS_CONSUMER_KEY = os.getenv('NS_CONSUMER_KEY') 13 | NS_CONSUMER_SECRET = os.getenv('NS_CONSUMER_SECRET') 14 | NS_TOKEN_KEY = os.getenv('NS_TOKEN_KEY') 15 | NS_TOKEN_SECRET = os.getenv('NS_TOKEN_SECRET') 16 | nc = NetSuiteConnection(account=NS_ACCOUNT, consumer_key=NS_CONSUMER_KEY, consumer_secret=NS_CONSUMER_SECRET, token_key=NS_TOKEN_KEY, token_secret=NS_TOKEN_SECRET) 17 | return nc 18 | -------------------------------------------------------------------------------- /test/integration/data/adv_inter_company_journal_entries/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "accountingBook": null, 3 | "accountingBookDetailList": null, 4 | "approved": null, 5 | "class": null, 6 | "createdDate": null, 7 | "createdFrom": null, 8 | "currency": { 9 | "name": "USD", 10 | "internalId": null, 11 | "externalId": null, 12 | "type": "currency" 13 | }, 14 | "customFieldList": null, 15 | "customForm": null, 16 | "department": null, 17 | "exchangeRate": null, 18 | "isBookSpecific": null, 19 | "lastModifiedDate": null, 20 | "lineList": [ 21 | { 22 | "lineSubsidiary": { 23 | "name": null, 24 | "internalId": "6", 25 | "externalId": null, 26 | "type": "subsidiary" 27 | }, 28 | "account": { 29 | "name": null, 30 | "internalId": "326", 31 | "externalId": null, 32 | "type": "account" 33 | }, 34 | "credit": 500, 35 | "memo": "Testing JournalEntry via Fyle SDK 3", 36 | "customFieldList": [ 37 | { 38 | "type": "String", 39 | "value": "NetsuiteSDK test field" 40 | }, 41 | { 42 | "type": "Select", 43 | "value": "45" 44 | } 45 | ] 46 | }, 47 | { 48 | "lineSubsidiary": { 49 | "name": null, 50 | "internalId": "6", 51 | "externalId": null, 52 | "type": "subsidiary" 53 | }, 54 | "account": { 55 | "name": null, 56 | "internalId": "326", 57 | "externalId": null, 58 | "type": "account" 59 | }, 60 | "debit": 500, 61 | "customFieldList": [ 62 | { 63 | "type": "String", 64 | "value": "NetsuiteSDK test field" 65 | }, 66 | { 67 | "type": "Select", 68 | "value": "45" 69 | } 70 | ], 71 | "memo": "Testing JournalEntry via Fyle SDK 3" 72 | }, 73 | { 74 | "lineSubsidiary": { 75 | "name": null, 76 | "internalId": "4", 77 | "externalId": null, 78 | "type": "subsidiary" 79 | }, 80 | "account": { 81 | "name": null, 82 | "internalId": "326", 83 | "externalId": null, 84 | "type": "account" 85 | }, 86 | "credit": 500, 87 | "customFieldList": [ 88 | { 89 | "type": "String", 90 | "value": "NetsuiteSDK test field" 91 | }, 92 | { 93 | "type": "Select", 94 | "value": "45" 95 | } 96 | ], 97 | "memo": "Testing JournalEntry via Fyle SDK 3" 98 | }, 99 | { 100 | "lineSubsidiary": { 101 | "name": null, 102 | "internalId": "4", 103 | "externalId": null, 104 | "type": "subsidiary" 105 | }, 106 | "account": { 107 | "name": null, 108 | "internalId": "326", 109 | "externalId": null, 110 | "type": "account" 111 | }, 112 | "debit": 500, 113 | "customFieldList": [ 114 | { 115 | "type": "String", 116 | "value": "NetsuiteSDK test field" 117 | }, 118 | { 119 | "type": "Select", 120 | "value": "45" 121 | } 122 | ], 123 | "memo": "Testing JournalEntry via Fyle SDK 3" 124 | } 125 | ], 126 | "location": null, 127 | "memo": "JE Testing Fyle SDK 3", 128 | "nexus": null, 129 | "parentExpenseAlloc": null, 130 | "postingPeriod": null, 131 | "reversalDate": null, 132 | "reversalDefer": null, 133 | "reversalEntry": null, 134 | "subsidiary": { 135 | "name": null, 136 | "internalId": "6", 137 | "externalId": null, 138 | "type": "subsidiary" 139 | }, 140 | "toSubsidiary": { 141 | "name": null, 142 | "internalId": "4", 143 | "externalId": null, 144 | "type": "subsidiary" 145 | }, 146 | "subsidiaryTaxRegNum": null, 147 | "taxPointDate": null, 148 | "toSubsidiary": null, 149 | "tranDate": null, 150 | "tranId": null, 151 | "externalId": "ADV_INTERCO_JE_04" 152 | } -------------------------------------------------------------------------------- /test/integration/data/billing_account/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "billingSchedule": { 3 | "externalId": null, 4 | "internalId": 1, 5 | "type": "billingSchedule" 6 | }, 7 | "cashSaleForm": null, 8 | "class": null, 9 | "createdBy": null, 10 | "createdDate": null, 11 | "currency": { 12 | "name": "USA", 13 | "internalId": null, 14 | "externalId": null, 15 | "type": "currency" 16 | }, 17 | "customer": { 18 | "externalId": null, 19 | "internalId": 1, 20 | "type": "customer" 21 | }, 22 | "customForm": null, 23 | "department": null, 24 | "frequency": null, 25 | "inactive": null, 26 | "invoiceForm": null, 27 | "lastBillCycleDate": null, 28 | "lastBillDate": null, 29 | "location": null, 30 | "memo": "Testing BillingAccounts using Fyle SDK", 31 | "name": "Test Case Account", 32 | "nextBillCycleDate": null, 33 | "startDate": "2022-07-21T17:32:28Z", 34 | "subsidiary": null, 35 | "externalId": "1237" 36 | } -------------------------------------------------------------------------------- /test/integration/data/credit_memo/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "account": null, 4 | "altHandlingCost": null, 5 | "altShippingCost": null, 6 | "amountPaid": null, 7 | "amountRemaining": null, 8 | "autoApply": null, 9 | "billAddressList": null, 10 | "billingAddress": null, 11 | "class": null, 12 | "createdDate": null, 13 | "createdFrom": null, 14 | "currency": null, 15 | "currencyName": null, 16 | "customForm": null, 17 | "deferredRevenue": null, 18 | "department": null, 19 | "discountItem": null, 20 | "discountRate": null, 21 | "discountTotal": null, 22 | "email": null, 23 | "entityTaxRegNum": null, 24 | "estGrossProfit": null, 25 | "estGrossProfitPercent": null, 26 | "excludeCommission": null, 27 | "fax": null, 28 | "handlingCost": null, 29 | "handlingTax1Rate": null, 30 | "handlingTax2Rate": null, 31 | "handlingTaxCode": null, 32 | "isMultiShipTo": null, 33 | "isTaxable": null, 34 | "itemList": [], 35 | "job": null, 36 | "lastModifiedDate": null, 37 | "leadSource": null, 38 | "location":null, 39 | "memo": "Testing CreditMemos using Fyle SDK", 40 | "nexus": null, 41 | "partner": null, 42 | "postingPeriod": null, 43 | "promoCode": null, 44 | "recognizedRevenue": null, 45 | "revenueStatus": null, 46 | "revRecOnRevCommitment": null, 47 | "shipMethod": null, 48 | "shippingCost": null, 49 | "shippingTax1Rate": null, 50 | "shippingTax2Rate": null, 51 | "shippingTaxCode": null, 52 | "source": null, 53 | "status": null, 54 | "subsidiary": null, 55 | "subsidiaryTaxRegNum": null, 56 | "subTotal": null, 57 | "tax2Total": null, 58 | "taxDetailsOverride": null, 59 | "taxItem": null, 60 | "taxPointDate": null, 61 | "taxRate": null, 62 | "taxRegOverride": null, 63 | "taxTotal": null, 64 | "toBeEmailed": null, 65 | "toBeFaxed": null, 66 | "toBePrinted": null, 67 | "total": null, 68 | "totalCostEstimate": null, 69 | "tranDate": null, 70 | "tranId": null, 71 | "tranIsVsoeBundle": null, 72 | "unapplied": null, 73 | "vatRegNum": null, 74 | "vsoeAutoCalc": null, 75 | "entity": { 76 | "name": null, 77 | "internalId": "1", 78 | "externalId": null, 79 | "type": "vendor" 80 | } 81 | } -------------------------------------------------------------------------------- /test/integration/data/custom_record/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "allowAttachments": null, 4 | "allowInlineEditing": null, 5 | "allowNumberingOverride": null, 6 | "allowQuickSearch": null, 7 | "altName": null, 8 | "autoName": null, 9 | "created": null, 10 | "customFieldList": null, 11 | "customForm": null, 12 | "customRecordId": "Cust123", 13 | "description": "Test Custom Record", 14 | "disclaimer": "Testing Record", 15 | "enablEmailMerge": null, 16 | "schema": null, 17 | "enableNumbering": null, 18 | "includeName": null, 19 | "isAvailableOffline": null, 20 | "isInactive": null, 21 | "isNumberingUpdateable": null, 22 | "isOrdered": null, 23 | "lastModified": null, 24 | "name": "TestNetsuite SDK", 25 | "numberingCurrentNumber": null, 26 | "numberingInit": null, 27 | "numberingMinDigits": null, 28 | "numberingPrefix": null, 29 | "numberingSuffix": null, 30 | "owner": null, 31 | "parent": null, 32 | "recType": { 33 | "name": "Favourite Bands", 34 | "internalId": "476", 35 | "externalId": null, 36 | "type": null 37 | }, 38 | "scriptId": null, 39 | "showCreationDate": null, 40 | "showCreationDateOnList": null, 41 | "showId": null, 42 | "showLastModified": null, 43 | "showLastModifiedOnList": null, 44 | "showNotes": null, 45 | "showOwner": null, 46 | "showOwnerAllowChange": null, 47 | "showOwnerOnList": null, 48 | "translationsList": null, 49 | "usePermissions": null, 50 | "recordName":"TestNetsuiteSDK" 51 | } -------------------------------------------------------------------------------- /test/integration/data/customers/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "nullFieldList": null, 3 | "customForm": null, 4 | "entityId": null, 5 | "altName": null, 6 | "isPerson": null, 7 | "phoneticName": null, 8 | "salutation": null, 9 | "firstName": null, 10 | "middleName": null, 11 | "lastName": null, 12 | "companyName": "Testing Customer SDK", 13 | "entityStatus": null, 14 | "parent": null, 15 | "phone": null, 16 | "fax": null, 17 | "email": null, 18 | "url": null, 19 | "defaultAddress": null, 20 | "isInactive": null, 21 | "category": null, 22 | "title": null, 23 | "printOnCheckAs": null, 24 | "altPhone": null, 25 | "homePhone": null, 26 | "mobilePhone": null, 27 | "altEmail": null, 28 | "language": null, 29 | "comments": null, 30 | "numberFormat": null, 31 | "negativeNumberFormat": null, 32 | "dateCreated": null, 33 | "image": null, 34 | "emailPreference": null, 35 | "subsidiary": { 36 | "name": null, 37 | "internalId": "1", 38 | "externalId": null, 39 | "type": "subsidiary" 40 | }, 41 | "representingSubsidiary": {}, 42 | "salesRep": null, 43 | "territory": null, 44 | "contribPct": null, 45 | "partner": null, 46 | "salesGroup": null, 47 | "vatRegNumber": null, 48 | "accountNumber": null, 49 | "taxExempt": null, 50 | "terms": null, 51 | "creditLimit": null, 52 | "creditHoldOverride": null, 53 | "monthlyClosing": {}, 54 | "overrideCurrencyFormat": null, 55 | "displaySymbol": null, 56 | "symbolPlacement": null, 57 | "balance": null, 58 | "overdueBalance": null, 59 | "daysOverdue": null, 60 | "unbilledOrders": null, 61 | "consolUnbilledOrders": null, 62 | "consolOverdueBalance": null, 63 | "consolDepositBalance": null, 64 | "consolBalance": null, 65 | "consolAging": null, 66 | "consolAging1": null, 67 | "consolAging2": null, 68 | "consolAging3": null, 69 | "consolAging4": null, 70 | "consolDaysOverdue": null, 71 | "priceLevel": null, 72 | "currency": {"internalId": "1"}, 73 | "prefCCProcessor": null, 74 | "depositBalance": null, 75 | "shipComplete": null, 76 | "taxable": null, 77 | "taxItem": null, 78 | "resaleNumber": null, 79 | "aging": null, 80 | "aging1": null, 81 | "aging2": null, 82 | "aging3": null, 83 | "aging4": null, 84 | "startDate": null, 85 | "alcoholRecipientType": null, 86 | "endDate": null, 87 | "reminderDays": null, 88 | "shippingItem": null, 89 | "thirdPartyAcct": null, 90 | "thirdPartyZipcode": null, 91 | "thirdPartyCountry": null, 92 | "giveAccess": null, 93 | "estimatedBudget": null, 94 | "accessRole": null, 95 | "sendEmail": null, 96 | "assignedWebSite": null, 97 | "password": null, 98 | "password2": null, 99 | "requirePwdChange": null, 100 | "campaignCategory": null, 101 | "sourceWebSite": null, 102 | "leadSource": null, 103 | "receivablesAccount": null, 104 | "drAccount": null, 105 | "fxAccount": null, 106 | "defaultOrderPriority": null, 107 | "webLead": null, 108 | "referrer": null, 109 | "keywords": null, 110 | "clickStream": null, 111 | "lastPageVisited": null, 112 | "visits": null, 113 | "firstVisit": null, 114 | "lastVisit": null, 115 | "billPay": null, 116 | "openingBalance": null, 117 | "lastModifiedDate": null, 118 | "openingBalanceDate": null, 119 | "openingBalanceAccount": null, 120 | "stage": null, 121 | "emailTransactions": null, 122 | "printTransactions": null, 123 | "faxTransactions": null, 124 | "defaultTaxReg": null, 125 | "syncPartnerTeams": null, 126 | "isBudgetApproved": null, 127 | "globalSubscriptionStatus": null, 128 | "salesReadiness": null, 129 | "salesTeamList": null, 130 | "buyingReason": null, 131 | "downloadList": null, 132 | "buyingTimeFrame": null, 133 | "addressbookList": null, 134 | "subscriptionsList": null, 135 | "contactRolesList": null, 136 | "currencyList": null, 137 | "creditCardsList": null, 138 | "partnersList": null, 139 | "groupPricingList": null, 140 | "itemPricingList": null, 141 | "taxRegistrationList": null, 142 | "customFieldList": null, 143 | "internalId": null, 144 | "externalId": "consumerTestApi" 145 | } 146 | -------------------------------------------------------------------------------- /test/integration/data/employee/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1235", 3 | "accountNumber": null, 4 | "adpId": null, 5 | "altName": null, 6 | "approvalLimit": null, 7 | "approver": null, 8 | "baseWage": null, 9 | "baseWageType": null, 10 | "billingClass": null, 11 | "billPay": null, 12 | "birthDate": null, 13 | "class": null, 14 | "comments": null, 15 | "commissionPaymentPreference": null, 16 | "compensationCurrency": null, 17 | "concurrentWebServicesUser": null, 18 | "currency": null, 19 | "customForm": null, 20 | "inheritIPRules": false, 21 | "dateCreated": null, 22 | "entityId": "1", 23 | "defaultAcctCorpCardExp": { 24 | "internalId": "213", 25 | "externalId": null, 26 | "type": "account" 27 | }, 28 | "directDeposit": null, 29 | "eligibleForCommission": null, 30 | "email": "testemployeenetsuitesdk@example.com", 31 | "employeeStatus": null, 32 | "employeeType": null, 33 | "ethnicity": null, 34 | "expenseLimit": null, 35 | "defaultExpenseReportCurrency": { 36 | "name": "USA", 37 | "internalId": 1, 38 | "externalId": null, 39 | "type": "currency" 40 | }, 41 | "subsidiary": { 42 | "name": null, 43 | "internalId": "1", 44 | "externalId": null, 45 | "type": "subsidiary" 46 | }, 47 | "fax": null, 48 | "firstName": "Test Netsuite", 49 | "lastName": "SDK", 50 | "gender": null, 51 | "workCalendar":{ 52 | "externalId": null, 53 | "internalId": null, 54 | "type": null 55 | }, 56 | "payFrequency":"_monthly", 57 | "location": { 58 | "name": null, 59 | "internalId": "1", 60 | "externalId": null, 61 | "type": "location" 62 | }, 63 | "department": { 64 | "name": null, 65 | "internalId": "10", 66 | "externalId": null, 67 | "type": "department" 68 | } 69 | } -------------------------------------------------------------------------------- /test/integration/data/expense_reports/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "nullFieldList": null, 3 | "createdDate": null, 4 | "lastModifiedDate": null, 5 | "status": null, 6 | "customForm": null, 7 | "account": { 8 | "name": null, 9 | "internalId": "118", 10 | "externalId": null, 11 | "type": "account" 12 | }, 13 | "entity": { 14 | "name": null, 15 | "internalId": "1648", 16 | "externalId": null, 17 | "type": "vendor" 18 | }, 19 | "expenseReportCurrency": { 20 | "name": "USA", 21 | "internalId": null, 22 | "externalId": null, 23 | "type": "currency" 24 | }, 25 | "expenseReportExchangeRate": null, 26 | "subsidiary": { 27 | "name": null, 28 | "internalId": "1", 29 | "externalId": null, 30 | "type": "subsidiary" 31 | }, 32 | "taxPointDate": null, 33 | "tranId": null, 34 | "acctCorpCardExp": null, 35 | "postingPeriod": null, 36 | "tranDate": null, 37 | "dueDate": null, 38 | "approvalStatus": null, 39 | "total": null, 40 | "nextApprover": null, 41 | "advance": null, 42 | "tax1Amt": null, 43 | "amount": null, 44 | "memo": "Testing ExpenseReport using Fyle SDK", 45 | "complete": null, 46 | "supervisorApproval": null, 47 | "accountingApproval": null, 48 | "useMultiCurrency": null, 49 | "tax2Amt": null, 50 | "department": null, 51 | "class": null, 52 | "location": null, 53 | "expenseList": [ 54 | { 55 | "amount": 100, 56 | "category": { 57 | "name": null, 58 | "internalId": "2", 59 | "externalId": null, 60 | "type": "account" 61 | }, 62 | "class": null, 63 | "corporateCreditCard": null, 64 | "currency": { 65 | "name": "USD", 66 | "internalId": "1", 67 | "externalId": null, 68 | "type": "currency" 69 | }, 70 | "customer": null, 71 | "customFieldList": [ 72 | { 73 | "type": "string", 74 | "value": "NetsuiteSDK test field" 75 | }, 76 | { 77 | "type": "select", 78 | "value": "45" 79 | } 80 | ], 81 | "department": null, 82 | "exchangeRate": null, 83 | "expenseDate": null, 84 | "expMediaItem": null, 85 | "foreignAmount": null, 86 | "grossAmt": null, 87 | "isBillable": null, 88 | "isNonReimbursable": null, 89 | "line": null, 90 | "location": null, 91 | "memo": "Testing ExpenseReports using Fyle SDK", 92 | "quantity": null, 93 | "rate": null, 94 | "receipt": null, 95 | "refNumber": null, 96 | "tax1Amt": null, 97 | "taxCode": null, 98 | "taxRate1": null, 99 | "taxRate2":null 100 | } 101 | ], 102 | "accountingBookDetailList": null, 103 | "customFieldList": null, 104 | "internalId": null, 105 | "externalId": "EXPR_1" 106 | } 107 | -------------------------------------------------------------------------------- /test/integration/data/file/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "altTagCaption": null, 4 | "attachFrom": "_computer", 5 | "bundleable": null, 6 | "caption": null, 7 | "encoding": "_utf8", 8 | "class": null, 9 | "createdDate": null, 10 | "department": null, 11 | "description": null, 12 | "featuredDescription": null, 13 | "fileType": null, 14 | "hideInBundle": null, 15 | "isInactive": null, 16 | "isOnline": null, 17 | "isPrivate": null, 18 | "lastModifiedDate": null, 19 | "mediaFile": null, 20 | "mediaTypeName": "_JSON", 21 | "mediaType": null, 22 | "name": "TestFolder", 23 | "folder": { 24 | "internalId": 8197, 25 | "externalId": null, 26 | "type": "folder" 27 | }, 28 | "siteDescription": null, 29 | "storeDisplayThumbnail": null, 30 | "textFileEncoding": null, 31 | "url": null, 32 | "urlComponent": null 33 | } -------------------------------------------------------------------------------- /test/integration/data/folder/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "bundleable": null, 4 | "class": null, 5 | "department": null, 6 | "description": "Test description", 7 | "folderType": "_documentsAndFiles", 8 | "group": null, 9 | "hideInBundle": null, 10 | "isInactive": null, 11 | "isOnline": null, 12 | "isPrivate": null, 13 | "location": null, 14 | "name": "testFolder", 15 | "parent": { 16 | "internalId": 8197, 17 | "externalId": null, 18 | "type": "folder" 19 | }, 20 | "subsidiary":{ 21 | "name": null, 22 | "internalId": "1", 23 | "externalId": null, 24 | "type": "subsidiary" 25 | } 26 | } -------------------------------------------------------------------------------- /test/integration/data/invoices/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "account": null, 4 | "accountingBookDetailList": null, 5 | "expCostDiscount": null, 6 | "expCostDiscPrint": null, 7 | "expCostDiscRate": null, 8 | "expCostDiscTax1Amt": null, 9 | "expCostDiscTaxable": null, 10 | "expCostList": null, 11 | "expCostTaxCode": null, 12 | "handlingCost": null, 13 | "itemCostList": null, 14 | "message": null, 15 | "messageSel": null, 16 | "nextApprover": null, 17 | "partner": null, 18 | "promotionsList": null, 19 | "salesGroup": null, 20 | "shipMethod": null, 21 | "shippingCost": null, 22 | "tranDate": "2022-07-21T17:32:28Z", 23 | "entity": { 24 | "name": null, 25 | "internalId": "14452", 26 | "externalId": null, 27 | "type": "vendor" 28 | }, 29 | "dueDate": "2023-02-15T17:32:28Z", 30 | "currency": null, 31 | "invoiceItemList": { 32 | "item": [ 33 | { 34 | "amount": null, 35 | "amountOrdered": null, 36 | "binNumbers": null, 37 | "catchUpPeriod": null, 38 | "chargesList": null, 39 | "chargeType": null, 40 | "class": null, 41 | "costEstimate": null, 42 | "costEstimateType": null, 43 | "currentPercent": null, 44 | "customFieldList": null, 45 | "deferRevRec": null, 46 | "department":null, 47 | "description": "Test Invoice item", 48 | "excludeFromRateRequest": null, 49 | "giftCertFrom": null, 50 | "item": null, 51 | "itemIsFulfilled": null, 52 | "line": null, 53 | "location": { 54 | "name": null, 55 | "internalId": "1", 56 | "externalId": null, 57 | "type": "location" 58 | }, 59 | "price": null, 60 | "quantity": 10, 61 | "rate": null, 62 | "units": null 63 | } 64 | ], 65 | "replaceAll": true 66 | } 67 | } -------------------------------------------------------------------------------- /test/integration/data/journal_entries/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "accountingBook": null, 3 | "accountingBookDetailList": null, 4 | "approved": null, 5 | "class": null, 6 | "createdDate": null, 7 | "createdFrom": null, 8 | "currency": { 9 | "name": "USA", 10 | "internalId": null, 11 | "externalId": null, 12 | "type": "currency" 13 | }, 14 | "customFieldList": null, 15 | "customForm": null, 16 | "department": null, 17 | "exchangeRate": null, 18 | "isBookSpecific": null, 19 | "lastModifiedDate": null, 20 | "lineList": [ 21 | { 22 | "account": { 23 | "name": null, 24 | "internalId": "25", 25 | "externalId": null, 26 | "type": "account" 27 | }, 28 | "department": { 29 | "name": null, 30 | "internalId": "10", 31 | "externalId": null, 32 | "type": "department" 33 | }, 34 | "location": { 35 | "name": null, 36 | "internalId": "2", 37 | "externalId": null, 38 | "type": "location" 39 | }, 40 | "class": { 41 | "name": null, 42 | "internalId": null, 43 | "externalId": null, 44 | "type": "account" 45 | }, 46 | "entity": { 47 | "name": null, 48 | "internalId": "1648", 49 | "externalId": null, 50 | "type": "vendor" 51 | }, 52 | "credit": 500, 53 | "creditTax": null, 54 | "customFieldList": [ 55 | { 56 | "type": "String", 57 | "value": "NetsuiteSDK test field1" 58 | }, 59 | { 60 | "type": "Select", 61 | "value": "45" 62 | } 63 | ], 64 | "debit": null, 65 | "debitTax": null, 66 | "eliminate": null, 67 | "endDate": null, 68 | "grossAmt": null, 69 | "line": null, 70 | "lineTaxCode": null, 71 | "lineTaxRate": null, 72 | "memo": "Testing JournalEntry via Fyle SDK 3", 73 | "residual": null, 74 | "revenueRecognitionRule": null, 75 | "schedule": null, 76 | "scheduleNum": null, 77 | "startDate": null, 78 | "tax1Acct": null, 79 | "tax1Amt": null, 80 | "taxAccount": null, 81 | "taxBasis": null, 82 | "taxCode": null, 83 | "taxRate1": null, 84 | "totalAmount": null 85 | }, 86 | { 87 | "account": { 88 | "name": null, 89 | "internalId": "25", 90 | "externalId": null, 91 | "type": "account" 92 | }, 93 | "department": { 94 | "name": null, 95 | "internalId": "10", 96 | "externalId": null, 97 | "type": "department" 98 | }, 99 | "location": { 100 | "name": null, 101 | "internalId": "2", 102 | "externalId": null, 103 | "type": "location" 104 | }, 105 | "class": { 106 | "name": null, 107 | "internalId": null, 108 | "externalId": null, 109 | "type": "account" 110 | }, 111 | "credit": null, 112 | "creditTax": null, 113 | "customFieldList": [ 114 | { 115 | "type": "String", 116 | "value": "NetsuiteSDK test field2" 117 | }, 118 | { 119 | "type": "Select", 120 | "value": "45" 121 | } 122 | ], 123 | "debit": 500, 124 | "debitTax": null, 125 | "eliminate": null, 126 | "endDate": null, 127 | "entity": { 128 | "name": null, 129 | "internalId": "1648", 130 | "externalId": null, 131 | "type": "vendor" 132 | }, 133 | "grossAmt": null, 134 | "line": null, 135 | "lineTaxCode": null, 136 | "lineTaxRate": null, 137 | "memo": "Testing JournalEntry via Fyle SDK 3", 138 | "residual": null, 139 | "revenueRecognitionRule": null, 140 | "schedule": null, 141 | "scheduleNum": null, 142 | "startDate": null, 143 | "tax1Acct": null, 144 | "tax1Amt": null, 145 | "taxAccount": null, 146 | "taxBasis": null, 147 | "taxCode": null, 148 | "taxRate1": null, 149 | "totalAmount": null 150 | } 151 | ], 152 | "location": null, 153 | "memo": "JE Testing Fyle SDK 3", 154 | "nexus": null, 155 | "parentExpenseAlloc": null, 156 | "postingPeriod": null, 157 | "reversalDate": null, 158 | "reversalDefer": null, 159 | "reversalEntry": null, 160 | "subsidiary": { 161 | "name": null, 162 | "internalId": "1", 163 | "externalId": null, 164 | "type": "subsidiary" 165 | }, 166 | "subsidiaryTaxRegNum": null, 167 | "taxPointDate": null, 168 | "toSubsidiary": null, 169 | "tranDate": null, 170 | "tranId": null, 171 | "externalId": "JE_05" 172 | } -------------------------------------------------------------------------------- /test/integration/data/usage/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "customer": null, 3 | "customForm": null, 4 | "item": null, 5 | "memo": "Testing Usage using Fyle SDK", 6 | "subscriptionPlan": null, 7 | "usageDate": "2022-07-21T17:32:28Z", 8 | "usageQuantity": 15, 9 | "externalId": "1237" 10 | } -------------------------------------------------------------------------------- /test/integration/data/vendor/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "currency":{ 4 | "name": "USA", 5 | "internalId": null, 6 | "externalId": null, 7 | "type": "currency" 8 | }, 9 | "representingSubsidiary":{ 10 | "name": null, 11 | "internalId": "4", 12 | "externalId": null, 13 | "type": "subsidiary" 14 | }, 15 | "subsidiary":{ 16 | "name": null, 17 | "internalId": "6", 18 | "externalId": null, 19 | "type": "subsidiary" 20 | }, 21 | "workCalendar": { 22 | "externalId": null, 23 | "internalId": null, 24 | "type": null 25 | }, 26 | "companyName": "Netsuite SDK Testing" 27 | } -------------------------------------------------------------------------------- /test/integration/data/vendor_bills/860860_sb1.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "currency": { 4 | "name": null, 5 | "internalId": "1", 6 | "externalId": null, 7 | "type": "currency" 8 | }, 9 | "expenseList": [ 10 | { 11 | "account": { 12 | "name": null, 13 | "internalId": 200, 14 | "externalId": null, 15 | "type": "account" 16 | }, 17 | "amount": 10.0, 18 | "department": { 19 | "name": null, 20 | "internalId": "1", 21 | "externalId": null, 22 | "type": "department" 23 | }, 24 | "class": { 25 | "name": null, 26 | "internalId": 1, 27 | "externalId": null, 28 | "type": "classification" 29 | }, 30 | "location": { 31 | "name": null, 32 | "internalId": "1", 33 | "externalId": null, 34 | "type": "location" 35 | }, 36 | "memo": "Expense ID 20190101" 37 | }, 38 | { 39 | "account": { 40 | "name": null, 41 | "internalId": 198, 42 | "externalId": null, 43 | "type": "account" 44 | }, 45 | "amount": 15.0, 46 | "department": { 47 | "name": null, 48 | "internalId": "1", 49 | "externalId": null, 50 | "type": "department" 51 | }, 52 | "class": { 53 | "name": null, 54 | "internalId": 23, 55 | "externalId": null, 56 | "type": "classification" 57 | }, 58 | "location": { 59 | "name": null, 60 | "internalId": "1", 61 | "externalId": null, 62 | "type": "location" 63 | }, 64 | "memo": "Expense ID 20190102" 65 | }, 66 | { 67 | "account": { 68 | "name": null, 69 | "internalId": 199, 70 | "externalId": null, 71 | "type": "account" 72 | }, 73 | "amount": 5.0, 74 | "department": { 75 | "name": null, 76 | "internalId": "1", 77 | "externalId": null, 78 | "type": "department" 79 | }, 80 | "class": { 81 | "name": null, 82 | "internalId": 23, 83 | "externalId": null, 84 | "type": "classification" 85 | }, 86 | "location": { 87 | "name": null, 88 | "internalId": "1", 89 | "externalId": null, 90 | "type": "location" 91 | }, 92 | "memo": "Expense ID 20190103" 93 | } 94 | ], 95 | "memo": "Created by Fyle Integration", 96 | "department": { 97 | "name": null, 98 | "internalId": "1", 99 | "externalId": null, 100 | "type": "department" 101 | }, 102 | "entity": { 103 | "name": null, 104 | "internalId": 1560159, 105 | "externalId": null, 106 | "type": "vendor" 107 | } 108 | } -------------------------------------------------------------------------------- /test/integration/data/vendor_bills/data_expenses_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "nullFieldList": null, 3 | "createdDate": null, 4 | "lastModifiedDate": null, 5 | "nexus": null, 6 | "subsidiaryTaxRegNum": null, 7 | "taxRegOverride": null, 8 | "taxDetailsOverride": null, 9 | "customForm": null, 10 | "billAddressList": null, 11 | "account": { 12 | "name": null, 13 | "internalId": "25", 14 | "externalId": null, 15 | "type": "account" 16 | }, 17 | "entity": { 18 | "name": null, 19 | "internalId": "3382", 20 | "externalId": null, 21 | "type": "vendor" 22 | }, 23 | "subsidiary": { 24 | "name": null, 25 | "internalId": "1", 26 | "externalId": null, 27 | "type": "subsidiary" 28 | }, 29 | "location": { 30 | "name": null, 31 | "internalId": null, 32 | "externalId": null, 33 | "type": "location" 34 | }, 35 | "department": { 36 | "name": null, 37 | "internalId": null, 38 | "externalId": null, 39 | "type": "department" 40 | }, 41 | "class": { 42 | "name": null, 43 | "internalId": null, 44 | "externalId": null, 45 | "type": "classification" 46 | }, 47 | "approvalStatus": null, 48 | "nextApprover": null, 49 | "vatRegNum": null, 50 | "postingPeriod": null, 51 | "tranDate": "2024-11-08T00:00:00", 52 | "currencyName": null, 53 | "billingAddress": null, 54 | "exchangeRate": null, 55 | "entityTaxRegNum": null, 56 | "taxPointDate": null, 57 | "terms": null, 58 | "dueDate": null, 59 | "discountDate": null, 60 | "tranId": "E/2024/11/T/34447", 61 | "userTotal": null, 62 | "discountAmount": null, 63 | "taxTotal": null, 64 | "paymentHold": null, 65 | "memo": "Reimbursable expenses by admin1@fylefordemocctransactions.org", 66 | "tax2Total": null, 67 | "creditLimit": null, 68 | "availableVendorCredit": null, 69 | "currency": { 70 | "name": null, 71 | "internalId": "1", 72 | "externalId": null, 73 | "type": "currency" 74 | }, 75 | "status": null, 76 | "landedCostMethod": null, 77 | "landedCostPerLine": null, 78 | "transactionNumber": null, 79 | "expenseList": [ 80 | { 81 | "orderDoc": null, 82 | "orderLine": null, 83 | "line": null, 84 | "amount": 30.0, 85 | "grossAmt": 30.0, 86 | "taxDetailsReference": null, 87 | "department": { 88 | "name": null, 89 | "internalId": null, 90 | "externalId": null, 91 | "type": "department" 92 | }, 93 | "class": { 94 | "name": null, 95 | "internalId": null, 96 | "externalId": null, 97 | "type": "classification" 98 | }, 99 | "location": { 100 | "name": null, 101 | "internalId": null, 102 | "externalId": null, 103 | "type": "location" 104 | }, 105 | "customer": { 106 | "name": null, 107 | "internalId": null, 108 | "externalId": null, 109 | "type": "customer" 110 | }, 111 | "customFieldList": [ 112 | { 113 | "scriptId": "custcolfyle_expense_url", 114 | "type": "String", 115 | "value": "https://staging1.fyle.tech/app/admin/#/enterprise/view_expense/txBFlLMIQNJt?org_id=orT192eaSf2q" 116 | }, 117 | { 118 | "scriptId": "custcolfyle_expense_url_2", 119 | "type": "String", 120 | "value": "https://staging1.fyle.tech/app/admin/#/enterprise/view_expense/txBFlLMIQNJt?org_id=orT192eaSf2q" 121 | } 122 | ], 123 | "isBillable": null, 124 | "tax1Amt": null, 125 | "taxAmount": null, 126 | "taxCode": { 127 | "name": null, 128 | "internalId": null, 129 | "externalId": null, 130 | "type": "taxGroup" 131 | }, 132 | "taxRate1": null, 133 | "taxRate2": null, 134 | "amortizationSched": null, 135 | "amortizStartDate": null, 136 | "amortizationEndDate": null, 137 | "amortizationResidual": null, 138 | "account": { 139 | "name": null, 140 | "internalId": "224", 141 | "externalId": null, 142 | "type": "account" 143 | }, 144 | "category": null, 145 | "memo": "admin1@fylefordemocctransactions.org - Fedex - Professional Services - 2024-11-08 - C/2024/11/R/2", 146 | "projectTask": null 147 | } 148 | ], 149 | "itemList": null, 150 | "accountingBookDetailList": null, 151 | "landedCostsList": null, 152 | "purchaseOrderList": null, 153 | "taxDetailsList": null, 154 | "customFieldList": null, 155 | "internalId": null, 156 | "externalId": "12345" 157 | } -------------------------------------------------------------------------------- /test/integration/data/vendor_bills/data_items_only.json: -------------------------------------------------------------------------------- 1 | { 2 | "nullFieldList": null, 3 | "createdDate": null, 4 | "lastModifiedDate": null, 5 | "nexus": null, 6 | "subsidiaryTaxRegNum": null, 7 | "taxRegOverride": null, 8 | "taxDetailsOverride": null, 9 | "customForm": null, 10 | "billAddressList": null, 11 | "account": { 12 | "name": null, 13 | "internalId": "25", 14 | "externalId": null, 15 | "type": "account" 16 | }, 17 | "entity": { 18 | "name": null, 19 | "internalId": "1674", 20 | "externalId": null, 21 | "type": "vendor" 22 | }, 23 | "subsidiary": { 24 | "name": null, 25 | "internalId": "3", 26 | "externalId": null, 27 | "type": "subsidiary" 28 | }, 29 | "location": { 30 | "name": null, 31 | "internalId": "8", 32 | "externalId": null, 33 | "type": "location" 34 | }, 35 | "approvalStatus": null, 36 | "nextApprover": null, 37 | "vatRegNum": null, 38 | "postingPeriod": null, 39 | "tranDate": "2023-05-11T07:06:21", 40 | "currencyName": null, 41 | "billingAddress": null, 42 | "exchangeRate": null, 43 | "entityTaxRegNum": null, 44 | "taxPointDate": null, 45 | "terms": null, 46 | "dueDate": null, 47 | "discountDate": null, 48 | "tranId": null, 49 | "userTotal": null, 50 | "discountAmount": null, 51 | "taxTotal": null, 52 | "paymentHold": null, 53 | "memo": null, 54 | "tax2Total": null, 55 | "creditLimit": null, 56 | "availableVendorCredit": null, 57 | "currency": { 58 | "name": null, 59 | "internalId": "1", 60 | "externalId": null, 61 | "type": "currency" 62 | }, 63 | "status": null, 64 | "landedCostMethod": null, 65 | "landedCostPerLine": null, 66 | "transactionNumber": null, 67 | "itemList": [ 68 | { 69 | "item": { 70 | "name": null, 71 | "internalId": "504", 72 | "externalId": null, 73 | "type": null 74 | }, 75 | "line": null, 76 | "vendorName": null, 77 | "orderDoc": null, 78 | "orderLine": null, 79 | "quantity": 1.0, 80 | "units": null, 81 | "inventoryDetail": null, 82 | "description": "custom thing", 83 | "serialNumbers": null, 84 | "binNumbers": null, 85 | "expirationDate": null, 86 | "taxCode": null, 87 | "taxRate1": null, 88 | "taxRate2": null, 89 | "grossAmt": null, 90 | "tax1Amt": null, 91 | "rate": "71.00", 92 | "amount": 71.0, 93 | "options": null, 94 | "department": null, 95 | "class": null, 96 | "location": { 97 | "name": null, 98 | "internalId": "8", 99 | "externalId": null, 100 | "type": null 101 | }, 102 | "customer": null, 103 | "customFieldList":[ 104 | { 105 | "scriptId":"custcolfyle_expense_url", 106 | "type":"String", 107 | "value":"https://localhost:8000/api/app/main/#/enterprise/view_expense/tx4Suhvjafbd?org_id=orpMfWO2KOsU" 108 | }, 109 | { 110 | "scriptId":"custcol788", 111 | "type":"Select", 112 | "value":"1" 113 | } 114 | ], 115 | "landedCostCategory": null, 116 | "isBillable": null, 117 | "billVarianceStatus": null, 118 | "billreceiptsList": null, 119 | "amortizationSched": null, 120 | "amortizStartDate": null, 121 | "amortizationEndDate": null, 122 | "amortizationResidual": null, 123 | "taxAmount": null, 124 | "taxDetailsReference": null, 125 | "landedCost": null 126 | }, 127 | { 128 | "item": { 129 | "name": null, 130 | "internalId": "387", 131 | "externalId": null, 132 | "type": null 133 | }, 134 | "line": null, 135 | "vendorName": null, 136 | "orderDoc": null, 137 | "orderLine": null, 138 | "quantity": 1.0, 139 | "units": null, 140 | "inventoryDetail": null, 141 | "description": "custom thing", 142 | "serialNumbers": null, 143 | "binNumbers": null, 144 | "expirationDate": null, 145 | "taxCode": null, 146 | "taxRate1": null, 147 | "taxRate2": null, 148 | "grossAmt": null, 149 | "tax1Amt": null, 150 | "rate": "101.00", 151 | "amount": 101.0, 152 | "options": null, 153 | "department": null, 154 | "class": null, 155 | "location": { 156 | "name": null, 157 | "internalId": "8", 158 | "externalId": null, 159 | "type": null 160 | }, 161 | "customer": null, 162 | "customFieldList":[ 163 | { 164 | "scriptId":"custcolfyle_expense_url", 165 | "type":"String", 166 | "value":"https://localhost:8000/api/app/main/#/enterprise/view_expense/tx4Suhvjafbd?org_id=orpMfWO2KOsU" 167 | }, 168 | { 169 | "scriptId":"custcol788", 170 | "type":"Select", 171 | "value":"1" 172 | } 173 | ], 174 | "landedCostCategory": null, 175 | "isBillable": null, 176 | "billVarianceStatus": null, 177 | "billreceiptsList": null, 178 | "amortizationSched": null, 179 | "amortizStartDate": null, 180 | "amortizationEndDate": null, 181 | "amortizationResidual": null, 182 | "taxAmount": null, 183 | "taxDetailsReference": null, 184 | "landedCost": null 185 | } 186 | ], 187 | "accountingBookDetailList": null, 188 | "landedCostsList": null, 189 | "purchaseOrderList": null, 190 | "taxDetailsList": null, 191 | "customFieldList": null, 192 | "internalId": null, 193 | "externalId": "G/2022/05/R/tims" 194 | } -------------------------------------------------------------------------------- /test/integration/data/vendor_credit/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "externalId": "1237", 3 | "account": null, 4 | "applied": null, 5 | "autoApply": null, 6 | "billAddressList": null, 7 | "billingAddress": null, 8 | "class": null, 9 | "createdDate": null, 10 | "createdFrom": null, 11 | "currency": null, 12 | "currencyName": null, 13 | "customForm": null, 14 | "department": null, 15 | "entity": { 16 | "name": null, 17 | "internalId": "14452", 18 | "externalId": null, 19 | "type": "vendor" 20 | }, 21 | "entityTaxRegNum": null, 22 | "lastModifiedDate": null, 23 | "location": { 24 | "name": null, 25 | "internalId": "1", 26 | "externalId": null, 27 | "type": "location" 28 | }, 29 | "memo": "Testing VendorCredits using Fyle SDK", 30 | "nexus": null, 31 | "postingPeriod": null, 32 | "subsidiary": null, 33 | "subsidiaryTaxRegNum": null, 34 | "taxDetailsOverride": null, 35 | "taxPointDate": null, 36 | "taxRegOverride": null, 37 | "total": null, 38 | "tranDate": "2022-07-21T17:32:28Z", 39 | "tranId": null, 40 | "unApplied": null, 41 | "userTaxTotal": null, 42 | "userTotal": null, 43 | "itemList": { 44 | "item": [ 45 | { 46 | "amount": null, 47 | "class": null, 48 | "customer": { 49 | "internalId": "1", 50 | "externalId": null, 51 | "type": null 52 | }, 53 | "customFieldList": null, 54 | "department": { 55 | "name": null, 56 | "internalId": "10", 57 | "externalId": null, 58 | "type": "department" 59 | }, 60 | "description": "test vendor credit", 61 | "item": { 62 | "internalId": "20", 63 | "externalId": null, 64 | "type": "vendorCredit" 65 | }, 66 | "location": { 67 | "name": null, 68 | "internalId": "1", 69 | "externalId": null, 70 | "type": "location" 71 | } 72 | 73 | } 74 | ], 75 | "replaceAll": true 76 | } 77 | } -------------------------------------------------------------------------------- /test/integration/data/vendor_payment/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "nullFieldList": null, 3 | "createdDate": null, 4 | "lastModifiedDate": null, 5 | "customForm": null, 6 | "account": { 7 | "name": null, 8 | "internalId": null, 9 | "externalId": null, 10 | "type": null 11 | }, 12 | "balance": null, 13 | "apAcct": { 14 | "name": null, 15 | "internalId": "25", 16 | "externalId": null, 17 | "type": null 18 | }, 19 | "entity": { 20 | "name": null, 21 | "internalId": "3382", 22 | "externalId": null, 23 | "type": null 24 | }, 25 | "address": null, 26 | "tranDate": null, 27 | "voidJournal": null, 28 | "postingPeriod": null, 29 | "currencyName": null, 30 | "exchangeRate": null, 31 | "toAch": "false", 32 | "toBePrinted": "false", 33 | "printVoucher": "false", 34 | "tranId": null, 35 | "total": null, 36 | "currency": { 37 | "name": null, 38 | "internalId": "1", 39 | "externalId": null, 40 | "type": null 41 | }, 42 | "department": { 43 | "name": null, 44 | "internalId": null, 45 | "externalId": null, 46 | "type": "department" 47 | }, 48 | "memo": "Payment for bill by admin1@fylefordemocctransactions.org", 49 | "subsidiary": { 50 | "name": null, 51 | "internalId": "1", 52 | "externalId": null, 53 | "type": null 54 | }, 55 | "class": { 56 | "name": null, 57 | "internalId": null, 58 | "externalId": null, 59 | "type": "classification" 60 | }, 61 | "location": { 62 | "name": null, 63 | "internalId": null, 64 | "externalId": null, 65 | "type": "location" 66 | }, 67 | "status": null, 68 | "transactionNumber": null, 69 | "applyList": { 70 | "apply": [ 71 | { 72 | "apply": "true", 73 | "doc": "749863", 74 | "line": 0, 75 | "job": null, 76 | "applyDate": null, 77 | "type": null, 78 | "refNum": null, 79 | "total": null, 80 | "due": null, 81 | "currency": null, 82 | "discDate": null, 83 | "discAmt": null, 84 | "disc": null, 85 | "amount": null 86 | } 87 | ], 88 | "replaceAll": "true" 89 | }, 90 | "creditList": null, 91 | "billPay": null, 92 | "accountingBookDetailList": null, 93 | "availableBalance": null, 94 | "isInTransitPayment": null, 95 | "approvalStatus": null, 96 | "nextApprover": null, 97 | "customFieldList": null, 98 | "internalId": null, 99 | "externalId": "bill 111 - admin1@fylefordemocctransactions.org-2" 100 | } -------------------------------------------------------------------------------- /test/integration/test_accounts.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.accounts.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.accounts.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | 17 | def test_get_all_generator(nc): 18 | get_all_response = nc.accounts.get_all() 19 | get_all_generator_response = [] 20 | for r in nc.accounts.get_all_generator(page_size=200): 21 | get_all_generator_response.append(r) 22 | len_get_all_genrator_respose = 0 23 | for i in get_all_generator_response: 24 | len_get_all_genrator_respose = len_get_all_genrator_respose + len(i) 25 | assert len(get_all_response) == len_get_all_genrator_respose, 'changing page size is returning different results' 26 | 27 | 28 | def test_post(nc): 29 | data = {} 30 | with pytest.raises(NotImplementedError) as ex: 31 | nc.accounts.post(data) 32 | 33 | -------------------------------------------------------------------------------- /test/integration/test_adv_inter_company_journal_entries.py: -------------------------------------------------------------------------------- 1 | from .test_journal_entries import TestJournalEntries as TestAdvInterCompanyJournalEntries 2 | 3 | TYPE = 'advInterCompanyJournalEntry' 4 | API = 'adv_inter_company_journal_entries' 5 | -------------------------------------------------------------------------------- /test/integration/test_billing_account.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import pytest 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 9 | def test_post(nc): 10 | with open('./test/integration/data/billing_account/data.json') as oj: 11 | s = oj.read() 12 | billing_account = json.loads(s) 13 | logger.debug('billing_account = %s', billing_account) 14 | res = nc.billing_accounts.post(billing_account) 15 | logger.debug('res = %s', res) 16 | assert res['externalId'] == billing_account['externalId'], 'ID Number does not match' 17 | assert res['type'] == 'billingAccount', 'Type does not match' 18 | -------------------------------------------------------------------------------- /test/integration/test_classifications.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.classifications.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.classifications.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_post(nc): 17 | data = {} 18 | with pytest.raises(NotImplementedError) as ex: 19 | nc.classifications.post(data) 20 | -------------------------------------------------------------------------------- /test/integration/test_credit_memos.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import pytest 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 9 | def test_post(nc): 10 | with open('./test/integration/data/credit_memo/data.json') as oj: 11 | s = oj.read() 12 | credit_memo = json.loads(s) 13 | logger.debug('credit_memo = %s', credit_memo) 14 | res = nc.credit_memos.post(credit_memo) 15 | logger.debug('res = %s', res) 16 | assert res['externalId'] == credit_memo['externalId'], 'ID Number does not match' 17 | assert res['type'] == 'creditMemo', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_currencies.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.currencies.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.currencies.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_get_all_generator(nc): 17 | res1 = nc.currencies.get_all() 18 | res2 = [] 19 | for r in nc.currencies.get_all_generator(): 20 | res2.append(r) 21 | assert len(res1) == len(res2), 'get all and generator are returning different' 22 | 23 | def test_post(nc): 24 | data = {} 25 | with pytest.raises(NotImplementedError) as ex: 26 | nc.currencies.post(data) 27 | -------------------------------------------------------------------------------- /test/integration/test_custom_list.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 7 | def test_get(nc): 8 | data = nc.custom_lists.get_all() 9 | logger.debug('data = %s', data) 10 | assert data, 'get all didnt work' 11 | 12 | internal_id = data[0]['internalId'] 13 | data = nc.custom_lists.get(internalId=internal_id) 14 | logger.debug('data = %s', data) 15 | assert data, f'No object with internalId {internal_id}' 16 | 17 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 18 | def test_get_all_generator(nc): 19 | res1 = nc.custom_lists.get_all() 20 | res2 = [] 21 | for r in nc.custom_lists.get_all_generator(): 22 | res2.append(r) 23 | assert len(res1) == len(res2), 'get all and generator are returning different' -------------------------------------------------------------------------------- /test/integration/test_custom_record_types.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get_all_by_id(nc): 7 | custom_record_type_response = nc.custom_record_types.get_all_by_id(476) 8 | assert custom_record_type_response[0]["recType"]["internalId"] == "476", "InternalId does not match" 9 | -------------------------------------------------------------------------------- /test/integration/test_custom_records.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_post(nc): 8 | with open('./test/integration/data/custom_record/data.json') as oj: 9 | s = oj.read() 10 | custom_record = json.loads(s) 11 | logger.debug('custom_record = %s', custom_record) 12 | res = nc.custom_records.post(custom_record) 13 | logger.debug('res = %s', res) 14 | assert res['externalId'] == custom_record['externalId'], 'ID Number does not match' -------------------------------------------------------------------------------- /test/integration/test_custom_segments.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | @pytest.mark.skip(reason="customSegment is not a legal value for") 7 | def test_get(nc): 8 | data = nc.custom_segments.get_all() 9 | logger.debug('data = %s', data) 10 | assert data, 'get all didnt work' 11 | 12 | internal_id = data[0]['internalId'] 13 | data = nc.custom_segments.get(internalId=internal_id) 14 | logger.debug('data = %s', data) 15 | assert data, f'No object with internalId {internal_id}' 16 | 17 | @pytest.mark.skip(reason="customSegment is not a legal value for") 18 | def test_get_all_generator(nc): 19 | res1 = nc.custom_segments.get_all() 20 | res2 = [] 21 | for r in nc.custom_segments.get_all_generator(): 22 | res2.append(r) 23 | assert len(res1) == len(res2), 'get all and generator are returning different' -------------------------------------------------------------------------------- /test/integration/test_customers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.customers.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.customers.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_post(nc): 17 | with open('./test/integration/data/customers/data.json') as oj: 18 | s = oj.read() 19 | expr1 = json.loads(s) 20 | logger.debug('expr1 = %s', expr1) 21 | res = nc.customers.post(expr1) 22 | logger.debug('res = %s', res) 23 | assert res['externalId'] == expr1['externalId'], 'External ID does not match' 24 | assert res['type'] == 'customer', 'Type does not match' 25 | 26 | expr2 = nc.customers.get(externalId=res['externalId']) 27 | logger.debug('expr2 = %s', expr2) 28 | assert expr2['externalId'] == expr1['externalId'], 'External ID does not match' 29 | assert expr2['companyName'] == expr1['companyName'], 'companyName does not match' 30 | -------------------------------------------------------------------------------- /test/integration/test_departments.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.departments.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.departments.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_post(nc): 17 | data = {} 18 | with pytest.raises(NotImplementedError) as ex: 19 | nc.departments.post(data) 20 | -------------------------------------------------------------------------------- /test/integration/test_employees.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_post(nc): 8 | with open('./test/integration/data/employee/data.json') as oj: 9 | s = oj.read() 10 | employee = json.loads(s) 11 | logger.debug('employee = %s', employee) 12 | res = nc.employees.post(employee) 13 | logger.debug('res = %s', res) 14 | assert res['externalId'] == employee['externalId'], 'ID Number does not match' 15 | assert res['type'] == 'employee', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_expense_reports.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import json 3 | from datetime import datetime 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_get(nc): 8 | data = nc.expense_reports.get_all_generator() 9 | logger.debug('data = %s', data) 10 | assert data, 'get all generator didnt work' 11 | assert data[0]['externalId'] == 'entity-5', f'No object found with externalId' 12 | assert data[0]['internalId'] == '-5', f'No object found with internalId' 13 | 14 | data = nc.expense_reports.get(externalId='EXPR_1') 15 | logger.debug('data = %s', data) 16 | assert data, f'No object with externalId' 17 | assert data['externalId'] == 'EXPR_1', f'No object with externalId' 18 | assert data['internalId'] == '10613', f'No object with internalId' 19 | 20 | def test_post(nc): 21 | with open('./test/integration/data/expense_reports/data.json') as oj: 22 | s = oj.read() 23 | expr1 = json.loads(s) 24 | expr1['tranDate'] = datetime.today().date().replace(day=1).strftime('%Y-%m-%dT%H:%M:%S') 25 | logger.debug('expr1 = %s', expr1) 26 | res = nc.expense_reports.post(expr1) 27 | logger.debug('res = %s', res) 28 | assert res['externalId'] == expr1['externalId'], 'External ID does not match' 29 | assert res['type'] == 'expenseReport', 'Type does not match' 30 | 31 | expr2 = nc.expense_reports.get(externalId=res['externalId']) 32 | logger.debug('expr2 = %s', expr2) 33 | assert expr2['amount'] == 100.0, 'Amount does not match' 34 | assert expr2['externalId'] == 'EXPR_1', 'External ID does not match' 35 | assert expr2['internalId'] == '10613', 'Internal ID does not match' 36 | -------------------------------------------------------------------------------- /test/integration/test_files.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_post(nc): 8 | with open('./test/integration/data/file/data.json') as oj: 9 | s = oj.read() 10 | file_data = json.loads(s) 11 | file_data['content'] = b"test_file" 12 | logger.debug('file_data = %s', file_data) 13 | res = nc.files.post(file_data) 14 | logger.debug('res = %s', res) 15 | assert res['externalId'] == file_data['externalId'], 'ID Number does not match' 16 | assert res['type'] == 'file', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_folder.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_post(nc): 8 | with open('./test/integration/data/folder/data.json') as oj: 9 | s = oj.read() 10 | folder_data = json.loads(s) 11 | logger.debug('file_data = %s', folder_data) 12 | res = nc.folders.post(folder_data) 13 | logger.debug('res = %s', res) 14 | assert res['externalId'] == folder_data['externalId'], 'ID Number does not match' 15 | assert res['type'] == 'folder', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_invoices.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import pytest 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 9 | def test_post(nc): 10 | with open('./test/integration/data/invoices/data.json') as oj: 11 | s = oj.read() 12 | invoice = json.loads(s) 13 | logger.debug('invoice = %s', invoice) 14 | res = nc.invoices.post(invoice) 15 | logger.debug('res = %s', res) 16 | assert res['externalId'] == invoice['externalId'], 'ID Number does not match' 17 | assert res['type'] == 'invoice', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_items.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.items.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | -------------------------------------------------------------------------------- /test/integration/test_journal_entries.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import json 4 | import os 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | TYPE = 'journalEntry' 9 | API = 'journal_entries' 10 | 11 | class TestJournalEntries(): 12 | 13 | @pytest.fixture(scope="class") 14 | def je(self): 15 | with open(f'./test/integration/data/{API}/data.json') as oj: 16 | s = oj.read() 17 | return json.loads(s) 18 | 19 | def get_api(self, nc): 20 | return getattr(nc, API) 21 | 22 | def validate_je(self, expected, result): 23 | logger.debug('result = %s', result) 24 | assert result, f'No object with externalId' 25 | assert result['internalId'] == expected['internalId'], f'Internal ID does not match' 26 | assert result['externalId'] == expected['externalId'], f'External ID does not match' 27 | assert result['currency']['name'] == expected['currency']['name'], f'Currency does not match' 28 | assert result['subsidiary']['internalId'] == expected['subsidiary']['internalId'], f'Subsidiary does not match' 29 | 30 | def validate_result(self, expected, result): 31 | logger.debug('result = %s', result) 32 | assert result['internalId'] == expected['internalId'], 'Internal ID does not match' 33 | assert result['externalId'] == expected['externalId'], 'External ID does not match' 34 | assert result['type'] == TYPE, 'Type does not match' 35 | 36 | def test_post(self, nc, je): 37 | api = self.get_api(nc) 38 | logger.debug('rvb1 = %s', je) 39 | 40 | # Test post of new journal entry 41 | res = api.post(je) 42 | je['internalId'] = res['internalId'] 43 | self.validate_result(je, res) 44 | 45 | 46 | def test_get(self, nc, je): 47 | api = self.get_api(nc) 48 | data = api.get(externalId=je['externalId']) 49 | self.validate_je(je, data) 50 | 51 | def test_get_all(self, nc, je): 52 | api = self.get_api(nc) 53 | data = api.get_all_generator() 54 | logger.debug('data = %s', data) 55 | assert data, 'get all generator didnt work' 56 | assert len(data) > 0, f'No data found' 57 | 58 | je2 = next(je2 for je2 in data if je2['externalId'] == je['externalId']) 59 | self.validate_je(je, je2) 60 | 61 | def test_delete(self, nc, je): 62 | api = self.get_api(nc) 63 | res = api.delete(externalId=je['externalId']) 64 | self.validate_result(je, res) 65 | -------------------------------------------------------------------------------- /test/integration/test_locations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.locations.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.locations.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_post(nc): 17 | data = {} 18 | with pytest.raises(NotImplementedError) as ex: 19 | nc.locations.post(data) 20 | -------------------------------------------------------------------------------- /test/integration/test_subsidiaries.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | def test_get(nc): 7 | data = nc.subsidiaries.get_all() 8 | logger.debug('data = %s', data) 9 | assert data, 'get all didnt work' 10 | 11 | internal_id = data[0]['internalId'] 12 | data = nc.subsidiaries.get(internalId=internal_id) 13 | logger.debug('data = %s', data) 14 | assert data, f'No object with internalId {internal_id}' 15 | 16 | def test_get_all_generator(nc): 17 | get_all_response = nc.subsidiaries.get_all() 18 | get_all_generator_response = [] 19 | for r in nc.subsidiaries.get_all_generator(page_size=200): 20 | get_all_generator_response.append(r) 21 | len_get_all_generator_response = 0 22 | for i in get_all_generator_response: 23 | len_get_all_generator_response += len(i) 24 | assert len(get_all_response) == len_get_all_generator_response, 'changing page size is returning different results' 25 | 26 | def test_post(nc): 27 | data = {} 28 | with pytest.raises(NotImplementedError) as ex: 29 | nc.subsidiaries.post(data) 30 | -------------------------------------------------------------------------------- /test/integration/test_terms.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | 4 | logger = logging.getLogger(__name__) 5 | 6 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 7 | def test_get(nc): 8 | data = nc.terms.get_all() 9 | logger.debug('data = %s', data) 10 | assert data, 'get all didnt work' 11 | 12 | internal_id = data[0]['internalId'] 13 | data = nc.terms.get(internalId=internal_id) 14 | logger.debug('data = %s', data) 15 | assert data, f'No object with internalId {internal_id}' 16 | 17 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 18 | def test_post(nc): 19 | term = { 20 | 'dateDriven': None, 21 | 'dayDiscountExpires': None, 22 | 'dayOfMonthNetDue': None, 23 | 'daysUntilExpiry': None, 24 | 'daysUntilNetDue': 10, 25 | 'discountPercent': None, 26 | 'discountPercentDateDriven': None, 27 | 'dueNextMonthIfWithinDays': None, 28 | 'externalId': "testTermsAPI", 29 | 'installment': None, 30 | 'internalId': None, 31 | 'isInactive': None, 32 | 'name': "Test Term", 33 | 'percentagesList': None, 34 | 'preferred': None, 35 | 'recurrenceCount': None, 36 | 'recurrenceFrequency': None, 37 | 'repeatEvery': None, 38 | 'splitEvenly': None, 39 | } 40 | 41 | res = nc.terms.post(term) 42 | logger.debug('res = %s', res) 43 | assert res['externalId'] == term['externalId'], 'External ID does not match' 44 | assert res['type'] == 'term', 'Type does not match' 45 | 46 | expr2 = nc.terms.get(externalId=res['externalId']) 47 | logger.debug('expr2 = %s', expr2) 48 | assert expr2['externalId'] == term['externalId'], 'External ID does not match' 49 | assert expr2['name'] == term['name'], 'name does not match' 50 | -------------------------------------------------------------------------------- /test/integration/test_usage.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | import logging 4 | import pytest 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 10 | def test_post(nc): 11 | with open('./test/integration/data/usage/data.json') as oj: 12 | s = oj.read() 13 | usage = json.loads(s) 14 | logger.debug('credit_memo = %s', usage) 15 | res = nc.usages.post(usage) 16 | logger.debug('res = %s', res) 17 | assert res['externalId'] == usage['externalId'], 'ID Number does not match' 18 | assert res['type'] == 'creditMemo', 'Type does not match' -------------------------------------------------------------------------------- /test/integration/test_vendor_bills.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import json 4 | import os 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | def test_get(nc): 9 | data = nc.vendor_bills.get_all_generator() 10 | logger.debug('data = %s', data) 11 | assert data, 'get all generator didnt work' 12 | 13 | internal_id = data[0]['internalId'] 14 | data = nc.vendor_bills.get(internalId=internal_id) 15 | logger.debug('data = %s', data) 16 | assert data, f'No object with internalId {internal_id}' 17 | 18 | def test_post(nc): 19 | vb1 = {} 20 | with open('./test/integration/data/vendor_bills/data_expenses_only.json') as oj: 21 | s = oj.read() 22 | vb1 = json.loads(s) 23 | logger.debug('rvb1 = %s', vb1) 24 | res = nc.vendor_bills.post(vb1) 25 | logger.debug('res = %s', res) 26 | assert res['externalId'] == vb1['externalId'], 'External ID does not match' 27 | 28 | vb2 = nc.vendor_bills.get(externalId=res['externalId']) 29 | logger.debug('vb2 = %s', vb2) 30 | assert (29.99 < vb2['userTotal']) and (vb2['userTotal'] < 30.01), 'Bill total is not 30.0' 31 | 32 | 33 | # vb3 = {} 34 | # with open('./test/integration/data/vendor_bills/data_items_only.json') as oj: 35 | # s = oj.read() 36 | # vb3 = json.loads(s) 37 | # logger.debug('rvb1 = %s', vb3) 38 | # res = nc.vendor_bills.post(vb3) 39 | # logger.debug('res = %s', res) 40 | # assert res['externalId'] == vb3['externalId'], 'External ID does not match' 41 | 42 | 43 | # vb4 = {} 44 | # with open('./test/integration/data/vendor_bills/data_expense_and_items.json') as oj: 45 | # s = oj.read() 46 | # vb4 = json.loads(s) 47 | # logger.debug('rvb1 = %s', vb4) 48 | # res = nc.vendor_bills.post(vb4) 49 | # logger.debug('res = %s', res) 50 | # assert res['externalId'] == vb4['externalId'], 'External ID does not match' 51 | -------------------------------------------------------------------------------- /test/integration/test_vendor_credit.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | import pytest 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | @pytest.mark.skip(reason="Can't test this due to persmission issues") 9 | def test_post(nc): 10 | with open('./test/integration/data/vendor_credit/data.json') as oj: 11 | s = oj.read() 12 | vendor_credit = json.loads(s) 13 | logger.debug('vendor_credit = %s', vendor_credit) 14 | res = nc.vendor_credits.post(vendor_credit) 15 | logger.debug('res = %s', res) 16 | assert res['externalId'] == vendor_credit['externalId'], 'Transaction Number does not match' 17 | assert res['type'] == 'vendorCredit', 'Type does not match' 18 | -------------------------------------------------------------------------------- /test/integration/test_vendor_payments.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import logging 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | def test_get(nc): 8 | get_all_respose = nc.vendor_payments.get_all() 9 | get_all_generator_response = [] 10 | for r in nc.vendor_payments.get_all_generator(): 11 | get_all_generator_response.append(r) 12 | len_get_all_generator_response = 0 13 | for i in get_all_generator_response: 14 | len_get_all_generator_response += len(i) 15 | assert len(get_all_respose) == len_get_all_generator_response, 'changing page size is returning different results' 16 | 17 | 18 | def test_post(nc): 19 | with open('./test/integration/data/vendor_payment/data.json') as oj: 20 | s = oj.read() 21 | vendor_payment = json.loads(s) 22 | logger.debug('vendor_payment = %s', vendor_payment) 23 | res = nc.vendor_payments.post(vendor_payment) 24 | logger.debug('res = %s', res) 25 | assert res['externalId'] == vendor_payment['externalId'], 'Transaction Number does not match' 26 | assert res['type'] == 'vendorPayment', 'Type does not match' 27 | -------------------------------------------------------------------------------- /test/integration/test_vendors.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import json 4 | import os 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | def test_get(nc): 9 | data = nc.vendors.get_all() 10 | logger.debug('data = %s', data) 11 | assert data, 'get all didnt work' 12 | 13 | internal_id = data[0]['internalId'] 14 | data = nc.vendors.get(internalId=internal_id) 15 | logger.debug('data = %s', data) 16 | assert data, f'No object with internalId {internal_id}' 17 | 18 | def test_post(nc): 19 | with open('./test/integration/data/vendor/data.json') as oj: 20 | s = oj.read() 21 | vendor = json.loads(s) 22 | logger.debug('vendor = %s', vendor) 23 | res = nc.vendors.post(vendor) 24 | logger.debug('res = %s', res) 25 | assert res['externalId'] == vendor['externalId'], 'Transaction Number does not match' 26 | assert res['type'] == 'vendor', 'Type does not match' 27 | -------------------------------------------------------------------------------- /test/internal/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fylein/netsuite-sdk-py/c66c398997d0e624101a26b036958d88a55a85d2/test/internal/__init__.py -------------------------------------------------------------------------------- /test/internal/conftest.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import pytest 5 | from netsuitesdk.internal.client import NetSuiteClient 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | @pytest.fixture(scope='module') 10 | def ns(): 11 | """ 12 | Returns: (ns, headers) 13 | """ 14 | NS_ACCOUNT = os.getenv('NS_ACCOUNT') 15 | NS_CONSUMER_KEY = os.getenv('NS_CONSUMER_KEY') 16 | NS_CONSUMER_SECRET = os.getenv('NS_CONSUMER_SECRET') 17 | NS_TOKEN_KEY = os.getenv('NS_TOKEN_KEY') 18 | NS_TOKEN_SECRET = os.getenv('NS_TOKEN_SECRET') 19 | ns = NetSuiteClient(account=NS_ACCOUNT) 20 | ns.connect_tba(consumer_key=NS_CONSUMER_KEY, consumer_secret=NS_CONSUMER_SECRET, 21 | token_key=NS_TOKEN_KEY, token_secret=NS_TOKEN_SECRET, signature_algorithm='HMAC-SHA256') 22 | return ns 23 | -------------------------------------------------------------------------------- /test/internal/test_base.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.api.base import ApiBase 2 | import logging 3 | import pytest 4 | from unittest import mock 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | @pytest.fixture 10 | def instance(): 11 | ns_client = mock.Mock() 12 | return ApiBase(ns_client, "type") 13 | 14 | 15 | def test_build_simple_fields(instance): 16 | source_data = { 17 | 'field1': 'value1', 18 | 'field2': 'value2', 19 | } 20 | target = {} 21 | instance.build_simple_fields(['field1', 'field2'], source_data, target) 22 | 23 | assert target['field1'] == source_data['field1'] 24 | assert target['field2'] == source_data['field2'] 25 | 26 | 27 | def test_build_record_ref_fields(instance): 28 | source_data = { 29 | 'field1': {"externalId": "value1"}, 30 | 'field2': {"externalId": "value2"}, 31 | } 32 | target = {} 33 | instance.build_record_ref_fields(['field1', 'field2'], source_data, target) 34 | 35 | assert target['field1'] == mock.ANY 36 | assert target['field1'] == mock.ANY 37 | -------------------------------------------------------------------------------- /test/internal/test_count.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.internal.utils import PaginatedSearch 2 | import logging 3 | import pytest 4 | 5 | 6 | @pytest.mark.parametrize('type_name', ['Account', 'Vendor', 'Department', 'Location', 'Classification', 'Subsidiary', 'Employee']) 7 | def test_count(ns, type_name): 8 | ps = PaginatedSearch(client=ns, type_name=type_name, pageSize=10) 9 | assert ps.total_records > 0, 'Count cannot be 0' -------------------------------------------------------------------------------- /test/internal/test_get.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.internal.utils import PaginatedSearch 2 | import logging 3 | import pytest 4 | from netsuitesdk.internal.exceptions import NetSuiteRequestError 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | def test_get_currency(ns): 9 | record = ns.get(recordType='currency', internalId='1') 10 | assert record, 'No currency record for internalId 1' 11 | 12 | def test_get_vendor_bill(ns): 13 | record = ns.get(recordType='vendorBill', externalId='1234') 14 | assert record, 'No vendor bill found' 15 | 16 | 17 | def test_get_journal_entry(ns): 18 | record = ns.get(recordType='journalEntry', externalId='JE_01') 19 | assert record, 'No journal entry found' 20 | 21 | def test_get_employee(ns): 22 | record = ns.get(recordType='employee', internalId='1648') 23 | assert record, 'No employee record for internalId 1' 24 | 25 | def test_get_expense_report(ns): 26 | record = ns.get(recordType='ExpenseReport', externalId='EXPR_1') 27 | assert record, 'No expense report found' 28 | 29 | def test_get_currency_not_supported(ns): 30 | with pytest.raises(NetSuiteRequestError) as ex: 31 | record = ns.get(recordType='currency', internalId='15') 32 | assert 'An error occured in a get request' in str(ex.value.message) 33 | 34 | # def test_get_currency1(nc): 35 | # currency = nc.currency.get(internal_id='1') 36 | # logger.info('currency is %s', currency) -------------------------------------------------------------------------------- /test/internal/test_get_all.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import zeep 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | @pytest.mark.parametrize('type_name', ['currency']) 8 | def test_get_all(ns, type_name): 9 | records = ns.getAll(recordType=type_name) 10 | assert len(records) > 0, f'No records of type {type_name} returned' 11 | 12 | @pytest.mark.parametrize('type_name', ['account', 'vendor', 'department', 'location', 'classification', 'subsidiaries', 'employees']) 13 | def test_get_all_not_supported(ns, type_name): 14 | with pytest.raises(zeep.exceptions.Fault) as ex: 15 | records = ns.getAll(recordType=type_name) 16 | assert 'is not a legal value' in str(ex.value) 17 | -------------------------------------------------------------------------------- /test/internal/test_get_complex_type_element.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import pytest 3 | import zeep 4 | 5 | logger = logging.getLogger(__name__) 6 | 7 | @pytest.mark.parametrize('type_name', ['RecordList']) 8 | def test_get_complex_type_element(ns, type_name): 9 | type_element = ns.get_complex_type_elements(complex_type = type_name) 10 | assert type_element == [('record', 'Record')] 11 | 12 | @pytest.mark.parametrize('type_name', ['RecordList']) 13 | def test_get_complex_type_info(ns, type_name): 14 | type_info = ns.get_complex_type_info(complex_type = type_name) 15 | assert type_info 16 | 17 | @pytest.mark.parametrize('type_name', ['RecordList']) 18 | def test_get_complex_type_attributes(ns, type_name): 19 | type_attributes = ns.get_complex_type_attributes(complex_type = type_name) 20 | assert type_attributes == [] -------------------------------------------------------------------------------- /test/internal/test_helper.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from netsuitesdk.errors.helpers import replace_numbers, convert_to_camelcase, export_error_matcher 3 | 4 | errors = [ 5 | ('An error occured in a upsert request: Invalid category reference key 1 for entity 12', {'expense_category': 1, 'employee': 12}), 6 | ('An error occured in a upsert request: Invalid account reference key 1 for subsidiary 12', {'account': 1, 'subsidiary': 12}), 7 | ('An error occured in a upsert request: Invalid customer reference key 1 for entity 12', {'customer': 1, 'employee': 12}), 8 | ('An error occured in a upsert request: Invalid location reference key 1 for subsidiary 12', {'location': 1, 'subsidiary': 12}), 9 | ('An error occured in a upsert request: Invalid department reference key 1 for subsidiary 12', {'department': 1, 'subsidiary': 12}), 10 | ('An error occured in a upsert request: Invalid currency reference key 1 for subsidiary 12', {'currency': 1, 'subsidiary': 12}) 11 | ] 12 | 13 | def test_replace_number(): 14 | final_string = "An error occured in a upsert request: Invalid category reference key Travel for entity John Doe" 15 | replaced_string = replace_numbers('An error occured in a upsert request: Invalid category reference key 1 for entity 2', 'Travel', 'John Doe', '1', '2') 16 | assert final_string == replaced_string 17 | 18 | 19 | def test_convert_to_camelcase(): 20 | entity_type = 'ExpenseCategory' 21 | result = convert_to_camelcase('expense_category') 22 | assert entity_type == result 23 | 24 | entity_type = 'Currency' 25 | result = convert_to_camelcase('currency') 26 | assert entity_type == result 27 | 28 | 29 | @pytest.mark.parametrize("input, output", errors) 30 | def test_export_error_matcher(input, output): 31 | 32 | result = export_error_matcher(input, 'expense_report') 33 | assert result == output 34 | -------------------------------------------------------------------------------- /test/internal/test_logout.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | import time 4 | 5 | import pytest 6 | from netsuitesdk.internal.client import NetSuiteClient 7 | from netsuitesdk.internal.utils import PaginatedSearch 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | def test_logout(): 12 | """ 13 | Test if logout method is supported. We will not use this often. 14 | """ 15 | NS_ACCOUNT = os.getenv("NS_ACCOUNT") 16 | ns = NetSuiteClient(account=NS_ACCOUNT) 17 | ns.logout() 18 | -------------------------------------------------------------------------------- /test/internal/test_parser.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from netsuitesdk.errors.parser import ErrorParser 3 | 4 | 5 | def test_export_error_parser(mocker, ns): 6 | 7 | mocker.patch( 8 | 'netsuitesdk.errors.parser.ErrorParser.get_entity_values', 9 | return_value={ 'Travel', 'jhon@gmail.com'} 10 | ) 11 | 12 | parser = ErrorParser(ns) 13 | result = parser.export_error_parser({'test': '1', 'employee': '22'}, 'An error occured in a upsert request: Invalid wiered reference key 1 for entity 22') 14 | assert result == "An error occured in a upsert request: Invalid wiered reference key 1 for entity 22" 15 | -------------------------------------------------------------------------------- /test/internal/test_search.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.internal.utils import PaginatedSearch 2 | import logging 3 | import pytest 4 | import time 5 | from netsuitesdk.internal.exceptions import NetSuiteError 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | def test_search_vendor_bills(ns): 10 | record_type_search_field = ns.SearchStringField(searchValue='VendorBill', operator='contains') 11 | basic_search = ns.basic_search_factory('Transaction', recordType=record_type_search_field) 12 | paginated_search = PaginatedSearch(client=ns, 13 | type_name='Transaction', 14 | basic_search=basic_search, 15 | pageSize=5) 16 | assert len(paginated_search.records) > 0, 'There are no vendor bills' 17 | logger.debug('record = %s', str(paginated_search.records[0])) 18 | 19 | def test_search_journal_entries(ns): 20 | record_type_search_field = ns.SearchStringField(searchValue='JournalEntry', operator='contains') 21 | basic_search = ns.basic_search_factory('Transaction', recordType=record_type_search_field) 22 | paginated_search = PaginatedSearch(client=ns, 23 | type_name='Transaction', 24 | basic_search=basic_search, 25 | pageSize=5) 26 | assert len(paginated_search.records) > 0, 'There are no journal entries' 27 | logger.debug('record = %s', str(paginated_search.records[0])) 28 | 29 | def test_search_expense_reports(ns): 30 | record_type_search_field = ns.SearchStringField(searchValue='ExpenseReport', operator='contains') 31 | basic_search = ns.basic_search_factory('Transaction', recordType=record_type_search_field) 32 | paginated_search = PaginatedSearch(client=ns, 33 | type_name='Transaction', 34 | basic_search=basic_search, 35 | pageSize=5) 36 | assert len(paginated_search.records) > 0, 'There are no expense reports' 37 | logger.debug('record = %s', str(paginated_search.records[0])) 38 | 39 | def test_search_expense_reports_not_supported(ns): 40 | record_type_search_field = ns.SearchStringField(searchValue='ExpenseReport', operator='contains') 41 | with pytest.raises(NetSuiteError) as ex: 42 | basic_search = ns.basic_search_factory('Transaction1', recordType=record_type_search_field) 43 | assert "Transaction1 is not a searchable NetSuite type" in str(ex.value.message) 44 | 45 | @pytest.mark.parametrize('type_name', ['Account', 'Vendor', 'Department', 'Location', 'Classification', 'Subsidiary', 'Employee']) 46 | def test_search_all(ns, type_name): 47 | paginated_search = PaginatedSearch(client=ns, type_name=type_name, pageSize=20) 48 | assert len(paginated_search.records) > 0, f'There are no records of type {type_name}' 49 | logger.debug('record = %s', str(paginated_search.records[0])) 50 | 51 | def test_basic_stringfield_search(ns): 52 | with pytest.raises(AttributeError) as ex: 53 | search_entity = ns.basic_stringfield_search("entityId","Amazon","contains") 54 | assert "'NetSuiteClient' object has no attribute 'entityIdSearchBasic'" in str(ex.value) 55 | -------------------------------------------------------------------------------- /test/internal/test_upsert.py: -------------------------------------------------------------------------------- 1 | from netsuitesdk.internal.utils import PaginatedSearch 2 | from netsuitesdk.internal.exceptions import NetSuiteRequestError 3 | import logging 4 | from datetime import datetime 5 | 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | #@pytest.mark.parametrize('type_name', ['Account', 'Vendor', 'Department', 'Location', 'Classification']) 10 | def get_record(ns, type_name): 11 | paginated_search = PaginatedSearch(client=ns, type_name=type_name, pageSize=20) 12 | return paginated_search.records[0] 13 | 14 | def get_location(ns): 15 | return get_record(ns, 'Location') 16 | 17 | def get_department(ns): 18 | return get_record(ns, 'Department') 19 | 20 | def get_class(ns): 21 | return get_record(ns, 'Classification') 22 | 23 | def get_vendor(ns): 24 | return get_record(ns, 'Vendor') 25 | 26 | def get_category_account(ns): 27 | return ns.get(recordType='account', internalId=84) 28 | 29 | def get_currency(ns): 30 | return ns.get(recordType='currency', internalId='1') 31 | 32 | def get_employee(ns): 33 | return ns.get(recordType='employee', internalId='3482') 34 | 35 | def test_upsert_vendor_bill(ns): 36 | vendor_ref = ns.RecordRef(type='vendor', internalId=get_vendor(ns).internalId) 37 | bill_account_ref = ns.RecordRef(type='account', internalId=25) 38 | cat_account_ref = ns.RecordRef(type='account', internalId=get_category_account(ns).internalId) 39 | loc_ref = ns.RecordRef(type='location', internalId=get_location(ns).internalId) 40 | dep_ref = ns.RecordRef(type='department', internalId=get_department(ns).internalId) 41 | class_ref = ns.RecordRef(type='classification', internalId=get_department(ns).internalId) 42 | expenses = [] 43 | 44 | vbe1 = ns.VendorBillExpense() 45 | vbe1['account'] = cat_account_ref 46 | vbe1['amount'] = 10.0 47 | vbe1['department'] = dep_ref 48 | # vbe1['class'] = class_ref 49 | # vbe1['location'] = loc_ref 50 | 51 | expenses.append(vbe1) 52 | vbe1 = ns.VendorBillExpense() 53 | vbe1['account'] = cat_account_ref 54 | vbe1['amount'] = 20.0 55 | vbe1['department'] = dep_ref 56 | # vbe1['class'] = class_ref 57 | # vbe1['location'] = loc_ref 58 | 59 | expenses.append(vbe1) 60 | 61 | bill = ns.VendorBill(externalId='12345') 62 | bill['currency'] = ns.RecordRef(type='currency', internalId=get_currency(ns).internalId) # US dollar 63 | bill['exchangerate'] = 1.0 64 | bill['expenseList'] = ns.VendorBillExpenseList(expense=expenses) 65 | bill['memo'] = 'test memo' 66 | # bill['class'] = class_ref 67 | # bill['location'] = loc_ref 68 | bill['entity'] = vendor_ref 69 | bill['tranDate'] = datetime.now().strftime('%Y-%m-%dT%H:%M:%S') 70 | logger.debug('upserting bill %s', bill) 71 | record_ref = ns.upsert(bill) 72 | logger.debug('record_ref = %s', record_ref) 73 | assert record_ref['externalId'] == '12345', 'External ID does not match' 74 | 75 | bill2 = ns.get(recordType='vendorBill', externalId='12345') 76 | logger.debug('bill2 = %s', str(bill2)) 77 | assert (29.99 < bill2['userTotal']) and (bill2['userTotal'] < 30.01), 'Bill total is not 30.0' 78 | 79 | subs_ref = ns.RecordRef(type='subsdiary', internalId=5) 80 | bill_account_ref = ns.RecordRef(type='account', internalId=84) 81 | vbe1['account'] = bill_account_ref 82 | vbe1['subsdiary'] = subs_ref 83 | 84 | try: 85 | record_ref = ns.upsert(bill, record_type='bills') 86 | logger.debug('record_ref = %s', record_ref) 87 | except NetSuiteRequestError as e: 88 | assert e.message == 'An error occured in a upsert request: Invalid account reference key UK EXP Account for subsidiary Honeycomb Mfg..' 89 | assert e.code == 'INVALID_KEY_OR_REF' 90 | 91 | 92 | def test_upsert_journal_entry(ns): 93 | vendor_ref = ns.RecordRef(type='vendor', internalId=get_vendor(ns).internalId) 94 | cat_account_ref = ns.RecordRef(type='account', internalId=get_category_account(ns).internalId) 95 | loc_ref = ns.RecordRef(type='location', internalId=get_location(ns).internalId) 96 | dep_ref = ns.RecordRef(type='department', internalId=get_department(ns).internalId) 97 | class_ref = ns.RecordRef(type='classification', internalId=get_department(ns).internalId) 98 | lines = [] 99 | 100 | credit_line = ns.JournalEntryLine() 101 | credit_line['account'] = cat_account_ref 102 | credit_line['department'] = dep_ref 103 | credit_line['class'] = class_ref 104 | credit_line['location'] = loc_ref 105 | credit_line['entity'] = vendor_ref 106 | credit_line['credit'] = 20.0 107 | 108 | lines.append(credit_line) 109 | 110 | debit_line = ns.JournalEntryLine() 111 | debit_line['account'] = cat_account_ref 112 | debit_line['department'] = dep_ref 113 | debit_line['class'] = class_ref 114 | debit_line['location'] = loc_ref 115 | debit_line['entity'] = vendor_ref 116 | debit_line['debit'] = 20.0 117 | 118 | lines.append(debit_line) 119 | 120 | journal_entry = ns.JournalEntry(externalId='JE_12345') 121 | journal_entry['currency'] = ns.RecordRef(type='currency', internalId=get_currency(ns).internalId) # US dollar 122 | journal_entry['subsidiary'] = ns.RecordRef(type='subsidiary', internalId='1') 123 | journal_entry['exchangerate'] = 1.0 124 | journal_entry['lineList'] = ns.JournalEntryLineList(line=lines) 125 | journal_entry['memo'] = 'test memo' 126 | journal_entry['tranDate'] = datetime.today().date().replace(day=1) 127 | logger.debug('upserting journal entry %s', journal_entry) 128 | record_ref = ns.upsert(journal_entry) 129 | logger.debug('record_ref = %s', record_ref) 130 | assert record_ref['externalId'] == 'JE_12345', 'External ID does not match' 131 | 132 | je = ns.get(recordType='journalEntry', externalId='JE_12345') 133 | logger.debug('je = %s', str(je)) 134 | assert (je['externalId'] == 'JE_12345'), 'Journal Entry External ID does not match' 135 | 136 | cat_account_ref = ns.RecordRef(type='account', internalId=25) 137 | credit_line['account'] = cat_account_ref 138 | 139 | try: 140 | record_ref = ns.upsert(journal_entry, 'journal_entry') 141 | logger.debug('record_ref = %s', record_ref) 142 | except NetSuiteRequestError as e: 143 | assert e.message == 'An error occured in a upsert request: Invalid account reference key UK EXP Account for subsidiary Honeycomb Mfg..' 144 | assert e.code == 'INVALID_KEY_OR_REF' 145 | 146 | 147 | def test_upsert_expense_report(ns): 148 | employee_ref = ns.RecordRef(type='employee', internalId=get_employee(ns).internalId) 149 | bill_account_ref = ns.RecordRef(type='account', internalId=25) 150 | cat_account_ref = ns.RecordRef(type='account', internalId='3') 151 | 152 | loc_ref = ns.RecordRef(type='location', internalId=get_location(ns).internalId) 153 | dep_ref = ns.RecordRef(type='department', internalId=get_department(ns).internalId) 154 | class_ref = ns.RecordRef(type='classification', internalId=get_department(ns).internalId) 155 | currency_ref = ns.RecordRef(type='currency', internalId=get_currency(ns).internalId) 156 | expenses = [] 157 | 158 | er = ns.ExpenseReportExpense() 159 | er['category'] = cat_account_ref 160 | er['amount'] = 10.0 161 | er['department'] = dep_ref 162 | er['class'] = class_ref 163 | er['location'] = loc_ref 164 | er['currency'] = currency_ref 165 | 166 | expenses.append(er) 167 | 168 | expense_report = ns.ExpenseReport(externalId='EXPR_1') 169 | expense_report['expenseReportCurrency'] = currency_ref # US dollar 170 | expense_report['exchangerate'] = 1.0 171 | expense_report['expenseList'] = ns.ExpenseReportExpenseList(expense=expenses) 172 | expense_report['memo'] = 'test memo' 173 | expense_report['entity'] = employee_ref 174 | logger.debug('upserting expense report %s', expense_report) 175 | record_ref = ns.upsert(expense_report) 176 | logger.debug('record_ref = %s', record_ref) 177 | assert record_ref['externalId'] == 'EXPR_1', 'External ID does not match' 178 | 179 | expr = ns.get(recordType='ExpenseReport', externalId='EXPR_1') 180 | logger.debug('expense report = %s', str(expr)) 181 | 182 | 183 | loc_ref = ns.RecordRef(type='location', internalId=2) 184 | subs_ref = ns.RecordRef(type='subsdiary', internalId=1) 185 | 186 | er['location'] = loc_ref 187 | er['subsdiary'] = subs_ref 188 | 189 | try: 190 | record_ref = ns.upsert(expense_report, record_type='expense_report') 191 | logger.debug('record_ref = %s', record_ref) 192 | except NetSuiteRequestError as e: 193 | assert e.message == 'An error occured in a upsert request: Invalid location reference key UK Location for subsidiary Honeycomb Mfg..' 194 | assert e.code == 'INVALID_KEY_OR_REF' 195 | --------------------------------------------------------------------------------