From 058072e2262b25c8287fab6848941bc2de3eab5c Mon Sep 17 00:00:00 2001 From: Tyler Matteson Date: Tue, 23 Jul 2024 09:26:40 -0400 Subject: [PATCH] Track stock manufacturing overrides v15 (#104) * ci: track overrides for stock and manufacturing cycles (v15) * ci: update workflows * ci: track overrides for opurchase cycles (v15) * ci: fix override hash * chore: update pre-commit and track overrides hashes * chore: update pre-commit * fix: merge conficts, update override hash * chore: track overrides * chore: track overrides --------- Co-authored-by: Rohan Bansal Co-authored-by: fproldan --- .github/validate_customizations.py | 164 -- .github/workflows/lint.yaml | 327 ++- .github/workflows/overrides.yaml | 17 + .github/workflows/pytest.yaml | 12 +- .github/workflows/release.yaml | 3 +- .pre-commit-config.yaml | 56 +- .prettierrc.js => .prettierrc.cjs | 0 CHANGELOG.md | 1512 ++++++------ README.md | 3 + inventory_tools/__init__.py | 3 + inventory_tools/customize.py | 91 +- inventory_tools/docs/exampledata.md | 3 + inventory_tools/docs/index.md | 43 +- inventory_tools/docs/landed_costing.md | 3 + .../docs/manufacturing_capacity.md | 3 + inventory_tools/docs/material_demand.md | 149 +- inventory_tools/docs/quotation_demand.md | 3 + inventory_tools/docs/uom_enforcement.md | 81 +- inventory_tools/docs/warehouse_path.md | 39 +- inventory_tools/docs/wo_subcontracting.md | 3 + .../docs/work_order_subcontracting.md | 19 +- inventory_tools/hooks.py | 13 +- inventory_tools/inventory_tools/__init__.py | 2 + .../inventory_tools/custom/bom.json | 344 ++- .../inventory_tools/custom/item_supplier.json | 123 +- .../inventory_tools/custom/operation.json | 126 +- .../custom/purchase_invoice.json | 464 +--- .../custom/purchase_invoice_item.json | 56 +- .../custom/purchase_order.json | 571 ++--- .../inventory_tools/custom/sales_order.json | 125 +- .../custom/stock_entry_detail.json | 147 +- .../inventory_tools/custom/supplier.json | 184 +- .../inventory_tools/custom/work_order.json | 245 +- .../inventory_tools/doctype/__init__.py | 2 + .../alternative_workstations.json | 60 +- .../inventory_tools_settings/__init__.py | 2 + .../inventory_tools_settings.json | 398 ++-- .../__init__.py | 2 + ...urchase_invoice_subcontracting_detail.json | 248 +- .../__init__.py | 2 + .../purchase_order_subcontracting_detail.json | 154 +- .../subcontracting_default/__init__.py | 2 + .../subcontracting_default.json | 96 +- .../inventory_tools/overrides/job_card.py | 10 + .../inventory_tools/overrides/operation.py | 3 + .../overrides/production_plan.py | 105 +- .../overrides/purchase_invoice.py | 382 ++-- .../overrides/purchase_order.py | 528 ++--- .../overrides/purchase_receipt.py | 82 +- .../inventory_tools/overrides/stock_entry.py | 23 +- .../inventory_tools/overrides/warehouse.py | 161 +- .../inventory_tools/overrides/work_order.py | 52 +- .../inventory_tools/overrides/workstation.py | 33 +- .../inventory_tools/report/__init__.py | 2 + .../report/manufacturing_capacity/__init__.py | 2 + .../manufacturing_capacity.json | 58 +- .../report/material_demand/__init__.py | 2 + .../report/quotation_demand/__init__.py | 2 + .../quotation_demand/quotation_demand.json | 68 +- inventory_tools/public/js/custom/utils.js | 3 + .../public/js/inventory_tools.bundle.js | 3 + inventory_tools/public/js/job_card_custom.js | 3 + inventory_tools/public/js/operation_custom.js | 3 + .../public/js/purchase_invoice_custom.js | 3 + .../public/js/purchase_order_custom.js | 3 + .../public/js/stock_entry_custom.js | 3 + inventory_tools/public/js/uom_enforcement.js | 3 + .../public/js/work_order_custom.js | 3 + inventory_tools/tests/conftest.py | 87 +- inventory_tools/tests/fixtures.py | 2019 +++++++++-------- inventory_tools/tests/setup.py | 1515 +++++++------ .../tests/test_manufacturing_capacity.py | 3 + inventory_tools/tests/test_material_demand.py | 465 ++-- inventory_tools/tests/test_overproduction.py | 3 + .../tests/test_quotation_demand_report.py | 3 + inventory_tools/tests/test_uom.py | 41 +- inventory_tools/tests/test_warehouse_path.py | 37 +- inventory_tools/www/bulk_order.py | 3 + package.json | 8 +- setup.py | 3 + 80 files changed, 5444 insertions(+), 6183 deletions(-) delete mode 100644 .github/validate_customizations.py create mode 100644 .github/workflows/overrides.yaml rename .prettierrc.js => .prettierrc.cjs (100%) diff --git a/.github/validate_customizations.py b/.github/validate_customizations.py deleted file mode 100644 index cd67c22..0000000 --- a/.github/validate_customizations.py +++ /dev/null @@ -1,164 +0,0 @@ -import json -import pathlib -import sys - - -def scrub(txt: str) -> str: - return txt.replace(" ", "_").replace("-", "_").lower() - - -def unscrub(txt: str) -> str: - return txt.replace("_", " ").replace("-", " ").title() - - -def get_customized_doctypes(): - apps_dir = pathlib.Path(__file__).resolve().parent.parent.parent - apps_order = pathlib.Path(__file__).resolve().parent.parent.parent.parent / "sites" / "apps.txt" - apps_order = apps_order.read_text().split("\n") - customized_doctypes = {} - for _app_dir in apps_order: - app_dir = (apps_dir / _app_dir).resolve() - if not app_dir.is_dir(): - continue - modules = (app_dir / _app_dir / "modules.txt").read_text().split("\n") - for module in modules: - if not (app_dir / _app_dir / scrub(module) / "custom").exists(): - continue - for custom_file in list((app_dir / _app_dir / scrub(module) / "custom").glob("**/*.json")): - if custom_file.stem in customized_doctypes: - customized_doctypes[custom_file.stem].append(custom_file.resolve()) - else: - customized_doctypes[custom_file.stem] = [custom_file.resolve()] - - return dict(sorted(customized_doctypes.items())) - - -def validate_module(customized_doctypes, set_module=False): - exceptions = [] - app_dir = pathlib.Path(__file__).resolve().parent.parent - this_app = app_dir.stem - for doctype, customize_files in customized_doctypes.items(): - for customize_file in customize_files: - if not this_app == customize_file.parent.parent.parent.parent.stem: - continue - module = customize_file.parent.parent.stem - file_contents = json.loads(customize_file.read_text()) - if file_contents.get("custom_fields"): - for custom_field in file_contents.get("custom_fields"): - if set_module: - custom_field["module"] = unscrub(module) - continue - if not custom_field.get("module"): - exceptions.append( - f"Custom Field for {custom_field.get('dt')} in {this_app} '{custom_field.get('fieldname')}' does not have a module key" - ) - continue - elif custom_field.get("module") != unscrub(module): - exceptions.append( - f"Custom Field for {custom_field.get('dt')} in {this_app} '{custom_field.get('fieldname')}' has module key ({custom_field.get('module')}) associated with another app" - ) - continue - if file_contents.get("property_setters"): - for ps in file_contents.get("property_setters"): - if set_module: - ps["module"] = unscrub(module) - continue - if not ps.get("module"): - exceptions.append( - f"Property Setter for {ps.get('doc_type')} in {this_app} '{ps.get('property')}' on {ps.get('field_name')} does not have a module key" - ) - continue - elif ps.get("module") != unscrub(module): - exceptions.append( - f"Property Setter for {ps.get('doc_type')} in {this_app} '{ps.get('property')}' on {ps.get('field_name')} has module key ({ps.get('module')}) associated with another app" - ) - continue - if set_module: - with customize_file.open("w", encoding="UTF-8") as target: - json.dump(file_contents, target, sort_keys=True, indent=2) - - return exceptions - - -def validate_no_custom_perms(customized_doctypes): - exceptions = [] - this_app = pathlib.Path(__file__).resolve().parent.parent.stem - for doctype, customize_files in customized_doctypes.items(): - for customize_file in customize_files: - if not this_app == customize_file.parent.parent.parent.parent.stem: - continue - file_contents = json.loads(customize_file.read_text()) - if file_contents.get("custom_perms"): - exceptions.append(f"Customization for {doctype} in {this_app} contains custom permissions") - return exceptions - - -def validate_duplicate_customizations(customized_doctypes): - exceptions = [] - common_fields = {} - common_property_setters = {} - app_dir = pathlib.Path(__file__).resolve().parent.parent - this_app = app_dir.stem - for doctype, customize_files in customized_doctypes.items(): - if len(customize_files) == 1: - continue - common_fields[doctype] = {} - common_property_setters[doctype] = {} - for customize_file in customize_files: - module = customize_file.parent.parent.stem - app = customize_file.parent.parent.parent.parent.stem - file_contents = json.loads(customize_file.read_text()) - if file_contents.get("custom_fields"): - fields = [cf.get("fieldname") for cf in file_contents.get("custom_fields")] - common_fields[doctype][module] = fields - if file_contents.get("property_setters"): - ps = [ps.get("name") for ps in file_contents.get("property_setters")] - common_property_setters[doctype][module] = ps - - for doctype, module_and_fields in common_fields.items(): - if this_app not in module_and_fields.keys(): - continue - this_modules_fields = module_and_fields.pop(this_app) - for module, fields in module_and_fields.items(): - for field in fields: - if field in this_modules_fields: - exceptions.append( - f"Custom Field for {unscrub(doctype)} in {this_app} '{field}' also appears in customizations for {module}" - ) - - for doctype, module_and_ps in common_property_setters.items(): - if this_app not in module_and_ps.keys(): - continue - this_modules_ps = module_and_ps.pop(this_app) - for module, ps in module_and_ps.items(): - for p in ps: - if p in this_modules_ps: - exceptions.append( - f"Property Setter for {unscrub(doctype)} in {this_app} on '{p}' also appears in customizations for {module}" - ) - - return exceptions - - -def validate_customizations(set_module): - customized_doctypes = get_customized_doctypes() - exceptions = validate_no_custom_perms(customized_doctypes) - exceptions += validate_module(customized_doctypes, set_module) - exceptions += validate_duplicate_customizations(customized_doctypes) - - return exceptions - - -if __name__ == "__main__": - exceptions = [] - set_module = False - for arg in sys.argv: - if arg == "--set-module": - set_module = True - exceptions.append(validate_customizations(set_module)) - - if exceptions: - for exception in exceptions: - [print(e) for e in exception] # TODO: colorize - - sys.exit(1) if all(exceptions) else sys.exit(0) diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml index 1c29675..d702d91 100644 --- a/.github/workflows/lint.yaml +++ b/.github/workflows/lint.yaml @@ -1,165 +1,162 @@ -name: Linters - -on: - push: - branches: - - version-14 - - version-15 - pull_request: - branches: - - version-14 - - version-15 - -env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - -jobs: - mypy: - needs: [ py_json_merge ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - fetch-depth: 2 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install mypy - run: pip install mypy - - - name: Install mypy types - run: mypy ./inventory_tools/. --install-types - - - name: Run mypy - uses: sasanquaneuf/mypy-github-action@releases/v1 - with: - checkName: 'mypy' - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - black: - needs: [ py_json_merge ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - fetch-depth: 2 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.10' - - - name: Install Black (Frappe) - run: pip install git+https://github.com/frappe/black.git - - - name: Run Black (Frappe) - run: black --check . - - prettier: - needs: [ py_json_merge ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.head_ref }} - fetch-depth: 2 - - - name: Prettify code - uses: rutajdash/prettier-cli-action@v1.0.0 - with: - config_path: ./.prettierrc.js - ignore_path: ./.prettierignore - - - name: Prettier Output - if: ${{ failure() }} - shell: bash - run: | - echo "The following files are not formatted:" - echo "${{steps.prettier-run.outputs.prettier_output}}" >> $GITHUB_OUTPUT - - json_diff: - needs: [ py_json_merge ] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - with: - ref: ${{ github.ref }} - fetch-depth: 2 - - - name: Find JSON changes - id: changed-json - uses: tj-actions/changed-files@v43 - with: - files: | - **/*.json - include_all_old_new_renamed_files: true - - - name: Copy head paths files - run: | - mkdir head - touch head/acmr.txt - for file in ${{ steps.changed-json.outputs.added_files }}; do - echo "A,head/${file}" >> head/acmr.txt - cp --parents $file head/ - done - for file in ${{ steps.changed-json.outputs.copied_files }}; do - echo "C,head/${file}" >> head/acmr.txt - cp --parents $file head/ - done - for file in ${{ steps.changed-json.outputs.modified_files }}; do - echo "M,head/${file}" >> head/acmr.txt - cp --parents $file head/ - done - for file in ${{ steps.changed-json.outputs.renamed_files }}; do - echo "R,head/${file}" >> head/acmr.txt - cp --parents $file head/ - done - - - name: Checkout base - run: git checkout $(git --no-pager log --oneline -n 2 | awk 'NR==2 {print $1}') - - - name: Copy base paths - run: | - mkdir base - touch base/mrd.txt - for file in ${{ steps.changed-json.outputs.modified_files }}; do - echo "M,${file}" >> base/mrd.txt - done - for file in ${{ steps.changed-json.outputs.all_old_new_renamed_files }}; do - echo "R,${file}" >> base/mrd.txt - done - for file in ${{ steps.changed-json.outputs.deleted_files }}; do - echo "D,${file}" >> base/mrd.txt - done - - - py_json_merge: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - - name: Fetch validator - run: git clone --depth 1 https://gist.github.com/f1bf2c11f78331b2417189c385022c28.git validate_json - - - name: Validate JSON - run: python3 validate_json/validate_json.py ./inventory_tools/inventory_tools/ - - - name: Compile - run: python3 -m compileall -q ./ - - - name: Check merge - run: | - if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}" - then echo "Found merge conflicts" - exit 1 - fi +name: Linters + +on: + push: + branches: + - version-14 + - version-15 + pull_request: + +env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + +jobs: + mypy: + needs: [ py_json_merge ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install mypy + run: pip install mypy + + - name: Install mypy types + run: mypy ./inventory_tools/. --install-types + + - name: Run mypy + uses: sasanquaneuf/mypy-github-action@releases/v1 + with: + checkName: 'mypy' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + black: + needs: [ py_json_merge ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.10' + + - name: Install Black (Frappe) + run: pip install git+https://github.com/frappe/black.git + + - name: Run Black (Frappe) + run: black --check . + + prettier: + needs: [ py_json_merge ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.head_ref }} + fetch-depth: 2 + + - name: Prettify code + uses: rutajdash/prettier-cli-action@v1.0.0 + with: + config_path: ./.prettierrc.js + ignore_path: ./.prettierignore + + - name: Prettier Output + if: ${{ failure() }} + shell: bash + run: | + echo "The following files are not formatted:" + echo "${{steps.prettier-run.outputs.prettier_output}}" >> $GITHUB_OUTPUT + + json_diff: + needs: [ py_json_merge ] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + ref: ${{ github.ref }} + fetch-depth: 2 + + - name: Find JSON changes + id: changed-json + uses: tj-actions/changed-files@v43 + with: + files: | + **/*.json + include_all_old_new_renamed_files: true + + - name: Copy head paths files + run: | + mkdir head + touch head/acmr.txt + for file in ${{ steps.changed-json.outputs.added_files }}; do + echo "A,head/${file}" >> head/acmr.txt + cp --parents $file head/ + done + for file in ${{ steps.changed-json.outputs.copied_files }}; do + echo "C,head/${file}" >> head/acmr.txt + cp --parents $file head/ + done + for file in ${{ steps.changed-json.outputs.modified_files }}; do + echo "M,head/${file}" >> head/acmr.txt + cp --parents $file head/ + done + for file in ${{ steps.changed-json.outputs.renamed_files }}; do + echo "R,head/${file}" >> head/acmr.txt + cp --parents $file head/ + done + + - name: Checkout base + run: git checkout $(git --no-pager log --oneline -n 2 | awk 'NR==2 {print $1}') + + - name: Copy base paths + run: | + mkdir base + touch base/mrd.txt + for file in ${{ steps.changed-json.outputs.modified_files }}; do + echo "M,${file}" >> base/mrd.txt + done + for file in ${{ steps.changed-json.outputs.all_old_new_renamed_files }}; do + echo "R,${file}" >> base/mrd.txt + done + for file in ${{ steps.changed-json.outputs.deleted_files }}; do + echo "D,${file}" >> base/mrd.txt + done + + + py_json_merge: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Fetch validator + run: git clone --depth 1 https://gist.github.com/f1bf2c11f78331b2417189c385022c28.git validate_json + + - name: Validate JSON + run: python3 validate_json/validate_json.py ./inventory_tools/inventory_tools/ + + - name: Compile + run: python3 -m compileall -q ./ + + - name: Check merge + run: | + if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}" + then echo "Found merge conflicts" + exit 1 + fi diff --git a/.github/workflows/overrides.yaml b/.github/workflows/overrides.yaml new file mode 100644 index 0000000..9e90f0c --- /dev/null +++ b/.github/workflows/overrides.yaml @@ -0,0 +1,17 @@ +name: Track Overrides + +on: + pull_request: + +jobs: + track_overrides: + runs-on: ubuntu-latest + name: Track Overrides + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Track Overrides + uses: diamorafaela/track-overrides@main + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/pytest.yaml b/.github/workflows/pytest.yaml index d2f574a..ba1dc5a 100644 --- a/.github/workflows/pytest.yaml +++ b/.github/workflows/pytest.yaml @@ -6,9 +6,6 @@ on: - version-14 - version-15 pull_request: - branches: - - version-14 - - version-15 permissions: contents: write @@ -18,12 +15,8 @@ permissions: jobs: tests: - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] - fail-fast: false name: Server + runs-on: ubuntu-latest services: mariadb: @@ -92,8 +85,7 @@ jobs: env: MYSQL_HOST: 'localhost' MYSQL_PWD: 'admin' - run: | - bash ${{ github.workspace }}/.github/helper/install.sh + run: bash ${{ github.workspace }}/.github/helper/install.sh - name: Run Tests working-directory: /home/runner/frappe-bench diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 66e4ba6..e20079a 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -1,4 +1,5 @@ name: Release + on: push: branches: @@ -22,4 +23,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} git_committer_name: AgriTheory - git_committer_email: support@agritheory.dev \ No newline at end of file + git_committer_email: support@agritheory.dev diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 2b6e6e3..3e13846 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -4,14 +4,14 @@ fail_fast: false repos: - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.6.0 hooks: - id: trailing-whitespace - files: 'beam.*' + files: 'inventory_tools.*' exclude: '.*json$|.*txt$|.*csv|.*md|.*svg' - id: check-yaml - id: no-commit-to-branch - args: ['--branch', 'develop'] + args: ['--branch', 'version-15'] - id: check-merge-conflict - id: check-ast - id: check-json @@ -23,53 +23,51 @@ repos: rev: v2.34.0 hooks: - id: pyupgrade - args: ['--py38-plus'] - - - repo: https://github.com/frappe/black - rev: 951ccf4d5bb0d692b457a5ebc4215d755618eb68 - hooks: - - id: black - - - repo: https://github.com/pre-commit/mirrors-prettier - rev: v2.7.1 - hooks: - - id: prettier - types_or: [javascript] - # Ignore any files that might contain jinja / bundles - exclude: | - (?x)^( - beam/public/dist/.*| - .*node_modules.*| - .*boilerplate.*| - beam/www/website_script.js| - beam/templates/includes/.*| - beam/public/js/lib/.* - )$ + args: ['--py310-plus'] - repo: https://github.com/PyCQA/isort rev: 5.12.0 hooks: - id: isort + - repo: https://github.com/frappe/black + rev: 951ccf4d5bb0d692b457a5ebc4215d755618eb68 + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 rev: 5.0.4 hooks: - id: flake8 additional_dependencies: ['flake8-bugbear'] + - repo: https://github.com/pre-commit/mirrors-mypy + rev: v1.5.1 + hooks: + - id: mypy + exclude: ^tests/ + args: [--ignore-missing-imports] + - repo: https://github.com/agritheory/test_utils - rev: v0.11.0 + rev: v0.13.1 hooks: + - id: update_pre_commit_config - id: validate_copyright files: '\.(js|ts|py|md)$' - args: ["--app", "inventory_tools"] + args: ['--app', 'inventory_tools'] - id: clean_customized_doctypes - args: ["--app", "inventory_tools"] + args: ['--app', 'inventory_tools'] - id: validate_customizations - args: ["--set-module", "True"] - id: validate_python_dependencies - id: validate_javascript_dependencies + - repo: local + hooks: + - id: prettier + name: prettier + entry: npx prettier -w . --config .prettierrc.cjs --ignore-path .prettierignore + language: system + ci: autoupdate_schedule: weekly skip: [] diff --git a/.prettierrc.js b/.prettierrc.cjs similarity index 100% rename from .prettierrc.js rename to .prettierrc.cjs diff --git a/CHANGELOG.md b/CHANGELOG.md index a006df2..68ac4f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,803 +1,709 @@ -# CHANGELOG - -## v15.1.0 (2024-07-13) - -### Feature - -* feat: update release.yaml to be compatible with both 14 and 15 branches (#97) - -* feat: update release.yaml to be compatible with both 14 and 15 branches - -* fix: include version-15 branch in release in package.json - -* format: format conftest.py with black - -* format: conftest.py - -* fix: removed change log and updated __init__.py with v15.0.0 - -* refactor: pyproject.toml to poetry (#99) - -* refactor: pyproject.toml to poetry - -* fix: move dev dependencies - -* format: conftest.py - -* fix: poetry install before running tests - -* fix: poetry install before running tests - -* fix: permissions for job - -* feat: add inventory tools settings to boot (v15) (#92) - -* feat: add inventory tools settings to boot (#81) - -* 14.6.0 - -Automatically generated by python-semantic-release - -* refactor: pyproject.toml to poetry (#99) - -* refactor: pyproject.toml to poetry - -* fix: move dev dependencies - -* format: conftest.py - -* fix: poetry install before running tests - -* fix: poetry install before running tests - -* fix: permissions for job - ---------- - -Co-authored-by: Tyler Matteson <tyler@agritheory.com> -Co-authored-by: AgriTheory <support@agritheory.dev> -Co-authored-by: Myuddin Khatri <53251406+MyuddinKhatri@users.noreply.github.com> - ---------- - -Co-authored-by: Rohan <Alchez@users.noreply.github.com> -Co-authored-by: Tyler Matteson <tyler@agritheory.com> -Co-authored-by: AgriTheory <support@agritheory.dev> ([`0e437fe`](https://github.com/agritheory/inventory_tools/commit/0e437fe498bc1b72ea3ea9585767640b067c7638)) - -* feat: add inventory tools settings to boot (v15) (#92) - -* feat: add inventory tools settings to boot (#81) - -* 14.6.0 - -Automatically generated by python-semantic-release - -* refactor: pyproject.toml to poetry (#99) - -* refactor: pyproject.toml to poetry - -* fix: move dev dependencies - -* format: conftest.py - -* fix: poetry install before running tests - -* fix: poetry install before running tests - -* fix: permissions for job - ---------- - -Co-authored-by: Tyler Matteson <tyler@agritheory.com> -Co-authored-by: AgriTheory <support@agritheory.dev> -Co-authored-by: Myuddin Khatri <53251406+MyuddinKhatri@users.noreply.github.com> ([`95a08da`](https://github.com/agritheory/inventory_tools/commit/95a08da27649ea6097f69bd0e8b02f8a37a713bc)) - -### Refactor - -* refactor: pyproject.toml to poetry (#99) - -* refactor: pyproject.toml to poetry - -* fix: move dev dependencies - -* format: conftest.py - -* fix: poetry install before running tests - -* fix: poetry install before running tests - -* fix: permissions for job ([`b06bf26`](https://github.com/agritheory/inventory_tools/commit/b06bf2680a3cdb714db4ecc38047128fa44c5f56)) - -## v15.0.0 (2024-05-13) - -### Chore - -* chore: update quotation demand docs (#77) ([`f168079`](https://github.com/agritheory/inventory_tools/commit/f168079e29d99056d4c5eb2321d84957cff84f3b)) - -### Unknown - -* wip: version-15 ([`ffb173d`](https://github.com/agritheory/inventory_tools/commit/ffb173d1fb74f806dc76f35b39127ad9e8c63a0a)) - -## v14.5.0 (2024-04-29) - -### Feature - -* feat: allow user to bulk upload items with quantity to shopping cart (#70) - -* feat: allow user to bulk upload items with quantity to shoping cart - -* nit: variable and syntactical changes - -* fix: removed line by line order placing, optimised code, added enqueue - -* fix: minor cleanup - ---------- - -Co-authored-by: Rohan Bansal <rohan@agritheory.dev> ([`748a9bc`](https://github.com/agritheory/inventory_tools/commit/748a9bc85034094dcd60544fe2b822b67322fb44)) - -### Unknown - -* Aggregate and/or Split Quotations into multiple Sales Orders (#65) - -* feat: Quotation Demand - -* feat: hook - -* feat: settings for quotation demand - -* feat: fixes, SO validate_warehouse - -* feat: quotation demand tests - -* feat: documentation - -* feat: improvements - -* feat: improve tests - -* feat: total selected, price, draft so - -* feat: tests ([`6eceeb5`](https://github.com/agritheory/inventory_tools/commit/6eceeb5623856d659db1ffc3dd7a3ce352cd8000)) - -## v14.4.0 (2024-04-25) - -### Ci - -* ci: add black to ci (#61) - -* ci: add black to ci - -* chore: black ([`db5a742`](https://github.com/agritheory/inventory_tools/commit/db5a7427f068d7005f8f07125d81f1430c49eb9b)) - -### Feature - -* feat: aggregate POs (#71) ([`5ff8fee`](https://github.com/agritheory/inventory_tools/commit/5ff8fee2ef356990e2dcda0d2db76d1b856a0685)) - -### Unknown - -* Material Demand Test (#64) - -* wip: material demand tests - -* test: material demand aggregation tests and fixes with and without warehouse - -* test: material demand tests and bug fixes - -* chore: add mypy types isntall - -* fix: remove duplicate function - -* tests: add pytest runner file - -* tests: add helper files - -* tests: correct app name - -* ci: fix site_config - -* ci: update action version to use node 20 - -* ci: hrms get-app - -* ci: add base branch sensitvie get-app - -* ci: add hrms to install - -* ci: add node 20 versions - -* ci: remove hrms from install-apps - -* ci: get-app for hrms - -* ci: update prettier and changed-files actions - -* ci: ad dhrms to apps.txt - -* ci: get-app for hrms again - -* ci: get-app for hrms only - -* ci: overrite erpnext install (hrms is tryign to install develop branch) - -* ci: add app name - -* ci: add repo to required apps - -* ci: try install hrms first with overwrite - -* ci: remove "install_apps" key from site_config - -* ci: remove required apps - -* feat: use query builder - -* ci: remove resolve deps - -* ci: fix install apps - -* test: fix manufacturing capacity test and report - -* test: cleanup uom test - -* test: fix error message assert - -* test: add ordering - -* fix: test - -* fix: use frappe function - -* fix: use frappe function - ---------- - -Co-authored-by: fproldan <franciscoproldan@gmail.com> ([`6f4f63f`](https://github.com/agritheory/inventory_tools/commit/6f4f63f22f3f7278e20c6aba9e5ef6a021ae03b2)) - -* Manufacturing capacity report (#57) - -* ci: update app in str(file path) code to more exact matching - -* chore: remove unused code - -* feat: add manufacturing capacity report - -* tests: start manufacturing capacity report tests - -* chore: uncomment formatting code - -* docs: add manufacturing capacity report documentation - -* fix: div by zero in parts can build calc - -* docs: update for calculation differences - -* fix: in stock qty to zero if none from query ([`2936cde`](https://github.com/agritheory/inventory_tools/commit/2936cde9aac6583edeb383d94d74142461cf243a)) - -## v14.3.0 (2024-03-01) - -### Feature - -* feat: Alternative workstation in job card and operation (#56) - -* feat: New field and link filters to select alternative workstation - -* fix: add alternativ workstation in fixture - -* fix:change alternative workstation in fixture - -* change to error key function - -* fix: set validation on workstation if not operation - -* changes field name in testcase - -* fix: change a class base function to saperate function - -* added a searchfield in query - -* added a searchfield in query - -* added a searchfield in query - -* added a searchfield in query - -* added a searchfield in query - -* feat: validation to not allow default workstation in alternative - -* added comment on function ([`0788d3c`](https://github.com/agritheory/inventory_tools/commit/0788d3caa67ad7820a5a3cdfdc950c85bd6f0cd7)) - -### Unknown - -* Prompt Material Transfer upon Completion of Manufacturing Stock Entry (#51) - -* feat: wip stock entry next action - -* feat: improve message - -* fix: on_submit hook - -* fix: parameter hardcoded - -* feat: change wording ([`d1e126b`](https://github.com/agritheory/inventory_tools/commit/d1e126b8dbaf09370f6d4c173e24af33ac994bd7)) - -## v14.2.0 (2024-02-01) - -### Feature - -* feat: manufacturing over/under production - -* feat: WIP Manufacturing Over/Under Production - -* feat: WIP Manufacturing Over/Under Production - -* feat: WIP Manufacturing Over/Under Production - -* fix: unused import - -* feat: override onload for work order - -* feat: oveeride get_pending_raw_materials - -* fix: allowed_qty in job card - -* fix: indentation - -* fix: import - -* fixes - -* wip: tests - -* feat: test_get_allowance_percentage - -* feat: test test_validate_finished_goods - -* fix: validate_job_card and test - -* fix: test ([`baeb469`](https://github.com/agritheory/inventory_tools/commit/baeb4690d42429a062595eb9058ddd2770b190c9)) - -### Unknown - -* Make Creation of Job Card(s) on Submit of Work Order configurable (#49) - -* feat: configurable creation of job card - -* feat: configurable creation of job card ([`5bf9486`](https://github.com/agritheory/inventory_tools/commit/5bf94860c58d81faccb1106828f16121c081871a)) - -## v14.1.3 (2024-01-04) - -### Fix - -* fix: validate customizations (#35) - -* fix: validate customizations - -* fix: only install inventory tools customizations ([`e1d86a0`](https://github.com/agritheory/inventory_tools/commit/e1d86a0739e56f3b987b599c275644ee5c29fc0a)) - -## v14.1.2 (2023-09-12) - -### Chore - -* chore: remove console.log ([`f82b590`](https://github.com/agritheory/inventory_tools/commit/f82b590d061608818170092f07e3da8d9153b756)) - -### Ci - -* ci: update release action user and email (#32) ([`4264bdd`](https://github.com/agritheory/inventory_tools/commit/4264bdde25c0bff1390923e7f18fce7c353844db)) - -### Fix - -* fix: make uom enforcement respond better to toggle on/off ([`3734cb4`](https://github.com/agritheory/inventory_tools/commit/3734cb42e880168bb4c4a71c67820da63857e0bf)) - -### Unknown - -* Merge pull request #33 from agritheory/uom_enforcement_fix - -fix: make uom enforcement respond better to toggle on/off ([`13ad883`](https://github.com/agritheory/inventory_tools/commit/13ad8832a346799563e0f7ae2f1b20c457d10d5c)) - -## v14.1.1 (2023-08-30) - -### Fix - -* fix: add is_subcontracted check for additional validation/submit/cancel code ([`8182dac`](https://github.com/agritheory/inventory_tools/commit/8182dacfd35d2a2ee4c5ee94211cbfc7ac00abfd)) - -### Unknown - -* Merge pull request #30 from agritheory/fix_subc_validation - -fix: add is_subcontracted check for additional validation/submit/cancel code ([`c4e7b83`](https://github.com/agritheory/inventory_tools/commit/c4e7b83fd1700d08e8f711e10f1a77c81da0de32)) - -## v14.1.0 (2023-08-24) - -### Chore - -* chore: update test data for erpnext codebase changes (#24) ([`b7dfc02`](https://github.com/agritheory/inventory_tools/commit/b7dfc02e228c4ebdca86000dac65d1a903640b15)) - -### Ci - -* ci: update remote name ([`021b8a4`](https://github.com/agritheory/inventory_tools/commit/021b8a47355cb9028bac7ac8e60d224247d5b0e8)) - -* ci: update version number ([`9d82150`](https://github.com/agritheory/inventory_tools/commit/9d821506af9d6eb72e3bfb1345ac652aa86896dc)) - -* ci: add python semantic release ([`703803d`](https://github.com/agritheory/inventory_tools/commit/703803df7949fb0cb43699482425ec31595dd1b9)) - -### Documentation - -* docs: update material demand section for expanded functionality ([`1af88f6`](https://github.com/agritheory/inventory_tools/commit/1af88f6d06fdc8e6908381f9dd9011fe470dd9d7)) - -### Feature - -* feat: select email template ([`02196e3`](https://github.com/agritheory/inventory_tools/commit/02196e37c7234982c5dafda9e814117d374565a9)) - -* feat: based on item option ([`cc90229`](https://github.com/agritheory/inventory_tools/commit/cc90229b9ccf27cb5a872b24109dc7071f146802)) - -* feat: wip, make rfqs ([`c5a8867`](https://github.com/agritheory/inventory_tools/commit/c5a88673c3d57480a10ec68091e04a75488e18cb)) - -* feat: wip material demand options ([`bedd3d4`](https://github.com/agritheory/inventory_tools/commit/bedd3d42f299c73c1c1fa80c3a4928316d1428a2)) - -* feat: requires_rfq custom field, creation options in report ([`cd4ec42`](https://github.com/agritheory/inventory_tools/commit/cd4ec42e15fdbfa2cc24dc41b16562e74daf6124)) - -### Fix - -* fix: blank email template for PO; skip supplier-only rows for RFQ ([`2a0e8cd`](https://github.com/agritheory/inventory_tools/commit/2a0e8cd6c6aefdd1a0d7b978652f97ddc755523f)) - -* fix: fix JS after adding draft PO column (#26) ([`e0d3229`](https://github.com/agritheory/inventory_tools/commit/e0d322950e87dcf09f13441859e273a0e44a1192)) - -### Unknown - -* Merge pull request #23 from agritheory/issue_19 - -Allow Creation of RFQ from Material Demand report ([`8a0e350`](https://github.com/agritheory/inventory_tools/commit/8a0e35024968ce5a0f6da05e71f62c091bf2b38f)) - -* Merge branch 'version-14' into issue_19 ([`ee0a27f`](https://github.com/agritheory/inventory_tools/commit/ee0a27f0f03a8e3c9e9fa54011d02e44554b8bbb)) - -* Documentation (#29) - -* docs: add index page - -* docs: add screen shots and workflow - -* docs: add screen shots, text edits - -* docs: add example data page - -* docs: add placeholder pages - -* docs: add subcontracting via WO section - -* docs: edits, conform text conventions ([`2fc980d`](https://github.com/agritheory/inventory_tools/commit/2fc980d41105759cffa33e610b779b6d464cf24c)) - -* tests: test cadence (#28) ([`6b5bd47`](https://github.com/agritheory/inventory_tools/commit/6b5bd47089fb2f8a09159635e618425743cc9dff)) - -* Warehouse path (#25) - -* wip: warehouse path - -* wip: warehouse path - -* wip: warehouse path feature - -* feat: warehouse path builder - -* feat: undo query when not configured; setup tweaks - -* chore: update test data for erpnext codebase changes (#24) - -* wip: warehouse path feature - -* wip: test setup - -* chore: update yarn - -* tests: trying to defaeat logger problem - -* test: fix conftest logger issue - -* docs: add docs for warehouse path - -* chore: union types for whitelisted function - ---------- - -Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`370dd6f`](https://github.com/agritheory/inventory_tools/commit/370dd6f9789156ebcbbe7b111d891a74731a477b)) - -* Enforce UOMs to those that exist in the Item's conversion detail (#27) - -* wip: uom restricted query - -* feat: refactor UOM enforcement validation to be hookable - -* docs: add docs for UOM enforcement - -* tests: fix test logger problem, add xfail uom test ([`d4c145a`](https://github.com/agritheory/inventory_tools/commit/d4c145a94d8402fa441619289b4cc6438b7d5c45)) - -* Work Order Subcontracting (#13) - -* tests: update test data for additional manufacturing workflow - -* feat: work order subcontracting validations - -* tests: add valuation rate for subcontracted item - -* feat: start wo subcontracting feat - -* feat: make subcontracting section visible by check and settings - -* feat: add ste detail paid field and new pi cols - -* feat: setup hooks and custom po - -* tests: update data for default supplier and price lists - -* feat: include doctype js - -* feat: add work order customizations - -* feat: add purchase order customizations - -* feat: update purchase invoice customizations - -* feat: remove unused code blocks - -* fix: add module to json - -* fix: update custom doc path - -* feat: consolidate custom PI code and modularize class functions - -* feat: combine and refactor PO code - -* feat: update server function paths - -* feat: add BOM field default - -* feat: update to use BOM field vs Item for is_subcontracted - -* feat: code cleanup and refactoring for BOM field - -* feat: update for uom conversion and new svc item - -* fix: move UOM conversions to item - -* add todos in JS - -* feat: rewire item adjustments for conversion factor - -* wip: integrate with production plan - -* feat: add supplier field in WO, allow selection of supplier in dialog - -* chore: add comment explaining precision code - -* wip: subcontractor workflow - -* feat: subcontracting workflow with correct warehouses - -* feat: show/hide subcontracting columns - -* feat: colorize fetach stock entries button - -* fix: text artifacts, new PO errors - -* feat: fetch supplier warehouse, added connectiosn to PI and PO from WO - -* feat: add filters and looks to both PI and PO - -* fix: monkey patch validate_item_details - -* feat: remove buttons and update stockfield in WO subc workflow - -* Enforce UOMs to those that exist in the Item's conversion detail (#27) - -* wip: uom restricted query - -* feat: refactor UOM enforcement validation to be hookable - -* docs: add docs for UOM enforcement - -* tests: fix test logger problem, add xfail uom test - -* Warehouse path (#25) - -* wip: warehouse path - -* wip: warehouse path - -* wip: warehouse path feature - -* feat: warehouse path builder - -* feat: undo query when not configured; setup tweaks - -* chore: update test data for erpnext codebase changes (#24) - -* wip: warehouse path feature - -* wip: test setup - -* chore: update yarn - -* tests: trying to defaeat logger problem - -* test: fix conftest logger issue - -* docs: add docs for warehouse path - -* chore: union types for whitelisted function - ---------- - -Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> - -* tests: test cadence (#28) - -* fix: no cancelled PO in se query, code clean up - -* chore: add comment to explain monkey patch rationale - ---------- - -Co-authored-by: Tyler Matteson <tyler@agritheory.com> ([`ac11c1d`](https://github.com/agritheory/inventory_tools/commit/ac11c1df4daad2189916ca9841480aa1796e42e3)) - -* Documentation (#29) - -* docs: add index page - -* docs: add screen shots and workflow - -* docs: add screen shots, text edits - -* docs: add example data page - -* docs: add placeholder pages - -* docs: add subcontracting via WO section - -* docs: edits, conform text conventions ([`198e110`](https://github.com/agritheory/inventory_tools/commit/198e110ab0c5461b9c46925fc05af351151abd38)) - -* tests: test cadence (#28) ([`b91e024`](https://github.com/agritheory/inventory_tools/commit/b91e0246f6e384f40685af40d3a249daa9d03a8c)) - -* Warehouse path (#25) - -* wip: warehouse path - -* wip: warehouse path - -* wip: warehouse path feature - -* feat: warehouse path builder - -* feat: undo query when not configured; setup tweaks - -* chore: update test data for erpnext codebase changes (#24) - -* wip: warehouse path feature - -* wip: test setup - -* chore: update yarn - -* tests: trying to defaeat logger problem - -* test: fix conftest logger issue - -* docs: add docs for warehouse path - -* chore: union types for whitelisted function - ---------- - -Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`e3fb9c7`](https://github.com/agritheory/inventory_tools/commit/e3fb9c7f2a8ed3c42f4cb47078086bcdd60f91c7)) - -* Enforce UOMs to those that exist in the Item's conversion detail (#27) - -* wip: uom restricted query - -* feat: refactor UOM enforcement validation to be hookable - -* docs: add docs for UOM enforcement - -* tests: fix test logger problem, add xfail uom test ([`65d42e1`](https://github.com/agritheory/inventory_tools/commit/65d42e126d81f3ed1b98178f7b6c68c6a070986e)) - -## v14.0.1 (2023-08-10) - -### Chore - -* chore: update test data for erpnext codebase changes (#24) ([`48160aa`](https://github.com/agritheory/inventory_tools/commit/48160aa27f5d0deb3be5e6e55f16d35fd92ae086)) - -### Ci - -* ci: update remote name ([`218eb06`](https://github.com/agritheory/inventory_tools/commit/218eb06a6a4a2ac3f5b9ee214a130e2c8ba30d29)) - -* ci: update version number ([`6e0c194`](https://github.com/agritheory/inventory_tools/commit/6e0c194235a19011b0c7db5adb8ed7e5954ba5eb)) - -* ci: add python semantic release ([`3382787`](https://github.com/agritheory/inventory_tools/commit/3382787d4726d7483a7243eafff03eb775d0ac3e)) - -### Fix - -* fix: fix JS after adding draft PO column (#26) ([`42aefa9`](https://github.com/agritheory/inventory_tools/commit/42aefa9abab60962f923870a151d7bacbceba141)) - -### Unknown - -* Merge pull request #22 from agritheory/ci_fix - -ci: update remote name ([`946657b`](https://github.com/agritheory/inventory_tools/commit/946657b179d2a8ba6934c2df7ca5d83f9bb04f29)) - -* Merge pull request #21 from agritheory/py_sem_rel_14 - -ci: add python semantic release ([`13b41fa`](https://github.com/agritheory/inventory_tools/commit/13b41fad052f9c45e017b6fbac6897bdf38b7883)) - -## v14.0.0 (2023-07-21) - -### Documentation - -* docs: wip material demand docs ([`6116a1b`](https://github.com/agritheory/inventory_tools/commit/6116a1b30b77565ff0470af32f8f59aa7a949786)) - -### Feature - -* feat: add column for draft PO amount ([`59d837b`](https://github.com/agritheory/inventory_tools/commit/59d837b1d3dbf4798d08618d033f732cad76cf1f)) - -* feat: create inventory tools settings when company is created ([`edff215`](https://github.com/agritheory/inventory_tools/commit/edff215f58ce8163da37436af893e1395164520c)) - -* feat: create inventory tools settings when company is created ([`0121499`](https://github.com/agritheory/inventory_tools/commit/0121499bd99fa9ff5126b7432dd1d9a1d2816dd4)) - -* feat: material demand PO creation ([`794f735`](https://github.com/agritheory/inventory_tools/commit/794f7352b27eb000ca96ece71c8081b69f519751)) - -* feat: add setting doctype ([`25a75de`](https://github.com/agritheory/inventory_tools/commit/25a75de2f1415ed78634d45c255438f1ed7a3ad1)) - -* feat: Initialize App ([`9e932fe`](https://github.com/agritheory/inventory_tools/commit/9e932fe49e70d5f2507d5900839c32f063a20898)) - -### Fix - -* fix: purchase order custom filed missing, carry price list from report to PO ([`4fe5ac8`](https://github.com/agritheory/inventory_tools/commit/4fe5ac84f52d656db3017af5e5a2a08111b2a729)) - -* fix: add back price list filter, fix schema ([`ceda857`](https://github.com/agritheory/inventory_tools/commit/ceda85797cc62a2d0e70e3a1135a88492f5c2cc0)) - -* fix: rebased v14 conflicts ([`b1614cb`](https://github.com/agritheory/inventory_tools/commit/b1614cb28a3f57d31c2056bf4e4506fd94a6e997)) - -* fix: supplier level de-selection and filtering ([`94140d0`](https://github.com/agritheory/inventory_tools/commit/94140d042a2e3fec79a56f0cacb86725dc2539b7)) - -* fix: module import name ([`ae290f8`](https://github.com/agritheory/inventory_tools/commit/ae290f8a392d9c5b3ea941244e68c8f1099728ef)) - -### Unknown - -* Merge pull request #20 from agritheory/material_demand - -Material Demand report fixes ([`7eb3c87`](https://github.com/agritheory/inventory_tools/commit/7eb3c877932a4d072ff56af23ad77958aba2fc36)) - -* Merge pull request #15 from agritheory/material_demand - -Material Demand ([`486fde6`](https://github.com/agritheory/inventory_tools/commit/486fde69cfdfa8810d93445ef4b32eb0753e799c)) - -* Merge branch 'version-14' into material_demand ([`9275dbf`](https://github.com/agritheory/inventory_tools/commit/9275dbf04f30a7391a0c003fff77cc5b105bd4cb)) - -* Merge pull request #16 from agritheory/settings_hook - -feat: create inventory tools settings when company is created ([`acc2b9c`](https://github.com/agritheory/inventory_tools/commit/acc2b9c640e1cb9eadf2e96060518a323ce34990)) - -* wip: material demand report improvements ([`d167804`](https://github.com/agritheory/inventory_tools/commit/d167804e90e10fc8299a49f6f0b90a512e93b5a3)) - -* wip: material demand ([`067b0d7`](https://github.com/agritheory/inventory_tools/commit/067b0d71dc372c35ac988556b20d13b9ab95f009)) - -* wip: material demand - -selection helpers look good except for supplier-level deselect, which toggles everything backwards ([`de7e09c`](https://github.com/agritheory/inventory_tools/commit/de7e09c4e04456fcd5d23fdbfa30c3a8a1933c28)) - -* wip: warehouse path ([`3ef9d3e`](https://github.com/agritheory/inventory_tools/commit/3ef9d3e7a30a98339f7d09c5ecba2e666e877b49)) - -* wip: material demand report improvements ([`c20379a`](https://github.com/agritheory/inventory_tools/commit/c20379aae9659fc1280f3c884bc888cce765116d)) - -* wip: material demand ([`3f50d5f`](https://github.com/agritheory/inventory_tools/commit/3f50d5f96b5b0a57993320e4f3c56034a490cd9a)) - -* wip: material demand ([`0cb8694`](https://github.com/agritheory/inventory_tools/commit/0cb869487fc576470f5ddb7f1b8f3b23f4cc1a57)) - -* Merge pull request #7 from agritheory/settings_doctype - -feat: add setting doctype ([`5285a99`](https://github.com/agritheory/inventory_tools/commit/5285a9967fc3615f16c7d4b06ac6f55589966975)) - -* Merge pull request #6 from agritheory/test_data_fixes - -fix: module import name ([`6edf7fb`](https://github.com/agritheory/inventory_tools/commit/6edf7fb0127648c90b0e3c203473681b8b964b63)) - -* initial commit ([`a09e1ed`](https://github.com/agritheory/inventory_tools/commit/a09e1ed6724ea49e39d5e208e6031283bf282f97)) +# CHANGELOG + + +## v14.6.0 (2024-05-13) + +### Chore + +* chore: update quotation demand docs (#77) ([`f168079`](https://github.com/agritheory/inventory_tools/commit/f168079e29d99056d4c5eb2321d84957cff84f3b)) + +### Feature + +* feat: add inventory tools settings to boot (#81) ([`7ac2e15`](https://github.com/agritheory/inventory_tools/commit/7ac2e15aa74676ba5741887231814cdf9e675755)) + + +## v14.5.0 (2024-04-29) + +### Feature + +* feat: allow user to bulk upload items with quantity to shopping cart (#70) + +* feat: allow user to bulk upload items with quantity to shoping cart + +* nit: variable and syntactical changes + +* fix: removed line by line order placing, optimised code, added enqueue + +* fix: minor cleanup + +--------- + +Co-authored-by: Rohan Bansal <rohan@agritheory.dev> ([`748a9bc`](https://github.com/agritheory/inventory_tools/commit/748a9bc85034094dcd60544fe2b822b67322fb44)) + +### Unknown + +* Aggregate and/or Split Quotations into multiple Sales Orders (#65) + +* feat: Quotation Demand + +* feat: hook + +* feat: settings for quotation demand + +* feat: fixes, SO validate_warehouse + +* feat: quotation demand tests + +* feat: documentation + +* feat: improvements + +* feat: improve tests + +* feat: total selected, price, draft so + +* feat: tests ([`6eceeb5`](https://github.com/agritheory/inventory_tools/commit/6eceeb5623856d659db1ffc3dd7a3ce352cd8000)) + + +## v14.4.0 (2024-04-25) + +### Ci + +* ci: add black to ci (#61) + +* ci: add black to ci + +* chore: black ([`db5a742`](https://github.com/agritheory/inventory_tools/commit/db5a7427f068d7005f8f07125d81f1430c49eb9b)) + +### Feature + +* feat: aggregate POs (#71) ([`5ff8fee`](https://github.com/agritheory/inventory_tools/commit/5ff8fee2ef356990e2dcda0d2db76d1b856a0685)) + +### Unknown + +* Material Demand Test (#64) + +* wip: material demand tests + +* test: material demand aggregation tests and fixes with and without warehouse + +* test: material demand tests and bug fixes + +* chore: add mypy types isntall + +* fix: remove duplicate function + +* tests: add pytest runner file + +* tests: add helper files + +* tests: correct app name + +* ci: fix site_config + +* ci: update action version to use node 20 + +* ci: hrms get-app + +* ci: add base branch sensitvie get-app + +* ci: add hrms to install + +* ci: add node 20 versions + +* ci: remove hrms from install-apps + +* ci: get-app for hrms + +* ci: update prettier and changed-files actions + +* ci: ad dhrms to apps.txt + +* ci: get-app for hrms again + +* ci: get-app for hrms only + +* ci: overrite erpnext install (hrms is tryign to install develop branch) + +* ci: add app name + +* ci: add repo to required apps + +* ci: try install hrms first with overwrite + +* ci: remove "install_apps" key from site_config + +* ci: remove required apps + +* feat: use query builder + +* ci: remove resolve deps + +* ci: fix install apps + +* test: fix manufacturing capacity test and report + +* test: cleanup uom test + +* test: fix error message assert + +* test: add ordering + +* fix: test + +* fix: use frappe function + +* fix: use frappe function + +--------- + +Co-authored-by: fproldan <franciscoproldan@gmail.com> ([`6f4f63f`](https://github.com/agritheory/inventory_tools/commit/6f4f63f22f3f7278e20c6aba9e5ef6a021ae03b2)) + +* Manufacturing capacity report (#57) + +* ci: update app in str(file path) code to more exact matching + +* chore: remove unused code + +* feat: add manufacturing capacity report + +* tests: start manufacturing capacity report tests + +* chore: uncomment formatting code + +* docs: add manufacturing capacity report documentation + +* fix: div by zero in parts can build calc + +* docs: update for calculation differences + +* fix: in stock qty to zero if none from query ([`2936cde`](https://github.com/agritheory/inventory_tools/commit/2936cde9aac6583edeb383d94d74142461cf243a)) + + +## v14.3.0 (2024-03-01) + +### Feature + +* feat: Alternative workstation in job card and operation (#56) + +* feat: New field and link filters to select alternative workstation + +* fix: add alternativ workstation in fixture + +* fix:change alternative workstation in fixture + +* change to error key function + +* fix: set validation on workstation if not operation + +* changes field name in testcase + +* fix: change a class base function to saperate function + +* added a searchfield in query + +* added a searchfield in query + +* added a searchfield in query + +* added a searchfield in query + +* added a searchfield in query + +* feat: validation to not allow default workstation in alternative + +* added comment on function ([`0788d3c`](https://github.com/agritheory/inventory_tools/commit/0788d3caa67ad7820a5a3cdfdc950c85bd6f0cd7)) + +### Unknown + +* Prompt Material Transfer upon Completion of Manufacturing Stock Entry (#51) + +* feat: wip stock entry next action + +* feat: improve message + +* fix: on_submit hook + +* fix: parameter hardcoded + +* feat: change wording ([`d1e126b`](https://github.com/agritheory/inventory_tools/commit/d1e126b8dbaf09370f6d4c173e24af33ac994bd7)) + + +## v14.2.0 (2024-02-01) + +### Feature + +* feat: manufacturing over/under production + +* feat: WIP Manufacturing Over/Under Production + +* feat: WIP Manufacturing Over/Under Production + +* feat: WIP Manufacturing Over/Under Production + +* fix: unused import + +* feat: override onload for work order + +* feat: oveeride get_pending_raw_materials + +* fix: allowed_qty in job card + +* fix: indentation + +* fix: import + +* fixes + +* wip: tests + +* feat: test_get_allowance_percentage + +* feat: test test_validate_finished_goods + +* fix: validate_job_card and test + +* fix: test ([`baeb469`](https://github.com/agritheory/inventory_tools/commit/baeb4690d42429a062595eb9058ddd2770b190c9)) + +### Unknown + +* Make Creation of Job Card(s) on Submit of Work Order configurable (#49) + +* feat: configurable creation of job card + +* feat: configurable creation of job card ([`5bf9486`](https://github.com/agritheory/inventory_tools/commit/5bf94860c58d81faccb1106828f16121c081871a)) + + +## v14.1.3 (2024-01-04) + +### Fix + +* fix: validate customizations (#35) + +* fix: validate customizations + +* fix: only install inventory tools customizations ([`e1d86a0`](https://github.com/agritheory/inventory_tools/commit/e1d86a0739e56f3b987b599c275644ee5c29fc0a)) + + +## v14.1.2 (2023-09-12) + +### Chore + +* chore: remove console.log ([`f82b590`](https://github.com/agritheory/inventory_tools/commit/f82b590d061608818170092f07e3da8d9153b756)) + +### Ci + +* ci: update release action user and email (#32) ([`4264bdd`](https://github.com/agritheory/inventory_tools/commit/4264bdde25c0bff1390923e7f18fce7c353844db)) + +### Fix + +* fix: make uom enforcement respond better to toggle on/off ([`3734cb4`](https://github.com/agritheory/inventory_tools/commit/3734cb42e880168bb4c4a71c67820da63857e0bf)) + +### Unknown + +* Merge pull request #33 from agritheory/uom_enforcement_fix + +fix: make uom enforcement respond better to toggle on/off ([`13ad883`](https://github.com/agritheory/inventory_tools/commit/13ad8832a346799563e0f7ae2f1b20c457d10d5c)) + + +## v14.1.1 (2023-08-30) + +### Fix + +* fix: add is_subcontracted check for additional validation/submit/cancel code ([`8182dac`](https://github.com/agritheory/inventory_tools/commit/8182dacfd35d2a2ee4c5ee94211cbfc7ac00abfd)) + +### Unknown + +* Merge pull request #30 from agritheory/fix_subc_validation + +fix: add is_subcontracted check for additional validation/submit/cancel code ([`c4e7b83`](https://github.com/agritheory/inventory_tools/commit/c4e7b83fd1700d08e8f711e10f1a77c81da0de32)) + + +## v14.1.0 (2023-08-24) + +### Chore + +* chore: update test data for erpnext codebase changes (#24) ([`b7dfc02`](https://github.com/agritheory/inventory_tools/commit/b7dfc02e228c4ebdca86000dac65d1a903640b15)) + +### Ci + +* ci: update remote name ([`021b8a4`](https://github.com/agritheory/inventory_tools/commit/021b8a47355cb9028bac7ac8e60d224247d5b0e8)) + +* ci: update version number ([`9d82150`](https://github.com/agritheory/inventory_tools/commit/9d821506af9d6eb72e3bfb1345ac652aa86896dc)) + +* ci: add python semantic release ([`703803d`](https://github.com/agritheory/inventory_tools/commit/703803df7949fb0cb43699482425ec31595dd1b9)) + +### Documentation + +* docs: update material demand section for expanded functionality ([`1af88f6`](https://github.com/agritheory/inventory_tools/commit/1af88f6d06fdc8e6908381f9dd9011fe470dd9d7)) + +### Feature + +* feat: select email template ([`02196e3`](https://github.com/agritheory/inventory_tools/commit/02196e37c7234982c5dafda9e814117d374565a9)) + +### Fix + +* fix: blank email template for PO; skip supplier-only rows for RFQ ([`2a0e8cd`](https://github.com/agritheory/inventory_tools/commit/2a0e8cd6c6aefdd1a0d7b978652f97ddc755523f)) + +* fix: fix JS after adding draft PO column (#26) ([`e0d3229`](https://github.com/agritheory/inventory_tools/commit/e0d322950e87dcf09f13441859e273a0e44a1192)) + +### Unknown + +* Merge pull request #23 from agritheory/issue_19 + +Allow Creation of RFQ from Material Demand report ([`8a0e350`](https://github.com/agritheory/inventory_tools/commit/8a0e35024968ce5a0f6da05e71f62c091bf2b38f)) + +* Work Order Subcontracting (#13) + +* tests: update test data for additional manufacturing workflow + +* feat: work order subcontracting validations + +* tests: add valuation rate for subcontracted item + +* feat: start wo subcontracting feat + +* feat: make subcontracting section visible by check and settings + +* feat: add ste detail paid field and new pi cols + +* feat: setup hooks and custom po + +* tests: update data for default supplier and price lists + +* feat: include doctype js + +* feat: add work order customizations + +* feat: add purchase order customizations + +* feat: update purchase invoice customizations + +* feat: remove unused code blocks + +* fix: add module to json + +* fix: update custom doc path + +* feat: consolidate custom PI code and modularize class functions + +* feat: combine and refactor PO code + +* feat: update server function paths + +* feat: add BOM field default + +* feat: update to use BOM field vs Item for is_subcontracted + +* feat: code cleanup and refactoring for BOM field + +* feat: update for uom conversion and new svc item + +* fix: move UOM conversions to item + +* add todos in JS + +* feat: rewire item adjustments for conversion factor + +* wip: integrate with production plan + +* feat: add supplier field in WO, allow selection of supplier in dialog + +* chore: add comment explaining precision code + +* wip: subcontractor workflow + +* feat: subcontracting workflow with correct warehouses + +* feat: show/hide subcontracting columns + +* feat: colorize fetach stock entries button + +* fix: text artifacts, new PO errors + +* feat: fetch supplier warehouse, added connectiosn to PI and PO from WO + +* feat: add filters and looks to both PI and PO + +* fix: monkey patch validate_item_details + +* feat: remove buttons and update stockfield in WO subc workflow + +* Enforce UOMs to those that exist in the Item's conversion detail (#27) + +* wip: uom restricted query + +* feat: refactor UOM enforcement validation to be hookable + +* docs: add docs for UOM enforcement + +* tests: fix test logger problem, add xfail uom test + +* Warehouse path (#25) + +* wip: warehouse path + +* wip: warehouse path + +* wip: warehouse path feature + +* feat: warehouse path builder + +* feat: undo query when not configured; setup tweaks + +* chore: update test data for erpnext codebase changes (#24) + +* wip: warehouse path feature + +* wip: test setup + +* chore: update yarn + +* tests: trying to defaeat logger problem + +* test: fix conftest logger issue + +* docs: add docs for warehouse path + +* chore: union types for whitelisted function + +--------- + +Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> + +* tests: test cadence (#28) + +* fix: no cancelled PO in se query, code clean up + +* chore: add comment to explain monkey patch rationale + +--------- + +Co-authored-by: Tyler Matteson <tyler@agritheory.com> ([`ac11c1d`](https://github.com/agritheory/inventory_tools/commit/ac11c1df4daad2189916ca9841480aa1796e42e3)) + +* Merge branch 'version-14' into issue_19 ([`ee0a27f`](https://github.com/agritheory/inventory_tools/commit/ee0a27f0f03a8e3c9e9fa54011d02e44554b8bbb)) + +* Documentation (#29) + +* docs: add index page + +* docs: add screen shots and workflow + +* docs: add screen shots, text edits + +* docs: add example data page + +* docs: add placeholder pages + +* docs: add subcontracting via WO section + +* docs: edits, conform text conventions ([`2fc980d`](https://github.com/agritheory/inventory_tools/commit/2fc980d41105759cffa33e610b779b6d464cf24c)) + +* tests: test cadence (#28) ([`6b5bd47`](https://github.com/agritheory/inventory_tools/commit/6b5bd47089fb2f8a09159635e618425743cc9dff)) + +* Warehouse path (#25) + +* wip: warehouse path + +* wip: warehouse path + +* wip: warehouse path feature + +* feat: warehouse path builder + +* feat: undo query when not configured; setup tweaks + +* chore: update test data for erpnext codebase changes (#24) + +* wip: warehouse path feature + +* wip: test setup + +* chore: update yarn + +* tests: trying to defaeat logger problem + +* test: fix conftest logger issue + +* docs: add docs for warehouse path + +* chore: union types for whitelisted function + +--------- + +Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`370dd6f`](https://github.com/agritheory/inventory_tools/commit/370dd6f9789156ebcbbe7b111d891a74731a477b)) + +* Enforce UOMs to those that exist in the Item's conversion detail (#27) + +* wip: uom restricted query + +* feat: refactor UOM enforcement validation to be hookable + +* docs: add docs for UOM enforcement + +* tests: fix test logger problem, add xfail uom test ([`d4c145a`](https://github.com/agritheory/inventory_tools/commit/d4c145a94d8402fa441619289b4cc6438b7d5c45)) + +* Documentation (#29) + +* docs: add index page + +* docs: add screen shots and workflow + +* docs: add screen shots, text edits + +* docs: add example data page + +* docs: add placeholder pages + +* docs: add subcontracting via WO section + +* docs: edits, conform text conventions ([`198e110`](https://github.com/agritheory/inventory_tools/commit/198e110ab0c5461b9c46925fc05af351151abd38)) + +* tests: test cadence (#28) ([`b91e024`](https://github.com/agritheory/inventory_tools/commit/b91e0246f6e384f40685af40d3a249daa9d03a8c)) + +* Warehouse path (#25) + +* wip: warehouse path + +* wip: warehouse path + +* wip: warehouse path feature + +* feat: warehouse path builder + +* feat: undo query when not configured; setup tweaks + +* chore: update test data for erpnext codebase changes (#24) + +* wip: warehouse path feature + +* wip: test setup + +* chore: update yarn + +* tests: trying to defaeat logger problem + +* test: fix conftest logger issue + +* docs: add docs for warehouse path + +* chore: union types for whitelisted function + +--------- + +Co-authored-by: Heather Kusmierz <heather.kusmierz@gmail.com> ([`e3fb9c7`](https://github.com/agritheory/inventory_tools/commit/e3fb9c7f2a8ed3c42f4cb47078086bcdd60f91c7)) + +* Enforce UOMs to those that exist in the Item's conversion detail (#27) + +* wip: uom restricted query + +* feat: refactor UOM enforcement validation to be hookable + +* docs: add docs for UOM enforcement + +* tests: fix test logger problem, add xfail uom test ([`65d42e1`](https://github.com/agritheory/inventory_tools/commit/65d42e126d81f3ed1b98178f7b6c68c6a070986e)) + + +## v14.0.1 (2023-08-10) + +### Chore + +* chore: update test data for erpnext codebase changes (#24) ([`48160aa`](https://github.com/agritheory/inventory_tools/commit/48160aa27f5d0deb3be5e6e55f16d35fd92ae086)) + +### Ci + +* ci: update remote name ([`218eb06`](https://github.com/agritheory/inventory_tools/commit/218eb06a6a4a2ac3f5b9ee214a130e2c8ba30d29)) + +* ci: update version number ([`6e0c194`](https://github.com/agritheory/inventory_tools/commit/6e0c194235a19011b0c7db5adb8ed7e5954ba5eb)) + +* ci: add python semantic release ([`3382787`](https://github.com/agritheory/inventory_tools/commit/3382787d4726d7483a7243eafff03eb775d0ac3e)) + +### Feature + +* feat: based on item option ([`cc90229`](https://github.com/agritheory/inventory_tools/commit/cc90229b9ccf27cb5a872b24109dc7071f146802)) + +* feat: wip, make rfqs ([`c5a8867`](https://github.com/agritheory/inventory_tools/commit/c5a88673c3d57480a10ec68091e04a75488e18cb)) + +* feat: wip material demand options ([`bedd3d4`](https://github.com/agritheory/inventory_tools/commit/bedd3d42f299c73c1c1fa80c3a4928316d1428a2)) + +* feat: requires_rfq custom field, creation options in report ([`cd4ec42`](https://github.com/agritheory/inventory_tools/commit/cd4ec42e15fdbfa2cc24dc41b16562e74daf6124)) + +### Fix + +* fix: fix JS after adding draft PO column (#26) ([`42aefa9`](https://github.com/agritheory/inventory_tools/commit/42aefa9abab60962f923870a151d7bacbceba141)) + +### Unknown + +* Merge pull request #22 from agritheory/ci_fix + +ci: update remote name ([`946657b`](https://github.com/agritheory/inventory_tools/commit/946657b179d2a8ba6934c2df7ca5d83f9bb04f29)) + +* Merge pull request #21 from agritheory/py_sem_rel_14 + +ci: add python semantic release ([`13b41fa`](https://github.com/agritheory/inventory_tools/commit/13b41fad052f9c45e017b6fbac6897bdf38b7883)) + + +## v14.0.0 (2023-07-21) + +### Documentation + +* docs: wip material demand docs ([`6116a1b`](https://github.com/agritheory/inventory_tools/commit/6116a1b30b77565ff0470af32f8f59aa7a949786)) + +### Feature + +* feat: add column for draft PO amount ([`59d837b`](https://github.com/agritheory/inventory_tools/commit/59d837b1d3dbf4798d08618d033f732cad76cf1f)) + +* feat: create inventory tools settings when company is created ([`0121499`](https://github.com/agritheory/inventory_tools/commit/0121499bd99fa9ff5126b7432dd1d9a1d2816dd4)) + +* feat: create inventory tools settings when company is created ([`edff215`](https://github.com/agritheory/inventory_tools/commit/edff215f58ce8163da37436af893e1395164520c)) + +* feat: material demand PO creation ([`794f735`](https://github.com/agritheory/inventory_tools/commit/794f7352b27eb000ca96ece71c8081b69f519751)) + +* feat: add setting doctype ([`25a75de`](https://github.com/agritheory/inventory_tools/commit/25a75de2f1415ed78634d45c255438f1ed7a3ad1)) + +* feat: Initialize App ([`9e932fe`](https://github.com/agritheory/inventory_tools/commit/9e932fe49e70d5f2507d5900839c32f063a20898)) + +### Fix + +* fix: purchase order custom filed missing, carry price list from report to PO ([`4fe5ac8`](https://github.com/agritheory/inventory_tools/commit/4fe5ac84f52d656db3017af5e5a2a08111b2a729)) + +* fix: add back price list filter, fix schema ([`ceda857`](https://github.com/agritheory/inventory_tools/commit/ceda85797cc62a2d0e70e3a1135a88492f5c2cc0)) + +* fix: rebased v14 conflicts ([`b1614cb`](https://github.com/agritheory/inventory_tools/commit/b1614cb28a3f57d31c2056bf4e4506fd94a6e997)) + +* fix: supplier level de-selection and filtering ([`94140d0`](https://github.com/agritheory/inventory_tools/commit/94140d042a2e3fec79a56f0cacb86725dc2539b7)) + +* fix: module import name ([`ae290f8`](https://github.com/agritheory/inventory_tools/commit/ae290f8a392d9c5b3ea941244e68c8f1099728ef)) + +### Unknown + +* Merge pull request #20 from agritheory/material_demand + +Material Demand report fixes ([`7eb3c87`](https://github.com/agritheory/inventory_tools/commit/7eb3c877932a4d072ff56af23ad77958aba2fc36)) + +* Merge pull request #15 from agritheory/material_demand + +Material Demand ([`486fde6`](https://github.com/agritheory/inventory_tools/commit/486fde69cfdfa8810d93445ef4b32eb0753e799c)) + +* Merge branch 'version-14' into material_demand ([`9275dbf`](https://github.com/agritheory/inventory_tools/commit/9275dbf04f30a7391a0c003fff77cc5b105bd4cb)) + +* wip: material demand report improvements ([`d167804`](https://github.com/agritheory/inventory_tools/commit/d167804e90e10fc8299a49f6f0b90a512e93b5a3)) + +* wip: material demand ([`067b0d7`](https://github.com/agritheory/inventory_tools/commit/067b0d71dc372c35ac988556b20d13b9ab95f009)) + +* Merge pull request #16 from agritheory/settings_hook + +feat: create inventory tools settings when company is created ([`acc2b9c`](https://github.com/agritheory/inventory_tools/commit/acc2b9c640e1cb9eadf2e96060518a323ce34990)) + +* wip: material demand + +selection helpers look good except for supplier-level deselect, which toggles everything backwards ([`de7e09c`](https://github.com/agritheory/inventory_tools/commit/de7e09c4e04456fcd5d23fdbfa30c3a8a1933c28)) + +* wip: warehouse path ([`3ef9d3e`](https://github.com/agritheory/inventory_tools/commit/3ef9d3e7a30a98339f7d09c5ecba2e666e877b49)) + +* wip: material demand report improvements ([`c20379a`](https://github.com/agritheory/inventory_tools/commit/c20379aae9659fc1280f3c884bc888cce765116d)) + +* wip: material demand ([`3f50d5f`](https://github.com/agritheory/inventory_tools/commit/3f50d5f96b5b0a57993320e4f3c56034a490cd9a)) + +* wip: material demand ([`0cb8694`](https://github.com/agritheory/inventory_tools/commit/0cb869487fc576470f5ddb7f1b8f3b23f4cc1a57)) + +* Merge pull request #7 from agritheory/settings_doctype + +feat: add setting doctype ([`5285a99`](https://github.com/agritheory/inventory_tools/commit/5285a9967fc3615f16c7d4b06ac6f55589966975)) + +* Merge pull request #6 from agritheory/test_data_fixes + +fix: module import name ([`6edf7fb`](https://github.com/agritheory/inventory_tools/commit/6edf7fb0127648c90b0e3c203473681b8b964b63)) + +* initial commit ([`a09e1ed`](https://github.com/agritheory/inventory_tools/commit/a09e1ed6724ea49e39d5e208e6031283bf282f97)) + diff --git a/README.md b/README.md index db2a2f3..7d26232 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ + + ## Inventory Tools Inventory Tools for ERPNext diff --git a/inventory_tools/__init__.py b/inventory_tools/__init__.py index 4338b10..cb714b5 100644 --- a/inventory_tools/__init__.py +++ b/inventory_tools/__init__.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + __version__ = "15.1.0" diff --git a/inventory_tools/customize.py b/inventory_tools/customize.py index 8a435be..25a6f64 100644 --- a/inventory_tools/customize.py +++ b/inventory_tools/customize.py @@ -1,44 +1,47 @@ -import json -from pathlib import Path - -import frappe - - -def load_customizations(): - customizations_directory = ( - Path().cwd().parent / "apps" / "check_run" / "check_run" / "check_run" / "custom" - ) - files = list(customizations_directory.glob("**/*.json")) - for file in files: - customizations = json.loads(Path(file).read_text()) - for field in customizations.get("custom_fields"): - if field.get("module") != "Inventory Tools": - continue - existing_field = frappe.get_value("Custom Field", field.get("name")) - custom_field = ( - frappe.get_doc("Custom Field", field.get("name")) - if existing_field - else frappe.new_doc("Custom Field") - ) - field.pop("modified") - {custom_field.set(key, value) for key, value in field.items()} - custom_field.flags.ignore_permissions = True - custom_field.flags.ignore_version = True - custom_field.save() - for prop in customizations.get("property_setters"): - if prop.get("module") != "Inventory Tools": - continue - property_setter = frappe.get_doc( - { - "name": prop.get("name"), - "doctype": "Property Setter", - "doctype_or_field": prop.get("doctype_or_field"), - "doc_type": prop.get("doc_type"), - "field_name": prop.get("field_name"), - "property": prop.get("property"), - "value": prop.get("value"), - "property_type": prop.get("property_type"), - } - ) - property_setter.flags.ignore_permissions = True - property_setter.insert() +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import json +from pathlib import Path + +import frappe + + +def load_customizations(): + customizations_directory = ( + Path().cwd().parent / "apps" / "check_run" / "check_run" / "check_run" / "custom" + ) + files = list(customizations_directory.glob("**/*.json")) + for file in files: + customizations = json.loads(Path(file).read_text()) + for field in customizations.get("custom_fields"): + if field.get("module") != "Inventory Tools": + continue + existing_field = frappe.get_value("Custom Field", field.get("name")) + custom_field = ( + frappe.get_doc("Custom Field", field.get("name")) + if existing_field + else frappe.new_doc("Custom Field") + ) + field.pop("modified") + {custom_field.set(key, value) for key, value in field.items()} + custom_field.flags.ignore_permissions = True + custom_field.flags.ignore_version = True + custom_field.save() + for prop in customizations.get("property_setters"): + if prop.get("module") != "Inventory Tools": + continue + property_setter = frappe.get_doc( + { + "name": prop.get("name"), + "doctype": "Property Setter", + "doctype_or_field": prop.get("doctype_or_field"), + "doc_type": prop.get("doc_type"), + "field_name": prop.get("field_name"), + "property": prop.get("property"), + "value": prop.get("value"), + "property_type": prop.get("property_type"), + } + ) + property_setter.flags.ignore_permissions = True + property_setter.insert() diff --git a/inventory_tools/docs/exampledata.md b/inventory_tools/docs/exampledata.md index 158eef5..fb25f84 100644 --- a/inventory_tools/docs/exampledata.md +++ b/inventory_tools/docs/exampledata.md @@ -1,3 +1,6 @@ + + # Using the Example Data to Experiment with Inventory Tools The Inventory Tools application comes with a `setup.py` script that is completely optional to use. If you execute the script, it populates an ERPNext site with demo business data for a fictitious company called Ambrosia Pie Company. The data enable you to experiment and test the Inventory Tools application's functionality before installing the app into your ERPNext site. diff --git a/inventory_tools/docs/index.md b/inventory_tools/docs/index.md index 01a0a61..1dd9280 100644 --- a/inventory_tools/docs/index.md +++ b/inventory_tools/docs/index.md @@ -1,20 +1,23 @@ -# Inventory Tools Documentation - -The Inventory Tools application enhances and extends inventory-related functionality and workflows in ERPNext. It includes the following features: - -- **[Material Demand](./material_demand.md)**: a report-based interface to aggregate required Items across multiple sources, then optionally create Purchase Orders or Request for Quotations -- **[UOM Enforcement](./uom_enforcement.md)**: for doctypes that have an Items table or Unit of Measure (UOM) fields, this feature restricts the user's options from arbitrary selections to only UOMs defined in the Item master with a specified conversion factor -- **[Warehouse Path](./warehouse_path.md)**: for any warehouse selection field, this features helps clearly identify warehouses by creating a warehouse path and adding a human-readable string under the warehouse name in the format "parent warehouse(s)->warehouse" -- **[Subcontracting Workflow via Work Order](./wo_subcontracting.md)**: an alternative to ERPNext's subcontracting workflow that enables a user to employ Work Orders, subcontracting Purchase Orders, and manufacturing Stock Entries in lieu of Purchase Receipts or Subcontracting Orders/Receipts. Enhancements to the subcontracting Purchase Invoice allow a user to quickly reconcile what Items have been received with what is being invoiced -- **[Inline Landed Costing](./landed_costing.md)**: Coming soon! This features enables a user to include any additional costs to be capitalized into an Item's valuation directly in a Purchase Receipt or Purchase Invoice without needing to create a separate Landed Cost Voucher -- **[Manufacturing Capacity](./manufacturing_capacity.md)**: a report-based interface to show, for a given BOM, the entire hierarchy of any BOM tree containing that BOM with demand and in-stock quantities for all levels - -## Configuration -Any feature in Inventory Tools may be toggled on or off via the Inventory Tools Settings document. The only exception to this is the Material Demand report, which is generally available upon installation of the app. There may be one settings document for each company in ERPNext to enable features on a per-company basis. Follow the links above for further details around feature-specific configuration. - -![Screen shot of ](./assets/settings.png) - -## Installation -Full [installation instructions](https://github.com/agritheory/inventory_tools) can be found on the application's repository. - -Note that the application includes a script to install example data to experiment and test the app's features. See the [Using the Example Data to Experiment with Inventory Tools page](./exampledata.md) for more details. + + +# Inventory Tools Documentation + +The Inventory Tools application enhances and extends inventory-related functionality and workflows in ERPNext. It includes the following features: + +- **[Material Demand](./material_demand.md)**: a report-based interface to aggregate required Items across multiple sources, then optionally create Purchase Orders or Request for Quotations +- **[UOM Enforcement](./uom_enforcement.md)**: for doctypes that have an Items table or Unit of Measure (UOM) fields, this feature restricts the user's options from arbitrary selections to only UOMs defined in the Item master with a specified conversion factor +- **[Warehouse Path](./warehouse_path.md)**: for any warehouse selection field, this features helps clearly identify warehouses by creating a warehouse path and adding a human-readable string under the warehouse name in the format "parent warehouse(s)->warehouse" +- **[Subcontracting Workflow via Work Order](./wo_subcontracting.md)**: an alternative to ERPNext's subcontracting workflow that enables a user to employ Work Orders, subcontracting Purchase Orders, and manufacturing Stock Entries in lieu of Purchase Receipts or Subcontracting Orders/Receipts. Enhancements to the subcontracting Purchase Invoice allow a user to quickly reconcile what Items have been received with what is being invoiced +- **[Inline Landed Costing](./landed_costing.md)**: Coming soon! This features enables a user to include any additional costs to be capitalized into an Item's valuation directly in a Purchase Receipt or Purchase Invoice without needing to create a separate Landed Cost Voucher +- **[Manufacturing Capacity](./manufacturing_capacity.md)**: a report-based interface to show, for a given BOM, the entire hierarchy of any BOM tree containing that BOM with demand and in-stock quantities for all levels + +## Configuration +Any feature in Inventory Tools may be toggled on or off via the Inventory Tools Settings document. The only exception to this is the Material Demand report, which is generally available upon installation of the app. There may be one settings document for each company in ERPNext to enable features on a per-company basis. Follow the links above for further details around feature-specific configuration. + +![Screen shot of ](./assets/settings.png) + +## Installation +Full [installation instructions](https://github.com/agritheory/inventory_tools) can be found on the application's repository. + +Note that the application includes a script to install example data to experiment and test the app's features. See the [Using the Example Data to Experiment with Inventory Tools page](./exampledata.md) for more details. diff --git a/inventory_tools/docs/landed_costing.md b/inventory_tools/docs/landed_costing.md index 99f316f..66b9679 100644 --- a/inventory_tools/docs/landed_costing.md +++ b/inventory_tools/docs/landed_costing.md @@ -1,3 +1,6 @@ + + # Inline Landed Costing Coming soon! diff --git a/inventory_tools/docs/manufacturing_capacity.md b/inventory_tools/docs/manufacturing_capacity.md index 841a2d3..a22ea55 100644 --- a/inventory_tools/docs/manufacturing_capacity.md +++ b/inventory_tools/docs/manufacturing_capacity.md @@ -1,3 +1,6 @@ + + # Manufacturing Capacity Report Manufacturing Capacity is a report-based interface that, given a BOM and Warehouse, displays the demand and in-stock quantities for the entire hierarchy of any BOM tree containing that BOM. diff --git a/inventory_tools/docs/material_demand.md b/inventory_tools/docs/material_demand.md index ecf8424..d09bb7c 100644 --- a/inventory_tools/docs/material_demand.md +++ b/inventory_tools/docs/material_demand.md @@ -1,73 +1,76 @@ -# Material Demand - -Material Demand is a report-based interface that allows you to aggregate required Items across multiple Material Requests, Suppliers, and requesting Companies. From there, you can create draft Purchase Orders (PO), draft Request for Quotations (RFQ), or a combination of the two based on the Item's configuration. - -![Screen shot of the Material Demand report showing rows of Items grouped by supplier with columns for the Supplier, Material Request document ID, Required By date, Item, MR Qty, Draft POs, Total Selected, UOM, Price, and Selected Amount](./assets/md_report_view.png) - -The right-hand side of the report has selection boxes to indicate which rows of Items to include to create the documents. Ticking the top-level supplier box will automatically check all the Items for that supplier. - -![Screen shot of a Material Demand Report with the boxes next to supplier Chelsea Fruit Co's Items all checked](./assets/md_selection.png) - -Once you're satisfied with your selections, clicking the Create button will give you three options to generate draft documents: - -1. Create PO(s) will create a Purchase Order for each supplier selected. If there is more than one company requesting materials from the same supplier, it marks the PO as a Multi-Company Purchase Order -2. Create RFQ(s) will create a Request for Quotation for each supplier-item combination -3. Create based on Item will create RFQs and/or POs depending on how the Item's supplier list is configured (in the Item master) - -All generated documents remain in draft status to allow you to make edits as needed before submitting them. - -### Create Purchase Orders -If you select the Create PO(s) option, a dialog window will appear to select the Company if it hasn't already been supplied in the filter section. - -![Screen shot of the dialog window to enter the Company for the Purchase Orders](./assets/md_po_dialog.png) - -You can find the new documents in the Purchase Order listview. - -![Screen shot of the Purchase Order listview showing the new draft Purchase Order for Chelsea Fruit Co](./assets/md_purchase_order.png) - -After generating the draft Purchase Orders, the Material Demand report updates to display the quantity ordered in the Draft PO column. Note that after you submit the Purchase Orders, the Items rows no longer show in the report. - -![Screen shot of the Material Demand report where the Draft POs column shows the quantity ordered for the Chelsea Fruit Co Items that were selected to be in the Purchase Order](./assets/md_draft_po_qty.png) - -### Create Request for Quotation -![Screen shot of Material Demand report with Parchment Paper, Pie Box, and Pie Tin Items selected for both the Freedom Provisions and Unity Bakery Supply suppliers. The Create RFQ(s) selection is highlighted in the Create button dropdown](./assets/md_create_rfq.png) - -If you select the Create RFQ(s) option, a dialog window will appear to select the Company and Email Template. - -![Screen shot of the dialog window to enter the Company and Email Template for the RFQs](./assets/md_rfq_dialog.png) - -You can find the new documents in the Request for Quotation listview and make edits as needed before submitting them. - -![Screen shot of a Request for Quotation document with two suppliers (Freedom Provisions and Unity Bakery Supply) and Parchment Paper, Pie Box, and Pie Tin listed in the Items table](./assets/md_rfq.png) - -### Create Based on Item -The final option Create based on Item will create Purchase Orders and/or RFQs depending on how each Item's supplier list is configured in the Item master. - -For a given Item, go to the Supplier Items table (found in the Purchasing tab's Supplier Details section) and click Edit for a Suppler. If you check the Requires RFQ? box, the Material Demand report will create an RFQ for that Item. If the box is left unchecked, the report generates a Purchase Order. - -![Screen shot of the Edit Supplier form for the Supplier Items table for the Pie Tin Item. The Supplier Freedom Provisions has the Requires RFQ? box selected](./assets/md_supplier_item_rfq.png) - -The selection process works the same as the other options. - -![Screen shot of the Material Demand report with Pie Tin, Salt, and Sugar Items selected for the Freedom Provisions Supplier and the Create based on Item option highlighted in the Create button dropdown](./assets/md_based_on_item.png) - -The report displays a banner to notify you of how many of each document were created. - -![Screen shot of a banner that says 1 Purchase Orders created 1 Request For Quotation created](./assets/md_item_based_banner.png) - -The Items in each document will correspond to the Item Supplier configuration. Since the Pie Tin Item is the only one that required an RFQ, it's included in the new RFQ, whereas the other Items are in the new Purchase Order. - -![Screen shot of the draft RFQ for Freedom Provisions with Pie Tin in the Items table](./assets/md_item_based_rfq.png) - -![Screen shot of the draft PO for Freedom Provisions with Salt and Sugar in the Items table](./assets/md_item_based_po.png) - -## Configuration -The Material Demand report is available on installation of the Inventory Tools application, but there are configuration options in Inventory Tools Settings to modify its behavior. - -![Screen shot of the two relevant fields (Purchase Order Aggregation Company and Aggregated Purchasing Warehouse) to configure the Material Demand report](./assets/md_settings_detail.png) - -When the Material Demand report generates Purchase Orders, it fills the PO Company field with the company specified in the filter, or if that's blank, the one provided in the dialog window. To retain this default behavior, leave the Purchase Order Aggregation Company field in Inventory Tools Settings blank. However, if you populate this field, the report will use its value in the Purchase Order's Company field instead. In either case, if there's more than one company requesting materials from the same supplier, the report will select the Multi-Company Purchase Order box for that supplier's PO. - -The Aggregated Purchasing Warehouse field has a similar impact on the report's behavior. By default, the field is blank and the Material Demand report applies the warehouses set per Item in the Material Request as the Item's warehouse in the new Purchase Order. If you set a value in this field, the report will instead use the specified warehouse for each Item in the Purchase Order. - -See the Create Based on Item section for instructions on how to configure specific Item-Supplier combinations to require an RFQ. + + +# Material Demand + +Material Demand is a report-based interface that allows you to aggregate required Items across multiple Material Requests, Suppliers, and requesting Companies. From there, you can create draft Purchase Orders (PO), draft Request for Quotations (RFQ), or a combination of the two based on the Item's configuration. + +![Screen shot of the Material Demand report showing rows of Items grouped by supplier with columns for the Supplier, Material Request document ID, Required By date, Item, MR Qty, Draft POs, Total Selected, UOM, Price, and Selected Amount](./assets/md_report_view.png) + +The right-hand side of the report has selection boxes to indicate which rows of Items to include to create the documents. Ticking the top-level supplier box will automatically check all the Items for that supplier. + +![Screen shot of a Material Demand Report with the boxes next to supplier Chelsea Fruit Co's Items all checked](./assets/md_selection.png) + +Once you're satisfied with your selections, clicking the Create button will give you three options to generate draft documents: + +1. Create PO(s) will create a Purchase Order for each supplier selected. If there is more than one company requesting materials from the same supplier, it marks the PO as a Multi-Company Purchase Order +2. Create RFQ(s) will create a Request for Quotation for each supplier-item combination +3. Create based on Item will create RFQs and/or POs depending on how the Item's supplier list is configured (in the Item master) + +All generated documents remain in draft status to allow you to make edits as needed before submitting them. + +### Create Purchase Orders +If you select the Create PO(s) option, a dialog window will appear to select the Company if it hasn't already been supplied in the filter section. + +![Screen shot of the dialog window to enter the Company for the Purchase Orders](./assets/md_po_dialog.png) + +You can find the new documents in the Purchase Order listview. + +![Screen shot of the Purchase Order listview showing the new draft Purchase Order for Chelsea Fruit Co](./assets/md_purchase_order.png) + +After generating the draft Purchase Orders, the Material Demand report updates to display the quantity ordered in the Draft PO column. Note that after you submit the Purchase Orders, the Items rows no longer show in the report. + +![Screen shot of the Material Demand report where the Draft POs column shows the quantity ordered for the Chelsea Fruit Co Items that were selected to be in the Purchase Order](./assets/md_draft_po_qty.png) + +### Create Request for Quotation +![Screen shot of Material Demand report with Parchment Paper, Pie Box, and Pie Tin Items selected for both the Freedom Provisions and Unity Bakery Supply suppliers. The Create RFQ(s) selection is highlighted in the Create button dropdown](./assets/md_create_rfq.png) + +If you select the Create RFQ(s) option, a dialog window will appear to select the Company and Email Template. + +![Screen shot of the dialog window to enter the Company and Email Template for the RFQs](./assets/md_rfq_dialog.png) + +You can find the new documents in the Request for Quotation listview and make edits as needed before submitting them. + +![Screen shot of a Request for Quotation document with two suppliers (Freedom Provisions and Unity Bakery Supply) and Parchment Paper, Pie Box, and Pie Tin listed in the Items table](./assets/md_rfq.png) + +### Create Based on Item +The final option Create based on Item will create Purchase Orders and/or RFQs depending on how each Item's supplier list is configured in the Item master. + +For a given Item, go to the Supplier Items table (found in the Purchasing tab's Supplier Details section) and click Edit for a Suppler. If you check the Requires RFQ? box, the Material Demand report will create an RFQ for that Item. If the box is left unchecked, the report generates a Purchase Order. + +![Screen shot of the Edit Supplier form for the Supplier Items table for the Pie Tin Item. The Supplier Freedom Provisions has the Requires RFQ? box selected](./assets/md_supplier_item_rfq.png) + +The selection process works the same as the other options. + +![Screen shot of the Material Demand report with Pie Tin, Salt, and Sugar Items selected for the Freedom Provisions Supplier and the Create based on Item option highlighted in the Create button dropdown](./assets/md_based_on_item.png) + +The report displays a banner to notify you of how many of each document were created. + +![Screen shot of a banner that says 1 Purchase Orders created 1 Request For Quotation created](./assets/md_item_based_banner.png) + +The Items in each document will correspond to the Item Supplier configuration. Since the Pie Tin Item is the only one that required an RFQ, it's included in the new RFQ, whereas the other Items are in the new Purchase Order. + +![Screen shot of the draft RFQ for Freedom Provisions with Pie Tin in the Items table](./assets/md_item_based_rfq.png) + +![Screen shot of the draft PO for Freedom Provisions with Salt and Sugar in the Items table](./assets/md_item_based_po.png) + +## Configuration +The Material Demand report is available on installation of the Inventory Tools application, but there are configuration options in Inventory Tools Settings to modify its behavior. + +![Screen shot of the two relevant fields (Purchase Order Aggregation Company and Aggregated Purchasing Warehouse) to configure the Material Demand report](./assets/md_settings_detail.png) + +When the Material Demand report generates Purchase Orders, it fills the PO Company field with the company specified in the filter, or if that's blank, the one provided in the dialog window. To retain this default behavior, leave the Purchase Order Aggregation Company field in Inventory Tools Settings blank. However, if you populate this field, the report will use its value in the Purchase Order's Company field instead. In either case, if there's more than one company requesting materials from the same supplier, the report will select the Multi-Company Purchase Order box for that supplier's PO. + +The Aggregated Purchasing Warehouse field has a similar impact on the report's behavior. By default, the field is blank and the Material Demand report applies the warehouses set per Item in the Material Request as the Item's warehouse in the new Purchase Order. If you set a value in this field, the report will instead use the specified warehouse for each Item in the Purchase Order. + +See the Create Based on Item section for instructions on how to configure specific Item-Supplier combinations to require an RFQ. diff --git a/inventory_tools/docs/quotation_demand.md b/inventory_tools/docs/quotation_demand.md index 099b6ab..ab7d1da 100644 --- a/inventory_tools/docs/quotation_demand.md +++ b/inventory_tools/docs/quotation_demand.md @@ -1,3 +1,6 @@ + + # Quotation Demand Quotation Demand is a report-based interface that allows you to aggregate required Items across multiple Quotations, Customers, and requesting Companies. From there, you can create draft Sales Orders (SO). diff --git a/inventory_tools/docs/uom_enforcement.md b/inventory_tools/docs/uom_enforcement.md index 2a516fa..bbac954 100644 --- a/inventory_tools/docs/uom_enforcement.md +++ b/inventory_tools/docs/uom_enforcement.md @@ -1,39 +1,42 @@ -# UOM Enforcement - -By default, ERPNext allows its users to select any Unit of Measure (UOM) for any Item. If no conversion ratio exists between the UOM selected and the Item's stock UOM, ERPNext assumes it should be 1:1. This feature enforces that a user is only able to select and use valid UOMs. If an Item has no way to be understood in "Linear Feet" or "Volts", those UOMs will not be included as options in any UOM field for that Item. - -The following example shows the Parchment Paper Item has two defined UOMs in the Item master. - -![Screen shot of the Item master Inventory section for Parchment Paper showing two defined Units of Measure in the UOMs table. There is Nos with a conversion factor of 1 and Box with a conversion factor of 100](./assets/uom_item.png) - -In a Purchase Order, the Edit Item form for Parchment Paper has only two options in the UOM field - the two defined UOMs from the Item master. - -![Screen shot of a Purchase Order Edit Item form for Parchment Paper where the dropdown selections for the UOM field only shows Nos and Box as options](./assets/uom_options.png) - -## Configuration -To enable this feature, check the "Enforce UOMs" box in Inventory Tools Settings. - -## Extending or Overriding This Feature -In the event you need to enter arbitrary UOMs in a specific doctype, you can selectively override this feature in your custom app. The following example shows how to override UOM enforcement in the Opportunity doctype. - -```python -# custom_app/hooks.py -inventory_tools_uom_enforcement = { - "Opportunity": {"Opportunity Item": {"items": []}}, -} -``` -Here we have removed "uom" from the list of fields to check. - -To extend this feature to a custom doctype, follow the pattern established in the configuration object: - -```python -# custom_app/hooks.py -inventory_tools_uom_enforcement = { - "My Custom Doctype": { - "My Custom Doctype": ["uom"] # a UOM field at parent/ form level - "My Custom Doctype Child Table": {"items": ["uom", "weight_uom", ]}, # UOM fields in a child table - "My Second Custom Doctype Child Table": {"mistakes": ["uom", "weight_uom", ]}, # UOM fields in a second child table - }, -} -``` - + + +# UOM Enforcement + +By default, ERPNext allows its users to select any Unit of Measure (UOM) for any Item. If no conversion ratio exists between the UOM selected and the Item's stock UOM, ERPNext assumes it should be 1:1. This feature enforces that a user is only able to select and use valid UOMs. If an Item has no way to be understood in "Linear Feet" or "Volts", those UOMs will not be included as options in any UOM field for that Item. + +The following example shows the Parchment Paper Item has two defined UOMs in the Item master. + +![Screen shot of the Item master Inventory section for Parchment Paper showing two defined Units of Measure in the UOMs table. There is Nos with a conversion factor of 1 and Box with a conversion factor of 100](./assets/uom_item.png) + +In a Purchase Order, the Edit Item form for Parchment Paper has only two options in the UOM field - the two defined UOMs from the Item master. + +![Screen shot of a Purchase Order Edit Item form for Parchment Paper where the dropdown selections for the UOM field only shows Nos and Box as options](./assets/uom_options.png) + +## Configuration +To enable this feature, check the "Enforce UOMs" box in Inventory Tools Settings. + +## Extending or Overriding This Feature +In the event you need to enter arbitrary UOMs in a specific doctype, you can selectively override this feature in your custom app. The following example shows how to override UOM enforcement in the Opportunity doctype. + +```python +# custom_app/hooks.py +inventory_tools_uom_enforcement = { + "Opportunity": {"Opportunity Item": {"items": []}}, +} +``` +Here we have removed "uom" from the list of fields to check. + +To extend this feature to a custom doctype, follow the pattern established in the configuration object: + +```python +# custom_app/hooks.py +inventory_tools_uom_enforcement = { + "My Custom Doctype": { + "My Custom Doctype": ["uom"] # a UOM field at parent/ form level + "My Custom Doctype Child Table": {"items": ["uom", "weight_uom", ]}, # UOM fields in a child table + "My Second Custom Doctype Child Table": {"mistakes": ["uom", "weight_uom", ]}, # UOM fields in a second child table + }, +} +``` + diff --git a/inventory_tools/docs/warehouse_path.md b/inventory_tools/docs/warehouse_path.md index 9345e10..f7b7367 100644 --- a/inventory_tools/docs/warehouse_path.md +++ b/inventory_tools/docs/warehouse_path.md @@ -1,18 +1,21 @@ -# Warehouse Path -ERPNext allows its user to construct hierarchial abstractions for their physical facilities. This can make it difficult to know when you are selecting a Warehouse if it is "Bin A" in the "Storage Closet" or if is "Bin A" from the "Repair Supplies" Warehouse. - -This feature encodes the Warehouse hierarchy into a string, which becomes searchable, and allows the user to more easily understand which Warehouse they are selecting. - -## Example -In this example there are two Warehouses that start with "Refridger..." and while they are different, they could be mixed up. - -![Screen shot of the example company's Warehouse Tree. It includes a Refrigerated Display Warehouse (under the Baked Goods group) and a Refrigerator Warehouse](assets/warehouse_tree.png) - -In the Link dropdown, the full path is given, omitting the root "All Warehouses - APC" and the company abbreviation at each level. - -![Screen shot of a Target Warehouse field with "Refr" typed in, and two Warehouse options in the dropdown. The Refrigerated Display option has "Baked Goods -> Refrigerated Display" under its name, and the Refrigerator option has "Refrigerator" under its name](assets/fridge.png) - -This view shows the user-provided search text of "Refr..." matches the two similarly-named Warehouses. The Warehouse path under each option's name clearly distinguishes the choices by specifying each Warehouse's hierarchy. - -## Configuration -To enable this feature, check the "Update Warehouse Path" box in Inventory Tools Settings. + + +# Warehouse Path +ERPNext allows its user to construct hierarchial abstractions for their physical facilities. This can make it difficult to know when you are selecting a Warehouse if it is "Bin A" in the "Storage Closet" or if is "Bin A" from the "Repair Supplies" Warehouse. + +This feature encodes the Warehouse hierarchy into a string, which becomes searchable, and allows the user to more easily understand which Warehouse they are selecting. + +## Example +In this example there are two Warehouses that start with "Refridger..." and while they are different, they could be mixed up. + +![Screen shot of the example company's Warehouse Tree. It includes a Refrigerated Display Warehouse (under the Baked Goods group) and a Refrigerator Warehouse](assets/warehouse_tree.png) + +In the Link dropdown, the full path is given, omitting the root "All Warehouses - APC" and the company abbreviation at each level. + +![Screen shot of a Target Warehouse field with "Refr" typed in, and two Warehouse options in the dropdown. The Refrigerated Display option has "Baked Goods -> Refrigerated Display" under its name, and the Refrigerator option has "Refrigerator" under its name](assets/fridge.png) + +This view shows the user-provided search text of "Refr..." matches the two similarly-named Warehouses. The Warehouse path under each option's name clearly distinguishes the choices by specifying each Warehouse's hierarchy. + +## Configuration +To enable this feature, check the "Update Warehouse Path" box in Inventory Tools Settings. diff --git a/inventory_tools/docs/wo_subcontracting.md b/inventory_tools/docs/wo_subcontracting.md index 1991df3..f3c688a 100644 --- a/inventory_tools/docs/wo_subcontracting.md +++ b/inventory_tools/docs/wo_subcontracting.md @@ -1,3 +1,6 @@ + + # Subcontracting Workflow via Work Order ERPNext's subcontracting workflow has changed substantially with each version release of the software, generally without backwards compatibility. This feature offers an alternative to using a Purchase Receipt or the Version-14 Subcontracting Order and Subcontracting Receipt documents. Instead, it allows you to manage the subcontracting process with Work Orders, Purchase Orders and Invoices, and Stock Entries. diff --git a/inventory_tools/docs/work_order_subcontracting.md b/inventory_tools/docs/work_order_subcontracting.md index 0959a38..0c36606 100644 --- a/inventory_tools/docs/work_order_subcontracting.md +++ b/inventory_tools/docs/work_order_subcontracting.md @@ -1,8 +1,11 @@ -# Work Order Subcontracting - -Material Demand is a report-based interface that allows a user to aggregate required items across multiple Material Requests, Suppliers, requesting Companies and create draft Purchase Orders in that process. - -... describe selection process with screenshots - -## Configuration -Multi-company setup and workflow + + +# Work Order Subcontracting + +Material Demand is a report-based interface that allows a user to aggregate required items across multiple Material Requests, Suppliers, requesting Companies and create draft Purchase Orders in that process. + +... describe selection process with screenshots + +## Configuration +Multi-company setup and workflow diff --git a/inventory_tools/hooks.py b/inventory_tools/hooks.py index 03f91bb..6ff16d9 100644 --- a/inventory_tools/hooks.py +++ b/inventory_tools/hooks.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + from . import __version__ as app_version app_name = "inventory_tools" @@ -35,6 +38,8 @@ "Purchase Invoice": "public/js/purchase_invoice_custom.js", "Purchase Order": "public/js/purchase_order_custom.js", "Operation": "public/js/operation_custom.js", + "Purchase Invoice": "public/js/purchase_invoice_custom.js", + "Purchase Order": "public/js/purchase_order_custom.js", "Stock Entry": "public/js/stock_entry_custom.js", "Work Order": "public/js/work_order_custom.js", } @@ -109,14 +114,14 @@ # Override standard doctype classes override_doctype_class = { - "Work Order": "inventory_tools.inventory_tools.overrides.work_order.InventoryToolsWorkOrder", + "Job Card": "inventory_tools.inventory_tools.overrides.job_card.InventoryToolsJobCard", + "Production Plan": "inventory_tools.inventory_tools.overrides.production_plan.InventoryToolsProductionPlan", "Purchase Invoice": "inventory_tools.inventory_tools.overrides.purchase_invoice.InventoryToolsPurchaseInvoice", "Purchase Order": "inventory_tools.inventory_tools.overrides.purchase_order.InventoryToolsPurchaseOrder", "Purchase Receipt": "inventory_tools.inventory_tools.overrides.purchase_receipt.InventoryToolsPurchaseReceipt", - "Production Plan": "inventory_tools.inventory_tools.overrides.production_plan.InventoryToolsProductionPlan", - "Stock Entry": "inventory_tools.inventory_tools.overrides.stock_entry.InventoryToolsStockEntry", - "Job Card": "inventory_tools.inventory_tools.overrides.job_card.InventoryToolsJobCard", "Sales Order": "inventory_tools.inventory_tools.overrides.sales_order.InventoryToolsSalesOrder", + "Stock Entry": "inventory_tools.inventory_tools.overrides.stock_entry.InventoryToolsStockEntry", + "Work Order": "inventory_tools.inventory_tools.overrides.work_order.InventoryToolsWorkOrder", } diff --git a/inventory_tools/inventory_tools/__init__.py b/inventory_tools/inventory_tools/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/__init__.py +++ b/inventory_tools/inventory_tools/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/custom/bom.json b/inventory_tools/inventory_tools/custom/bom.json index 4f7122f..61b193c 100644 --- a/inventory_tools/inventory_tools/custom/bom.json +++ b/inventory_tools/inventory_tools/custom/bom.json @@ -1,192 +1,154 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-20 11:26:07.355568", - "default": "0", - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "BOM", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "is_subcontracted", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 10, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "allow_alternative_item", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Is Subcontracted", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-20 11:26:07.355568", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "BOM-is_subcontracted", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-01-09 18:02:12.685077", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "BOM", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "create_job_cards_automatically", - "fieldtype": "Select", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 12, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "set_rate_of_sub_assembly_item_based_on_bom", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Create Job Card(s) Automatically", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-01-09 18:02:12.685077", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "BOM-create_job_cards_automatically", - "no_copy": 0, - "non_negative": 0, - "options": "\nYes\nNo", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-01-11 10:05:40.558053", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "BOM", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "overproduction_percentage_for_work_order", - "fieldtype": "Percent", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 12, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "create_job_cards_automatically", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Overproduction Percentage For Work Order", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-01-11 10:07:40.558053", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "BOM-overproduction_percentage_for_work_order", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "BOM", - "links": [], - "property_setters": [], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-20 11:26:07.355568", + "default": "0", + "docstatus": 0, + "dt": "BOM", + "fetch_if_empty": 0, + "fieldname": "is_subcontracted", + "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 10, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "allow_alternative_item", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Is Subcontracted", + "length": 0, + "modified": "2024-07-13 08:52:49.183205", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "BOM-is_subcontracted", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2024-01-09 18:02:12.685077", + "default": null, + "docstatus": 0, + "dt": "BOM", + "fetch_if_empty": 0, + "fieldname": "create_job_cards_automatically", + "fieldtype": "Select", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 12, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "set_rate_of_sub_assembly_item_based_on_bom", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Create Job Card(s) Automatically", + "length": 0, + "modified": "2024-07-13 08:52:49.183215", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "BOM-create_job_cards_automatically", + "no_copy": 0, + "non_negative": 0, + "options": "\nYes\nNo", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2024-01-11 10:05:40.558053", + "default": null, + "docstatus": 0, + "dt": "BOM", + "fetch_if_empty": 0, + "fieldname": "overproduction_percentage_for_work_order", + "fieldtype": "Percent", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 12, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "create_job_cards_automatically", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Overproduction Percentage For Work Order", + "length": 0, + "modified": "2024-07-13 08:52:49.183220", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "BOM-overproduction_percentage_for_work_order", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "BOM", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/item_supplier.json b/inventory_tools/inventory_tools/custom/item_supplier.json index 7c24e2d..aa5c17e 100644 --- a/inventory_tools/inventory_tools/custom/item_supplier.json +++ b/inventory_tools/inventory_tools/custom/item_supplier.json @@ -1,69 +1,56 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-28 06:45:43.330833", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Item Supplier", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "requires_rfq", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 3, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "supplier_part_no", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Requires RFQ?", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-28 06:45:43.330833", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Item Supplier-requires_rfq", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Item Supplier", - "links": [], - "property_setters": [], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-28 06:45:43.330833", + "default": null, + "docstatus": 0, + "dt": "Item Supplier", + "fetch_if_empty": 0, + "fieldname": "requires_rfq", + "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 3, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "supplier_part_no", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Requires RFQ?", + "length": 0, + "modified": "2024-07-13 08:52:49.182757", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Item Supplier-requires_rfq", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Item Supplier", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/operation.json b/inventory_tools/inventory_tools/custom/operation.json index f48d696..48baa60 100644 --- a/inventory_tools/inventory_tools/custom/operation.json +++ b/inventory_tools/inventory_tools/custom/operation.json @@ -1,70 +1,58 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-02-16 05:04:42.422068", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Operation", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "alternative_workstations", - "fieldtype": "Table MultiSelect", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 1, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "workstation", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Alternative Workstations", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-02-16 05:04:42.422068", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Operation-alternative_workstations", - "no_copy": 0, - "non_negative": 0, - "options": "Alternative Workstations", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Operation", - "links": [], - "property_setters": [], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2024-02-16 05:04:42.422068", + "default": null, + "docstatus": 0, + "dt": "Operation", + "fetch_if_empty": 0, + "fieldname": "alternative_workstations", + "fieldtype": "Table MultiSelect", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 1, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "workstation", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Alternative Workstations", + "length": 0, + "modified": "2024-07-13 08:52:49.184527", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Operation-alternative_workstations", + "no_copy": 0, + "non_negative": 0, + "options": "Alternative Workstations", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Operation", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/purchase_invoice.json b/inventory_tools/inventory_tools/custom/purchase_invoice.json index 8a52ad1..9c591d8 100644 --- a/inventory_tools/inventory_tools/custom/purchase_invoice.json +++ b/inventory_tools/inventory_tools/custom/purchase_invoice.json @@ -1,360 +1,106 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-06-27 15:18:35.081502", - "default": null, - "depends_on": "is_subcontracted", - "description": null, - "docstatus": 0, - "dt": "Purchase Invoice", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "subcontracting_detail", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 57, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "base_tax_withholding_net_total", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Subcontracting Detail", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-06-27 15:18:35.081502", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-subcontracting_detail", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-06-27 15:18:35.540995", - "default": null, - "depends_on": "is_subcontracted", - "description": null, - "docstatus": 0, - "dt": "Purchase Invoice", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "subcontracting", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 58, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "subcontracting_detail", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Subcontracting", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-06-27 15:18:35.540995", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-subcontracting", - "no_copy": 0, - "non_negative": 0, - "options": "Purchase Invoice Subcontracting Detail", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Purchase Invoice", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-27 01:21:25.069596", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "payment_schedule", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-27 01:21:25.069596", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-payment_schedule-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-27 01:21:25.061243", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "due_date", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-27 01:21:25.061243", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-due_date-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:33.675155", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "scan_barcode", - "idx": 0, - "is_system_generated": 1, - "modified": "2023-06-26 15:52:33.675155", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-scan_barcode-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.864204", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "in_words", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.864204", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-in_words-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.852955", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "in_words", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.852955", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-in_words-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.650420", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "disable_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.650420", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-disable_rounded_total-default", - "owner": "Administrator", - "property": "default", - "property_type": "Text", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.638873", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.638873", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-rounded_total-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.627534", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.627534", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-rounded_total-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.615862", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "base_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.615862", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-base_rounded_total-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.604450", - "default_value": null, - "doc_type": "Purchase Invoice", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "base_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.604450", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice-base_rounded_total-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-06-27 15:18:35.081502", + "default": null, + "depends_on": "is_subcontracted", + "docstatus": 0, + "dt": "Purchase Invoice", + "fetch_if_empty": 0, + "fieldname": "subcontracting_detail", + "fieldtype": "Section Break", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 57, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "base_tax_withholding_net_total", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Subcontracting Detail", + "length": 0, + "modified": "2024-07-13 08:52:49.182355", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Invoice-subcontracting_detail", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-06-27 15:18:35.540995", + "default": null, + "depends_on": "is_subcontracted", + "docstatus": 0, + "dt": "Purchase Invoice", + "fetch_if_empty": 0, + "fieldname": "subcontracting", + "fieldtype": "Table", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 58, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "subcontracting_detail", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Subcontracting", + "length": 0, + "modified": "2024-07-13 08:52:49.182365", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Invoice-subcontracting", + "no_copy": 0, + "non_negative": 0, + "options": "Purchase Invoice Subcontracting Detail", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Purchase Invoice", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/purchase_invoice_item.json b/inventory_tools/inventory_tools/custom/purchase_invoice_item.json index 985fae4..e7b27d3 100644 --- a/inventory_tools/inventory_tools/custom/purchase_invoice_item.json +++ b/inventory_tools/inventory_tools/custom/purchase_invoice_item.json @@ -1,32 +1,26 @@ { - "custom_fields": [], - "custom_perms": [], - "doctype": "Purchase Invoice Item", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:33.804721", - "default_value": null, - "doc_type": "Purchase Invoice Item", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "from_warehouse", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:33.804721", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice Item-from_warehouse-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "1" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [], + "custom_perms": [], + "doctype": "Purchase Invoice Item", + "links": [], + "property_setters": [ + { + "creation": "2023-06-26 15:52:33.804721", + "doc_type": "Purchase Invoice Item", + "docstatus": 0, + "doctype_or_field": "DocField", + "field_name": "from_warehouse", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-13 08:36:13.058409", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Invoice Item-from_warehouse-hidden", + "owner": "Administrator", + "property": "hidden", + "property_type": "Check", + "value": "1" + } + ], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/purchase_order.json b/inventory_tools/inventory_tools/custom/purchase_order.json index 5d81534..af06738 100644 --- a/inventory_tools/inventory_tools/custom/purchase_order.json +++ b/inventory_tools/inventory_tools/custom/purchase_order.json @@ -1,420 +1,153 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-05-24 18:36:08.786651", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Purchase Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "multi_company_purchase_order", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 17, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "is_subcontracted", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Multi-Company Purchase Order", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-06-21 15:25:35.417289", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-multi_company_purchase_order", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-06 18:49:53.075476", - "default": null, - "depends_on": "is_subcontracted", - "description": null, - "docstatus": 0, - "dt": "Purchase Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "subcontracting_detail", - "fieldtype": "Section Break", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 54, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "supplied_items", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Subcontracting Detail", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 18:49:53.075476", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-subcontracting_detail", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-06 18:49:53.405459", - "default": null, - "depends_on": "is_subcontracted", - "description": null, - "docstatus": 0, - "dt": "Purchase Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "subcontracting", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 55, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "subcontracting_detail", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Subcontracting", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-06 18:49:53.405459", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-subcontracting", - "no_copy": 0, - "non_negative": 0, - "options": "Purchase Order Subcontracting Detail", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Purchase Order", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-27 01:21:25.052754", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "payment_schedule", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-27 01:21:25.052754", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-payment_schedule-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-27 01:21:25.044854", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "due_date", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-27 01:21:25.044854", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-due_date-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:33.522801", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "scan_barcode", - "idx": 0, - "is_system_generated": 1, - "modified": "2023-06-26 15:52:33.522801", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-scan_barcode-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.841692", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "in_words", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.841692", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-in_words-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.830425", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "in_words", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.830425", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-in_words-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.593057", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "disable_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.593057", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-disable_rounded_total-default", - "owner": "Administrator", - "property": "default", - "property_type": "Text", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.581038", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.581038", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-rounded_total-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.568298", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.568298", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-rounded_total-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.555242", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "base_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.555242", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-base_rounded_total-print_hide", - "owner": "Administrator", - "property": "print_hide", - "property_type": "Check", - "row_name": null, - "value": "1" - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:24.542389", - "default_value": null, - "doc_type": "Purchase Order", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "base_rounded_total", - "idx": 0, - "is_system_generated": 0, - "modified": "2023-06-26 15:52:24.542389", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order-base_rounded_total-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-05-24 18:36:08.786651", + "default": null, + "docstatus": 0, + "dt": "Purchase Order", + "fetch_if_empty": 0, + "fieldname": "multi_company_purchase_order", + "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 17, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "is_subcontracted", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Multi-Company Purchase Order", + "length": 0, + "modified": "2024-07-13 08:52:49.185184", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Order-multi_company_purchase_order", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-06 18:49:53.075476", + "default": null, + "depends_on": "is_subcontracted", + "docstatus": 0, + "dt": "Purchase Order", + "fetch_if_empty": 0, + "fieldname": "subcontracting_detail", + "fieldtype": "Section Break", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 54, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "supplied_items", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Subcontracting Detail", + "length": 0, + "modified": "2024-07-13 08:52:49.185218", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Order-subcontracting_detail", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + }, + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-06 18:49:53.405459", + "default": null, + "depends_on": "is_subcontracted", + "docstatus": 0, + "dt": "Purchase Order", + "fetch_if_empty": 0, + "fieldname": "subcontracting", + "fieldtype": "Table", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 55, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "subcontracting_detail", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Subcontracting", + "length": 0, + "modified": "2024-07-13 08:52:49.185223", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Order-subcontracting", + "no_copy": 0, + "non_negative": 0, + "options": "Purchase Order Subcontracting Detail", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Purchase Order", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/sales_order.json b/inventory_tools/inventory_tools/custom/sales_order.json index 529142b..de9e820 100644 --- a/inventory_tools/inventory_tools/custom/sales_order.json +++ b/inventory_tools/inventory_tools/custom/sales_order.json @@ -1,70 +1,57 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2024-03-14 10:10:39.991377", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Sales Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "multi_company_sales_order", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 9, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "order_type", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Multi-Company Sales Order", - "length": 0, - "mandatory_depends_on": null, - "modified": "2024-03-14 10:09:39.991377", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Sales Order-multi_company_sales_order", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "sort_options": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Sales Order", - "links": [], - "property_setters": [], - "sync_on_migrate": 1 - } \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2024-03-14 10:10:39.991377", + "default": null, + "docstatus": 0, + "dt": "Sales Order", + "fetch_if_empty": 0, + "fieldname": "multi_company_sales_order", + "fieldtype": "Check", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 9, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "order_type", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Multi-Company Sales Order", + "length": 0, + "modified": "2024-07-13 08:52:49.182103", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Sales Order-multi_company_sales_order", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "sort_options": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Sales Order", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/stock_entry_detail.json b/inventory_tools/inventory_tools/custom/stock_entry_detail.json index 421ff4c..183b394 100644 --- a/inventory_tools/inventory_tools/custom/stock_entry_detail.json +++ b/inventory_tools/inventory_tools/custom/stock_entry_detail.json @@ -1,93 +1,56 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-05 16:09:54.479727", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Stock Entry Detail", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "paid_qty", - "fieldtype": "Float", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 66, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "job_card_item", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Paid Qty", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-05 16:09:54.479727", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Stock Entry Detail-paid_qty", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Stock Entry Detail", - "links": [], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-06-26 15:52:33.190835", - "default_value": null, - "doc_type": "Stock Entry Detail", - "docstatus": 0, - "doctype_or_field": "DocField", - "field_name": "barcode", - "idx": 0, - "is_system_generated": 1, - "modified": "2023-06-26 15:52:33.190835", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Stock Entry Detail-barcode-hidden", - "owner": "Administrator", - "property": "hidden", - "property_type": "Check", - "row_name": null, - "value": "0" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-05 16:09:54.479727", + "default": null, + "docstatus": 0, + "dt": "Stock Entry Detail", + "fetch_if_empty": 0, + "fieldname": "paid_qty", + "fieldtype": "Float", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 66, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "job_card_item", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Paid Qty", + "length": 0, + "modified": "2024-07-13 08:52:49.182949", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Stock Entry Detail-paid_qty", + "no_copy": 0, + "non_negative": 0, + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Stock Entry Detail", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/supplier.json b/inventory_tools/inventory_tools/custom/supplier.json index a684263..4566dfd 100644 --- a/inventory_tools/inventory_tools/custom/supplier.json +++ b/inventory_tools/inventory_tools/custom/supplier.json @@ -1,129 +1,57 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-27 15:45:43.601325", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Supplier", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "irs_1099", - "fieldtype": "Check", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 28, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "tax_id", - "is_system_generated": 1, - "is_virtual": 0, - "label": "Is IRS 1099 reporting required for supplier?", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-01 10:25:59.395190", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Supplier-irs_1099", - "no_copy": 0, - "non_negative": 0, - "options": null, - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 1, - "unique": 0, - "width": null - }, - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-08-01 10:37:52.840344", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Supplier", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "subcontracting_defaults", - "fieldtype": "Table", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 62, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "section_break_suus6", - "is_system_generated": 0, - "is_virtual": 0, - "label": "", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-08-01 10:37:52.840344", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Supplier-subcontracting_defaults", - "no_copy": 0, - "non_negative": 0, - "options": "Subcontracting Default", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 0, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Supplier", - "links": [], - "property_setters": [], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-08-01 10:37:52.840344", + "default": null, + "docstatus": 0, + "dt": "Supplier", + "fetch_if_empty": 0, + "fieldname": "subcontracting_defaults", + "fieldtype": "Table", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 62, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "section_break_suus6", + "is_system_generated": 0, + "is_virtual": 0, + "label": "", + "length": 0, + "modified": "2024-07-13 08:52:49.184094", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Supplier-subcontracting_defaults", + "no_copy": 0, + "non_negative": 0, + "options": "Subcontracting Default", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 0, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Supplier", + "links": [], + "property_setters": [], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/custom/work_order.json b/inventory_tools/inventory_tools/custom/work_order.json index be2b699..dd59865 100644 --- a/inventory_tools/inventory_tools/custom/work_order.json +++ b/inventory_tools/inventory_tools/custom/work_order.json @@ -1,134 +1,113 @@ { - "custom_fields": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "allow_in_quick_entry": 0, - "allow_on_submit": 1, - "bold": 0, - "collapsible": 0, - "collapsible_depends_on": null, - "columns": 0, - "creation": "2023-07-25 14:44:39.211400", - "default": null, - "depends_on": null, - "description": null, - "docstatus": 0, - "dt": "Work Order", - "fetch_from": null, - "fetch_if_empty": 0, - "fieldname": "supplier", - "fieldtype": "Link", - "hidden": 0, - "hide_border": 0, - "hide_days": 0, - "hide_seconds": 0, - "idx": 16, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_preview": 0, - "in_standard_filter": 0, - "insert_after": "project", - "is_system_generated": 0, - "is_virtual": 0, - "label": "Subcontract Supplier", - "length": 0, - "mandatory_depends_on": null, - "modified": "2023-07-25 14:44:39.211400", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Work Order-supplier", - "no_copy": 0, - "non_negative": 0, - "options": "Supplier", - "owner": "Administrator", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "print_width": null, - "read_only": 1, - "read_only_depends_on": null, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "translatable": 0, - "unique": 0, - "width": null - } - ], - "custom_perms": [], - "doctype": "Work Order", - "links": [ - { - "creation": "2023-08-09 15:31:20.121332", - "custom": 1, - "docstatus": 0, - "group": null, - "hidden": 0, - "idx": 1, - "is_child_table": 1, - "link_doctype": "Purchase Invoice Subcontracting Detail", - "link_fieldname": "work_order", - "modified": "2023-08-09 15:34:47.812937", - "modified_by": "Administrator", - "name": "2bb6c50f93", - "owner": "Administrator", - "parent": "Work Order", - "parent_doctype": "Purchase Invoice", - "parentfield": "links", - "parenttype": "Customize Form", - "table_fieldname": "subcontracting" - }, - { - "creation": "2023-08-09 15:29:40.533005", - "custom": 1, - "docstatus": 0, - "group": null, - "hidden": 0, - "idx": 0, - "is_child_table": 1, - "link_doctype": "Purchase Order Subcontracting Detail", - "link_fieldname": "work_order", - "modified": "2023-08-09 15:34:47.802753", - "modified_by": "Administrator", - "name": "6c25a06ffd", - "owner": "Administrator", - "parent": "Work Order", - "parent_doctype": "Purchase Order", - "parentfield": "links", - "parenttype": "Customize Form", - "table_fieldname": "subcontracting" - } - ], - "property_setters": [ - { - "_assign": null, - "_comments": null, - "_liked_by": null, - "_user_tags": null, - "creation": "2023-08-09 15:34:47.822577", - "default_value": null, - "doc_type": "Work Order", - "docstatus": 0, - "doctype_or_field": "DocType", - "field_name": null, - "idx": 0, - "is_system_generated": 0, - "modified": "2023-08-09 15:34:47.822577", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Work Order-main-links_order", - "owner": "Administrator", - "property": "links_order", - "property_type": "Small Text", - "row_name": null, - "value": "[\"6c25a06ffd\", \"2bb6c50f93\"]" - } - ], - "sync_on_migrate": 1 -} \ No newline at end of file + "custom_fields": [ + { + "allow_in_quick_entry": 0, + "allow_on_submit": 1, + "bold": 0, + "collapsible": 0, + "columns": 0, + "creation": "2023-07-25 14:44:39.211400", + "default": null, + "docstatus": 0, + "dt": "Work Order", + "fetch_if_empty": 0, + "fieldname": "supplier", + "fieldtype": "Link", + "hidden": 0, + "hide_border": 0, + "hide_days": 0, + "hide_seconds": 0, + "idx": 16, + "ignore_user_permissions": 0, + "ignore_xss_filter": 0, + "in_global_search": 0, + "in_list_view": 0, + "in_preview": 0, + "in_standard_filter": 0, + "insert_after": "project", + "is_system_generated": 0, + "is_virtual": 0, + "label": "Subcontract Supplier", + "length": 0, + "modified": "2024-07-13 08:52:49.183557", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Work Order-supplier", + "no_copy": 0, + "non_negative": 0, + "options": "Supplier", + "owner": "Administrator", + "permlevel": 0, + "precision": "", + "print_hide": 0, + "print_hide_if_no_value": 0, + "read_only": 1, + "report_hide": 0, + "reqd": 0, + "search_index": 0, + "translatable": 0, + "unique": 0 + } + ], + "custom_perms": [], + "doctype": "Work Order", + "links": [ + { + "creation": "2023-08-09 15:31:20.121332", + "custom": 1, + "docstatus": 0, + "hidden": 0, + "idx": 1, + "is_child_table": 1, + "link_doctype": "Purchase Invoice Subcontracting Detail", + "link_fieldname": "work_order", + "modified": "2024-07-13 08:52:49.183571", + "modified_by": "Administrator", + "name": "2bb6c50f93", + "owner": "Administrator", + "parent": "Work Order", + "parent_doctype": "Purchase Invoice", + "parentfield": "links", + "parenttype": "Customize Form", + "table_fieldname": "subcontracting" + }, + { + "creation": "2023-08-09 15:29:40.533005", + "custom": 1, + "docstatus": 0, + "hidden": 0, + "idx": 0, + "is_child_table": 1, + "link_doctype": "Purchase Order Subcontracting Detail", + "link_fieldname": "work_order", + "modified": "2024-07-13 08:52:49.183578", + "modified_by": "Administrator", + "name": "6c25a06ffd", + "owner": "Administrator", + "parent": "Work Order", + "parent_doctype": "Purchase Order", + "parentfield": "links", + "parenttype": "Customize Form", + "table_fieldname": "subcontracting" + } + ], + "property_setters": [ + { + "creation": "2023-08-09 15:34:47.822577", + "doc_type": "Work Order", + "docstatus": 0, + "doctype_or_field": "DocType", + "idx": 0, + "is_system_generated": 0, + "modified": "2024-07-13 08:52:49.183585", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Work Order-main-links_order", + "owner": "Administrator", + "property": "links_order", + "property_type": "Small Text", + "value": "[\"6c25a06ffd\", \"2bb6c50f93\"]" + } + ], + "sync_on_migrate": 1 +} diff --git a/inventory_tools/inventory_tools/doctype/__init__.py b/inventory_tools/inventory_tools/doctype/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/doctype/__init__.py +++ b/inventory_tools/inventory_tools/doctype/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json index 470af37..2526e7c 100644 --- a/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json +++ b/inventory_tools/inventory_tools/doctype/alternative_workstations/alternative_workstations.json @@ -1,32 +1,30 @@ { - "actions": [], - "allow_rename": 1, - "creation": "2024-02-01 04:03:58.033322", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "workstation" - ], - "fields": [ - { - "fieldname": "workstation", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Workstation", - "options": "Workstation" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2024-02-01 07:04:30.059469", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Alternative Workstations", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "allow_rename": 1, + "creation": "2024-02-01 04:03:58.033322", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["workstation"], + "fields": [ + { + "fieldname": "workstation", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Workstation", + "options": "Workstation" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2024-02-01 07:04:30.059469", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Alternative Workstations", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/inventory_tools/inventory_tools/doctype/inventory_tools_settings/__init__.py b/inventory_tools/inventory_tools/doctype/inventory_tools_settings/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/doctype/inventory_tools_settings/__init__.py +++ b/inventory_tools/inventory_tools/doctype/inventory_tools_settings/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/doctype/inventory_tools_settings/inventory_tools_settings.json b/inventory_tools/inventory_tools/doctype/inventory_tools_settings/inventory_tools_settings.json index aa7da3a..d7d8742 100644 --- a/inventory_tools/inventory_tools/doctype/inventory_tools_settings/inventory_tools_settings.json +++ b/inventory_tools/inventory_tools/doctype/inventory_tools_settings/inventory_tools_settings.json @@ -1,200 +1,200 @@ { - "actions": [], - "autoname": "field:company", - "creation": "2023-05-30 20:24:26.832647", - "default_view": "List", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "company", - "material_demand_section", - "purchase_order_aggregation_company", - "column_break_vgsiq", - "aggregated_purchasing_warehouse", - "quotation_demand_section", - "sales_order_aggregation_company", - "column_break_ljy4o", - "aggregated_sales_warehouse", - "work_order_subcontracting_section", - "enable_work_order_subcontracting", - "create_purchase_orders", - "bom_column", - "create_job_cards_automatically", - "column_break_ilobm", - "overproduction_percentage_for_work_order", - "section_break_0", - "update_warehouse_path", - "section_break_gzcbr", - "uoms_section", - "enforce_uoms" - ], - "fields": [ - { - "fieldname": "company", - "fieldtype": "Link", - "label": "Company", - "options": "Company", - "unique": 1 - }, - { - "fieldname": "material_demand_section", - "fieldtype": "Section Break", - "label": "Material Demand" - }, - { - "description": "Leave this field blank to disallow aggregation for this company", - "fieldname": "purchase_order_aggregation_company", - "fieldtype": "Link", - "label": "Purchase Order Aggregation Company", - "options": "Company" - }, - { - "fieldname": "column_break_vgsiq", - "fieldtype": "Column Break" - }, - { - "description": "When set, this will override the Material Requests Receiving Warehouse. If not set, this will map Warehouses from Material Request into Purchase Order.", - "fieldname": "aggregated_purchasing_warehouse", - "fieldtype": "Link", - "label": "Aggregated Purchasing Warehouse", - "options": "Warehouse" - }, - { - "fieldname": "work_order_subcontracting_section", - "fieldtype": "Section Break", - "label": "Work Order Subcontracting" - }, - { - "default": "0", - "fieldname": "enable_work_order_subcontracting", - "fieldtype": "Check", - "label": "Enable Work Order Subcontracting" - }, - { - "default": "0", - "fieldname": "create_purchase_orders", - "fieldtype": "Check", - "label": "Create Purchase Orders in Production Plan" - }, - { - "fieldname": "section_break_0", - "fieldtype": "Section Break" - }, - { - "default": "0", - "fieldname": "update_warehouse_path", - "fieldtype": "Check", - "label": "Update Warehouse Path" - }, - { - "fieldname": "section_break_gzcbr", - "fieldtype": "Section Break", - "label": "Warehouses" - }, - { - "fieldname": "uoms_section", - "fieldtype": "Section Break", - "label": "UOMs" - }, - { - "default": "0", - "fieldname": "enforce_uoms", - "fieldtype": "Check", - "label": "Enforce UOMs" - }, - { - "fieldname": "bom_column", - "fieldtype": "Section Break", - "label": "Work Order" - }, - { - "default": "Yes", - "fieldname": "create_job_cards_automatically", - "fieldtype": "Select", - "label": "Create Job Card(s) Automatically", - "options": "Yes\nNo" - }, - { - "fieldname": "column_break_ilobm", - "fieldtype": "Column Break" - }, - { - "fieldname": "overproduction_percentage_for_work_order", - "fieldtype": "Percent", - "label": "Overproduction Percentage For Work Order" - }, - { - "fieldname": "quotation_demand_section", - "fieldtype": "Section Break", - "label": "Quotation Demand" - }, - { - "description": "Leave this field blank to disallow aggregation for this company", - "fieldname": "sales_order_aggregation_company", - "fieldtype": "Link", - "label": "Sales Order Aggregation Company", - "options": "Company" - }, - { - "fieldname": "column_break_ljy4o", - "fieldtype": "Column Break" - }, - { - "description": "When set, this will override the Quotation Warehouse. If not set, this will map Warehouses from Quotation into Sales Order.", - "fieldname": "aggregated_sales_warehouse", - "fieldtype": "Link", - "label": "Aggregated Sales Warehouse", - "options": "Warehouse" - } - ], - "index_web_pages_for_search": 1, - "links": [], - "modified": "2024-04-12 10:02:32.688310", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Inventory Tools Settings", - "naming_rule": "Expression (old style)", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Stock Manager", - "share": 1, - "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Purchase Master Manager", - "share": 1, - "write": 1 - } - ], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "autoname": "field:company", + "creation": "2023-05-30 20:24:26.832647", + "default_view": "List", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "company", + "material_demand_section", + "purchase_order_aggregation_company", + "column_break_vgsiq", + "aggregated_purchasing_warehouse", + "quotation_demand_section", + "sales_order_aggregation_company", + "column_break_ljy4o", + "aggregated_sales_warehouse", + "work_order_subcontracting_section", + "enable_work_order_subcontracting", + "create_purchase_orders", + "bom_column", + "create_job_cards_automatically", + "column_break_ilobm", + "overproduction_percentage_for_work_order", + "section_break_0", + "update_warehouse_path", + "section_break_gzcbr", + "uoms_section", + "enforce_uoms" + ], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "label": "Company", + "options": "Company", + "unique": 1 + }, + { + "fieldname": "material_demand_section", + "fieldtype": "Section Break", + "label": "Material Demand" + }, + { + "description": "Leave this field blank to disallow aggregation for this company", + "fieldname": "purchase_order_aggregation_company", + "fieldtype": "Link", + "label": "Purchase Order Aggregation Company", + "options": "Company" + }, + { + "fieldname": "column_break_vgsiq", + "fieldtype": "Column Break" + }, + { + "description": "When set, this will override the Material Requests Receiving Warehouse. If not set, this will map Warehouses from Material Request into Purchase Order.", + "fieldname": "aggregated_purchasing_warehouse", + "fieldtype": "Link", + "label": "Aggregated Purchasing Warehouse", + "options": "Warehouse" + }, + { + "fieldname": "work_order_subcontracting_section", + "fieldtype": "Section Break", + "label": "Work Order Subcontracting" + }, + { + "default": "0", + "fieldname": "enable_work_order_subcontracting", + "fieldtype": "Check", + "label": "Enable Work Order Subcontracting" + }, + { + "default": "0", + "fieldname": "create_purchase_orders", + "fieldtype": "Check", + "label": "Create Purchase Orders in Production Plan" + }, + { + "fieldname": "section_break_0", + "fieldtype": "Section Break" + }, + { + "default": "0", + "fieldname": "update_warehouse_path", + "fieldtype": "Check", + "label": "Update Warehouse Path" + }, + { + "fieldname": "section_break_gzcbr", + "fieldtype": "Section Break", + "label": "Warehouses" + }, + { + "fieldname": "uoms_section", + "fieldtype": "Section Break", + "label": "UOMs" + }, + { + "default": "0", + "fieldname": "enforce_uoms", + "fieldtype": "Check", + "label": "Enforce UOMs" + }, + { + "fieldname": "bom_column", + "fieldtype": "Section Break", + "label": "Work Order" + }, + { + "default": "Yes", + "fieldname": "create_job_cards_automatically", + "fieldtype": "Select", + "label": "Create Job Card(s) Automatically", + "options": "Yes\nNo" + }, + { + "fieldname": "column_break_ilobm", + "fieldtype": "Column Break" + }, + { + "fieldname": "overproduction_percentage_for_work_order", + "fieldtype": "Percent", + "label": "Overproduction Percentage For Work Order" + }, + { + "fieldname": "quotation_demand_section", + "fieldtype": "Section Break", + "label": "Quotation Demand" + }, + { + "description": "Leave this field blank to disallow aggregation for this company", + "fieldname": "sales_order_aggregation_company", + "fieldtype": "Link", + "label": "Sales Order Aggregation Company", + "options": "Company" + }, + { + "fieldname": "column_break_ljy4o", + "fieldtype": "Column Break" + }, + { + "description": "When set, this will override the Quotation Warehouse. If not set, this will map Warehouses from Quotation into Sales Order.", + "fieldname": "aggregated_sales_warehouse", + "fieldtype": "Link", + "label": "Aggregated Sales Warehouse", + "options": "Warehouse" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2024-04-12 10:02:32.688310", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Inventory Tools Settings", + "naming_rule": "Expression (old style)", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Stock Manager", + "share": 1, + "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Purchase Master Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/__init__.py b/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/__init__.py +++ b/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/purchase_invoice_subcontracting_detail.json b/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/purchase_invoice_subcontracting_detail.json index 569418c..9942b15 100644 --- a/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/purchase_invoice_subcontracting_detail.json +++ b/inventory_tools/inventory_tools/doctype/purchase_invoice_subcontracting_detail/purchase_invoice_subcontracting_detail.json @@ -1,125 +1,125 @@ { - "actions": [], - "allow_rename": 1, - "creation": "2023-06-27 14:58:07.361859", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "work_order", - "stock_entry", - "purchase_order", - "se_detail_name", - "item_code", - "item_name", - "qty", - "transfer_qty", - "paid_qty", - "to_pay_qty", - "uom", - "stock_uom", - "conversion_factor", - "valuation_rate" - ], - "fields": [ - { - "columns": 2, - "fieldname": "work_order", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Work Order", - "options": "Work Order" - }, - { - "columns": 2, - "fieldname": "stock_entry", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Stock Entry", - "options": "Stock Entry" - }, - { - "fieldname": "purchase_order", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Purchase Order", - "options": "Purchase Order" - }, - { - "fieldname": "se_detail_name", - "fieldtype": "Data", - "hidden": 1, - "label": "Stock Entry Detail Name" - }, - { - "fieldname": "item_code", - "fieldtype": "Link", - "label": "Item Code", - "options": "Item" - }, - { - "fieldname": "item_name", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Item Name" - }, - { - "fieldname": "qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Total Qty" - }, - { - "fieldname": "transfer_qty", - "fieldtype": "Float", - "label": "Qty as per Stock UOM" - }, - { - "fieldname": "uom", - "fieldtype": "Link", - "label": "UOM", - "options": "UOM" - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM" - }, - { - "fieldname": "conversion_factor", - "fieldtype": "Float", - "label": "Conversion Factor" - }, - { - "fieldname": "valuation_rate", - "fieldtype": "Currency", - "label": "Valuation Rate", - "options": "Company:company:default_currency" - }, - { - "fieldname": "paid_qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Paid Qty" - }, - { - "fieldname": "to_pay_qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "To Pay Qty" - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2023-08-09 15:38:04.549747", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Invoice Subcontracting Detail", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "allow_rename": 1, + "creation": "2023-06-27 14:58:07.361859", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "work_order", + "stock_entry", + "purchase_order", + "se_detail_name", + "item_code", + "item_name", + "qty", + "transfer_qty", + "paid_qty", + "to_pay_qty", + "uom", + "stock_uom", + "conversion_factor", + "valuation_rate" + ], + "fields": [ + { + "columns": 2, + "fieldname": "work_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Work Order", + "options": "Work Order" + }, + { + "columns": 2, + "fieldname": "stock_entry", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Stock Entry", + "options": "Stock Entry" + }, + { + "fieldname": "purchase_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Purchase Order", + "options": "Purchase Order" + }, + { + "fieldname": "se_detail_name", + "fieldtype": "Data", + "hidden": 1, + "label": "Stock Entry Detail Name" + }, + { + "fieldname": "item_code", + "fieldtype": "Link", + "label": "Item Code", + "options": "Item" + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Item Name" + }, + { + "fieldname": "qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Total Qty" + }, + { + "fieldname": "transfer_qty", + "fieldtype": "Float", + "label": "Qty as per Stock UOM" + }, + { + "fieldname": "uom", + "fieldtype": "Link", + "label": "UOM", + "options": "UOM" + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM" + }, + { + "fieldname": "conversion_factor", + "fieldtype": "Float", + "label": "Conversion Factor" + }, + { + "fieldname": "valuation_rate", + "fieldtype": "Currency", + "label": "Valuation Rate", + "options": "Company:company:default_currency" + }, + { + "fieldname": "paid_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Paid Qty" + }, + { + "fieldname": "to_pay_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "To Pay Qty" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-08-09 15:38:04.549747", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Invoice Subcontracting Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/__init__.py b/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/__init__.py +++ b/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/purchase_order_subcontracting_detail.json b/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/purchase_order_subcontracting_detail.json index a353768..eb29068 100644 --- a/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/purchase_order_subcontracting_detail.json +++ b/inventory_tools/inventory_tools/doctype/purchase_order_subcontracting_detail/purchase_order_subcontracting_detail.json @@ -1,82 +1,74 @@ { - "actions": [], - "allow_rename": 1, - "creation": "2023-07-06 18:45:16.830715", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "work_order", - "warehouse", - "item_name", - "fg_item", - "fg_item_qty", - "bom", - "stock_uom" - ], - "fields": [ - { - "fieldname": "work_order", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Work Order", - "options": "Work Order", - "reqd": 1 - }, - { - "fieldname": "item_name", - "fieldtype": "Data", - "label": "Item Name" - }, - { - "fieldname": "fg_item", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Finished Good", - "options": "Item", - "reqd": 1 - }, - { - "columns": 1, - "fieldname": "fg_item_qty", - "fieldtype": "Float", - "in_list_view": 1, - "label": "Finished Good Qty", - "reqd": 1 - }, - { - "fieldname": "bom", - "fieldtype": "Link", - "label": "BOM", - "options": "BOM", - "read_only": 1 - }, - { - "fieldname": "stock_uom", - "fieldtype": "Link", - "label": "Stock UOM", - "options": "UOM", - "read_only": 1 - }, - { - "fieldname": "warehouse", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Warehouse", - "options": "Warehouse", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2023-08-01 08:17:24.132999", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Purchase Order Subcontracting Detail", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "allow_rename": 1, + "creation": "2023-07-06 18:45:16.830715", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["work_order", "warehouse", "item_name", "fg_item", "fg_item_qty", "bom", "stock_uom"], + "fields": [ + { + "fieldname": "work_order", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Work Order", + "options": "Work Order", + "reqd": 1 + }, + { + "fieldname": "item_name", + "fieldtype": "Data", + "label": "Item Name" + }, + { + "fieldname": "fg_item", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Finished Good", + "options": "Item", + "reqd": 1 + }, + { + "columns": 1, + "fieldname": "fg_item_qty", + "fieldtype": "Float", + "in_list_view": 1, + "label": "Finished Good Qty", + "reqd": 1 + }, + { + "fieldname": "bom", + "fieldtype": "Link", + "label": "BOM", + "options": "BOM", + "read_only": 1 + }, + { + "fieldname": "stock_uom", + "fieldtype": "Link", + "label": "Stock UOM", + "options": "UOM", + "read_only": 1 + }, + { + "fieldname": "warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Warehouse", + "options": "Warehouse", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-08-01 08:17:24.132999", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Purchase Order Subcontracting Detail", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/inventory_tools/inventory_tools/doctype/subcontracting_default/__init__.py b/inventory_tools/inventory_tools/doctype/subcontracting_default/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/doctype/subcontracting_default/__init__.py +++ b/inventory_tools/inventory_tools/doctype/subcontracting_default/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/doctype/subcontracting_default/subcontracting_default.json b/inventory_tools/inventory_tools/doctype/subcontracting_default/subcontracting_default.json index e2de4c8..8168cd5 100644 --- a/inventory_tools/inventory_tools/doctype/subcontracting_default/subcontracting_default.json +++ b/inventory_tools/inventory_tools/doctype/subcontracting_default/subcontracting_default.json @@ -1,51 +1,47 @@ { - "actions": [], - "allow_rename": 1, - "creation": "2023-08-01 08:31:15.167625", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "company", - "wip_warehouse", - "return_warehouse" - ], - "fields": [ - { - "fieldname": "company", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Company", - "options": "Company", - "reqd": 1 - }, - { - "fieldname": "wip_warehouse", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Work In Process Warehouse", - "options": "Warehouse", - "reqd": 1 - }, - { - "fieldname": "return_warehouse", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Return Warehouse", - "options": "Warehouse", - "reqd": 1 - } - ], - "index_web_pages_for_search": 1, - "istable": 1, - "links": [], - "modified": "2023-08-01 10:55:44.125187", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Subcontracting Default", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "states": [] -} \ No newline at end of file + "actions": [], + "allow_rename": 1, + "creation": "2023-08-01 08:31:15.167625", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": ["company", "wip_warehouse", "return_warehouse"], + "fields": [ + { + "fieldname": "company", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Company", + "options": "Company", + "reqd": 1 + }, + { + "fieldname": "wip_warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Work In Process Warehouse", + "options": "Warehouse", + "reqd": 1 + }, + { + "fieldname": "return_warehouse", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Return Warehouse", + "options": "Warehouse", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2023-08-01 10:55:44.125187", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Subcontracting Default", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "states": [] +} diff --git a/inventory_tools/inventory_tools/overrides/job_card.py b/inventory_tools/inventory_tools/overrides/job_card.py index f86de01..7ac1c8f 100644 --- a/inventory_tools/inventory_tools/overrides/job_card.py +++ b/inventory_tools/inventory_tools/overrides/job_card.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe from erpnext.manufacturing.doctype.job_card.job_card import JobCard from frappe import _, bold @@ -8,6 +11,13 @@ class InventoryToolsJobCard(JobCard): def validate_job_card(self): + """ + HASH: 6554f192fbe90033a71fa323462633c5130e1b46 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/job_card/job_card.py + METHOD: validate_job_card + """ + if ( self.work_order and frappe.get_cached_value("Work Order", self.work_order, "status") == "Stopped" diff --git a/inventory_tools/inventory_tools/overrides/operation.py b/inventory_tools/inventory_tools/overrides/operation.py index d649d48..6486ab1 100644 --- a/inventory_tools/inventory_tools/overrides/operation.py +++ b/inventory_tools/inventory_tools/overrides/operation.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe diff --git a/inventory_tools/inventory_tools/overrides/production_plan.py b/inventory_tools/inventory_tools/overrides/production_plan.py index ce6d41a..5485fbb 100644 --- a/inventory_tools/inventory_tools/overrides/production_plan.py +++ b/inventory_tools/inventory_tools/overrides/production_plan.py @@ -1,43 +1,62 @@ -import json - -import frappe -from erpnext.manufacturing.doctype.production_plan.production_plan import ProductionPlan -from erpnext.manufacturing.doctype.work_order.work_order import get_default_warehouse - - -class InventoryToolsProductionPlan(ProductionPlan): - @frappe.whitelist() - def make_work_order(self): - wo_list, po_list = [], [] - subcontracted_po = {} - default_warehouses = get_default_warehouse() - - self.make_work_order_for_finished_goods(wo_list, default_warehouses) - self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses) - if frappe.get_value("Inventory Tools Settings", self.company, "create_purchase_orders"): - self.make_subcontracted_purchase_order(subcontracted_po, po_list) - self.show_list_created_message("Work Order", wo_list) - self.show_list_created_message("Purchase Order", po_list) - - def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses): - for row in self.sub_assembly_items: - if row.type_of_manufacturing == "Subcontract": - subcontracted_po.setdefault(row.supplier, []).append(row) - if not frappe.get_value( - "Inventory Tools Settings", self.company, "enable_work_order_subcontracting" - ): - continue - - if row.type_of_manufacturing == "Material Request": - continue - - work_order_data = { - "wip_warehouse": default_warehouses.get("wip_warehouse"), - "fg_warehouse": default_warehouses.get("fg_warehouse"), - "company": self.get("company"), - } - - self.prepare_data_for_sub_assembly_items(row, work_order_data) - work_order = self.create_work_order(work_order_data) - if work_order: - wo_list.append(work_order) +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +from erpnext.manufacturing.doctype.production_plan.production_plan import ProductionPlan +from erpnext.manufacturing.doctype.work_order.work_order import get_default_warehouse +from frappe import _ + + +class InventoryToolsProductionPlan(ProductionPlan): + @frappe.whitelist() + def make_work_order(self): + """ + HASH: 30c0b2bb546c1c78bd5db29311a78d71a02efdf1 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/production_plan/production_plan.py + METHOD: make_work_order + """ + + wo_list, po_list = [], [] + subcontracted_po = {} + default_warehouses = get_default_warehouse() + + self.make_work_order_for_finished_goods(wo_list, default_warehouses) + self.make_work_order_for_subassembly_items(wo_list, subcontracted_po, default_warehouses) + if frappe.get_value("Inventory Tools Settings", self.company, "create_purchase_orders"): + self.make_subcontracted_purchase_order(subcontracted_po, po_list) + self.show_list_created_message("Work Order", wo_list) + self.show_list_created_message("Purchase Order", po_list) + + if not wo_list: + frappe.msgprint(_("No Work Orders were created")) + + def make_work_order_for_subassembly_items(self, wo_list, subcontracted_po, default_warehouses): + """ + HASH: 30c0b2bb546c1c78bd5db29311a78d71a02efdf1 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/production_plan/production_plan.py + METHOD: make_work_order_for_subassembly_items + """ + + for row in self.sub_assembly_items: + if row.type_of_manufacturing == "Subcontract": + subcontracted_po.setdefault(row.supplier, []).append(row) + if not frappe.get_value( + "Inventory Tools Settings", self.company, "enable_work_order_subcontracting" + ): + continue + + if row.type_of_manufacturing == "Material Request": + continue + + work_order_data = { + "wip_warehouse": default_warehouses.get("wip_warehouse"), + "fg_warehouse": default_warehouses.get("fg_warehouse"), + "company": self.get("company"), + } + + self.prepare_data_for_sub_assembly_items(row, work_order_data) + work_order = self.create_work_order(work_order_data) + if work_order: + wo_list.append(work_order) diff --git a/inventory_tools/inventory_tools/overrides/purchase_invoice.py b/inventory_tools/inventory_tools/overrides/purchase_invoice.py index 5c64525..c0d6a7d 100644 --- a/inventory_tools/inventory_tools/overrides/purchase_invoice.py +++ b/inventory_tools/inventory_tools/overrides/purchase_invoice.py @@ -1,187 +1,195 @@ -# Copyright (c) 2023, AgriTheory and Contributors -# See license.txt - -import datetime -import json - -import frappe -from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import PurchaseInvoice -from frappe import _ -from frappe.utils.data import cint - - -class InventoryToolsPurchaseInvoice(PurchaseInvoice): - def validate_with_previous_doc(self): - config = { - "Purchase Order": { - "ref_dn_field": "purchase_order", - "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], - }, - "Purchase Order Item": { - "ref_dn_field": "po_detail", - "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]], - "is_child_table": True, - "allow_duplicate_prev_row_id": True, - }, - "Purchase Receipt": { - "ref_dn_field": "purchase_receipt", - "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], - }, - "Purchase Receipt Item": { - "ref_dn_field": "pr_detail", - "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]], - "is_child_table": True, - }, - } - pos = list({r.purchase_order for r in self.items}) - if len(pos) == 1 and frappe.get_value("Purchase Order", pos[0], "multi_company_purchase_order"): - config["Purchase Order"]["compare_fields"] = [["currency", "="]] - - super(PurchaseInvoice, self).validate_with_previous_doc(config) - - if ( - cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")) - and not self.is_return - and not self.is_internal_supplier - ): - self.validate_rate_with_reference_doc( - [ - ["Purchase Order", "purchase_order", "po_detail"], - ["Purchase Receipt", "purchase_receipt", "pr_detail"], - ] - ) - - def validate(self): - if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: - if not self.supplier_warehouse: - self.supplier_warehouse = fetch_supplier_warehouse(self.company, self.supplier) - self.validate_subcontracting_to_pay_qty() - return super().validate() - - def on_submit(self): - if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: - self.on_submit_save_se_paid_qty() - return super().on_submit() - - def on_cancel(self): - if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: - self.on_cancel_revert_se_paid_qty() - return super().on_cancel() - - def is_work_order_subcontracting_enabled(self): - settings = frappe.get_doc("Inventory Tools Settings", {"company": self.company}) - return bool(settings and settings.enable_work_order_subcontracting) - - def validate_subcontracting_to_pay_qty(self): - # Checks the qty the invoice will cover is not more than the outstanding qty - for subc in self.get("subcontracting"): - if subc.to_pay_qty > (subc.qty - subc.paid_qty): - frappe.throw( - _( - f"The To Pay Qty in Subcontracting Detail row {subc.idx} cannot be more than Total Qty less the already Paid Qty." - ) - ) - - def on_submit_save_se_paid_qty(self): - # Saves the invoiced quantity for the Stock Entry Detail row into paid_qty field - for ste in self.get("subcontracting"): - frappe.db.set_value( - "Stock Entry Detail", ste.se_detail_name, "paid_qty", ste.paid_qty + ste.to_pay_qty - ) - - def on_cancel_revert_se_paid_qty(self): - # Reduces the Stock Entry Detail item's paid_qty by the to_pay_qty amount in the invoice - for ste in self.get("subcontracting"): - cur_paid = frappe.db.get_value("Stock Entry Detail", ste.se_detail_name, "paid_qty") - frappe.db.set_value( - "Stock Entry Detail", ste.se_detail_name, "paid_qty", cur_paid - ste.to_pay_qty - ) - - -@frappe.whitelist() -def get_stock_entries(purchase_orders, from_date=None, to_date=None): - # # Commented code is useful if having PO and attaching WOs to them is enforced - # if isinstance(purchase_orders, str): - # purchase_orders = json.loads(purchase_orders) - - if not from_date: - from_date = datetime.date(1900, 1, 1) - - if not to_date: - to_date = datetime.date(2100, 12, 31) - - # work_orders, fg_items = [], set() - # for po in purchase_orders: - # work_orders.extend( - # frappe.get_all( - # "Purchase Order Subcontracting Detail", - # fields="work_order", - # filters={"parent": po}, - # pluck="work_order" - # ) - # ) - # for item in frappe.get_doc("Purchase Order", po).get("items"): - # fg_items.add(item.get("fg_item")) - - stock_entry = frappe.qb.DocType("Stock Entry") - se_detail = frappe.qb.DocType("Stock Entry Detail") - po_sub = frappe.qb.DocType("Purchase Order Subcontracting Detail") - po = frappe.qb.DocType("Purchase Order") - item = frappe.qb.DocType("Item") - - query = ( - frappe.qb.from_(stock_entry) - .inner_join(se_detail) - .on(stock_entry.name == se_detail.parent) - .left_join(po_sub) - .on(stock_entry.work_order == po_sub.work_order) - .left_join(item) - .on(se_detail.item_code == item.item_code) - .left_join(po) - .on(po_sub.parent == po.name) - .select( - stock_entry.work_order, - (stock_entry.name).as_("stock_entry"), - (se_detail.name).as_("se_detail_name"), - (po_sub.parent).as_("purchase_order"), - se_detail.item_code, - se_detail.item_name, - se_detail.qty, - se_detail.transfer_qty, - se_detail.uom, - se_detail.stock_uom, - se_detail.conversion_factor, - se_detail.valuation_rate, - se_detail.paid_qty, - ) - .where(stock_entry.docstatus == 1) - .where(stock_entry.stock_entry_type == "Manufacture") - .where(stock_entry.posting_date >= from_date) - .where(stock_entry.posting_date <= to_date) - # .where(stock_entry.work_order.isin(work_orders)) - # .where(se_detail.item_code.isin(fg_items)) - .where(se_detail.is_finished_item == 1) - .where(se_detail.paid_qty < se_detail.qty) - .where(item.is_sub_contracted_item == 1) - .where(po.docstatus != 2) - ) - - return frappe.db.sql( - query.get_sql(), - { - "from_date": from_date, - "to_date": to_date, - # "work_orders": work_orders, - # "fg_items": fg_items, - }, - as_dict=1, - ) - - -@frappe.whitelist() -def fetch_supplier_warehouse(company, supplier): - return frappe.db.get_value( - "Subcontracting Default", - {"parent": supplier, "company": company}, - ["return_warehouse"], - ) +# Copyright (c) 2023, AgriTheory and Contributors +# See license.txt + +import datetime +import json + +import frappe +from erpnext.accounts.doctype.purchase_invoice.purchase_invoice import PurchaseInvoice +from frappe import _ +from frappe.utils.data import cint + + +class InventoryToolsPurchaseInvoice(PurchaseInvoice): + def validate_with_previous_doc(self): + """ + HASH: 804f1d4772e80994be17b276d9a0af7b66dde20d + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/accounts/doctype/purchase_invoice/purchase_invoice.py + METHOD: validate_with_previous_doc + """ + + config = { + "Purchase Order": { + "ref_dn_field": "purchase_order", + "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], + }, + "Purchase Order Item": { + "ref_dn_field": "po_detail", + "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + "Purchase Receipt": { + "ref_dn_field": "purchase_receipt", + "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], + }, + "Purchase Receipt Item": { + "ref_dn_field": "pr_detail", + "compare_fields": [["project", "="], ["item_code", "="], ["uom", "="]], + "is_child_table": True, + }, + } + + pos = list({r.purchase_order for r in self.items}) + if len(pos) == 1 and frappe.get_value("Purchase Order", pos[0], "multi_company_purchase_order"): + config["Purchase Order"]["compare_fields"] = [["currency", "="]] + + super(PurchaseInvoice, self).validate_with_previous_doc(config) + + if ( + cint(frappe.get_cached_value("Buying Settings", "None", "maintain_same_rate")) + and not self.is_return + and not self.is_internal_supplier + ): + self.validate_rate_with_reference_doc( + [ + ["Purchase Order", "purchase_order", "po_detail"], + ["Purchase Receipt", "purchase_receipt", "pr_detail"], + ] + ) + + def validate(self): + if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: + if not self.supplier_warehouse: + self.supplier_warehouse = fetch_supplier_warehouse(self.company, self.supplier) + self.validate_subcontracting_to_pay_qty() + return super().validate() + + def on_submit(self): + if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: + self.on_submit_save_se_paid_qty() + return super().on_submit() + + def on_cancel(self): + if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: + self.on_cancel_revert_se_paid_qty() + return super().on_cancel() + + def is_work_order_subcontracting_enabled(self): + settings = frappe.get_doc("Inventory Tools Settings", {"company": self.company}) + return bool(settings and settings.enable_work_order_subcontracting) + + def validate_subcontracting_to_pay_qty(self): + # Checks the qty the invoice will cover is not more than the outstanding qty + for subc in self.get("subcontracting"): + if subc.to_pay_qty > (subc.qty - subc.paid_qty): + frappe.throw( + _( + f"The To Pay Qty in Subcontracting Detail row {subc.idx} cannot be more than Total Qty less the already Paid Qty." + ) + ) + + def on_submit_save_se_paid_qty(self): + # Saves the invoiced quantity for the Stock Entry Detail row into paid_qty field + for ste in self.get("subcontracting"): + frappe.db.set_value( + "Stock Entry Detail", ste.se_detail_name, "paid_qty", ste.paid_qty + ste.to_pay_qty + ) + + def on_cancel_revert_se_paid_qty(self): + # Reduces the Stock Entry Detail item's paid_qty by the to_pay_qty amount in the invoice + for ste in self.get("subcontracting"): + cur_paid = frappe.db.get_value("Stock Entry Detail", ste.se_detail_name, "paid_qty") + frappe.db.set_value( + "Stock Entry Detail", ste.se_detail_name, "paid_qty", cur_paid - ste.to_pay_qty + ) + + +@frappe.whitelist() +def get_stock_entries(purchase_orders, from_date=None, to_date=None): + # # Commented code is useful if having PO and attaching WOs to them is enforced + # if isinstance(purchase_orders, str): + # purchase_orders = json.loads(purchase_orders) + + if not from_date: + from_date = datetime.date(1900, 1, 1) + + if not to_date: + to_date = datetime.date(2100, 12, 31) + + # work_orders, fg_items = [], set() + # for po in purchase_orders: + # work_orders.extend( + # frappe.get_all( + # "Purchase Order Subcontracting Detail", + # fields="work_order", + # filters={"parent": po}, + # pluck="work_order" + # ) + # ) + # for item in frappe.get_doc("Purchase Order", po).get("items"): + # fg_items.add(item.get("fg_item")) + + stock_entry = frappe.qb.DocType("Stock Entry") + se_detail = frappe.qb.DocType("Stock Entry Detail") + po_sub = frappe.qb.DocType("Purchase Order Subcontracting Detail") + po = frappe.qb.DocType("Purchase Order") + item = frappe.qb.DocType("Item") + + query = ( + frappe.qb.from_(stock_entry) + .inner_join(se_detail) + .on(stock_entry.name == se_detail.parent) + .left_join(po_sub) + .on(stock_entry.work_order == po_sub.work_order) + .left_join(item) + .on(se_detail.item_code == item.item_code) + .left_join(po) + .on(po_sub.parent == po.name) + .select( + stock_entry.work_order, + (stock_entry.name).as_("stock_entry"), + (se_detail.name).as_("se_detail_name"), + (po_sub.parent).as_("purchase_order"), + se_detail.item_code, + se_detail.item_name, + se_detail.qty, + se_detail.transfer_qty, + se_detail.uom, + se_detail.stock_uom, + se_detail.conversion_factor, + se_detail.valuation_rate, + se_detail.paid_qty, + ) + .where(stock_entry.docstatus == 1) + .where(stock_entry.stock_entry_type == "Manufacture") + .where(stock_entry.posting_date >= from_date) + .where(stock_entry.posting_date <= to_date) + # .where(stock_entry.work_order.isin(work_orders)) + # .where(se_detail.item_code.isin(fg_items)) + .where(se_detail.is_finished_item == 1) + .where(se_detail.paid_qty < se_detail.qty) + .where(item.is_sub_contracted_item == 1) + .where(po.docstatus != 2) + ) + + return frappe.db.sql( + query.get_sql(), + { + "from_date": from_date, + "to_date": to_date, + # "work_orders": work_orders, + # "fg_items": fg_items, + }, + as_dict=1, + ) + + +@frappe.whitelist() +def fetch_supplier_warehouse(company, supplier): + return frappe.db.get_value( + "Subcontracting Default", + {"parent": supplier, "company": company}, + ["return_warehouse"], + ) diff --git a/inventory_tools/inventory_tools/overrides/purchase_order.py b/inventory_tools/inventory_tools/overrides/purchase_order.py index 3a86972..e1d7a75 100644 --- a/inventory_tools/inventory_tools/overrides/purchase_order.py +++ b/inventory_tools/inventory_tools/overrides/purchase_order.py @@ -1,254 +1,274 @@ -# Copyright (c) 2023, AgriTheory and Contributors -# See license.txt - -import json -import types -from typing import Union - -import frappe -from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( - make_inter_company_purchase_invoice, -) -from erpnext.buying.doctype.purchase_order.purchase_order import ( - PurchaseOrder, - make_purchase_invoice, - make_purchase_receipt, -) -from erpnext.controllers.accounts_controller import get_default_taxes_and_charges -from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company -from frappe import _, throw - - -def _bypass(*args, **kwargs): - return - - -class InventoryToolsPurchaseOrder(PurchaseOrder): - def validate_with_previous_doc(self): - config = { - "Supplier Quotation": { - "ref_dn_field": "supplier_quotation", - "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], - }, - "Supplier Quotation Item": { - "ref_dn_field": "supplier_quotation_item", - "compare_fields": [ - ["project", "="], - ["item_code", "="], - ["uom", "="], - ["conversion_factor", "="], - ], - "is_child_table": True, - }, - "Material Request": { - "ref_dn_field": "material_request", - "compare_fields": [["company", "="]], - }, - "Material Request Item": { - "ref_dn_field": "material_request_item", - "compare_fields": [["project", "="], ["item_code", "="]], - "is_child_table": True, - }, - } - if self.multi_company_purchase_order: - config.pop("Material Request") - super(PurchaseOrder, self).validate_with_previous_doc(config) - - def validate_warehouse(self): - warehouses = list({d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)}) - - warehouses.extend( - list({d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)}) - ) - - warehouses.extend( - list({d.from_warehouse for d in self.get("items") if getattr(d, "from_warehouse", None)}) - ) - - for w in warehouses: - validate_disabled_warehouse(w) - if not self.multi_company_purchase_order: - validate_warehouse_company(w, self.company) - - def validate(self): - if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: - self.validate_subcontracting_fg_qty() - for row in self.subcontracting: - # TODO: set work order supplier to empty string in on_cancel - frappe.set_value("Work Order", row.work_order, "supplier", self.supplier) - - super().validate() - - def is_work_order_subcontracting_enabled(self): - settings = frappe.get_doc("Inventory Tools Settings", {"company": self.company}) - return bool(settings and settings.enable_work_order_subcontracting) - - def validate_subcontracting_fg_qty(self): - sub_wo = self.get("subcontracting") - if sub_wo: - items_fg_qty = sum(item.get("fg_item_qty") or 0 for item in self.get("items")) - subc_fg_qty = sum(row.get("fg_item_qty") or 0 for row in sub_wo) - # Check that the item finished good qty and the subcontracting qty are within the item's stock_qty field's precision number of decimals - precision = int(frappe.get_precision("Purchase Order Item", "stock_qty")) - diff = abs(items_fg_qty - subc_fg_qty) - if diff > (1 / (10**precision)): - frappe.msgprint( # Just a warning in the case: PO is created before WO's exist, several WOs needed to complete the work (each one has less than PO) - msg=_( - f"The total of Finished Good Item Qty for all items does not match the total Finished Good Item Qty in the Subcontracting table. There is a difference of {diff}." - ), - title=_("Warning"), - indicator="red", - ) - - -@frappe.whitelist() -def make_purchase_invoices(docname: str, rows: Union[list, str]) -> None: - rows = json.loads(rows) if isinstance(rows, str) else rows - doc = frappe.get_doc("Purchase Order", docname) - forwarding = frappe._dict() - for row in doc.items: - if row.name in rows: - if row.company in forwarding: - forwarding[row.company].append(row.name) - else: - forwarding[row.company] = [row.name] - - for company, rows in forwarding.items(): - pi = make_purchase_invoice(docname) - pi.company = company - pi.credit_to = frappe.get_value("Company", pi.company, "default_payable_account") - for row in pi.items: - if row.po_detail in rows: - continue - else: - pi.items.remove(row) - pi.save() - - -@frappe.whitelist() -def make_purchase_receipts(docname: str, rows: Union[list, str]) -> None: - rows = json.loads(rows) if isinstance(rows, str) else rows - doc = frappe.get_doc("Purchase Order", docname) - forwarding = frappe._dict() - for row in doc.items: - if row.name in rows: - if row.company in forwarding: - forwarding[row.company].append(row.name) - else: - forwarding[row.company] = [row.name] - - for company, rows in forwarding.items(): - pr = make_purchase_receipt(docname) - pr.company = company - for row in pr.items: - if row.purchase_order_item in rows: - continue - else: - pr.items.remove(row) - pr.save() - - -@frappe.whitelist() -def make_sales_invoices(docname: str, rows: Union[list, str]) -> None: - rows = json.loads(rows) if isinstance(rows, str) else rows - doc = frappe.get_doc("Purchase Order", docname) - buying_settings = frappe.get_doc("Buying Settings", "Buying Settings") - forwarding = frappe._dict() - - for row in doc.items: - if row.name in rows: - if row.company in forwarding: - forwarding[row.company].append(row.name) - else: - forwarding[row.company] = [row.name] - - for company, rows in forwarding.items(): - si = frappe.new_doc("Sales Invoice") - si.company = doc.company - si.customer = company - si.update_stock = 1 - si.selling_price_list = frappe.get_value("Price List", {"buying": 1, "selling": 1}) - for row in doc.items: - if row.name not in rows: - continue - si.append( - "items", - { - "item_code": row.item_code, - "item_name": row.item_name, - "item_description": row.description, - "qty": row.qty, - "uom": row.uom, - "rate": row.rate, - "purchase_order": doc.name, - "warehouse": buying_settings.aggregated_purchasing_warehouse, - "cost_center": frappe.get_value("Company", si.company, "cost_center"), - }, - ) - taxes_and_charges = get_default_taxes_and_charges( - "Sales Taxes and Charges Template", company=si.company - ) - si.taxes_and_charges = taxes_and_charges.get("taxes_and_charges") - for tax in taxes_and_charges.get("taxes"): - si.append("taxes", tax) - si.is_internal_supplier = 1 - si.bill_date = doc.schedule_date - si.set_total_in_words = types.MethodType(_bypass, si) - si.set_payment_schedule = types.MethodType(_bypass, si) - si.title = f"Transfer {doc.supplier} to {si.customer}" - si.save() - - pi = make_inter_company_purchase_invoice(si.name, None) - pi.update_stock = 1 - for row in si.items: - row.purchase_order = doc.name - row.warehouse = buying_settings.aggregated_purchasing_warehouse - pi.buying_price_list = si.selling_price_list - taxes_and_charges = get_default_taxes_and_charges( - "Purchase Taxes and Charges Template", company=pi.company - ) - pi.taxes_and_charges = taxes_and_charges.get("taxes_and_charges") - for tax in taxes_and_charges.get("taxes"): - pi.append("taxes", tax) - pi.is_internal_supplier = 1 - pi.inter_company_invoice_reference = si.name - pi.title = f"Transfer {doc.supplier} to {pi.company}" - pi.save() - - -@frappe.whitelist() -def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): - import erpnext.stock.get_item_details - - erpnext.stock.get_item_details.validate_item_details = validate_item_details - out = erpnext.stock.get_item_details.get_item_details( - args, doc, for_validate, overwrite_warehouse - ) - return out - - -@frappe.whitelist() -def validate_item_details(args, item): - if not args.company: - throw(_("Please specify Company")) - - settings = frappe.get_doc("Inventory Tools Settings", {"company": args.company}) - - from erpnext.stock.doctype.item.item import validate_end_of_life - - validate_end_of_life(item.name, item.end_of_life, item.disabled) - - if frappe.utils.cint(item.has_variants): - msg = f"Item {item.name} is a template, please select one of its variants" - - throw(_(msg), title=_("Template Item Selected")) - - elif args.transaction_type == "buying" and args.doctype != "Material Request": - if not (settings and settings.enable_work_order_subcontracting): - if args.get("is_subcontracted"): - if args.get("is_old_subcontracting_flow"): - if item.is_sub_contracted_item != 1: - throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) - else: - if item.is_stock_item: - throw(_("Item {0} must be a Non-Stock Item").format(item.name)) +# Copyright (c) 2023, AgriTheory and Contributors +# See license.txt + +import json +import types + +import frappe +from erpnext.accounts.doctype.sales_invoice.sales_invoice import ( + make_inter_company_purchase_invoice, +) +from erpnext.buying.doctype.purchase_order.purchase_order import ( + PurchaseOrder, + make_purchase_invoice, + make_purchase_receipt, +) +from erpnext.controllers.accounts_controller import get_default_taxes_and_charges +from erpnext.stock.utils import validate_disabled_warehouse, validate_warehouse_company +from frappe import _, throw + + +def _bypass(*args, **kwargs): + return + + +class InventoryToolsPurchaseOrder(PurchaseOrder): + def validate_with_previous_doc(self): + """ + HASH: 7cc5579ecaae99336b8164e7aeedc15b990d2257 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/buying/doctype/purchase_order/purchase_order.py + METHOD: validate_with_previous_doc + """ + + config = { + "Supplier Quotation": { + "ref_dn_field": "supplier_quotation", + "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], + }, + "Supplier Quotation Item": { + "ref_dn_field": "supplier_quotation_item", + "compare_fields": [ + ["project", "="], + ["item_code", "="], + ["uom", "="], + ["conversion_factor", "="], + ], + "is_child_table": True, + }, + "Material Request": { + "ref_dn_field": "material_request", + "compare_fields": [["company", "="]], + }, + "Material Request Item": { + "ref_dn_field": "material_request_item", + "compare_fields": [["project", "="], ["item_code", "="]], + "is_child_table": True, + }, + } + if self.multi_company_purchase_order: + config.pop("Material Request") + super(PurchaseOrder, self).validate_with_previous_doc(config) + + def validate_warehouse(self): + warehouses = list({d.warehouse for d in self.get("items") if getattr(d, "warehouse", None)}) + + warehouses.extend( + list({d.target_warehouse for d in self.get("items") if getattr(d, "target_warehouse", None)}) + ) + + warehouses.extend( + list({d.from_warehouse for d in self.get("items") if getattr(d, "from_warehouse", None)}) + ) + + for w in warehouses: + validate_disabled_warehouse(w) + if not self.multi_company_purchase_order: + validate_warehouse_company(w, self.company) + + def validate(self): + if self.is_work_order_subcontracting_enabled() and self.is_subcontracted: + self.validate_subcontracting_fg_qty() + for row in self.subcontracting: + # TODO: set work order supplier to empty string in on_cancel + frappe.set_value("Work Order", row.work_order, "supplier", self.supplier) + + super().validate() + + def is_work_order_subcontracting_enabled(self): + settings = frappe.get_doc("Inventory Tools Settings", {"company": self.company}) + return bool(settings and settings.enable_work_order_subcontracting) + + def validate_subcontracting_fg_qty(self): + sub_wo = self.get("subcontracting") + if sub_wo: + items_fg_qty = sum(item.get("fg_item_qty") or 0 for item in self.get("items")) + subc_fg_qty = sum(row.get("fg_item_qty") or 0 for row in sub_wo) + # Check that the item finished good qty and the subcontracting qty are within the item's stock_qty field's precision number of decimals + precision = int(frappe.get_precision("Purchase Order Item", "stock_qty")) + diff = abs(items_fg_qty - subc_fg_qty) + if diff > (1 / (10**precision)): + frappe.msgprint( # Just a warning in the case: PO is created before WO's exist, several WOs needed to complete the work (each one has less than PO) + msg=_( + f"The total of Finished Good Item Qty for all items does not match the total Finished Good Item Qty in the Subcontracting table. There is a difference of {diff}." + ), + title=_("Warning"), + indicator="red", + ) + + +@frappe.whitelist() +def make_purchase_invoices(docname: str, rows: list | str) -> None: + rows = json.loads(rows) if isinstance(rows, str) else rows + doc = frappe.get_doc("Purchase Order", docname) + forwarding = frappe._dict() + for row in doc.items: + if row.name in rows: + if row.company in forwarding: + forwarding[row.company].append(row.name) + else: + forwarding[row.company] = [row.name] + + for company, rows in forwarding.items(): + pi = make_purchase_invoice(docname) + pi.company = company + pi.credit_to = frappe.get_value("Company", pi.company, "default_payable_account") + for row in pi.items: + if row.po_detail in rows: + continue + else: + pi.items.remove(row) + pi.save() + + +@frappe.whitelist() +def make_purchase_receipts(docname: str, rows: list | str) -> None: + rows = json.loads(rows) if isinstance(rows, str) else rows + doc = frappe.get_doc("Purchase Order", docname) + forwarding = frappe._dict() + for row in doc.items: + if row.name in rows: + if row.company in forwarding: + forwarding[row.company].append(row.name) + else: + forwarding[row.company] = [row.name] + + for company, rows in forwarding.items(): + pr = make_purchase_receipt(docname) + pr.company = company + for row in pr.items: + if row.purchase_order_item in rows: + continue + else: + pr.items.remove(row) + pr.save() + + +@frappe.whitelist() +def make_sales_invoices(docname: str, rows: list | str) -> None: + rows = json.loads(rows) if isinstance(rows, str) else rows + doc = frappe.get_doc("Purchase Order", docname) + buying_settings = frappe.get_doc("Buying Settings", "Buying Settings") + forwarding = frappe._dict() + + for row in doc.items: + if row.name in rows: + if row.company in forwarding: + forwarding[row.company].append(row.name) + else: + forwarding[row.company] = [row.name] + + for company, rows in forwarding.items(): + si = frappe.new_doc("Sales Invoice") + si.company = doc.company + si.customer = company + si.update_stock = 1 + si.selling_price_list = frappe.get_value("Price List", {"buying": 1, "selling": 1}) + for row in doc.items: + if row.name not in rows: + continue + si.append( + "items", + { + "item_code": row.item_code, + "item_name": row.item_name, + "item_description": row.description, + "qty": row.qty, + "uom": row.uom, + "rate": row.rate, + "purchase_order": doc.name, + "warehouse": buying_settings.aggregated_purchasing_warehouse, + "cost_center": frappe.get_value("Company", si.company, "cost_center"), + }, + ) + taxes_and_charges = get_default_taxes_and_charges( + "Sales Taxes and Charges Template", company=si.company + ) + si.taxes_and_charges = taxes_and_charges.get("taxes_and_charges") + for tax in taxes_and_charges.get("taxes"): + si.append("taxes", tax) + si.is_internal_supplier = 1 + si.bill_date = doc.schedule_date + si.set_total_in_words = types.MethodType(_bypass, si) + si.set_payment_schedule = types.MethodType(_bypass, si) + si.title = f"Transfer {doc.supplier} to {si.customer}" + si.save() + + pi = make_inter_company_purchase_invoice(si.name, None) + pi.update_stock = 1 + for row in si.items: + row.purchase_order = doc.name + row.warehouse = buying_settings.aggregated_purchasing_warehouse + pi.buying_price_list = si.selling_price_list + taxes_and_charges = get_default_taxes_and_charges( + "Purchase Taxes and Charges Template", company=pi.company + ) + pi.taxes_and_charges = taxes_and_charges.get("taxes_and_charges") + for tax in taxes_and_charges.get("taxes"): + pi.append("taxes", tax) + pi.is_internal_supplier = 1 + pi.inter_company_invoice_reference = si.name + pi.title = f"Transfer {doc.supplier} to {pi.company}" + pi.save() + + +@frappe.whitelist() +def get_item_details(args, doc=None, for_validate=False, overwrite_warehouse=True): + """ + HASH: d396c18689e530d3f11a791ef1064c2d9775466e + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/get_item_details.py + METHOD: get_item_details + """ + + import erpnext.stock.get_item_details + + erpnext.stock.get_item_details.validate_item_details = validate_item_details + out = erpnext.stock.get_item_details.get_item_details( + args, doc, for_validate, overwrite_warehouse + ) + return out + + +@frappe.whitelist() +def validate_item_details(args, item): + """ + HASH: d396c18689e530d3f11a791ef1064c2d9775466e + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/get_item_details.py + METHOD: validate_item_details + """ + + if not args.company: + throw(_("Please specify Company")) + + settings = frappe.get_doc("Inventory Tools Settings", {"company": args.company}) + + from erpnext.stock.doctype.item.item import validate_end_of_life + + validate_end_of_life(item.name, item.end_of_life, item.disabled) + + if frappe.utils.cint(item.has_variants): + msg = f"Item {item.name} is a template, please select one of its variants" + + throw(_(msg), title=_("Template Item Selected")) + + elif args.transaction_type == "buying" and args.doctype != "Material Request": + if not (settings and settings.enable_work_order_subcontracting): + if args.get("is_subcontracted"): + if args.get("is_old_subcontracting_flow"): + if item.is_sub_contracted_item != 1: + throw(_("Item {0} must be a Sub-contracted Item").format(item.name)) + else: + if item.is_stock_item: + throw(_("Item {0} must be a Non-Stock Item").format(item.name)) diff --git a/inventory_tools/inventory_tools/overrides/purchase_receipt.py b/inventory_tools/inventory_tools/overrides/purchase_receipt.py index b6a2d2c..e4e09b3 100644 --- a/inventory_tools/inventory_tools/overrides/purchase_receipt.py +++ b/inventory_tools/inventory_tools/overrides/purchase_receipt.py @@ -1,37 +1,45 @@ -# Copyright (c) 2023, AgriTheory and Contributors -# See license.txt - -import json - -import frappe -from erpnext.stock.doctype.purchase_receipt.purchase_receipt import PurchaseReceipt -from frappe.utils.data import cint - - -class InventoryToolsPurchaseReceipt(PurchaseReceipt): - def validate_with_previous_doc(self): - config = { - "Purchase Order": { - "ref_dn_field": "purchase_order", - "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], - }, - "Purchase Order Item": { - "ref_dn_field": "purchase_order_item", - "compare_fields": [["project", "="], ["uom", "="], ["item_code", "="]], - "is_child_table": True, - "allow_duplicate_prev_row_id": True, - }, - } - pos = list({r.purchase_order for r in self.items}) - if len(pos) == 1 and frappe.get_value("Purchase Order", pos[0], "multi_company_purchase_order"): - config["Purchase Order"]["compare_fields"] = [["supplier", "="], ["currency", "="]] - super(PurchaseReceipt, self).validate_with_previous_doc(config) - - if ( - cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) - and not self.is_return - and not self.is_internal_supplier - ): - self.validate_rate_with_reference_doc( - [["Purchase Order", "purchase_order", "purchase_order_item"]] - ) +# Copyright (c) 2023, AgriTheory and Contributors +# See license.txt + +import json + +import frappe +from erpnext.stock.doctype.purchase_receipt.purchase_receipt import PurchaseReceipt +from frappe.utils.data import cint + + +class InventoryToolsPurchaseReceipt(PurchaseReceipt): + def validate_with_previous_doc(self): + """ + HASH: 427439c3f19bfd1401628cf39a30ddd291dbfec7 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/doctype/purchase_receipt/purchase_receipt.py + METHOD: validate_with_previous_doc + """ + + config = { + "Purchase Order": { + "ref_dn_field": "purchase_order", + "compare_fields": [["supplier", "="], ["company", "="], ["currency", "="]], + }, + "Purchase Order Item": { + "ref_dn_field": "purchase_order_item", + "compare_fields": [["project", "="], ["uom", "="], ["item_code", "="]], + "is_child_table": True, + "allow_duplicate_prev_row_id": True, + }, + } + + pos = list({r.purchase_order for r in self.items}) + if len(pos) == 1 and frappe.get_value("Purchase Order", pos[0], "multi_company_purchase_order"): + config["Purchase Order"]["compare_fields"] = [["supplier", "="], ["currency", "="]] + super(PurchaseReceipt, self).validate_with_previous_doc(config) + + if ( + cint(frappe.db.get_single_value("Buying Settings", "maintain_same_rate")) + and not self.is_return + and not self.is_internal_supplier + ): + self.validate_rate_with_reference_doc( + [["Purchase Order", "purchase_order", "purchase_order_item"]] + ) diff --git a/inventory_tools/inventory_tools/overrides/stock_entry.py b/inventory_tools/inventory_tools/overrides/stock_entry.py index 89f894a..14e7a37 100644 --- a/inventory_tools/inventory_tools/overrides/stock_entry.py +++ b/inventory_tools/inventory_tools/overrides/stock_entry.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe from erpnext.stock.doctype.stock_entry.stock_entry import FinishedGoodError, StockEntry from frappe import _ @@ -9,12 +12,18 @@ class InventoryToolsStockEntry(StockEntry): def check_if_operations_completed(self): """ + HASH: b5a2e5a375b39edf5f22a4d2276faeb3a4f85ced + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/doctype/stock_entry/stock_entry.py + METHOD: check_if_operations_completed + Original code checks that the stock entry amount plus what's already produced in the WO is not larger than any operation's completed quantity (plus the overallowance amount). Since customized code rewires so stock entries happen via a Job Card, the function now checks that the stock entry amount plus what's already been produced in the WO is not greater than the amount to be manufactured plus the overallowance amount. """ + prod_order = frappe.get_doc("Work Order", self.work_order) allowance_percentage = get_allowance_percentage(self.company, self.bom_no) @@ -45,10 +54,16 @@ def check_if_operations_completed(self): def validate_finished_goods(self): """ + HASH: b5a2e5a375b39edf5f22a4d2276faeb3a4f85ced + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/doctype/stock_entry/stock_entry.py + METHOD: validate_finished_goods + 1. Check if FG exists (mfg, repack) 2. Check if Multiple FG Items are present (mfg) 3. Check FG Item and Qty against WO if present (mfg) """ + production_item, wo_qty, finished_items = None, 0, [] wo_details = frappe.db.get_value("Work Order", self.work_order, ["production_item", "qty"]) @@ -79,7 +94,7 @@ def validate_finished_goods(self): if not finished_items: frappe.throw( - msg=_("There must be atleast 1 Finished Good in this Stock Entry").format(self.name), + msg=_("There must be at least 1 Finished Good in this Stock Entry").format(self.name), title=_("Missing Finished Good"), exc=FinishedGoodError, ) @@ -105,9 +120,15 @@ def validate_finished_goods(self): def get_pending_raw_materials(self, backflush_based_on=None): """ + HASH: b5a2e5a375b39edf5f22a4d2276faeb3a4f85ced + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/stock/doctype/stock_entry/stock_entry.py + METHOD: get_pending_raw_materials + issue (item quantity) that is pending to issue or desire to transfer, whichever is less """ + item_dict = self.get_pro_order_required_items(backflush_based_on) max_qty = flt(self.pro_doc.qty) diff --git a/inventory_tools/inventory_tools/overrides/warehouse.py b/inventory_tools/inventory_tools/overrides/warehouse.py index dc021b5..bbcab3c 100644 --- a/inventory_tools/inventory_tools/overrides/warehouse.py +++ b/inventory_tools/inventory_tools/overrides/warehouse.py @@ -1,77 +1,84 @@ -# Copyright (c) 2023, AgriTheory and contributors -# For license information, please see license.txt - -import frappe -from frappe.desk.reportview import get_filters_cond, get_match_cond -from frappe.desk.search import search_link - - -@frappe.whitelist() -def update_warehouse_path(doc, method=None) -> None: - if not frappe.db.exists("Inventory Tools Settings", doc.company): - return - warehouse_path = frappe.db.get_value( - "Inventory Tools Settings", doc.company, "update_warehouse_path" - ) - if not warehouse_path: - return - - def get_parents(doc): - parents = [doc.warehouse_name] - parent = doc.parent_warehouse - while parent: - parent_name = frappe.get_value("Warehouse", parent, "warehouse_name") - if parent_name != "All Warehouses": - parents.append(parent_name) - parent = frappe.get_value("Warehouse", parent, "parent_warehouse") - else: - break - return parents - - def _update_warehouse_path(doc): - parents = get_parents(doc) - if parents: - if len(parents) > 1: - if parents[1] in parents[0]: - parents[0] = parents[0].replace(parents[1], "") - parents[0] = parents[0].replace(" - ", "") - return " \u21D2 ".join(parents[::-1]) - else: - return "" - - doc.warehouse_path = _update_warehouse_path(doc) - - -@frappe.whitelist() -def warehouse_query(doctype, txt, searchfield, start, page_len, filters): - company = frappe.defaults.get_defaults().get("company") - if not company: - return search_link(doctype, txt, searchfield, start, page_len, filters) - if not frappe.db.exists("Inventory Tools Settings", company) and frappe.db.get_value( - "Inventory Tools Settings", company, "update_warehouse_path" - ): - return search_link(doctype, txt, searchfield, start, page_len, filters) - else: - doctype = "Warehouse" - conditions = [] - searchfields = frappe.get_meta(doctype).get_search_fields() - searchfields.remove("name") - searchfields = ["name"] + searchfields - - return frappe.db.sql( - f"""SELECT {', '.join(searchfields)} - FROM `tabWarehouse` - WHERE `tabWarehouse`.`{searchfield}` like %(txt)s - {get_filters_cond(doctype, filters, conditions).replace("%", "%%")} - {get_match_cond(doctype).replace("%", "%%")} - ORDER BY - IF(LOCATE(%(_txt)s, name), LOCATE(%(_txt)s, name), 99999), - idx DESC, name - LIMIT %(start)s, %(page_len)s""", - { - "txt": "%" + txt + "%", - "_txt": txt.replace("%", ""), - "start": start or 0, - "page_len": page_len or 20, - }, - ) +# Copyright (c) 2023, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +from frappe.desk.reportview import get_filters_cond, get_match_cond +from frappe.desk.search import search_link + + +@frappe.whitelist() +def update_warehouse_path(doc, method=None) -> None: + if not frappe.db.exists("Inventory Tools Settings", doc.company): + return + warehouse_path = frappe.db.get_value( + "Inventory Tools Settings", doc.company, "update_warehouse_path" + ) + if not warehouse_path: + return + + def get_parents(doc): + parents = [doc.warehouse_name] + parent = doc.parent_warehouse + while parent: + parent_name = frappe.get_value("Warehouse", parent, "warehouse_name") + if parent_name != "All Warehouses": + parents.append(parent_name) + parent = frappe.get_value("Warehouse", parent, "parent_warehouse") + else: + break + return parents + + def _update_warehouse_path(doc): + parents = get_parents(doc) + if parents: + if len(parents) > 1: + if parents[1] in parents[0]: + parents[0] = parents[0].replace(parents[1], "") + parents[0] = parents[0].replace(" - ", "") + return " \u21D2 ".join(parents[::-1]) + else: + return "" + + doc.warehouse_path = _update_warehouse_path(doc) + + +@frappe.whitelist() +def warehouse_query(doctype, txt, searchfield, start, page_len, filters): + """ + HASH: 2c9b3908dd61ae51e9c455dc0b4b03fd69ea15c0 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/controllers/queries.py + METHOD: warehouse_query + """ + + company = frappe.defaults.get_defaults().get("company") + if not company: + return search_link(doctype, txt, searchfield, start, page_len, filters) + if not frappe.db.exists("Inventory Tools Settings", company) and frappe.db.get_value( + "Inventory Tools Settings", company, "update_warehouse_path" + ): + return search_link(doctype, txt, searchfield, start, page_len, filters) + else: + doctype = "Warehouse" + conditions = [] + searchfields = frappe.get_meta(doctype).get_search_fields() + searchfields.remove("name") + searchfields = ["name"] + searchfields + + return frappe.db.sql( + f"""SELECT {', '.join(searchfields)} + FROM `tabWarehouse` + WHERE `tabWarehouse`.`{searchfield}` like %(txt)s + {get_filters_cond(doctype, filters, conditions).replace("%", "%%")} + {get_match_cond(doctype).replace("%", "%%")} + ORDER BY + IF(LOCATE(%(_txt)s, name), LOCATE(%(_txt)s, name), 99999), + idx DESC, name + LIMIT %(start)s, %(page_len)s""", + { + "txt": "%" + txt + "%", + "_txt": txt.replace("%", ""), + "start": start or 0, + "page_len": page_len or 20, + }, + ) diff --git a/inventory_tools/inventory_tools/overrides/work_order.py b/inventory_tools/inventory_tools/overrides/work_order.py index c093fa6..8e6f60d 100644 --- a/inventory_tools/inventory_tools/overrides/work_order.py +++ b/inventory_tools/inventory_tools/overrides/work_order.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe from erpnext.manufacturing.doctype.work_order.work_order import ( OverProductionError, @@ -8,11 +11,18 @@ make_stock_entry as _make_stock_entry, ) from frappe import _ -from frappe.utils import flt, get_link_to_form, getdate, nowdate +from frappe.utils import cint, flt, get_link_to_form, getdate class InventoryToolsWorkOrder(WorkOrder): def onload(self): + """ + HASH: ef2553edf967612bdbf580357d5886c6afacaea2 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/work_order/work_order.py + METHOD: onload + """ + ms = frappe.get_doc("Manufacturing Settings") self.set_onload("material_consumption", ms.material_consumption) self.set_onload("backflush_raw_materials_based_on", ms.backflush_raw_materials_based_on) @@ -102,8 +112,16 @@ def create_job_card(self): return super().create_job_card() def update_work_order_qty(self): - """Update **Manufactured Qty** and **Material Transferred for Qty** in Work Order - based on Stock Entry""" + """ + HASH: ef2553edf967612bdbf580357d5886c6afacaea2 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/work_order/work_order.py + METHOD: update_work_order_qty + + Update **Manufactured Qty** and **Material Transferred for Qty** in Work Order + based on Stock Entry + """ + allowance_percentage = get_allowance_percentage(self.company, self.bom_no) for purpose, fieldname in ( @@ -140,6 +158,13 @@ def update_work_order_qty(self): self.update_production_plan_status() def update_operation_status(self): + """ + HASH: ef2553edf967612bdbf580357d5886c6afacaea2 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/work_order/work_order.py + METHOD: update_operation_status + """ + allowance_percentage = get_allowance_percentage(self.company, self.bom_no) max_allowed_qty_for_wo = flt(self.qty) + (allowance_percentage / 100 * flt(self.qty)) @@ -156,10 +181,31 @@ def update_operation_status(self): frappe.throw(_("Completed Qty cannot be greater than 'Qty to Manufacture'")) def validate_qty(self): + """ + HASH: ef2553edf967612bdbf580357d5886c6afacaea2 + REPO: https://github.com/frappe/erpnext/ + PATH: erpnext/manufacturing/doctype/work_order/work_order.py + METHOD: validate_qty + """ if not self.qty > 0: frappe.throw(_("Quantity to Manufacture must be greater than 0.")) + if ( + self.stock_uom + and frappe.get_cached_value("UOM", self.stock_uom, "must_be_whole_number") + and abs(cint(self.qty) - flt(self.qty, self.precision("qty"))) > 0.0000001 + ): + frappe.throw( + _( + "Qty To Manufacture ({0}) cannot be a fraction for the UOM {2}. To allow this, disable '{1}' in the UOM {2}." + ).format( + flt(self.qty, self.precision("qty")), + frappe.bold(_("Must be Whole Number")), + frappe.bold(self.stock_uom), + ), + ) + if ( self.production_plan and self.production_plan_item diff --git a/inventory_tools/inventory_tools/overrides/workstation.py b/inventory_tools/inventory_tools/overrides/workstation.py index 2d3689f..4bfe8bf 100644 --- a/inventory_tools/inventory_tools/overrides/workstation.py +++ b/inventory_tools/inventory_tools/overrides/workstation.py @@ -1,26 +1,27 @@ -import json +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt import frappe """ This function fetch workstation of the document operation. - In Operation you can select multiple workstations in Alternative Workstation field. - In the Work Order, Operation table, and Jobcard, there exists an operation field. - When selecting an operation, this function is responsible for fetching the workstations + In Operation you can select multiple workstations in Alternative Workstation field. + In the Work Order, Operation table, and Jobcard, there exists an operation field. + When selecting an operation, this function is responsible for fetching the workstations both from the Alternative Workstation and the default workstation. - + Example : Operation : Cool Pie Op - Default Workstation: Cooling Racks Station - Alternative Workstation: - ````````````````````````````````````````````````````` - : Cooling Station , Refrigerator Station , : - : : - : : - `````````````````````````````````````````````````````` - In work order and job card when you select operation Cool Pie Op then you find below workstation in workstation field - : Cooling Station : - : Refrigerator Station : - : Cooling Racks Station : + Default Workstation: Cooling Racks Station + Alternative Workstation: + ````````````````````````````````````````````````````` + : Cooling Station , Refrigerator Station , : + : : + : : + `````````````````````````````````````````````````````` + In work order and job card when you select operation Cool Pie Op then you find below workstation in workstation field + : Cooling Station : + : Refrigerator Station : + : Cooling Racks Station : """ diff --git a/inventory_tools/inventory_tools/report/__init__.py b/inventory_tools/inventory_tools/report/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/report/__init__.py +++ b/inventory_tools/inventory_tools/report/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/report/manufacturing_capacity/__init__.py b/inventory_tools/inventory_tools/report/manufacturing_capacity/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/report/manufacturing_capacity/__init__.py +++ b/inventory_tools/inventory_tools/report/manufacturing_capacity/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/report/manufacturing_capacity/manufacturing_capacity.json b/inventory_tools/inventory_tools/report/manufacturing_capacity/manufacturing_capacity.json index 147e169..9e4fa89 100644 --- a/inventory_tools/inventory_tools/report/manufacturing_capacity/manufacturing_capacity.json +++ b/inventory_tools/inventory_tools/report/manufacturing_capacity/manufacturing_capacity.json @@ -1,30 +1,30 @@ { - "add_total_row": 0, - "columns": [], - "creation": "2024-02-16 12:17:16.951700", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "modified": "2024-02-16 12:17:16.951700", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Manufacturing Capacity", - "owner": "Administrator", - "prepared_report": 0, - "query": "", - "ref_doctype": "BOM", - "report_name": "Manufacturing Capacity", - "report_type": "Script Report", - "roles": [ - { - "role": "Manufacturing Manager" - }, - { - "role": "Manufacturing User" - } - ] -} \ No newline at end of file + "add_total_row": 0, + "columns": [], + "creation": "2024-02-16 12:17:16.951700", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2024-02-16 12:17:16.951700", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Manufacturing Capacity", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "BOM", + "report_name": "Manufacturing Capacity", + "report_type": "Script Report", + "roles": [ + { + "role": "Manufacturing Manager" + }, + { + "role": "Manufacturing User" + } + ] +} diff --git a/inventory_tools/inventory_tools/report/material_demand/__init__.py b/inventory_tools/inventory_tools/report/material_demand/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/report/material_demand/__init__.py +++ b/inventory_tools/inventory_tools/report/material_demand/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/report/quotation_demand/__init__.py b/inventory_tools/inventory_tools/report/quotation_demand/__init__.py index e69de29..6b9109e 100644 --- a/inventory_tools/inventory_tools/report/quotation_demand/__init__.py +++ b/inventory_tools/inventory_tools/report/quotation_demand/__init__.py @@ -0,0 +1,2 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt diff --git a/inventory_tools/inventory_tools/report/quotation_demand/quotation_demand.json b/inventory_tools/inventory_tools/report/quotation_demand/quotation_demand.json index f40127f..1e7c16d 100644 --- a/inventory_tools/inventory_tools/report/quotation_demand/quotation_demand.json +++ b/inventory_tools/inventory_tools/report/quotation_demand/quotation_demand.json @@ -1,35 +1,35 @@ { - "add_total_row": 0, - "columns": [], - "creation": "2024-04-11 07:05:15.429002", - "disable_prepared_report": 0, - "disabled": 0, - "docstatus": 0, - "doctype": "Report", - "filters": [], - "idx": 0, - "is_standard": "Yes", - "modified": "2024-04-11 07:05:15.429002", - "modified_by": "Administrator", - "module": "Inventory Tools", - "name": "Quotation Demand", - "owner": "Administrator", - "prepared_report": 0, - "ref_doctype": "Quotation", - "report_name": "Quotation Demand", - "report_type": "Script Report", - "roles": [ - { - "role": "Sales User" - }, - { - "role": "Sales Manager" - }, - { - "role": "Maintenance Manager" - }, - { - "role": "Maintenance User" - } - ] -} \ No newline at end of file + "add_total_row": 0, + "columns": [], + "creation": "2024-04-11 07:05:15.429002", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "modified": "2024-04-11 07:05:15.429002", + "modified_by": "Administrator", + "module": "Inventory Tools", + "name": "Quotation Demand", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Quotation", + "report_name": "Quotation Demand", + "report_type": "Script Report", + "roles": [ + { + "role": "Sales User" + }, + { + "role": "Sales Manager" + }, + { + "role": "Maintenance Manager" + }, + { + "role": "Maintenance User" + } + ] +} diff --git a/inventory_tools/public/js/custom/utils.js b/inventory_tools/public/js/custom/utils.js index 6480ba1..5651331 100644 --- a/inventory_tools/public/js/custom/utils.js +++ b/inventory_tools/public/js/custom/utils.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.provide('frappe.query_report') // required to add onload triggers to report view diff --git a/inventory_tools/public/js/inventory_tools.bundle.js b/inventory_tools/public/js/inventory_tools.bundle.js index 6992c47..07e1c36 100644 --- a/inventory_tools/public/js/inventory_tools.bundle.js +++ b/inventory_tools/public/js/inventory_tools.bundle.js @@ -1,2 +1,5 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + import './uom_enforcement.js' import './custom/utils.js' diff --git a/inventory_tools/public/js/job_card_custom.js b/inventory_tools/public/js/job_card_custom.js index b46df09..1770e9a 100644 --- a/inventory_tools/public/js/job_card_custom.js +++ b/inventory_tools/public/js/job_card_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Job Card', { refresh: frm => { if (frm.doc.operation) { diff --git a/inventory_tools/public/js/operation_custom.js b/inventory_tools/public/js/operation_custom.js index 2091221..8d2dace 100644 --- a/inventory_tools/public/js/operation_custom.js +++ b/inventory_tools/public/js/operation_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Operation', { refresh: frm => { get_filter_workstations(frm) diff --git a/inventory_tools/public/js/purchase_invoice_custom.js b/inventory_tools/public/js/purchase_invoice_custom.js index 7c1c63a..0c66d82 100644 --- a/inventory_tools/public/js/purchase_invoice_custom.js +++ b/inventory_tools/public/js/purchase_invoice_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Purchase Invoice', { refresh: function (frm) { show_subcontracting_fields(frm) diff --git a/inventory_tools/public/js/purchase_order_custom.js b/inventory_tools/public/js/purchase_order_custom.js index 3142a70..cf956cc 100644 --- a/inventory_tools/public/js/purchase_order_custom.js +++ b/inventory_tools/public/js/purchase_order_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Purchase Order', { refresh: frm => { show_subcontracting_fields(frm) diff --git a/inventory_tools/public/js/stock_entry_custom.js b/inventory_tools/public/js/stock_entry_custom.js index a850271..22215b2 100644 --- a/inventory_tools/public/js/stock_entry_custom.js +++ b/inventory_tools/public/js/stock_entry_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Stock Entry', { on_submit: frm => { if (frm.doc.docstatus === 1) { diff --git a/inventory_tools/public/js/uom_enforcement.js b/inventory_tools/public/js/uom_enforcement.js index cd449d3..82d8b69 100644 --- a/inventory_tools/public/js/uom_enforcement.js +++ b/inventory_tools/public/js/uom_enforcement.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + // Copyright(c) 2023, AgriTheory and contributors // For license information, please see license.txt diff --git a/inventory_tools/public/js/work_order_custom.js b/inventory_tools/public/js/work_order_custom.js index 7a87ae6..2272d94 100644 --- a/inventory_tools/public/js/work_order_custom.js +++ b/inventory_tools/public/js/work_order_custom.js @@ -1,3 +1,6 @@ +// Copyright (c) 2024, AgriTheory and contributors +// For license information, please see license.txt + frappe.ui.form.on('Work Order', { setup: frm => { frm.custom_make_buttons = { diff --git a/inventory_tools/tests/conftest.py b/inventory_tools/tests/conftest.py index ac79821..0dadb82 100644 --- a/inventory_tools/tests/conftest.py +++ b/inventory_tools/tests/conftest.py @@ -1,42 +1,45 @@ -import json -from pathlib import Path -from unittest.mock import MagicMock - -import frappe -import pytest -from frappe.utils import get_bench_path - - -def _get_logger(*args, **kwargs): - from frappe.utils.logger import get_logger - - return get_logger( - module=None, - with_more_info=False, - allow_site=True, - filter=None, - max_size=100_000, - file_count=20, - stream_only=True, - ) - - -@pytest.fixture(scope="module") -def monkeymodule(): - with pytest.MonkeyPatch.context() as mp: - yield mp - - -@pytest.fixture(scope="session", autouse=True) -def db_instance(): - frappe.logger = _get_logger - - currentsite = "test_site" - sites = Path(get_bench_path()) / "sites" - if (sites / "common_site_config.json").is_file(): - currentsite = json.loads((sites / "common_site_config.json").read_text()).get("default_site") - - frappe.init(site=currentsite, sites_path=sites) - frappe.connect() - frappe.db.commit = MagicMock() - yield frappe.db +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import json +from pathlib import Path +from unittest.mock import MagicMock + +import frappe +import pytest +from frappe.utils import get_bench_path + + +def _get_logger(*args, **kwargs): + from frappe.utils.logger import get_logger + + return get_logger( + module=None, + with_more_info=False, + allow_site=True, + filter=None, + max_size=100_000, + file_count=20, + stream_only=True, + ) + + +@pytest.fixture(scope="module") +def monkeymodule(): + with pytest.MonkeyPatch.context() as mp: + yield mp + + +@pytest.fixture(scope="session", autouse=True) +def db_instance(): + frappe.logger = _get_logger + + currentsite = "test_site" + sites = Path(get_bench_path()) / "sites" + if (sites / "common_site_config.json").is_file(): + currentsite = json.loads((sites / "common_site_config.json").read_text()).get("default_site") + + frappe.init(site=currentsite, sites_path=sites) + frappe.connect() + frappe.db.commit = MagicMock() + yield frappe.db diff --git a/inventory_tools/tests/fixtures.py b/inventory_tools/tests/fixtures.py index aead89c..6758a16 100644 --- a/inventory_tools/tests/fixtures.py +++ b/inventory_tools/tests/fixtures.py @@ -1,1008 +1,1011 @@ -suppliers = [ - ( - "Freedom Provisions", - None, - None, - None, - "Net 30", - { - "address_line1": "16 Margrave", - "city": "Carlisle", - "state": "NH", - "country": "United States", - "pincode": "57173", - }, - ), - ( - "Unity Bakery Supply", - None, - None, - None, - "Net 30", - { - "address_line1": "34 Pinar St", - "city": "Unity", - "state": "RI", - "country": "United States", - "pincode": "34291", - }, - ), - ( - "Chelsea Fruit Co", - None, - None, - None, - "Net 30", - { - "address_line1": "67C Sweeny Street", - "city": "Chelsea", - "state": "MA", - "country": "United States", - "pincode": "89077", - }, - ), - ( - "Credible Contract Baking", - None, - None, - None, - "Net 30", - { - "address_line1": "4 Crumb Circle", - "city": "Belmont", - "state": "MA", - "country": "United States", - "pincode": "89074", - }, - ), - ( - "Southern Fruit Supply", - None, - None, - None, - "Net 30", - { - "address_line1": "10001 Pineapple Way", - "city": "Largo", - "state": "TX", - "country": "United States", - "pincode": "89574", - }, - ), -] - -workstations = [ - ("Mix Pie Crust Station", "20"), - ("Roll Pie Crust Station", "20"), - ("Make Pie Filling Station", "20"), - ("Cooling Station", "100"), - ("Box Pie Station", "100"), - ("Baking Station", "20"), - ("Assemble Pie Station", "20"), - ("Mix Pie Filling Station", "20"), - ("Packaging Station", "2"), - ("Food Prep Table 2", "10"), - ("Food Prep Table 1", "5"), - ("Range Station", "20"), - ("Cooling Racks Station", "80"), - ("Refrigerator Station", "200"), - ("Oven Station", "20"), - ("Mixer Station", "10"), -] - -operations = [ - ( - "Gather Pie Crust Ingredients", - "Food Prep Table 2", - "5", - """- Remove flour, salt, and a pie tins from store room - - Remove butter and ice water from refrigerator - - Place ingredients at workstation - - Measure amounts for batch size into mixing bowl""", - ["Food Prep Table 1"], - ), - ( - "Gather Pie Filling Ingredients", - "Food Prep Table 1", - "5", - """- Remove fruit and butter from refrigerator - - Remove sugar and cornstarch - - Get water from sink - - Measure ingredients and place in pot, excluding 1/4 of fruit and butter""", - ["Food Prep Table 2"], - ), - ( - "Assemble Pie Op", - "Food Prep Table 2", - "5", - """- Use fresh pie filling or remove from refrigerator - - Remove rolled pie crusts from refrigerator - - Fill bottom crust with filling - - Create decorative cut out for top crust - - Layer top crust over bottom crust / filling and create a crimped seal""", - ["Food Prep Table 1", "Assemble Pie Station"], - ), - ( - "Cook Pie Filling Operation", - "Range Station", - "5", - """- Bring ingredients to simmer and cook for 15 minutes - - Remove from heat and mix in remaining 1/4 berries and butter - - Store in refrigerator if not using immediately""", - ), - ( - "Mix Dough Op", - "Mixer Station", - "5", - """- Combine flour, butter, salt, and ice water in mixer - - Pulse for 30 seconds - - Divide into equal-sized portions, one portion for each pie crust being made - - Put in refrigerator""", - ["Mix Pie Crust Station", "Mix Pie Filling Station"], - ), - ("Box Pie Op", "Packaging Station", "5", "- Place pie into box for sale"), - ( - "Roll Pie Crust Op", - "Food Prep Table 2", - "5", - """- Remove chilled pie crust portions from refrigerator - - Separate each portion into two (one for bottom crust, one for top) - - Flour board and roll out each portion into a circle - - Place bottom crust into pie tin, then layer a piece of parchment paper, followed by the top crust""", - ["Food Prep Table 1", "Roll Pie Crust Station"], - ), - ("Divide Dough Op", "Food Prep Table 2", "1", "Divide Dough Op", ["Food Prep Table 1"]), - ( - "Bake Op", - "Oven Station", - "1", - """- Place assembled pies into oven - - Bake at 375F for 50 minutes - - Remove from oven""", - ["Baking Station"], - ), - ( - "Chill Pie Crust Op", - "Refrigerator Station", - "1", - "- Chill pie crust for at least 30 minutes", - ["Cooling Station", "Cooling Racks Station"], - ), - ( - "Cool Pie Op", - "Cooling Racks Station", - "1", - "Cool baked pies for at least 30 minutes before boxing", - ["Cooling Station", "Refrigerator Station"], - ), - ( - "Assemble Pocket Op", - "Food Prep Table 1", - "5", - """- Fold 3 poppers into dough pocket""", - ), - ( - "Assemble Popper Op", - "Food Prep Table 1", - "5", - """- Top dough bite with fruit""", - ), - ( - "Assemble Combination Product", - "Food Prep Table 1", - "5", - """- Tower: package one pie and one pocket, and one popper - - Pocketful of Bay: package one pocket with two poppers""", - ), -] - -items = [ - { - "item_code": "Ambrosia Pie", - "item_group": "Baked Goods", - "uom": "Nos", - "item_price": 11.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Ambrosia Pie is the marquee product of Ambrosia Pie Company. A filling of heavenly cloudberries pair perfectly with the tart hairless rambutan, finished with drizzles of tayberry nectar. It's a feast fit for Mt Olympus!

", - }, - { - "item_code": "Double Plum Pie", - "uom": "Nos", - "item_group": "Baked Goods", - "item_price": 10.50, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Double the fun and double the flavor with our Double Plum Pie! We combine damson and cocoplums in a daring tropical-meets-temperate filling. Forbidden fruit never tasted this good.

", - }, - { - "item_code": "Gooseberry Pie", - "uom": "Nos", - "item_group": "Baked Goods", - "item_price": 12.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Our delicious take on the traditional gooseberry pie that tastes like the holidays. This classic pie is best shared with the ones you love.

", - }, - { - "item_code": "Kaduka Key Lime Pie", - "item_group": "Baked Goods", - "uom": "Nos", - "item_price": 11.50, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Take your tastebuds on an adventure with this whimsical twist on the classic Key Lime pie. Made with kaduka limes and the exotic limequat, this seasonal pie is sure to satisfy even the most weary culinary explorer. Grab it when you can - it's only available April through September.

", - }, - { - "item_code": "Tower of Bay-bel", - "uom": "Nos", - "item_group": "Baked Goods", - "item_price": 20.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Reach for the stars with this epic all-things-bayberry dessert that stacks a Bayberry Pocket on top of our Bayberry Pie.

", - }, - { - "item_code": "Pocketful of Bay", - "uom": "Nos", - "item_group": "Baked Goods", - "item_price": 12.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Try this delightful combination of a Bayberry Pocket and two additional Bayberry Poppers.

", - }, - { - "item_code": "Bayberry Pie", - "uom": "Nos", - "item_group": "Sub Assemblies", - # "item_price": 11.00, # can a finished good be included as sub-assembly for another good? - "default_warehouse": "Refrigerated Display - APC", - "description": "

This pie features the sweet and scrumptious bayberry and is sure to be a crowd-pleaser.

", - }, - { - "item_code": "Bayberry Pocket", - "uom": "Nos", - "item_group": "Sub Assemblies", - # "item_price": 8.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Need a little more than one popper? The Bayberry Pocket is a tasty dough pocket stuffed with several Bayberry Poppers.

", - }, - { - "item_code": "Bayberry Popper", - "uom": "Nos", - "item_group": "Sub Assemblies", - # "item_price": 3.00, - "default_warehouse": "Refrigerated Display - APC", - "description": "

Part cookie, part tart, these bite-sized treats will bring a little sweetness to your day.

", - }, - { - "item_code": "Ambrosia Pie Filling", - "uom": "Cup", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - "description": "Ambrosia Pie Filling", - }, - { - "item_code": "Double Plum Pie Filling", - "uom": "Cup", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - "description": "Double Plum Pie Filling", - }, - { - "item_code": "Gooseberry Pie Filling", - "uom": "Cup", - "description": "Gooseberry Pie Filling", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - }, - { - "item_code": "Bayberry Pie Filling", - "uom": "Cup", - "description": "Bayberry Pie Filling", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - }, - { - "item_code": "Kaduka Key Lime Pie Filling", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - "uom": "Cup", - "description": "Kaduka Key Lime Pie Filling", - }, - { - "item_code": "Pie Crust", - "uom": "Nos", - "description": "Pie Crust", - "item_group": "Sub Assemblies", - "default_warehouse": "Refrigerator - APC", - "is_sub_contracted_item": 1, - "item_price": 2.00, - "default_supplier": "", - "supplier": "", - "valuation_rate": 3.0196, - "uom_conversion_detail": {"Hour": 20}, - }, - { - "item_code": "Pie Crust Service per Crust", - "uom": "Nos", - "description": "Subcontracted pie crust manufacturing service. Item price is per crust.", - "item_group": "Sub Assemblies", - "default_warehouse": "Credible Contract Baking - APC", - "is_sub_contracted_item": 1, - "is_stock_item": 0, - "item_price": 2.00, - "default_supplier": "Credible Contract Baking", - "supplier": "Credible Contract Baking", - }, - { - "item_code": "Pie Crust Service per Hour", - "uom": "Hour", - "description": "Subcontracted pie crust manufacturing service. Item price is per hour.", - "item_group": "Sub Assemblies", - "default_warehouse": "Credible Contract Baking - APC", - "is_sub_contracted_item": 1, - "is_stock_item": 0, - "item_price": 40.00, # Assumes 5 crusts takes 15 mins (excluding chilling time), or 20 crusts/hour at rate of $2.00/crust - "default_supplier": "Credible Contract Baking", - "supplier": "Credible Contract Baking", - }, - { - "item_code": "Cloudberry", - "uom": "Pound", - "description": "Cloudberry", - "item_group": "Ingredients", - "item_price": 0.65, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Cocoplum", - "uom": "Pound", - "description": "Cocoplum", - "item_group": "Ingredients", - "item_price": 0.35, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Damson Plum", - "uom": "Pound", - "description": "Damson Plum", - "item_group": "Ingredients", - "item_price": 0.85, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Gooseberry", - "uom": "Pound", - "description": "Gooseberry", - "item_group": "Ingredients", - "item_price": 0.99, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Hairless Rambutan", - "uom": "Pound", - "description": "Hairless Rambutan", - "item_price": 0.50, - "item_group": "Ingredients", - "default_warehouse": "Storeroom - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Kaduka Lime", - "uom": "Pound", - "description": "Kaduka Lime", - "item_group": "Ingredients", - "item_price": 0.89, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Limequat", - "uom": "Pound", - "description": "Limequat", - "item_group": "Ingredients", - "item_price": 0.75, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Tayberry", - "uom": "Pound", - "description": "Tayberry", - "item_group": "Ingredients", - "item_price": 0.85, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Bayberry", - "uom": "Pound", - "description": "Bayberry", - "item_group": "Ingredients", - "item_price": 0.45, - "default_warehouse": "Refrigerator - APC", - "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], - }, - { - "item_code": "Butter", - "uom": "Pound", - "description": "Butter", - "item_group": "Ingredients", - "item_price": 4.5, - "default_warehouse": "Refrigerator - APC", - "supplier": [ - "Freedom Provisions", - "Chelsea Fruit Co", - ], - }, - { - "item_code": "Cornstarch", - "uom": "Pound", - "description": "Cornstarch", - "item_group": "Ingredients", - "item_price": 0.52, - "default_warehouse": "Storeroom - APC", - "supplier": "Freedom Provisions", - }, - { - "item_code": "Ice Water", - "uom": "Cup", - "description": "Ice Water - necessary for pie crusts", - "item_group": "Ingredients", - "item_price": 0.01, - "default_warehouse": "Refrigerator - APC", - "available_in_house": 1, - "opening_qty": 50, - }, - { - "item_code": "Flour", - "uom": "Pound", - "description": "Flour", - "item_group": "Ingredients", - "item_price": 0.66, - "default_warehouse": "Storeroom - APC", - "supplier": "Freedom Provisions", - }, - { - "item_code": "Pie Box", - "uom": "Nos", - "description": "Pie Box", - "item_group": "Bakery Supplies", - "item_price": 0.4, - "default_warehouse": "Storeroom - APC", - "supplier": ["Freedom Provisions", "Unity Bakery Supply"], - }, - { - "item_code": "Pie Tin", - "uom": "Nos", - "description": "Pie Tin", - "item_price": 0.18, - "item_group": "Bakery Supplies", - "default_warehouse": "Storeroom - APC", - "supplier": ["Freedom Provisions", "Unity Bakery Supply"], - }, - { - "item_code": "Parchment Paper", - "uom": "Nos", - "description": "Parchment Paper", - "item_group": "Bakery Supplies", - "item_price": 0.02, - "default_warehouse": "Storeroom - APC", - "supplier": ["Freedom Provisions", "Unity Bakery Supply"], - }, - { - "item_code": "Salt", - "uom": "Pound", - "description": "Salt", - "item_group": "Ingredients", - "item_price": 0.36, - "default_warehouse": "Storeroom - APC", - "supplier": "Freedom Provisions", - }, - { - "item_code": "Sugar", - "uom": "Pound", - "description": "Sugar", - "item_group": "Ingredients", - "item_price": 0.60, - "default_warehouse": "Storeroom - APC", - "supplier": "Freedom Provisions", - }, - { - "item_code": "Water", - "uom": "Cup", - "item_price": 0.05, - "description": "Water", - "item_group": "Ingredients", - "default_warehouse": "Kitchen - APC", - "available_in_house": 1, - "opening_qty": 50, - }, -] - -boms = [ - { - "item": "Tower of Bay-bel", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Bayberry Pie", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Combination Product", - "time_in_mins": 2.0, - "workstation": "Food Prep Table 1", - }, - ], - }, - { - "item": "Pocketful of Bay", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Bayberry Popper", "qty": 10.0, "qty_consumed_per_unit": 2.0, "uom": "Nos"}, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Combination Product", - "time_in_mins": 2.0, - "workstation": "Food Prep Table 1", - }, - ], - }, - { - "item": "Bayberry Pocket", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Flour", "qty": 1.5, "qty_consumed_per_unit": 0.3, "uom": "Pound"}, - {"item_code": "Butter", "qty": 0.75, "qty_consumed_per_unit": 0.15, "uom": "Pound"}, - {"item_code": "Sugar", "qty": 0.1, "qty_consumed_per_unit": 0.02, "uom": "Pound"}, - {"item_code": "Bayberry Popper", "qty": 15.0, "qty_consumed_per_unit": 3.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Mix Dough Op", - "time_in_mins": 5.0, - "workstation": "Mixer Station", - }, - { - "batch_size": 5, - "operation": "Assemble Pocket Op", - "time_in_mins": 2.0, - "workstation": "Food Prep Table 1", - }, - ], - }, - { - "item": "Bayberry Popper", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Flour", "qty": 0.5, "qty_consumed_per_unit": 0.1, "uom": "Pound"}, - {"item_code": "Butter", "qty": 0.25, "qty_consumed_per_unit": 0.05, "uom": "Pound"}, - {"item_code": "Sugar", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, - {"item_code": "Bayberry", "qty": 1.0, "qty_consumed_per_unit": 0.2, "uom": "Pound"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Mix Dough Op", - "time_in_mins": 5.0, - "workstation": "Mixer Station", - }, - { - "batch_size": 5, - "operation": "Assemble Popper Op", - "time_in_mins": 1.0, - "workstation": "Food Prep Table 1", - }, - ], - }, - { - "item": "Bayberry Pie", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - { - "item_code": "Bayberry Pie Filling", - "qty": 20.0, - "qty_consumed_per_unit": 4.0, - "uom": "Cup", - }, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Pie Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, - { - "batch_size": 1, - "operation": "Cool Pie Op", - "time_in_mins": 30.0, - "workstation": "Cooling Racks Station", - }, - { - "batch_size": 5, - "operation": "Box Pie Op", - "time_in_mins": 5.0, - "workstation": "Packaging Station", - }, - ], - }, - { - "item": "Double Plum Pie", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - { - "item_code": "Double Plum Pie Filling", - "qty": 20.0, - "qty_consumed_per_unit": 4.0, - "uom": "Cup", - }, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Pie Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, - { - "batch_size": 1, - "operation": "Cool Pie Op", - "time_in_mins": 30.0, - "workstation": "Cooling Racks Station", - }, - { - "batch_size": 5, - "operation": "Box Pie Op", - "time_in_mins": 5.0, - "workstation": "Packaging Station", - }, - ], - }, - { - "item": "Kaduka Key Lime Pie", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - { - "item_code": "Kaduka Key Lime Pie Filling", - "qty": 20.0, - "qty_consumed_per_unit": 4.0, - "uom": "Cup", - }, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Pie Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, - { - "batch_size": 1, - "operation": "Cool Pie Op", - "time_in_mins": 30.0, - "workstation": "Cooling Racks Station", - }, - { - "batch_size": 5, - "operation": "Box Pie Op", - "time_in_mins": 5.0, - "workstation": "Packaging Station", - }, - ], - }, - { - "item": "Gooseberry Pie", - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - { - "item_code": "Gooseberry Pie Filling", - "qty": 20.0, - "qty_consumed_per_unit": 4.0, - "uom": "Cup", - }, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Pie Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, - { - "batch_size": 1, - "operation": "Cool Pie Op", - "time_in_mins": 30.0, - "workstation": "Cooling Racks Station", - }, - { - "batch_size": 5, - "operation": "Box Pie Op", - "time_in_mins": 5.0, - "workstation": "Packaging Station", - }, - ], - }, - { - "item": "Ambrosia Pie", - "quantity": 5.0, - "uom": "Nos", - "overproduction_percentage_for_work_order": 100, - "items": [ - {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Ambrosia Pie Filling", "qty": 20.0, "qty_consumed_per_unit": 4.0, "uom": "Cup"}, - {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Assemble Pie Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, - { - "batch_size": 1, - "operation": "Cool Pie Op", - "time_in_mins": 30.0, - "workstation": "Cooling Racks Station", - }, - { - "batch_size": 5, - "operation": "Box Pie Op", - "time_in_mins": 5.0, - "workstation": "Packaging Station", - }, - ], - }, - { - "item": "Bayberry Pie Filling", - "quantity": 20.0, - "uom": "Cup", - "items": [ - {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Bayberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Filling Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 1", - }, - { - "batch_size": 5, - "operation": "Cook Pie Filling Operation", - "time_in_mins": 15.0, - "workstation": "Range Station", - }, - ], - }, - { - "item": "Double Plum Pie Filling", - "quantity": 20.0, - "uom": "Cup", - "items": [ - {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Cocoplum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, - {"item_code": "Damson Plum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Filling Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 1", - }, - { - "batch_size": 5, - "operation": "Cook Pie Filling Operation", - "time_in_mins": 15.0, - "workstation": "Range Station", - }, - ], - }, - { - "item": "Kaduka Key Lime Pie Filling", - "quantity": 20.0, - "uom": "Cup", - "items": [ - {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Kaduka Lime", "qty": 10.0, "qty_consumed_per_unit": 0.0335, "uom": "Pound"}, - {"item_code": "Limequat", "qty": 5.0, "qty_consumed_per_unit": 0.01675, "uom": "Pound"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Filling Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 1", - }, - { - "batch_size": 5, - "operation": "Cook Pie Filling Operation", - "time_in_mins": 15.0, - "workstation": "Range Station", - }, - ], - }, - { - "item": "Gooseberry Pie Filling", - "quantity": 20.0, - "uom": "Cup", - "items": [ - {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - {"item_code": "Gooseberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Filling Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 1", - }, - { - "batch_size": 5, - "operation": "Cook Pie Filling Operation", - "time_in_mins": 15.0, - "workstation": "Range Station", - }, - ], - }, - { - "item": "Ambrosia Pie Filling", - "quantity": 20.0, - "uom": "Cup", - "items": [ - {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, - {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, - {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, - { - "item_code": "Hairless Rambutan", - "qty": 5.0, - "qty_consumed_per_unit": 0.01675, - "uom": "Pound", - }, - {"item_code": "Tayberry", "qty": 2.5, "qty_consumed_per_unit": 0.0084, "uom": "Pound"}, - {"item_code": "Cloudberry", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, - {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Filling Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 1", - }, - { - "batch_size": 5, - "operation": "Cook Pie Filling Operation", - "time_in_mins": 15.0, - "workstation": "Range Station", - }, - ], - }, - { - "item": "Pie Crust", # Subcontracted BOM - "quantity": 5.0, - "uom": "Nos", - "is_default": 0, - "is_subcontracted": 1, - "with_operations": 0, - "items": [ - {"item_code": "Flour", "qty": 4.25, "qty_consumed_per_unit": 0.85, "uom": "Pound"}, - {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, - # {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, - {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, - {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [], # Subcontracted item -> operations done by supplier - }, - { - "item": "Pie Crust", # In-house BOM - "quantity": 5.0, - "uom": "Nos", - "items": [ - {"item_code": "Flour", "qty": 4.25, "qty_consumed_per_unit": 0.85, "uom": "Pound"}, - {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, - {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, - {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, - {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, - ], - "operations": [ - { - "batch_size": 5, - "operation": "Gather Pie Crust Ingredients", - "time_in_mins": 5.0, - "workstation": "Food Prep Table 2", - }, - { - "batch_size": 5, - "operation": "Mix Dough Op", - "time_in_mins": 5.0, - "workstation": "Mixer Station", - }, - { - "batch_size": 1, - "operation": "Divide Dough Op", - "time_in_mins": 2.0, - "workstation": "Food Prep Table 2", - }, - { - "batch_size": 1, - "operation": "Chill Pie Crust Op", - "time_in_mins": 30.0, - "workstation": "Refrigerator Station", - }, - { - "batch_size": 5, - "operation": "Roll Pie Crust Op", - "time_in_mins": 10.0, - "workstation": "Food Prep Table 2", - }, - ], - }, -] - -customers = [ - "Almacs Food Group", - "Beans and Dreams Roasters", - "Cafe 27 Cafeteria", - "Capital Grille Restaurant Group", - "Downtown Deli", - "Draws Groceries", - "Grab n Go Bodega", - "Grand North Station Baking Co", - "Happy Basket Food Distribution Group", - "Jitter Cafe", - "Longwoods Sandwich Shop", - "Midtown Munchies Inc", - "My Way Cup Coffee", - "Nom Nom Cafe", - "Round the World Donut Shop", - "Sand Street Deli", - "Starfood Cafe", - "Terrywood Terminal Bakery Inc", - "TransAmerica Bank Cafeteria", - "Whole Harvest Grocery Group", -] +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +suppliers = [ + ( + "Freedom Provisions", + None, + None, + None, + "Net 30", + { + "address_line1": "16 Margrave", + "city": "Carlisle", + "state": "NH", + "country": "United States", + "pincode": "57173", + }, + ), + ( + "Unity Bakery Supply", + None, + None, + None, + "Net 30", + { + "address_line1": "34 Pinar St", + "city": "Unity", + "state": "RI", + "country": "United States", + "pincode": "34291", + }, + ), + ( + "Chelsea Fruit Co", + None, + None, + None, + "Net 30", + { + "address_line1": "67C Sweeny Street", + "city": "Chelsea", + "state": "MA", + "country": "United States", + "pincode": "89077", + }, + ), + ( + "Credible Contract Baking", + None, + None, + None, + "Net 30", + { + "address_line1": "4 Crumb Circle", + "city": "Belmont", + "state": "MA", + "country": "United States", + "pincode": "89074", + }, + ), + ( + "Southern Fruit Supply", + None, + None, + None, + "Net 30", + { + "address_line1": "10001 Pineapple Way", + "city": "Largo", + "state": "TX", + "country": "United States", + "pincode": "89574", + }, + ), +] + +workstations = [ + ("Mix Pie Crust Station", "20"), + ("Roll Pie Crust Station", "20"), + ("Make Pie Filling Station", "20"), + ("Cooling Station", "100"), + ("Box Pie Station", "100"), + ("Baking Station", "20"), + ("Assemble Pie Station", "20"), + ("Mix Pie Filling Station", "20"), + ("Packaging Station", "2"), + ("Food Prep Table 2", "10"), + ("Food Prep Table 1", "5"), + ("Range Station", "20"), + ("Cooling Racks Station", "80"), + ("Refrigerator Station", "200"), + ("Oven Station", "20"), + ("Mixer Station", "10"), +] + +operations = [ + ( + "Gather Pie Crust Ingredients", + "Food Prep Table 2", + "5", + """- Remove flour, salt, and a pie tins from store room + - Remove butter and ice water from refrigerator + - Place ingredients at workstation + - Measure amounts for batch size into mixing bowl""", + ["Food Prep Table 1"], + ), + ( + "Gather Pie Filling Ingredients", + "Food Prep Table 1", + "5", + """- Remove fruit and butter from refrigerator + - Remove sugar and cornstarch + - Get water from sink + - Measure ingredients and place in pot, excluding 1/4 of fruit and butter""", + ["Food Prep Table 2"], + ), + ( + "Assemble Pie Op", + "Food Prep Table 2", + "5", + """- Use fresh pie filling or remove from refrigerator + - Remove rolled pie crusts from refrigerator + - Fill bottom crust with filling + - Create decorative cut out for top crust + - Layer top crust over bottom crust / filling and create a crimped seal""", + ["Food Prep Table 1", "Assemble Pie Station"], + ), + ( + "Cook Pie Filling Operation", + "Range Station", + "5", + """- Bring ingredients to simmer and cook for 15 minutes + - Remove from heat and mix in remaining 1/4 berries and butter + - Store in refrigerator if not using immediately""", + ), + ( + "Mix Dough Op", + "Mixer Station", + "5", + """- Combine flour, butter, salt, and ice water in mixer + - Pulse for 30 seconds + - Divide into equal-sized portions, one portion for each pie crust being made + - Put in refrigerator""", + ["Mix Pie Crust Station", "Mix Pie Filling Station"], + ), + ("Box Pie Op", "Packaging Station", "5", "- Place pie into box for sale"), + ( + "Roll Pie Crust Op", + "Food Prep Table 2", + "5", + """- Remove chilled pie crust portions from refrigerator + - Separate each portion into two (one for bottom crust, one for top) + - Flour board and roll out each portion into a circle + - Place bottom crust into pie tin, then layer a piece of parchment paper, followed by the top crust""", + ["Food Prep Table 1", "Roll Pie Crust Station"], + ), + ("Divide Dough Op", "Food Prep Table 2", "1", "Divide Dough Op", ["Food Prep Table 1"]), + ( + "Bake Op", + "Oven Station", + "1", + """- Place assembled pies into oven + - Bake at 375F for 50 minutes + - Remove from oven""", + ["Baking Station"], + ), + ( + "Chill Pie Crust Op", + "Refrigerator Station", + "1", + "- Chill pie crust for at least 30 minutes", + ["Cooling Station", "Cooling Racks Station"], + ), + ( + "Cool Pie Op", + "Cooling Racks Station", + "1", + "Cool baked pies for at least 30 minutes before boxing", + ["Cooling Station", "Refrigerator Station"], + ), + ( + "Assemble Pocket Op", + "Food Prep Table 1", + "5", + """- Fold 3 poppers into dough pocket""", + ), + ( + "Assemble Popper Op", + "Food Prep Table 1", + "5", + """- Top dough bite with fruit""", + ), + ( + "Assemble Combination Product", + "Food Prep Table 1", + "5", + """- Tower: package one pie and one pocket, and one popper + - Pocketful of Bay: package one pocket with two poppers""", + ), +] + +items = [ + { + "item_code": "Ambrosia Pie", + "item_group": "Baked Goods", + "uom": "Nos", + "item_price": 11.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Ambrosia Pie is the marquee product of Ambrosia Pie Company. A filling of heavenly cloudberries pair perfectly with the tart hairless rambutan, finished with drizzles of tayberry nectar. It's a feast fit for Mt Olympus!

", + }, + { + "item_code": "Double Plum Pie", + "uom": "Nos", + "item_group": "Baked Goods", + "item_price": 10.50, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Double the fun and double the flavor with our Double Plum Pie! We combine damson and cocoplums in a daring tropical-meets-temperate filling. Forbidden fruit never tasted this good.

", + }, + { + "item_code": "Gooseberry Pie", + "uom": "Nos", + "item_group": "Baked Goods", + "item_price": 12.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Our delicious take on the traditional gooseberry pie that tastes like the holidays. This classic pie is best shared with the ones you love.

", + }, + { + "item_code": "Kaduka Key Lime Pie", + "item_group": "Baked Goods", + "uom": "Nos", + "item_price": 11.50, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Take your tastebuds on an adventure with this whimsical twist on the classic Key Lime pie. Made with kaduka limes and the exotic limequat, this seasonal pie is sure to satisfy even the most weary culinary explorer. Grab it when you can - it's only available April through September.

", + }, + { + "item_code": "Tower of Bay-bel", + "uom": "Nos", + "item_group": "Baked Goods", + "item_price": 20.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Reach for the stars with this epic all-things-bayberry dessert that stacks a Bayberry Pocket on top of our Bayberry Pie.

", + }, + { + "item_code": "Pocketful of Bay", + "uom": "Nos", + "item_group": "Baked Goods", + "item_price": 12.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Try this delightful combination of a Bayberry Pocket and two additional Bayberry Poppers.

", + }, + { + "item_code": "Bayberry Pie", + "uom": "Nos", + "item_group": "Sub Assemblies", + # "item_price": 11.00, # can a finished good be included as sub-assembly for another good? + "default_warehouse": "Refrigerated Display - APC", + "description": "

This pie features the sweet and scrumptious bayberry and is sure to be a crowd-pleaser.

", + }, + { + "item_code": "Bayberry Pocket", + "uom": "Nos", + "item_group": "Sub Assemblies", + # "item_price": 8.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Need a little more than one popper? The Bayberry Pocket is a tasty dough pocket stuffed with several Bayberry Poppers.

", + }, + { + "item_code": "Bayberry Popper", + "uom": "Nos", + "item_group": "Sub Assemblies", + # "item_price": 3.00, + "default_warehouse": "Refrigerated Display - APC", + "description": "

Part cookie, part tart, these bite-sized treats will bring a little sweetness to your day.

", + }, + { + "item_code": "Ambrosia Pie Filling", + "uom": "Cup", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + "description": "Ambrosia Pie Filling", + }, + { + "item_code": "Double Plum Pie Filling", + "uom": "Cup", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + "description": "Double Plum Pie Filling", + }, + { + "item_code": "Gooseberry Pie Filling", + "uom": "Cup", + "description": "Gooseberry Pie Filling", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + }, + { + "item_code": "Bayberry Pie Filling", + "uom": "Cup", + "description": "Bayberry Pie Filling", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + }, + { + "item_code": "Kaduka Key Lime Pie Filling", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + "uom": "Cup", + "description": "Kaduka Key Lime Pie Filling", + }, + { + "item_code": "Pie Crust", + "uom": "Nos", + "description": "Pie Crust", + "item_group": "Sub Assemblies", + "default_warehouse": "Refrigerator - APC", + "is_sub_contracted_item": 1, + "item_price": 2.00, + "default_supplier": "", + "supplier": "", + "valuation_rate": 3.0196, + "uom_conversion_detail": {"Hour": 20}, + }, + { + "item_code": "Pie Crust Service per Crust", + "uom": "Nos", + "description": "Subcontracted pie crust manufacturing service. Item price is per crust.", + "item_group": "Sub Assemblies", + "default_warehouse": "Credible Contract Baking - APC", + "is_sub_contracted_item": 1, + "is_stock_item": 0, + "item_price": 2.00, + "default_supplier": "Credible Contract Baking", + "supplier": "Credible Contract Baking", + }, + { + "item_code": "Pie Crust Service per Hour", + "uom": "Hour", + "description": "Subcontracted pie crust manufacturing service. Item price is per hour.", + "item_group": "Sub Assemblies", + "default_warehouse": "Credible Contract Baking - APC", + "is_sub_contracted_item": 1, + "is_stock_item": 0, + "item_price": 40.00, # Assumes 5 crusts takes 15 mins (excluding chilling time), or 20 crusts/hour at rate of $2.00/crust + "default_supplier": "Credible Contract Baking", + "supplier": "Credible Contract Baking", + }, + { + "item_code": "Cloudberry", + "uom": "Pound", + "description": "Cloudberry", + "item_group": "Ingredients", + "item_price": 0.65, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Cocoplum", + "uom": "Pound", + "description": "Cocoplum", + "item_group": "Ingredients", + "item_price": 0.35, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Damson Plum", + "uom": "Pound", + "description": "Damson Plum", + "item_group": "Ingredients", + "item_price": 0.85, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Gooseberry", + "uom": "Pound", + "description": "Gooseberry", + "item_group": "Ingredients", + "item_price": 0.99, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Hairless Rambutan", + "uom": "Pound", + "description": "Hairless Rambutan", + "item_price": 0.50, + "item_group": "Ingredients", + "default_warehouse": "Storeroom - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Kaduka Lime", + "uom": "Pound", + "description": "Kaduka Lime", + "item_group": "Ingredients", + "item_price": 0.89, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Limequat", + "uom": "Pound", + "description": "Limequat", + "item_group": "Ingredients", + "item_price": 0.75, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Tayberry", + "uom": "Pound", + "description": "Tayberry", + "item_group": "Ingredients", + "item_price": 0.85, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Bayberry", + "uom": "Pound", + "description": "Bayberry", + "item_group": "Ingredients", + "item_price": 0.45, + "default_warehouse": "Refrigerator - APC", + "supplier": ["Chelsea Fruit Co", "Southern Fruit Supply"], + }, + { + "item_code": "Butter", + "uom": "Pound", + "description": "Butter", + "item_group": "Ingredients", + "item_price": 4.5, + "default_warehouse": "Refrigerator - APC", + "supplier": [ + "Freedom Provisions", + "Chelsea Fruit Co", + ], + }, + { + "item_code": "Cornstarch", + "uom": "Pound", + "description": "Cornstarch", + "item_group": "Ingredients", + "item_price": 0.52, + "default_warehouse": "Storeroom - APC", + "supplier": "Freedom Provisions", + }, + { + "item_code": "Ice Water", + "uom": "Cup", + "description": "Ice Water - necessary for pie crusts", + "item_group": "Ingredients", + "item_price": 0.01, + "default_warehouse": "Refrigerator - APC", + "available_in_house": 1, + "opening_qty": 50, + }, + { + "item_code": "Flour", + "uom": "Pound", + "description": "Flour", + "item_group": "Ingredients", + "item_price": 0.66, + "default_warehouse": "Storeroom - APC", + "supplier": "Freedom Provisions", + }, + { + "item_code": "Pie Box", + "uom": "Nos", + "description": "Pie Box", + "item_group": "Bakery Supplies", + "item_price": 0.4, + "default_warehouse": "Storeroom - APC", + "supplier": ["Freedom Provisions", "Unity Bakery Supply"], + }, + { + "item_code": "Pie Tin", + "uom": "Nos", + "description": "Pie Tin", + "item_price": 0.18, + "item_group": "Bakery Supplies", + "default_warehouse": "Storeroom - APC", + "supplier": ["Freedom Provisions", "Unity Bakery Supply"], + }, + { + "item_code": "Parchment Paper", + "uom": "Nos", + "description": "Parchment Paper", + "item_group": "Bakery Supplies", + "item_price": 0.02, + "default_warehouse": "Storeroom - APC", + "supplier": ["Freedom Provisions", "Unity Bakery Supply"], + }, + { + "item_code": "Salt", + "uom": "Pound", + "description": "Salt", + "item_group": "Ingredients", + "item_price": 0.36, + "default_warehouse": "Storeroom - APC", + "supplier": "Freedom Provisions", + }, + { + "item_code": "Sugar", + "uom": "Pound", + "description": "Sugar", + "item_group": "Ingredients", + "item_price": 0.60, + "default_warehouse": "Storeroom - APC", + "supplier": "Freedom Provisions", + }, + { + "item_code": "Water", + "uom": "Cup", + "item_price": 0.05, + "description": "Water", + "item_group": "Ingredients", + "default_warehouse": "Kitchen - APC", + "available_in_house": 1, + "opening_qty": 50, + }, +] + +boms = [ + { + "item": "Tower of Bay-bel", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Bayberry Pie", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Combination Product", + "time_in_mins": 2.0, + "workstation": "Food Prep Table 1", + }, + ], + }, + { + "item": "Pocketful of Bay", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Bayberry Pocket", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + {"item_code": "Bayberry Popper", "qty": 10.0, "qty_consumed_per_unit": 2.0, "uom": "Nos"}, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Combination Product", + "time_in_mins": 2.0, + "workstation": "Food Prep Table 1", + }, + ], + }, + { + "item": "Bayberry Pocket", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Flour", "qty": 1.5, "qty_consumed_per_unit": 0.3, "uom": "Pound"}, + {"item_code": "Butter", "qty": 0.75, "qty_consumed_per_unit": 0.15, "uom": "Pound"}, + {"item_code": "Sugar", "qty": 0.1, "qty_consumed_per_unit": 0.02, "uom": "Pound"}, + {"item_code": "Bayberry Popper", "qty": 15.0, "qty_consumed_per_unit": 3.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Mix Dough Op", + "time_in_mins": 5.0, + "workstation": "Mixer Station", + }, + { + "batch_size": 5, + "operation": "Assemble Pocket Op", + "time_in_mins": 2.0, + "workstation": "Food Prep Table 1", + }, + ], + }, + { + "item": "Bayberry Popper", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Flour", "qty": 0.5, "qty_consumed_per_unit": 0.1, "uom": "Pound"}, + {"item_code": "Butter", "qty": 0.25, "qty_consumed_per_unit": 0.05, "uom": "Pound"}, + {"item_code": "Sugar", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, + {"item_code": "Bayberry", "qty": 1.0, "qty_consumed_per_unit": 0.2, "uom": "Pound"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Mix Dough Op", + "time_in_mins": 5.0, + "workstation": "Mixer Station", + }, + { + "batch_size": 5, + "operation": "Assemble Popper Op", + "time_in_mins": 1.0, + "workstation": "Food Prep Table 1", + }, + ], + }, + { + "item": "Bayberry Pie", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Bayberry Pie Filling", + "qty": 20.0, + "qty_consumed_per_unit": 4.0, + "uom": "Cup", + }, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Pie Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Cool Pie Op", + "time_in_mins": 30.0, + "workstation": "Cooling Racks Station", + }, + { + "batch_size": 5, + "operation": "Box Pie Op", + "time_in_mins": 5.0, + "workstation": "Packaging Station", + }, + ], + }, + { + "item": "Double Plum Pie", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Double Plum Pie Filling", + "qty": 20.0, + "qty_consumed_per_unit": 4.0, + "uom": "Cup", + }, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Pie Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Cool Pie Op", + "time_in_mins": 30.0, + "workstation": "Cooling Racks Station", + }, + { + "batch_size": 5, + "operation": "Box Pie Op", + "time_in_mins": 5.0, + "workstation": "Packaging Station", + }, + ], + }, + { + "item": "Kaduka Key Lime Pie", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Kaduka Key Lime Pie Filling", + "qty": 20.0, + "qty_consumed_per_unit": 4.0, + "uom": "Cup", + }, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Pie Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Cool Pie Op", + "time_in_mins": 30.0, + "workstation": "Cooling Racks Station", + }, + { + "batch_size": 5, + "operation": "Box Pie Op", + "time_in_mins": 5.0, + "workstation": "Packaging Station", + }, + ], + }, + { + "item": "Gooseberry Pie", + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + { + "item_code": "Gooseberry Pie Filling", + "qty": 20.0, + "qty_consumed_per_unit": 4.0, + "uom": "Cup", + }, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Pie Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Cool Pie Op", + "time_in_mins": 30.0, + "workstation": "Cooling Racks Station", + }, + { + "batch_size": 5, + "operation": "Box Pie Op", + "time_in_mins": 5.0, + "workstation": "Packaging Station", + }, + ], + }, + { + "item": "Ambrosia Pie", + "quantity": 5.0, + "uom": "Nos", + "overproduction_percentage_for_work_order": 100, + "items": [ + {"item_code": "Pie Crust", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + {"item_code": "Ambrosia Pie Filling", "qty": 20.0, "qty_consumed_per_unit": 4.0, "uom": "Cup"}, + {"item_code": "Pie Box", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Assemble Pie Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + {"batch_size": 1, "operation": "Bake Op", "time_in_mins": 50.0, "workstation": "Oven Station"}, + { + "batch_size": 1, + "operation": "Cool Pie Op", + "time_in_mins": 30.0, + "workstation": "Cooling Racks Station", + }, + { + "batch_size": 5, + "operation": "Box Pie Op", + "time_in_mins": 5.0, + "workstation": "Packaging Station", + }, + ], + }, + { + "item": "Bayberry Pie Filling", + "quantity": 20.0, + "uom": "Cup", + "items": [ + {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, + {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, + {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + {"item_code": "Bayberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Filling Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 1", + }, + { + "batch_size": 5, + "operation": "Cook Pie Filling Operation", + "time_in_mins": 15.0, + "workstation": "Range Station", + }, + ], + }, + { + "item": "Double Plum Pie Filling", + "quantity": 20.0, + "uom": "Cup", + "items": [ + {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, + {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, + {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + {"item_code": "Cocoplum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, + {"item_code": "Damson Plum", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Filling Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 1", + }, + { + "batch_size": 5, + "operation": "Cook Pie Filling Operation", + "time_in_mins": 15.0, + "workstation": "Range Station", + }, + ], + }, + { + "item": "Kaduka Key Lime Pie Filling", + "quantity": 20.0, + "uom": "Cup", + "items": [ + {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, + {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, + {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + {"item_code": "Kaduka Lime", "qty": 10.0, "qty_consumed_per_unit": 0.0335, "uom": "Pound"}, + {"item_code": "Limequat", "qty": 5.0, "qty_consumed_per_unit": 0.01675, "uom": "Pound"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Filling Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 1", + }, + { + "batch_size": 5, + "operation": "Cook Pie Filling Operation", + "time_in_mins": 15.0, + "workstation": "Range Station", + }, + ], + }, + { + "item": "Gooseberry Pie Filling", + "quantity": 20.0, + "uom": "Cup", + "items": [ + {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, + {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, + {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + {"item_code": "Gooseberry", "qty": 15.0, "qty_consumed_per_unit": 0.05025, "uom": "Pound"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Filling Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 1", + }, + { + "batch_size": 5, + "operation": "Cook Pie Filling Operation", + "time_in_mins": 15.0, + "workstation": "Range Station", + }, + ], + }, + { + "item": "Ambrosia Pie Filling", + "quantity": 20.0, + "uom": "Cup", + "items": [ + {"item_code": "Sugar", "qty": 0.5, "qty_consumed_per_unit": 0.025, "uom": "Pound"}, + {"item_code": "Cornstarch", "qty": 0.1, "qty_consumed_per_unit": 0.005, "uom": "Pound"}, + {"item_code": "Butter", "qty": 0.313, "qty_consumed_per_unit": 0.01565, "uom": "Pound"}, + { + "item_code": "Hairless Rambutan", + "qty": 5.0, + "qty_consumed_per_unit": 0.01675, + "uom": "Pound", + }, + {"item_code": "Tayberry", "qty": 2.5, "qty_consumed_per_unit": 0.0084, "uom": "Pound"}, + {"item_code": "Cloudberry", "qty": 7.5, "qty_consumed_per_unit": 0.02515, "uom": "Pound"}, + {"item_code": "Water", "qty": 1.25, "qty_consumed_per_unit": 0.0625, "uom": "Cup"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Filling Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 1", + }, + { + "batch_size": 5, + "operation": "Cook Pie Filling Operation", + "time_in_mins": 15.0, + "workstation": "Range Station", + }, + ], + }, + { + "item": "Pie Crust", # Subcontracted BOM + "quantity": 5.0, + "uom": "Nos", + "is_default": 0, + "is_subcontracted": 1, + "with_operations": 0, + "items": [ + {"item_code": "Flour", "qty": 4.25, "qty_consumed_per_unit": 0.85, "uom": "Pound"}, + {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, + # {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, + {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, + {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [], # Subcontracted item -> operations done by supplier + }, + { + "item": "Pie Crust", # In-house BOM + "quantity": 5.0, + "uom": "Nos", + "items": [ + {"item_code": "Flour", "qty": 4.25, "qty_consumed_per_unit": 0.85, "uom": "Pound"}, + {"item_code": "Butter", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Pound"}, + {"item_code": "Ice Water", "qty": 2.5, "qty_consumed_per_unit": 0.5, "uom": "Cup"}, + {"item_code": "Salt", "qty": 0.05, "qty_consumed_per_unit": 0.01, "uom": "Pound"}, + {"item_code": "Parchment Paper", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + {"item_code": "Pie Tin", "qty": 5.0, "qty_consumed_per_unit": 1.0, "uom": "Nos"}, + ], + "operations": [ + { + "batch_size": 5, + "operation": "Gather Pie Crust Ingredients", + "time_in_mins": 5.0, + "workstation": "Food Prep Table 2", + }, + { + "batch_size": 5, + "operation": "Mix Dough Op", + "time_in_mins": 5.0, + "workstation": "Mixer Station", + }, + { + "batch_size": 1, + "operation": "Divide Dough Op", + "time_in_mins": 2.0, + "workstation": "Food Prep Table 2", + }, + { + "batch_size": 1, + "operation": "Chill Pie Crust Op", + "time_in_mins": 30.0, + "workstation": "Refrigerator Station", + }, + { + "batch_size": 5, + "operation": "Roll Pie Crust Op", + "time_in_mins": 10.0, + "workstation": "Food Prep Table 2", + }, + ], + }, +] + +customers = [ + "Almacs Food Group", + "Beans and Dreams Roasters", + "Cafe 27 Cafeteria", + "Capital Grille Restaurant Group", + "Downtown Deli", + "Draws Groceries", + "Grab n Go Bodega", + "Grand North Station Baking Co", + "Happy Basket Food Distribution Group", + "Jitter Cafe", + "Longwoods Sandwich Shop", + "Midtown Munchies Inc", + "My Way Cup Coffee", + "Nom Nom Cafe", + "Round the World Donut Shop", + "Sand Street Deli", + "Starfood Cafe", + "Terrywood Terminal Bakery Inc", + "TransAmerica Bank Cafeteria", + "Whole Harvest Grocery Group", +] diff --git a/inventory_tools/tests/setup.py b/inventory_tools/tests/setup.py index 2affd41..4642bb8 100644 --- a/inventory_tools/tests/setup.py +++ b/inventory_tools/tests/setup.py @@ -1,756 +1,759 @@ -import datetime -import types -from itertools import groupby - -import frappe -from erpnext.accounts.doctype.account.account import update_account_number -from erpnext.manufacturing.doctype.production_plan.production_plan import ( - get_items_for_material_requests, -) -from erpnext.setup.utils import enable_all_roles_and_domains, set_defaults_for_tests -from erpnext.stock.get_item_details import get_item_details -from frappe.desk.page.setup_wizard.setup_wizard import setup_complete -from frappe.utils import add_months, nowdate -from frappe.utils.data import flt, getdate - -from inventory_tools.tests.fixtures import ( - boms, - customers, - items, - operations, - suppliers, - workstations, -) - - -def before_test(): - frappe.clear_cache() - setup_complete( - { - "currency": "USD", - "full_name": "Administrator", - "company_name": "Ambrosia Pie Company", - "timezone": "America/New_York", - "company_abbr": "APC", - "domains": ["Distribution"], - "country": "United States", - "fy_start_date": getdate().replace(month=1, day=1).isoformat(), - "fy_end_date": getdate().replace(month=12, day=31).isoformat(), - "language": "english", - "company_tagline": "Ambrosia Pie Company", - "email": "support@agritheory.dev", - "password": "admin", - "chart_of_accounts": "Standard with Numbers", - "bank_account": "Primary Checking", - } - ) - set_defaults_for_tests() - for modu in frappe.get_all("Module Onboarding"): - frappe.db.set_value("Module Onboarding", modu, "is_complete", 1) - frappe.set_value("Website Settings", "Website Settings", "home_page", "login") - frappe.db.commit() - create_test_data() - - -def create_test_data(): - settings = frappe._dict( - { - "day": getdate().replace(month=1, day=1), - "company": "Ambrosia Pie Company", - "company_account": frappe.get_value( - "Account", - { - "account_type": "Bank", - "company": frappe.defaults.get_defaults().get("company"), - "is_group": 0, - }, - ), - } - ) - company_address = frappe.new_doc("Address") - company_address.title = settings.company - company_address.address_type = "Office" - company_address.address_line1 = "67C Sweeny Street" - company_address.city = "Chelsea" - company_address.state = "MA" - company_address.pincode = "89077" - company_address.is_your_company_address = 1 - company_address.append("links", {"link_doctype": "Company", "link_name": settings.company}) - company_address.save() - cfc = frappe.new_doc("Company") - cfc.company_name = "Chelsea Fruit Co" - cfc.default_currency = "USD" - cfc.create_chart_of_accounts_based_on = "Existing Company" - cfc.existing_company = settings.company - cfc.abbr = "CFC" - cfc.save() - - frappe.db.set_single_value("Stock Settings", "valuation_method", "Moving Average") - frappe.db.set_single_value("Stock Settings", "default_warehouse", "") - create_warehouses(settings) - setup_manufacturing_settings(settings) - create_workstations() - create_operations() - create_item_groups(settings) - create_price_lists(settings) - create_suppliers(settings) - create_customers(settings) - create_items(settings) - create_boms(settings) - prod_plan_from_doc = "Sales Order" - if prod_plan_from_doc == "Sales Order": - create_sales_order(settings) - else: - create_material_request(settings) - create_production_plan(settings, prod_plan_from_doc) - create_fruit_material_request(settings) - create_quotations(settings) - - -def create_suppliers(settings): - if not frappe.db.exists("Supplier Group", "Bakery"): - bsg = frappe.new_doc("Supplier Group") - bsg.supplier_group_name = "Bakery" - bsg.parent_supplier_group = "All Supplier Groups" - bsg.save() - - addresses = frappe._dict({}) - for supplier in suppliers: - biz = frappe.new_doc("Supplier") - biz.supplier_name = supplier[0] - biz.supplier_group = "Bakery" - biz.country = "United States" - biz.supplier_default_mode_of_payment = supplier[2] - if biz.supplier_default_mode_of_payment == "ACH/EFT": - biz.bank = "Local Bank" - biz.bank_account = "123456789" - biz.currency = "USD" - if biz.supplier_name == "Credible Contract Baking": - biz.append( - "subcontracting_defaults", - { - "company": settings.company, - "wip_warehouse": "Credible Contract Baking - APC", - "return_warehouse": "Baked Goods - APC", - }, - ) - biz.default_price_list = "Bakery Buying" - biz.save() - - existing_address = frappe.get_value("Address", {"address_line1": supplier[5]["address_line1"]}) - if not existing_address: - addr = frappe.new_doc("Address") - addr.address_title = f"{supplier[0]} - {supplier[5]['city']}" - addr.address_type = "Billing" - addr.address_line1 = supplier[5]["address_line1"] - addr.city = supplier[5]["city"] - addr.state = supplier[5]["state"] - addr.country = supplier[5]["country"] - addr.pincode = supplier[5]["pincode"] - else: - addr = frappe.get_doc("Address", existing_address) - addr.append("links", {"link_doctype": "Supplier", "link_name": supplier[0]}) - addr.save() - - -def create_customers(settings): - for customer_name in customers: - customer = frappe.new_doc("Customer") - customer.customer_name = customer_name - customer.customer_group = "Commercial" - customer.customer_type = "Company" - customer.territory = "United States" - customer.save() - - -def setup_manufacturing_settings(settings): - mfg_settings = frappe.get_doc("Manufacturing Settings", "Manufacturing Settings") - mfg_settings.material_consumption = 1 - mfg_settings.default_wip_warehouse = "Kitchen - APC" - mfg_settings.default_fg_warehouse = "Refrigerated Display - APC" - mfg_settings.overproduction_percentage_for_work_order = 5.00 - mfg_settings.job_card_excess_transfer = 1 - mfg_settings.save() - - if not frappe.db.exists( - "Account", {"account_name": "Work In Progress", "company": settings.company} - ): - wip = frappe.new_doc("Account") - wip.account_name = "Work in Progress" - wip.parent_account = "1400 - Stock Assets - APC" - wip.account_number = "1420" - wip.company = settings.company - wip.currency = "USD" - wip.report_type = "Balance Sheet" - wip.root_type = "Asset" - wip.save() - - frappe.set_value("Warehouse", "Kitchen - APC", "account", wip.name) - frappe.set_value( - "Inventory Tools Settings", settings.company, "enable_work_order_subcontracting", 1 - ) - frappe.set_value("Inventory Tools Settings", settings.company, "create_purchase_orders", 0) - frappe.set_value( - "Inventory Tools Settings", settings.company, "overproduction_percentage_for_work_order", 50 - ) - - -def create_workstations(): - for ws in workstations: - if frappe.db.exists("Workstation", ws[0]): - continue - work = frappe.new_doc("Workstation") - work.workstation_name = ws[0] - work.production_capacity = ws[1] - work.save() - - -def create_operations(): - for op in operations: - if frappe.db.exists("Operation", op[0]): - continue - oper = frappe.new_doc("Operation") - oper.name = op[0] - oper.workstation = op[1] - oper.batch_size = op[2] - oper.description = op[3] - if len(op) == 5: - for aw in op[4]: - oper.append( - "alternative_workstations", - { - "workstation": aw, - }, - ) - oper.save() - - -def create_item_groups(settings): - for ig_name in ( - "Baked Goods", - "Bakery Supplies", - "Ingredients", - "Bakery Equipment", - "Sub Assemblies", - ): - if frappe.db.exists("Item Group", ig_name): - continue - ig = frappe.new_doc("Item Group") - ig.item_group_name = ig_name - ig.parent_item_group = "All Item Groups" - ig.save() - - -def create_price_lists(settings): - if not frappe.db.exists("Price List", "Bakery Buying"): - pl = frappe.new_doc("Price List") - pl.price_list_name = "Bakery Buying" - pl.currency = "USD" - pl.buying = 1 - pl.append("countries", {"country": "United States"}) - pl.save() - - if not frappe.db.exists("Price List", "Bakery Wholesale"): - pl = frappe.new_doc("Price List") - pl.price_list_name = "Bakery Wholesale" - pl.currency = "USD" - pl.selling = 1 - pl.append("countries", {"country": "United States"}) - pl.save() - - if not frappe.db.exists("Pricing Rule", "Bakery Retail"): - pr = frappe.new_doc("Pricing Rule") - pr.title = "Bakery Retail" - pr.selling = 1 - pr.apply_on = "Item Group" - pr.company = settings.company - pr.margin_type = "Percentage" - pr.margin_rate_or_amount = 2.00 - pr.valid_from = settings.day - pr.for_price_list = "Bakery Wholesale" - pr.currency = "USD" - pr.append("item_groups", {"item_group": "Baked Goods"}) - pr.save() - - -def create_items(settings): - for item in items: - if frappe.db.exists("Item", item.get("item_code")): - continue - i = frappe.new_doc("Item") - i.item_code = i.item_name = item.get("item_code") - i.item_group = item.get("item_group") - i.stock_uom = item.get("uom") - i.description = item.get("description") - i.is_stock_item = 0 if item.get("is_stock_item") == 0 else 1 - i.include_item_in_manufacturing = 1 - i.valuation_rate = item.get("valuation_rate") or 0 - i.is_sub_contracted_item = item.get("is_sub_contracted_item") or 0 - i.default_warehouse = settings.get("warehouse") - i.default_material_request_type = ( - "Purchase" - if item.get("item_group") in ("Bakery Supplies", "Ingredients") - or item.get("is_sub_contracted_item") - else "Manufacture" - ) - i.valuation_method = "Moving Average" - if item.get("uom_conversion_detail"): - for uom, cf in item.get("uom_conversion_detail").items(): - i.append("uoms", {"uom": uom, "conversion_factor": cf}) - i.is_purchase_item = ( - 1 - if item.get("item_group") in ("Bakery Supplies", "Ingredients") - or item.get("is_sub_contracted_item") - else 0 - ) - i.is_sales_item = 1 if item.get("item_group") == "Baked Goods" else 0 - i.append( - "item_defaults", - { - "company": settings.company, - "default_warehouse": item.get("default_warehouse"), - "default_supplier": item.get("default_supplier"), - "requires_rfq": True if item.get("item_code") == "Cloudberry" else False, - }, - ) - if i.is_purchase_item and item.get("supplier"): - if isinstance(item.get("supplier"), list): - [i.append("supplier_items", {"supplier": s}) for s in item.get("supplier")] - else: - i.append("supplier_items", {"supplier": item.get("supplier")}) - i.save() - if item.get("item_price"): - ip = frappe.new_doc("Item Price") - ip.item_code = i.item_code - ip.uom = i.stock_uom - ip.price_list = "Bakery Wholesale" if i.is_sales_item else "Bakery Buying" - ip.buying = 1 - ip.valid_from = "2018-1-1" - ip.price_list_rate = item.get("item_price") - ip.save() - if item.get("available_in_house"): - se = frappe.new_doc("Stock Entry") - se.posting_date = settings.day - se.set_posting_time = 1 - se.stock_entry_type = "Material Receipt" - se.append( - "items", - { - "item_code": item.get("item_code"), - "t_warehouse": item.get("default_warehouse"), - "qty": item.get("opening_qty"), - "uom": item.get("uom"), - "stock_uom": item.get("uom"), - "conversion_factor": 1, - "basic_rate": item.get("item_price"), - "expense_account": "1910 - Temporary Opening - APC", - }, - ) - se.save() - se.submit() - - -def create_warehouses(settings): - inventory_tools_settings = frappe.get_doc("Inventory Tools Settings", settings.company) - inventory_tools_settings.enable_work_order_subcontracting = 1 - inventory_tools_settings.create_purchase_orders = 0 - inventory_tools_settings.update_warehouse_path = 1 - inventory_tools_settings.save() - - warehouses = [item.get("default_warehouse") for item in items] - root_wh = frappe.get_value("Warehouse", {"company": settings.company, "is_group": 1}) - if frappe.db.exists("Warehouse", "Stores - APC"): - frappe.rename_doc("Warehouse", "Stores - APC", "Storeroom - APC", force=True) - if frappe.db.exists("Warehouse", "Finished Goods - APC"): - frappe.rename_doc("Warehouse", "Finished Goods - APC", "Baked Goods - APC", force=True) - frappe.set_value("Warehouse", "Baked Goods - APC", "is_group", 1) - for wh in frappe.get_all("Warehouse", {"company": settings.company}, ["name", "is_group"]): - if wh.name not in warehouses and not wh.is_group: - frappe.delete_doc("Warehouse", wh.name) - for item in items: - if frappe.db.exists("Warehouse", item.get("default_warehouse")): - continue - wh = frappe.new_doc("Warehouse") - wh.warehouse_name = item.get("default_warehouse").split(" - ")[0] - wh.parent_warehouse = root_wh - wh.company = settings.company - wh.save() - - wh = frappe.new_doc("Warehouse") - wh.warehouse_name = "Bakery Display" - wh.parent_warehouse = "Baked Goods - APC" - wh.company = settings.company - wh.save() - - wh = frappe.get_doc("Warehouse", "Refrigerated Display - APC") - wh.parent_warehouse = "Baked Goods - APC" - wh.save() - - -def create_boms(settings): - for bom in boms[::-1]: # reversed - if frappe.db.exists("BOM", {"item": bom.get("item")}) and bom.get("item") != "Pie Crust": - continue - b = frappe.new_doc("BOM") - b.item = bom.get("item") - b.quantity = bom.get("quantity") - b.uom = bom.get("uom") - b.company = settings.company - b.is_default = 0 if bom.get("is_default") == 0 else 1 - b.is_subcontracted = bom.get("is_subcontracted") or 0 - b.overproduction_percentage_for_work_order = bom.get( - "overproduction_percentage_for_work_order", None - ) - b.rm_cost_as_per = "Price List" - b.buying_price_list = "Bakery Buying" - b.currency = "USD" - b.with_operations = 0 if bom.get("with_operations") == 0 else 1 - for item in bom.get("items"): - b.append("items", {**item, "stock_uom": item.get("uom")}) - b.items[-1].bom_no = frappe.get_value("BOM", {"item": item.get("item_code")}) - for operation in bom.get("operations"): - b.append("operations", {**operation, "hour_rate": 15.00}) - b.save() - b.submit() - - -def create_sales_order(settings): - so = frappe.new_doc("Sales Order") - so.transaction_date = settings.day - so.customer = customers[0] - so.order_type = "Sales" - so.currency = "USD" - so.selling_price_list = "Bakery Wholesale" - so.append( - "items", - { - "item_code": "Ambrosia Pie", - "delivery_date": so.transaction_date, - "qty": 30, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.append( - "items", - { - "item_code": "Double Plum Pie", - "delivery_date": so.transaction_date, - "qty": 30, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.append( - "items", - { - "item_code": "Gooseberry Pie", - "delivery_date": so.transaction_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.append( - "items", - { - "item_code": "Kaduka Key Lime Pie", - "delivery_date": so.transaction_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.append( - "items", - { - "item_code": "Pocketful of Bay", - "delivery_date": so.transaction_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.append( - "items", - { - "item_code": "Tower of Bay-bel", - "delivery_date": so.transaction_date, - "qty": 20, - "warehouse": "Refrigerated Display - APC", - }, - ) - so.save() - so.submit() - - -def create_material_request(settings): - mr = frappe.new_doc("Material Request") - mr.material_request_type = "Manufacture" - mr.schedule_date = mr.transaction_date = settings.day - mr.title = "Pies" - mr.company = settings.company - mr.append( - "items", - { - "item_code": "Ambrosia Pie", - "schedule_date": mr.schedule_date, - "qty": 40, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.append( - "items", - { - "item_code": "Double Plum Pie", - "schedule_date": mr.schedule_date, - "qty": 40, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.append( - "items", - { - "item_code": "Gooseberry Pie", - "schedule_date": mr.schedule_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.append( - "items", - { - "item_code": "Kaduka Key Lime Pie", - "schedule_date": mr.schedule_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.append( - "items", - { - "item_code": "Pocketful of Bay", - "delivery_date": mr.schedule_date, - "qty": 10, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.append( - "items", - { - "item_code": "Tower of Bay-bel", - "delivery_date": mr.schedule_date, - "qty": 20, - "warehouse": "Refrigerated Display - APC", - }, - ) - mr.save() - mr.submit() - mr = frappe.new_doc("Material Request") - mr.material_request_type = "Purchase" - mr.schedule_date = mr.transaction_date = settings.day - mr.title = "Boxes" - mr.company = settings.company - - -def create_production_plan(settings, prod_plan_from_doc): - pp = frappe.new_doc("Production Plan") - pp.posting_date = settings.day - pp.company = settings.company - pp.combine_sub_items = 1 - if prod_plan_from_doc == "Sales Order": - pp.get_items_from = "Sales Order" - pp.append( - "sales_orders", - { - "sales_order": frappe.get_last_doc("Sales Order").name, - }, - ) - pp.get_items() - else: - pp.get_items_from = "Material Request" - pp.append( - "material_requests", - { - "material_request": frappe.get_last_doc("Material Request").name, - }, - ) - pp.get_mr_items() - for item in pp.po_items: - item.planned_start_date = settings.day - pp.get_sub_assembly_items() - for item in pp.sub_assembly_items: - item.schedule_date = settings.day - if item.production_item == "Pie Crust": - idx = item.idx - item.type_of_manufacturing = "Subcontract" - item.supplier = "Credible Contract Baking" - item.qty = 50 - pp.append("sub_assembly_items", pp.sub_assembly_items[idx - 1].as_dict()) - pp.sub_assembly_items[-1].name = None - pp.sub_assembly_items[-1].type_of_manufacturing = "In House" - pp.sub_assembly_items[-1].bom_no = "BOM-Pie Crust-001" - pp.sub_assembly_items[-1].supplier = None - pp.for_warehouse = "Storeroom - APC" - raw_materials = get_items_for_material_requests( - pp.as_dict(), warehouses=None, get_parent_warehouse_data=None - ) - for row in raw_materials: - pp.append( - "mr_items", - { - **row, - "warehouse": frappe.get_value( - "Item Default", {"parent": row.get("item_code")}, "default_warehouse" - ), - }, - ) - pp.save() - pp.submit() - - pp.make_material_request() - mr = frappe.get_last_doc("Material Request") - mr.schedule_date = mr.transaction_date = settings.day - mr.company = settings.company - mr.save() - mr.submit() - - pp.make_work_order() - wos = frappe.get_all("Work Order", {"production_plan": pp.name}) - for wo in wos: - wo = frappe.get_doc("Work Order", wo) - wo.wip_warehouse = "Kitchen - APC" - wo.save() - wo.submit() - job_cards = frappe.get_all("Job Card", {"work_order": wo.name}) - for job_card in job_cards: - job_card = frappe.get_doc("Job Card", job_card) - job_card.append("time_logs", {"completed_qty": wo.qty}) - job_card.save() - job_card.submit() - - -def create_fruit_material_request(settings): - fruits = [ - "Bayberry", - "Cocoplum", - "Damson Plum", - "Gooseberry", - "Hairless Rambutan", - "Kaduka Lime", - "Limequat", - "Tayberry", - ] - - for fruit in fruits: - i = frappe.get_doc("Item", fruit) - i.append( - "item_defaults", - { - "company": "Chelsea Fruit Co", - "default_warehouse": "Stores - CFC", - "default_supplier": "Southern Fruit Supply", - }, - ) - i.save() - ip = frappe.copy_doc(frappe.get_doc("Item Price", {"item_code": fruit})) - ip.price_list = "Standard Buying" - ip.price_list_rate = flt(ip.price_list_rate * 0.75, 2) - ip.save() - - mr = frappe.new_doc("Material Request") - mr.company = "Chelsea Fruit Co" - mr.transaction_date = settings.day - mr.schedule_date = getdate() - mr.purpose = "Purchase" - for f in fruits: - mr.append( - "items", - { - "item_code": f, - "qty": 100, - "schedule_date": mr.schedule_date, - "warehouse": "Stores - CFC", - "uom": "Pound", - }, - ) - mr.save() - mr.submit() - - -def create_quotations(settings): - quotation = frappe.new_doc("Quotation") - - items = ["Ambrosia Pie", "Gooseberry Pie", "Double Plum Pie"] - for item in items: - i = frappe.get_doc("Item", item) - i.append( - "item_defaults", - { - "company": "Chelsea Fruit Co", - "default_warehouse": "Finished Goods - CFC", - }, - ) - i.save() - - values = { - "quotation_to": "Customer", - "order_type": "Sales", - "party_name": "Almacs Food Group", - "selling_price_list": "Bakery Wholesale", - "currency": "USD", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], - "company": settings.company, - } - quotation.update(values) - quotation.save() - quotation.submit() - - quotation = frappe.new_doc("Quotation") - values = { - "quotation_to": "Customer", - "order_type": "Sales", - "party_name": "Almacs Food Group", - "selling_price_list": "Bakery Wholesale", - "currency": "USD", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], - "company": settings.company, - } - quotation.update(values) - quotation.save() - quotation.submit() - - quotation = frappe.new_doc("Quotation") - values = { - "quotation_to": "Customer", - "order_type": "Sales", - "party_name": "Downtown Deli", - "selling_price_list": "Bakery Wholesale", - "currency": "USD", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 2}, {"item_code": "Double Plum Pie", "qty": 1}], - "company": settings.company, - } - quotation.update(values) - quotation.save() - quotation.submit() - - quotation = frappe.new_doc("Quotation") - values = { - "quotation_to": "Customer", - "order_type": "Sales", - "party_name": "Almacs Food Group", - "selling_price_list": "Bakery Wholesale", - "currency": "USD", - "conversion_rate": 1, - "transaction_date": nowdate(), - "valid_till": add_months(nowdate(), 1), - "items": [{"item_code": "Ambrosia Pie", "qty": 5}, {"item_code": "Double Plum Pie", "qty": 10}], - "company": "Chelsea Fruit Co", - } - quotation.update(values) - quotation.save() - quotation.submit() +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import datetime +import types +from itertools import groupby + +import frappe +from erpnext.accounts.doctype.account.account import update_account_number +from erpnext.manufacturing.doctype.production_plan.production_plan import ( + get_items_for_material_requests, +) +from erpnext.setup.utils import enable_all_roles_and_domains, set_defaults_for_tests +from erpnext.stock.get_item_details import get_item_details +from frappe.desk.page.setup_wizard.setup_wizard import setup_complete +from frappe.utils import add_months, nowdate +from frappe.utils.data import flt, getdate + +from inventory_tools.tests.fixtures import ( + boms, + customers, + items, + operations, + suppliers, + workstations, +) + + +def before_test(): + frappe.clear_cache() + setup_complete( + { + "currency": "USD", + "full_name": "Administrator", + "company_name": "Ambrosia Pie Company", + "timezone": "America/New_York", + "company_abbr": "APC", + "domains": ["Distribution"], + "country": "United States", + "fy_start_date": getdate().replace(month=1, day=1).isoformat(), + "fy_end_date": getdate().replace(month=12, day=31).isoformat(), + "language": "english", + "company_tagline": "Ambrosia Pie Company", + "email": "support@agritheory.dev", + "password": "admin", + "chart_of_accounts": "Standard with Numbers", + "bank_account": "Primary Checking", + } + ) + set_defaults_for_tests() + for modu in frappe.get_all("Module Onboarding"): + frappe.db.set_value("Module Onboarding", modu, "is_complete", 1) + frappe.set_value("Website Settings", "Website Settings", "home_page", "login") + frappe.db.commit() + create_test_data() + + +def create_test_data(): + settings = frappe._dict( + { + "day": getdate().replace(month=1, day=1), + "company": "Ambrosia Pie Company", + "company_account": frappe.get_value( + "Account", + { + "account_type": "Bank", + "company": frappe.defaults.get_defaults().get("company"), + "is_group": 0, + }, + ), + } + ) + company_address = frappe.new_doc("Address") + company_address.title = settings.company + company_address.address_type = "Office" + company_address.address_line1 = "67C Sweeny Street" + company_address.city = "Chelsea" + company_address.state = "MA" + company_address.pincode = "89077" + company_address.is_your_company_address = 1 + company_address.append("links", {"link_doctype": "Company", "link_name": settings.company}) + company_address.save() + cfc = frappe.new_doc("Company") + cfc.company_name = "Chelsea Fruit Co" + cfc.default_currency = "USD" + cfc.create_chart_of_accounts_based_on = "Existing Company" + cfc.existing_company = settings.company + cfc.abbr = "CFC" + cfc.save() + + frappe.db.set_single_value("Stock Settings", "valuation_method", "Moving Average") + frappe.db.set_single_value("Stock Settings", "default_warehouse", "") + create_warehouses(settings) + setup_manufacturing_settings(settings) + create_workstations() + create_operations() + create_item_groups(settings) + create_price_lists(settings) + create_suppliers(settings) + create_customers(settings) + create_items(settings) + create_boms(settings) + prod_plan_from_doc = "Sales Order" + if prod_plan_from_doc == "Sales Order": + create_sales_order(settings) + else: + create_material_request(settings) + create_production_plan(settings, prod_plan_from_doc) + create_fruit_material_request(settings) + create_quotations(settings) + + +def create_suppliers(settings): + if not frappe.db.exists("Supplier Group", "Bakery"): + bsg = frappe.new_doc("Supplier Group") + bsg.supplier_group_name = "Bakery" + bsg.parent_supplier_group = "All Supplier Groups" + bsg.save() + + addresses = frappe._dict({}) + for supplier in suppliers: + biz = frappe.new_doc("Supplier") + biz.supplier_name = supplier[0] + biz.supplier_group = "Bakery" + biz.country = "United States" + biz.supplier_default_mode_of_payment = supplier[2] + if biz.supplier_default_mode_of_payment == "ACH/EFT": + biz.bank = "Local Bank" + biz.bank_account = "123456789" + biz.currency = "USD" + if biz.supplier_name == "Credible Contract Baking": + biz.append( + "subcontracting_defaults", + { + "company": settings.company, + "wip_warehouse": "Credible Contract Baking - APC", + "return_warehouse": "Baked Goods - APC", + }, + ) + biz.default_price_list = "Bakery Buying" + biz.save() + + existing_address = frappe.get_value("Address", {"address_line1": supplier[5]["address_line1"]}) + if not existing_address: + addr = frappe.new_doc("Address") + addr.address_title = f"{supplier[0]} - {supplier[5]['city']}" + addr.address_type = "Billing" + addr.address_line1 = supplier[5]["address_line1"] + addr.city = supplier[5]["city"] + addr.state = supplier[5]["state"] + addr.country = supplier[5]["country"] + addr.pincode = supplier[5]["pincode"] + else: + addr = frappe.get_doc("Address", existing_address) + addr.append("links", {"link_doctype": "Supplier", "link_name": supplier[0]}) + addr.save() + + +def create_customers(settings): + for customer_name in customers: + customer = frappe.new_doc("Customer") + customer.customer_name = customer_name + customer.customer_group = "Commercial" + customer.customer_type = "Company" + customer.territory = "United States" + customer.save() + + +def setup_manufacturing_settings(settings): + mfg_settings = frappe.get_doc("Manufacturing Settings", "Manufacturing Settings") + mfg_settings.material_consumption = 1 + mfg_settings.default_wip_warehouse = "Kitchen - APC" + mfg_settings.default_fg_warehouse = "Refrigerated Display - APC" + mfg_settings.overproduction_percentage_for_work_order = 5.00 + mfg_settings.job_card_excess_transfer = 1 + mfg_settings.save() + + if not frappe.db.exists( + "Account", {"account_name": "Work In Progress", "company": settings.company} + ): + wip = frappe.new_doc("Account") + wip.account_name = "Work in Progress" + wip.parent_account = "1400 - Stock Assets - APC" + wip.account_number = "1420" + wip.company = settings.company + wip.currency = "USD" + wip.report_type = "Balance Sheet" + wip.root_type = "Asset" + wip.save() + + frappe.set_value("Warehouse", "Kitchen - APC", "account", wip.name) + frappe.set_value( + "Inventory Tools Settings", settings.company, "enable_work_order_subcontracting", 1 + ) + frappe.set_value("Inventory Tools Settings", settings.company, "create_purchase_orders", 0) + frappe.set_value( + "Inventory Tools Settings", settings.company, "overproduction_percentage_for_work_order", 50 + ) + + +def create_workstations(): + for ws in workstations: + if frappe.db.exists("Workstation", ws[0]): + continue + work = frappe.new_doc("Workstation") + work.workstation_name = ws[0] + work.production_capacity = ws[1] + work.save() + + +def create_operations(): + for op in operations: + if frappe.db.exists("Operation", op[0]): + continue + oper = frappe.new_doc("Operation") + oper.name = op[0] + oper.workstation = op[1] + oper.batch_size = op[2] + oper.description = op[3] + if len(op) == 5: + for aw in op[4]: + oper.append( + "alternative_workstations", + { + "workstation": aw, + }, + ) + oper.save() + + +def create_item_groups(settings): + for ig_name in ( + "Baked Goods", + "Bakery Supplies", + "Ingredients", + "Bakery Equipment", + "Sub Assemblies", + ): + if frappe.db.exists("Item Group", ig_name): + continue + ig = frappe.new_doc("Item Group") + ig.item_group_name = ig_name + ig.parent_item_group = "All Item Groups" + ig.save() + + +def create_price_lists(settings): + if not frappe.db.exists("Price List", "Bakery Buying"): + pl = frappe.new_doc("Price List") + pl.price_list_name = "Bakery Buying" + pl.currency = "USD" + pl.buying = 1 + pl.append("countries", {"country": "United States"}) + pl.save() + + if not frappe.db.exists("Price List", "Bakery Wholesale"): + pl = frappe.new_doc("Price List") + pl.price_list_name = "Bakery Wholesale" + pl.currency = "USD" + pl.selling = 1 + pl.append("countries", {"country": "United States"}) + pl.save() + + if not frappe.db.exists("Pricing Rule", "Bakery Retail"): + pr = frappe.new_doc("Pricing Rule") + pr.title = "Bakery Retail" + pr.selling = 1 + pr.apply_on = "Item Group" + pr.company = settings.company + pr.margin_type = "Percentage" + pr.margin_rate_or_amount = 2.00 + pr.valid_from = settings.day + pr.for_price_list = "Bakery Wholesale" + pr.currency = "USD" + pr.append("item_groups", {"item_group": "Baked Goods"}) + pr.save() + + +def create_items(settings): + for item in items: + if frappe.db.exists("Item", item.get("item_code")): + continue + i = frappe.new_doc("Item") + i.item_code = i.item_name = item.get("item_code") + i.item_group = item.get("item_group") + i.stock_uom = item.get("uom") + i.description = item.get("description") + i.is_stock_item = 0 if item.get("is_stock_item") == 0 else 1 + i.include_item_in_manufacturing = 1 + i.valuation_rate = item.get("valuation_rate") or 0 + i.is_sub_contracted_item = item.get("is_sub_contracted_item") or 0 + i.default_warehouse = settings.get("warehouse") + i.default_material_request_type = ( + "Purchase" + if item.get("item_group") in ("Bakery Supplies", "Ingredients") + or item.get("is_sub_contracted_item") + else "Manufacture" + ) + i.valuation_method = "Moving Average" + if item.get("uom_conversion_detail"): + for uom, cf in item.get("uom_conversion_detail").items(): + i.append("uoms", {"uom": uom, "conversion_factor": cf}) + i.is_purchase_item = ( + 1 + if item.get("item_group") in ("Bakery Supplies", "Ingredients") + or item.get("is_sub_contracted_item") + else 0 + ) + i.is_sales_item = 1 if item.get("item_group") == "Baked Goods" else 0 + i.append( + "item_defaults", + { + "company": settings.company, + "default_warehouse": item.get("default_warehouse"), + "default_supplier": item.get("default_supplier"), + "requires_rfq": True if item.get("item_code") == "Cloudberry" else False, + }, + ) + if i.is_purchase_item and item.get("supplier"): + if isinstance(item.get("supplier"), list): + [i.append("supplier_items", {"supplier": s}) for s in item.get("supplier")] + else: + i.append("supplier_items", {"supplier": item.get("supplier")}) + i.save() + if item.get("item_price"): + ip = frappe.new_doc("Item Price") + ip.item_code = i.item_code + ip.uom = i.stock_uom + ip.price_list = "Bakery Wholesale" if i.is_sales_item else "Bakery Buying" + ip.buying = 1 + ip.valid_from = "2018-1-1" + ip.price_list_rate = item.get("item_price") + ip.save() + if item.get("available_in_house"): + se = frappe.new_doc("Stock Entry") + se.posting_date = settings.day + se.set_posting_time = 1 + se.stock_entry_type = "Material Receipt" + se.append( + "items", + { + "item_code": item.get("item_code"), + "t_warehouse": item.get("default_warehouse"), + "qty": item.get("opening_qty"), + "uom": item.get("uom"), + "stock_uom": item.get("uom"), + "conversion_factor": 1, + "basic_rate": item.get("item_price"), + "expense_account": "1910 - Temporary Opening - APC", + }, + ) + se.save() + se.submit() + + +def create_warehouses(settings): + inventory_tools_settings = frappe.get_doc("Inventory Tools Settings", settings.company) + inventory_tools_settings.enable_work_order_subcontracting = 1 + inventory_tools_settings.create_purchase_orders = 0 + inventory_tools_settings.update_warehouse_path = 1 + inventory_tools_settings.save() + + warehouses = [item.get("default_warehouse") for item in items] + root_wh = frappe.get_value("Warehouse", {"company": settings.company, "is_group": 1}) + if frappe.db.exists("Warehouse", "Stores - APC"): + frappe.rename_doc("Warehouse", "Stores - APC", "Storeroom - APC", force=True) + if frappe.db.exists("Warehouse", "Finished Goods - APC"): + frappe.rename_doc("Warehouse", "Finished Goods - APC", "Baked Goods - APC", force=True) + frappe.set_value("Warehouse", "Baked Goods - APC", "is_group", 1) + for wh in frappe.get_all("Warehouse", {"company": settings.company}, ["name", "is_group"]): + if wh.name not in warehouses and not wh.is_group: + frappe.delete_doc("Warehouse", wh.name) + for item in items: + if frappe.db.exists("Warehouse", item.get("default_warehouse")): + continue + wh = frappe.new_doc("Warehouse") + wh.warehouse_name = item.get("default_warehouse").split(" - ")[0] + wh.parent_warehouse = root_wh + wh.company = settings.company + wh.save() + + wh = frappe.new_doc("Warehouse") + wh.warehouse_name = "Bakery Display" + wh.parent_warehouse = "Baked Goods - APC" + wh.company = settings.company + wh.save() + + wh = frappe.get_doc("Warehouse", "Refrigerated Display - APC") + wh.parent_warehouse = "Baked Goods - APC" + wh.save() + + +def create_boms(settings): + for bom in boms[::-1]: # reversed + if frappe.db.exists("BOM", {"item": bom.get("item")}) and bom.get("item") != "Pie Crust": + continue + b = frappe.new_doc("BOM") + b.item = bom.get("item") + b.quantity = bom.get("quantity") + b.uom = bom.get("uom") + b.company = settings.company + b.is_default = 0 if bom.get("is_default") == 0 else 1 + b.is_subcontracted = bom.get("is_subcontracted") or 0 + b.overproduction_percentage_for_work_order = bom.get( + "overproduction_percentage_for_work_order", None + ) + b.rm_cost_as_per = "Price List" + b.buying_price_list = "Bakery Buying" + b.currency = "USD" + b.with_operations = 0 if bom.get("with_operations") == 0 else 1 + for item in bom.get("items"): + b.append("items", {**item, "stock_uom": item.get("uom")}) + b.items[-1].bom_no = frappe.get_value("BOM", {"item": item.get("item_code")}) + for operation in bom.get("operations"): + b.append("operations", {**operation, "hour_rate": 15.00}) + b.save() + b.submit() + + +def create_sales_order(settings): + so = frappe.new_doc("Sales Order") + so.transaction_date = settings.day + so.customer = customers[0] + so.order_type = "Sales" + so.currency = "USD" + so.selling_price_list = "Bakery Wholesale" + so.append( + "items", + { + "item_code": "Ambrosia Pie", + "delivery_date": so.transaction_date, + "qty": 30, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.append( + "items", + { + "item_code": "Double Plum Pie", + "delivery_date": so.transaction_date, + "qty": 30, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.append( + "items", + { + "item_code": "Gooseberry Pie", + "delivery_date": so.transaction_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.append( + "items", + { + "item_code": "Kaduka Key Lime Pie", + "delivery_date": so.transaction_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.append( + "items", + { + "item_code": "Pocketful of Bay", + "delivery_date": so.transaction_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.append( + "items", + { + "item_code": "Tower of Bay-bel", + "delivery_date": so.transaction_date, + "qty": 20, + "warehouse": "Refrigerated Display - APC", + }, + ) + so.save() + so.submit() + + +def create_material_request(settings): + mr = frappe.new_doc("Material Request") + mr.material_request_type = "Manufacture" + mr.schedule_date = mr.transaction_date = settings.day + mr.title = "Pies" + mr.company = settings.company + mr.append( + "items", + { + "item_code": "Ambrosia Pie", + "schedule_date": mr.schedule_date, + "qty": 40, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.append( + "items", + { + "item_code": "Double Plum Pie", + "schedule_date": mr.schedule_date, + "qty": 40, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.append( + "items", + { + "item_code": "Gooseberry Pie", + "schedule_date": mr.schedule_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.append( + "items", + { + "item_code": "Kaduka Key Lime Pie", + "schedule_date": mr.schedule_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.append( + "items", + { + "item_code": "Pocketful of Bay", + "delivery_date": mr.schedule_date, + "qty": 10, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.append( + "items", + { + "item_code": "Tower of Bay-bel", + "delivery_date": mr.schedule_date, + "qty": 20, + "warehouse": "Refrigerated Display - APC", + }, + ) + mr.save() + mr.submit() + mr = frappe.new_doc("Material Request") + mr.material_request_type = "Purchase" + mr.schedule_date = mr.transaction_date = settings.day + mr.title = "Boxes" + mr.company = settings.company + + +def create_production_plan(settings, prod_plan_from_doc): + pp = frappe.new_doc("Production Plan") + pp.posting_date = settings.day + pp.company = settings.company + pp.combine_sub_items = 1 + if prod_plan_from_doc == "Sales Order": + pp.get_items_from = "Sales Order" + pp.append( + "sales_orders", + { + "sales_order": frappe.get_last_doc("Sales Order").name, + }, + ) + pp.get_items() + else: + pp.get_items_from = "Material Request" + pp.append( + "material_requests", + { + "material_request": frappe.get_last_doc("Material Request").name, + }, + ) + pp.get_mr_items() + for item in pp.po_items: + item.planned_start_date = settings.day + pp.get_sub_assembly_items() + for item in pp.sub_assembly_items: + item.schedule_date = settings.day + if item.production_item == "Pie Crust": + idx = item.idx + item.type_of_manufacturing = "Subcontract" + item.supplier = "Credible Contract Baking" + item.qty = 50 + pp.append("sub_assembly_items", pp.sub_assembly_items[idx - 1].as_dict()) + pp.sub_assembly_items[-1].name = None + pp.sub_assembly_items[-1].type_of_manufacturing = "In House" + pp.sub_assembly_items[-1].bom_no = "BOM-Pie Crust-001" + pp.sub_assembly_items[-1].supplier = None + pp.for_warehouse = "Storeroom - APC" + raw_materials = get_items_for_material_requests( + pp.as_dict(), warehouses=None, get_parent_warehouse_data=None + ) + for row in raw_materials: + pp.append( + "mr_items", + { + **row, + "warehouse": frappe.get_value( + "Item Default", {"parent": row.get("item_code")}, "default_warehouse" + ), + }, + ) + pp.save() + pp.submit() + + pp.make_material_request() + mr = frappe.get_last_doc("Material Request") + mr.schedule_date = mr.transaction_date = settings.day + mr.company = settings.company + mr.save() + mr.submit() + + pp.make_work_order() + wos = frappe.get_all("Work Order", {"production_plan": pp.name}) + for wo in wos: + wo = frappe.get_doc("Work Order", wo) + wo.wip_warehouse = "Kitchen - APC" + wo.save() + wo.submit() + job_cards = frappe.get_all("Job Card", {"work_order": wo.name}) + for job_card in job_cards: + job_card = frappe.get_doc("Job Card", job_card) + job_card.append("time_logs", {"completed_qty": wo.qty}) + job_card.save() + job_card.submit() + + +def create_fruit_material_request(settings): + fruits = [ + "Bayberry", + "Cocoplum", + "Damson Plum", + "Gooseberry", + "Hairless Rambutan", + "Kaduka Lime", + "Limequat", + "Tayberry", + ] + + for fruit in fruits: + i = frappe.get_doc("Item", fruit) + i.append( + "item_defaults", + { + "company": "Chelsea Fruit Co", + "default_warehouse": "Stores - CFC", + "default_supplier": "Southern Fruit Supply", + }, + ) + i.save() + ip = frappe.copy_doc(frappe.get_doc("Item Price", {"item_code": fruit})) + ip.price_list = "Standard Buying" + ip.price_list_rate = flt(ip.price_list_rate * 0.75, 2) + ip.save() + + mr = frappe.new_doc("Material Request") + mr.company = "Chelsea Fruit Co" + mr.transaction_date = settings.day + mr.schedule_date = getdate() + mr.purpose = "Purchase" + for f in fruits: + mr.append( + "items", + { + "item_code": f, + "qty": 100, + "schedule_date": mr.schedule_date, + "warehouse": "Stores - CFC", + "uom": "Pound", + }, + ) + mr.save() + mr.submit() + + +def create_quotations(settings): + quotation = frappe.new_doc("Quotation") + + items = ["Ambrosia Pie", "Gooseberry Pie", "Double Plum Pie"] + for item in items: + i = frappe.get_doc("Item", item) + i.append( + "item_defaults", + { + "company": "Chelsea Fruit Co", + "default_warehouse": "Finished Goods - CFC", + }, + ) + i.save() + + values = { + "quotation_to": "Customer", + "order_type": "Sales", + "party_name": "Almacs Food Group", + "selling_price_list": "Bakery Wholesale", + "currency": "USD", + "conversion_rate": 1, + "transaction_date": nowdate(), + "valid_till": add_months(nowdate(), 1), + "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], + "company": settings.company, + } + quotation.update(values) + quotation.save() + quotation.submit() + + quotation = frappe.new_doc("Quotation") + values = { + "quotation_to": "Customer", + "order_type": "Sales", + "party_name": "Almacs Food Group", + "selling_price_list": "Bakery Wholesale", + "currency": "USD", + "conversion_rate": 1, + "transaction_date": nowdate(), + "valid_till": add_months(nowdate(), 1), + "items": [{"item_code": "Ambrosia Pie", "qty": 1}, {"item_code": "Gooseberry Pie", "qty": 5}], + "company": settings.company, + } + quotation.update(values) + quotation.save() + quotation.submit() + + quotation = frappe.new_doc("Quotation") + values = { + "quotation_to": "Customer", + "order_type": "Sales", + "party_name": "Downtown Deli", + "selling_price_list": "Bakery Wholesale", + "currency": "USD", + "conversion_rate": 1, + "transaction_date": nowdate(), + "valid_till": add_months(nowdate(), 1), + "items": [{"item_code": "Ambrosia Pie", "qty": 2}, {"item_code": "Double Plum Pie", "qty": 1}], + "company": settings.company, + } + quotation.update(values) + quotation.save() + quotation.submit() + + quotation = frappe.new_doc("Quotation") + values = { + "quotation_to": "Customer", + "order_type": "Sales", + "party_name": "Almacs Food Group", + "selling_price_list": "Bakery Wholesale", + "currency": "USD", + "conversion_rate": 1, + "transaction_date": nowdate(), + "valid_till": add_months(nowdate(), 1), + "items": [{"item_code": "Ambrosia Pie", "qty": 5}, {"item_code": "Double Plum Pie", "qty": 10}], + "company": "Chelsea Fruit Co", + } + quotation.update(values) + quotation.save() + quotation.submit() diff --git a/inventory_tools/tests/test_manufacturing_capacity.py b/inventory_tools/tests/test_manufacturing_capacity.py index 5d32278..d3955b8 100644 --- a/inventory_tools/tests/test_manufacturing_capacity.py +++ b/inventory_tools/tests/test_manufacturing_capacity.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe import pytest from erpnext.manufacturing.doctype.work_order.work_order import ( diff --git a/inventory_tools/tests/test_material_demand.py b/inventory_tools/tests/test_material_demand.py index 7606d09..ccd22d1 100644 --- a/inventory_tools/tests/test_material_demand.py +++ b/inventory_tools/tests/test_material_demand.py @@ -1,231 +1,234 @@ -import frappe -import pytest -from frappe.utils import flt, getdate - -from inventory_tools.inventory_tools.report.material_demand.material_demand import ( - execute as execute_material_demand, -) - - -@pytest.mark.order(20) -def test_report_po_without_aggregation(): - filters = frappe._dict( - {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} - ) - columns, rows = execute_material_demand(filters) - assert len(rows) == 34 - assert rows[1].get("supplier") == "Chelsea Fruit Co" - - selected_rows = [ - row - for row in rows - if row.get("supplier") not in ["Southern Fruit Supply", "Unity Bakery Supply"] - ] - - frappe.call( - "inventory_tools.inventory_tools.report.material_demand.material_demand.create", - **{ - "company": "Ambrosia Pie Company", - "email_template": "", - "filters": filters, - "creation_type": "po", - "rows": frappe.as_json(selected_rows), - }, - ) - - pos = frappe.get_all("Purchase Order", ["name", "supplier", "grand_total"]) - assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] - for po in pos: - if po.supplier == "Chelsea Fruit Co": - assert po.grand_total == flt(501.07, 2) - elif po.supplier == "Freedom Provisions": - assert po.grand_total == flt(439.89, 2) - else: - raise AssertionError(f"{po.supplier} should not be in this test") - frappe.delete_doc("Purchase Order", po.name) - - -def test_report_rfq_without_aggregation(): - filters = frappe._dict( - {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} - ) - columns, rows = execute_material_demand(filters) - assert len(rows) == 34 - assert rows[1].get("supplier") == "Chelsea Fruit Co" - - selected_rows = [row for row in rows if row.get("supplier") not in ["Southern Fruit Supply"]] - - frappe.call( - "inventory_tools.inventory_tools.report.material_demand.material_demand.create", - **{ - "company": "Ambrosia Pie Company", - "email_template": "Dispatch Notification", - "filters": filters, - "creation_type": "rfq", - "rows": frappe.as_json(selected_rows), - }, - ) - - rfqs = [ - frappe.get_doc("Request for Quotation", r) for r in frappe.get_all("Request for Quotation") - ] - for rfq in rfqs: - if len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Chelsea Fruit Co"]: - assert len(rfq.items) == 9 - # Bayberry, Cloudberry, Cocoplum, Damson Plum, Gooseberry, Hairless Rambutan, Kaduka Lime, Limequat, Tayberry - elif len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Freedom Provisions"]: - assert len(rfq.items) == 4 # Cornstarch, Flour, Salt, Sugar - elif len(rfq.suppliers) == 2 and [r.supplier for r in rfq.suppliers] == [ - "Chelsea Fruit Co", - "Freedom Provisions", - ]: - assert len(rfq.items) == 1 # Butter - elif len(rfq.suppliers) == 2 and [r.supplier for r in rfq.suppliers] == [ - "Freedom Provisions", - "Unity Bakery Supply", - ]: - assert len(rfq.items) == 3 # Parchment Paper, Pie Box, Pie Tin - else: - raise AssertionError("RFQs items have not combined correctly") - rfq.delete() - - -@pytest.mark.order(21) -def test_report_item_based_without_aggregation(): - filters = frappe._dict( - {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} - ) - columns, rows = execute_material_demand(filters) - assert len(rows) == 34 - - selected_rows = [ - row - for row in rows - if row.get("supplier") not in ["Southern Fruit Supply", "Unity Bakery Supply"] - ] - - frappe.call( - "inventory_tools.inventory_tools.report.material_demand.material_demand.create", - **{ - "company": "Ambrosia Pie Company", - "email_template": "Dispatch Notification", - "filters": filters, - "creation_type": "item_based", - "rows": frappe.as_json(selected_rows), - }, - ) - - pos = frappe.get_all("Purchase Order", ["name", "supplier", "grand_total"]) - assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] - for po in pos: - if po.supplier == "Chelsea Fruit Co": - assert po.grand_total == flt(501.07, 2) - elif po.supplier == "Freedom Provisions": - assert po.grand_total == flt(439.89, 2) - else: - raise AssertionError(f"{po.supplier} should not be in this test") - frappe.delete_doc("Purchase Order", po.name) - - rfqs = [ - frappe.get_doc("Request for Quotation", r) for r in frappe.get_all("Request for Quotation") - ] - for rfq in rfqs: - if len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Chelsea Fruit Co"]: - assert len(rfq.items) == 1 - rfq.delete() - - -@pytest.mark.order(22) -def test_report_po_with_aggregation_and_no_aggregation_warehouse(): - settings = frappe.get_doc("Inventory Tools Settings", "Chelsea Fruit Co") - settings.purchase_order_aggregation_company = settings.name - settings.aggregated_purchasing_warehouse = None - settings.update_warehouse_path = True - settings.save() - - filters = frappe._dict({"end_date": getdate(), "price_list": "Bakery Buying"}) - columns, rows = execute_material_demand(filters) - assert len(rows) == 50 - assert rows[1].get("supplier") == "Chelsea Fruit Co" - - selected_rows = [ - row for row in rows if row.get("supplier") not in ["Chelsea Fruit Co", "Unity Bakery Supply"] - ] - - frappe.call( - "inventory_tools.inventory_tools.report.material_demand.material_demand.create", - **{ - "company": "Chelsea Fruit Co", - "email_template": "", - "filters": filters, - "creation_type": "po", - "rows": frappe.as_json(selected_rows), - }, - ) - - pos = [frappe.get_doc("Purchase Order", p) for p in frappe.get_all("Purchase Order")] - assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] - for po in pos: - if po.supplier == "Southern Fruit Supply": - assert po.grand_total == flt(765.90, 2) - for item in po.items: - mr_wh = frappe.get_value("Material Request Item", item.material_request_item, "warehouse") - assert item.warehouse == mr_wh - - elif po.supplier == "Freedom Provisions": - assert po.grand_total == flt(439.89, 2) - for item in po.items: - mr_wh = frappe.get_value("Material Request Item", item.material_request_item, "warehouse") - assert item.warehouse == mr_wh - - else: - raise AssertionError(f"{po.supplier} should not be in this test") - frappe.delete_doc("Purchase Order", po.name) - - -@pytest.mark.order(23) -def test_report_po_with_aggregation_and_aggregation_warehouse(): - settings = frappe.get_doc("Inventory Tools Settings", "Chelsea Fruit Co") - settings.purchase_order_aggregation_company = settings.name - settings.aggregated_purchasing_warehouse = "Stores - CFC" - settings.update_warehouse_path = True - settings.save() - - filters = frappe._dict({"end_date": getdate(), "price_list": "Bakery Buying"}) - columns, rows = execute_material_demand(filters) - assert len(rows) == 50 - assert rows[1].get("supplier") == "Chelsea Fruit Co" - - selected_rows = [ - row for row in rows if row.get("supplier") not in ["Chelsea Fruit Co", "Unity Bakery Supply"] - ] - - frappe.call( - "inventory_tools.inventory_tools.report.material_demand.material_demand.create", - **{ - "company": "Chelsea Fruit Co", - "email_template": "", - "filters": filters, - "creation_type": "po", - "rows": frappe.as_json(selected_rows), - }, - ) - - pos = [frappe.get_doc("Purchase Order", p) for p in frappe.get_all("Purchase Order")] - assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] - for po in pos: - if po.supplier == "Southern Fruit Supply": - assert po.grand_total == flt(765.90, 2) - for item in po.items: - wh_company = frappe.get_value("Warehouse", item.warehouse, "company") - assert wh_company == po.company - - elif po.supplier == "Freedom Provisions": - assert po.grand_total == flt(439.89, 2) - for item in po.items: - wh_company = frappe.get_value("Warehouse", item.warehouse, "company") - assert wh_company == po.company - - else: - raise AssertionError(f"{po.supplier} should not be in this test") - frappe.delete_doc("Purchase Order", po.name) +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +import pytest +from frappe.utils import flt, getdate + +from inventory_tools.inventory_tools.report.material_demand.material_demand import ( + execute as execute_material_demand, +) + + +@pytest.mark.order(20) +def test_report_po_without_aggregation(): + filters = frappe._dict( + {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} + ) + columns, rows = execute_material_demand(filters) + assert len(rows) == 34 + assert rows[1].get("supplier") == "Chelsea Fruit Co" + + selected_rows = [ + row + for row in rows + if row.get("supplier") not in ["Southern Fruit Supply", "Unity Bakery Supply"] + ] + + frappe.call( + "inventory_tools.inventory_tools.report.material_demand.material_demand.create", + **{ + "company": "Ambrosia Pie Company", + "email_template": "", + "filters": filters, + "creation_type": "po", + "rows": frappe.as_json(selected_rows), + }, + ) + + pos = frappe.get_all("Purchase Order", ["name", "supplier", "grand_total"]) + assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] + for po in pos: + if po.supplier == "Chelsea Fruit Co": + assert po.grand_total == flt(501.07, 2) + elif po.supplier == "Freedom Provisions": + assert po.grand_total == flt(439.89, 2) + else: + raise AssertionError(f"{po.supplier} should not be in this test") + frappe.delete_doc("Purchase Order", po.name) + + +def test_report_rfq_without_aggregation(): + filters = frappe._dict( + {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} + ) + columns, rows = execute_material_demand(filters) + assert len(rows) == 34 + assert rows[1].get("supplier") == "Chelsea Fruit Co" + + selected_rows = [row for row in rows if row.get("supplier") not in ["Southern Fruit Supply"]] + + frappe.call( + "inventory_tools.inventory_tools.report.material_demand.material_demand.create", + **{ + "company": "Ambrosia Pie Company", + "email_template": "Dispatch Notification", + "filters": filters, + "creation_type": "rfq", + "rows": frappe.as_json(selected_rows), + }, + ) + + rfqs = [ + frappe.get_doc("Request for Quotation", r) for r in frappe.get_all("Request for Quotation") + ] + for rfq in rfqs: + if len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Chelsea Fruit Co"]: + assert len(rfq.items) == 9 + # Bayberry, Cloudberry, Cocoplum, Damson Plum, Gooseberry, Hairless Rambutan, Kaduka Lime, Limequat, Tayberry + elif len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Freedom Provisions"]: + assert len(rfq.items) == 4 # Cornstarch, Flour, Salt, Sugar + elif len(rfq.suppliers) == 2 and [r.supplier for r in rfq.suppliers] == [ + "Chelsea Fruit Co", + "Freedom Provisions", + ]: + assert len(rfq.items) == 1 # Butter + elif len(rfq.suppliers) == 2 and [r.supplier for r in rfq.suppliers] == [ + "Freedom Provisions", + "Unity Bakery Supply", + ]: + assert len(rfq.items) == 3 # Parchment Paper, Pie Box, Pie Tin + else: + raise AssertionError("RFQs items have not combined correctly") + rfq.delete() + + +@pytest.mark.order(21) +def test_report_item_based_without_aggregation(): + filters = frappe._dict( + {"end_date": getdate(), "price_list": "Bakery Buying", "company": "Ambrosia Pie Company"} + ) + columns, rows = execute_material_demand(filters) + assert len(rows) == 34 + + selected_rows = [ + row + for row in rows + if row.get("supplier") not in ["Southern Fruit Supply", "Unity Bakery Supply"] + ] + + frappe.call( + "inventory_tools.inventory_tools.report.material_demand.material_demand.create", + **{ + "company": "Ambrosia Pie Company", + "email_template": "Dispatch Notification", + "filters": filters, + "creation_type": "item_based", + "rows": frappe.as_json(selected_rows), + }, + ) + + pos = frappe.get_all("Purchase Order", ["name", "supplier", "grand_total"]) + assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] + for po in pos: + if po.supplier == "Chelsea Fruit Co": + assert po.grand_total == flt(501.07, 2) + elif po.supplier == "Freedom Provisions": + assert po.grand_total == flt(439.89, 2) + else: + raise AssertionError(f"{po.supplier} should not be in this test") + frappe.delete_doc("Purchase Order", po.name) + + rfqs = [ + frappe.get_doc("Request for Quotation", r) for r in frappe.get_all("Request for Quotation") + ] + for rfq in rfqs: + if len(rfq.suppliers) == 1 and [r.supplier for r in rfq.suppliers] == ["Chelsea Fruit Co"]: + assert len(rfq.items) == 1 + rfq.delete() + + +@pytest.mark.order(22) +def test_report_po_with_aggregation_and_no_aggregation_warehouse(): + settings = frappe.get_doc("Inventory Tools Settings", "Chelsea Fruit Co") + settings.purchase_order_aggregation_company = settings.name + settings.aggregated_purchasing_warehouse = None + settings.update_warehouse_path = True + settings.save() + + filters = frappe._dict({"end_date": getdate(), "price_list": "Bakery Buying"}) + columns, rows = execute_material_demand(filters) + assert len(rows) == 50 + assert rows[1].get("supplier") == "Chelsea Fruit Co" + + selected_rows = [ + row for row in rows if row.get("supplier") not in ["Chelsea Fruit Co", "Unity Bakery Supply"] + ] + + frappe.call( + "inventory_tools.inventory_tools.report.material_demand.material_demand.create", + **{ + "company": "Chelsea Fruit Co", + "email_template": "", + "filters": filters, + "creation_type": "po", + "rows": frappe.as_json(selected_rows), + }, + ) + + pos = [frappe.get_doc("Purchase Order", p) for p in frappe.get_all("Purchase Order")] + assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] + for po in pos: + if po.supplier == "Southern Fruit Supply": + assert po.grand_total == flt(765.90, 2) + for item in po.items: + mr_wh = frappe.get_value("Material Request Item", item.material_request_item, "warehouse") + assert item.warehouse == mr_wh + + elif po.supplier == "Freedom Provisions": + assert po.grand_total == flt(439.89, 2) + for item in po.items: + mr_wh = frappe.get_value("Material Request Item", item.material_request_item, "warehouse") + assert item.warehouse == mr_wh + + else: + raise AssertionError(f"{po.supplier} should not be in this test") + frappe.delete_doc("Purchase Order", po.name) + + +@pytest.mark.order(23) +def test_report_po_with_aggregation_and_aggregation_warehouse(): + settings = frappe.get_doc("Inventory Tools Settings", "Chelsea Fruit Co") + settings.purchase_order_aggregation_company = settings.name + settings.aggregated_purchasing_warehouse = "Stores - CFC" + settings.update_warehouse_path = True + settings.save() + + filters = frappe._dict({"end_date": getdate(), "price_list": "Bakery Buying"}) + columns, rows = execute_material_demand(filters) + assert len(rows) == 50 + assert rows[1].get("supplier") == "Chelsea Fruit Co" + + selected_rows = [ + row for row in rows if row.get("supplier") not in ["Chelsea Fruit Co", "Unity Bakery Supply"] + ] + + frappe.call( + "inventory_tools.inventory_tools.report.material_demand.material_demand.create", + **{ + "company": "Chelsea Fruit Co", + "email_template": "", + "filters": filters, + "creation_type": "po", + "rows": frappe.as_json(selected_rows), + }, + ) + + pos = [frappe.get_doc("Purchase Order", p) for p in frappe.get_all("Purchase Order")] + assert "Unity Bakery Supply" not in [p.get("supplier") for p in pos] + for po in pos: + if po.supplier == "Southern Fruit Supply": + assert po.grand_total == flt(765.90, 2) + for item in po.items: + wh_company = frappe.get_value("Warehouse", item.warehouse, "company") + assert wh_company == po.company + + elif po.supplier == "Freedom Provisions": + assert po.grand_total == flt(439.89, 2) + for item in po.items: + wh_company = frappe.get_value("Warehouse", item.warehouse, "company") + assert wh_company == po.company + + else: + raise AssertionError(f"{po.supplier} should not be in this test") + frappe.delete_doc("Purchase Order", po.name) diff --git a/inventory_tools/tests/test_overproduction.py b/inventory_tools/tests/test_overproduction.py index ccdae21..284705f 100644 --- a/inventory_tools/tests/test_overproduction.py +++ b/inventory_tools/tests/test_overproduction.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe import pytest from erpnext.manufacturing.doctype.work_order.work_order import create_job_card, make_stock_entry diff --git a/inventory_tools/tests/test_quotation_demand_report.py b/inventory_tools/tests/test_quotation_demand_report.py index a1f19e4..fd2c303 100644 --- a/inventory_tools/tests/test_quotation_demand_report.py +++ b/inventory_tools/tests/test_quotation_demand_report.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe import pytest from frappe.utils import flt, getdate diff --git a/inventory_tools/tests/test_uom.py b/inventory_tools/tests/test_uom.py index 32a9d0c..7729c15 100644 --- a/inventory_tools/tests/test_uom.py +++ b/inventory_tools/tests/test_uom.py @@ -1,19 +1,22 @@ -import frappe -import pytest -from frappe.exceptions import ValidationError - - -@pytest.mark.order(40) -def test_uom_enforcement_validation(): - _so = frappe.get_last_doc("Sales Order") - inventory_tools_settings = frappe.get_doc("Inventory Tools Settings", _so.company) - inventory_tools_settings.enforce_uoms = True - inventory_tools_settings.save() - - so = frappe.copy_doc(_so) - assert so.items[0].uom == "Nos" - so.items[0].uom = "Box" - with pytest.raises(ValidationError) as exc_info: - so.save() - - assert "Invalid UOM" in exc_info.value.args[0] +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +import pytest +from frappe.exceptions import ValidationError + + +@pytest.mark.order(40) +def test_uom_enforcement_validation(): + _so = frappe.get_last_doc("Sales Order") + inventory_tools_settings = frappe.get_doc("Inventory Tools Settings", _so.company) + inventory_tools_settings.enforce_uoms = True + inventory_tools_settings.save() + + so = frappe.copy_doc(_so) + assert so.items[0].uom == "Nos" + so.items[0].uom = "Box" + with pytest.raises(ValidationError) as exc_info: + so.save() + + assert "Invalid UOM" in exc_info.value.args[0] diff --git a/inventory_tools/tests/test_warehouse_path.py b/inventory_tools/tests/test_warehouse_path.py index 51fe2ef..b456583 100644 --- a/inventory_tools/tests/test_warehouse_path.py +++ b/inventory_tools/tests/test_warehouse_path.py @@ -1,17 +1,20 @@ -import frappe -import pytest - - -@pytest.mark.order(1) -def test_warehouse_path(): - """ - In the setup script this feature is turned on - """ - wh = frappe.get_doc("Warehouse", "Bakery Display - APC") - assert wh.warehouse_path == "Finished Goods ⇒ Bakery Display" - wh.parent_warehouse = "All Warehouses - APC" - wh.save() - assert wh.warehouse_path == "Bakery Display" - wh.parent_warehouse = "Baked Goods - APC" - wh.save() - assert wh.warehouse_path == "Finished Goods ⇒ Bakery Display" +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + +import frappe +import pytest + + +@pytest.mark.order(1) +def test_warehouse_path(): + """ + In the setup script this feature is turned on + """ + wh = frappe.get_doc("Warehouse", "Bakery Display - APC") + assert wh.warehouse_path == "Finished Goods ⇒ Bakery Display" + wh.parent_warehouse = "All Warehouses - APC" + wh.save() + assert wh.warehouse_path == "Bakery Display" + wh.parent_warehouse = "Baked Goods - APC" + wh.save() + assert wh.warehouse_path == "Finished Goods ⇒ Bakery Display" diff --git a/inventory_tools/www/bulk_order.py b/inventory_tools/www/bulk_order.py index d2107ad..643199f 100644 --- a/inventory_tools/www/bulk_order.py +++ b/inventory_tools/www/bulk_order.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + import frappe from erpnext.e_commerce.shopping_cart.cart import update_cart diff --git a/package.json b/package.json index 46329bc..d8e5a75 100644 --- a/package.json +++ b/package.json @@ -12,11 +12,5 @@ "publishConfig": { "access": "restricted" }, - "private": true, - "release": { - "branches": [ - "version-14", - "version-15" - ] - } + "private": true } diff --git a/setup.py b/setup.py index 70a9a41..6b1f73a 100644 --- a/setup.py +++ b/setup.py @@ -1,3 +1,6 @@ +# Copyright (c) 2024, AgriTheory and contributors +# For license information, please see license.txt + from setuptools import setup # TODO: Remove this file when bench >=v5.11.0 is adopted / v15.0.0 is released